Blob Blame Raw

#include "private.h"
#include "world.h"
#include "drawing.h"


double heliDrawingColorStroke[4] = {0, 0, 0, 1};

#define colorStroke heliDrawingColorStroke
static double colorBack[4] = {1, 1, 1, 1};
static double colorFill[4] = {0.5, 0.5, 0.5, 1};
static double lineWidth = 1;
static double *path;
static size_t pathSize;
static size_t pathAllocated;



static void endPath(int close, int stroke, int fill, int fillSimple);

static void closePathSimple()
	{ endPath(TRUE, TRUE, FALSE, TRUE); }


void background(const char *color)
	{ heliParseColor(color, colorBack); }
void fill(const char *color)
	{ heliParseColor(color, colorFill); }
void noFill()
	{ fill("transparent"); }
void stroke(const char *color)
	{ heliParseColor(color, colorStroke); }
void noStroke()
	{ stroke("transparent"); }

void strokeWeight(double weight)
	{ lineWidth = weight; }

const char* rgba(double r, double g, double b, double a) {
	static char buf[1024];
	snprintf(buf, sizeof(buf) - 1, "%f %f %f %f", r, g, b, a);
	return buf;
}

const char* rgb(double r, double g, double b)
	{ return rgba(r, g, b, 1); }

void rect(double x, double y, double width, double height) {
	resetPath();
	moveTo(x, y);
	lineTo(x + width, y);
	lineTo(x + width, y + height);
	lineTo(x, y + height);
	closePathSimple();
}

void line(double x1, double y1, double x2, double y2) {
	resetPath();
	moveTo(x1, y1);
	lineTo(x2, y2);
	strokePath();
}

void ellipse(double x, double y, double width, double height) {
	resetPath();
	arcPath(x, y, width, height, 0, 360);
	closePathSimple();
}

void point(double x, double y) {
	double color[4];
	memcpy(color, colorFill, sizeof(color));
	memcpy(colorFill, colorStroke, sizeof(colorFill));
	colorStroke[3] = 0;
	ellipse(x - lineWidth*0.5, y - lineWidth*0.5, lineWidth, lineWidth);
	colorStroke[3] = colorFill[3];
	memcpy(colorFill, color, sizeof(colorFill));
}

void arcPath(double x, double y, double w, double h, double start, double stop) {
	const double step = PI/180;
	w *= 0.5; x += w;
	h *= 0.5; y += h;
	start *= PI/180;
	stop *= PI/180;
	double a = start;
	if (start < stop) {
		while(1) {
			if (a > stop) a = stop;
			lineTo(x + cos(a)*w, y + sin(a)*h);
			if (a == stop) break;
			a += step;
		}
	} else {
		while(1) {
			if (a < stop) a = stop;
			lineTo(x + cos(a)*w, y + sin(a)*h);
			if (a == stop) break;
			a -= step;
		}
	}
}

void arc(double x, double y, double w, double h, double start, double stop) {
	resetPath();
	arcPath(x, y, w, h, start, stop);
	strokePath();
}

void regularPolygon(double x, double y, int sides, double size) {
	resetPath();
	size *= 0.5;
	moveTo(x + size, y);
	for(int i = 1; i < sides; ++i) {
		double a = i*2*PI/sides;
		lineTo(x + size*cos(a), y + size*sin(a));
	}
	closePathSimple();
}


void resetPath()
	{ pathSize = 0; }

static void drawFillSimple() {
	if (colorFill[3] <= HELI_PRECISION) return;
	if (pathSize < 6) return;
	
	glColor4dv(colorFill);
	glEnable(GL_MULTISAMPLE);
	glBegin(GL_TRIANGLE_FAN);
	for(int i = 0; i < pathSize; i += 2)
		glVertex2dv(&path[i]);
	glEnd();
	glDisable(GL_MULTISAMPLE);
	glColor4d(1, 1, 1, 1);
}

static void drawFill() {
	if (colorFill[3] <= HELI_PRECISION) return;
	if (pathSize < 6) return;
	
	double l = path[0], r = l;
	double t = path[1], b = t;
	for(int i = 2; i < pathSize; i += 2) {
		if (l > path[i]) l = path[i];
		if (r < path[i]) r = path[i];
		if (t > path[i + 1]) t = path[i + 1];
		if (b < path[i + 1]) b = path[i + 1];
	}
	if ( r - l <= HELI_PRECISION
	  || b - t <= HELI_PRECISION) return;
	l -= 1; r += 1;
	t -= 1; b += 1;
	
	glEnable(GL_MULTISAMPLE);
	glColor4dv(colorFill);
	glEnable(GL_STENCIL_TEST);
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

	glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
	glBegin(GL_QUADS);
	glVertex2d(l, t);
	glVertex2d(r, t);
	glVertex2d(r, b);
	glVertex2d(l, b);
	glEnd();
	
	glStencilOpSeparate(GL_FRONT, GL_INCR_WRAP, GL_INCR_WRAP, GL_INCR_WRAP);
	glStencilOpSeparate(GL_BACK, GL_DECR_WRAP, GL_DECR_WRAP, GL_DECR_WRAP);
	glBegin(GL_TRIANGLE_FAN);
	for(int i = 0; i < pathSize; i += 2)
		glVertex2dv(&path[i]);
	glEnd();
	
	glStencilFunc(GL_NOTEQUAL, 0, -1u);
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	glBegin(GL_QUADS);
	glVertex2d(l, t);
	glVertex2d(r, t);
	glVertex2d(r, b);
	glVertex2d(l, b);
	glEnd();
	
	glStencilFunc(GL_ALWAYS, 0, -1u);
	glDisable(GL_STENCIL_TEST);
	glDisable(GL_MULTISAMPLE);
	glColor4d(1, 1, 1, 1);
}

static void drawStroke() {
	if (colorStroke[3] <= HELI_PRECISION) return;
	if (pathSize < 4) return;

	glColor4dv(colorStroke);
	glLineWidth(lineWidth);
	glBegin(GL_LINE_STRIP);
	for(int i = 0; i < pathSize; i += 2)
		glVertex2dv(&path[i]);
	glEnd();
	glColor4d(1, 1, 1, 1);
}

static void pushPathPoint(double x, double y) {
	if (pathAllocated < pathSize + 2) {
		pathAllocated += pathAllocated/4 + 32;
		path = realloc(path, pathAllocated*sizeof(*path));
		memset(&path[pathSize], 0, (pathAllocated - pathSize)*sizeof(*path));
	}
	path[pathSize++] = x;
	path[pathSize++] = y;
}

static void endPath(int close, int stroke, int fill, int fillSimple) {
	if (pathSize >= 4) {
		if (fillSimple) drawFillSimple();
			else if (fill) drawFill();
		if (stroke) {
			if ( close
			  && ( fabs(path[0] - path[pathSize - 2]) > HELI_PRECISION
			    || fabs(path[1] - path[pathSize - 1]) > HELI_PRECISION ))
			{
				pushPathPoint(path[0], path[1]);
			}
			drawStroke();
		}
	}
	resetPath();
}

void closePath()
	{ endPath(TRUE, TRUE, TRUE, FALSE); }
void strokePath()
	{ endPath(FALSE, TRUE, FALSE, FALSE); }
void lineTo(double x, double y)
	{ pushPathPoint(x, y); }
void moveTo(double x, double y)
	{ resetPath(); lineTo(x, y); }


void heliDrawingClearFrame() {
	glClearColor(colorBack[0], colorBack[1], colorBack[2], colorBack[3]);
	glClear(GL_COLOR_BUFFER_BIT);
}

void heliDrawingPrepareFrame() {
	resetPath();
	heliDrawingClearFrame();
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_STENCIL_TEST);
	glDisable(GL_CULL_FACE);
	glDisable(GL_MULTISAMPLE);
	
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_LINE_SMOOTH);
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
}

void heliDrawingFinish() {
	resetPath();
	free(path);
	path = NULL;
	pathAllocated = 0;
}