Blame src/drawing.c

07b70f
07b70f
#include "private.h"
d7d433
#include "window.h"
07b70f
#include "drawing.h"
07b70f
3c0f7f
07b70f
static double *path;
07b70f
static size_t pathSize;
07b70f
static size_t pathAllocated;
07b70f
3e7c5f
3e7c5f
static HeliDrawingState statesStack[1024];
3954ba
static int statesStackIndex = 0;
3954ba
3954ba
0b65e7
typedef struct _HeliStrokePoint {
0b65e7
	double x, y, dx, dy;
0b65e7
} HeliStrokePoint;
0b65e7
0b65e7
typedef struct _HeliStroke {
0b65e7
	HeliStrokePoint *points;
0b65e7
	int count;
0b65e7
	int allocatedCount;
0b65e7
} HeliStroke;
0b65e7
ba9f06
static void endPath(int close, int stroke, int fill, int fillSimple);
ba9f06
ba9f06
static void closePathSimple()
ba9f06
	{ endPath(TRUE, TRUE, FALSE, TRUE); }
ba9f06
b4b587
static int isStrokeSolid(HeliDrawingState *state, double *outAABorder) {
b4b587
	if ( state->strokeColor[3] >= 1.0 - HELI_PRECISION
b4b587
	  && state->strokeWidth > HELI_PRECISION
b4b587
	  && (!state->strokeTexture.animation || !animationGetGLTexId(state->strokeTexture.animation)) )
b4b587
	{
b4b587
		double aaBorder = heliGLGetAAResolution();
b4b587
		if (outAABorder) *outAABorder = aaBorder;
b4b587
		if (aaBorder > HELI_PRECISION && aaBorder < state->strokeWidth)
b4b587
			return TRUE;
b4b587
	} else
b4b587
	if (outAABorder) {
b4b587
		*outAABorder = heliGLGetAAResolution();
b4b587
	}
b4b587
	return FALSE;
b4b587
}
b4b587
ba9f06
deef1d
void heliDrawingApplyTexture(double *color, HeliTextureState *state) {
deef1d
	glColor4dv(color);
deef1d
	unsigned int texid = state->animation ? animationGetGLTexId(state->animation) : 0;
deef1d
	if (texid) {
909bc2
		HeliPair *pair = heliUIntGet(&heliDrawingFramebuffersToFlush, texid);
909bc2
		if (pair) framebufferFlush((Framebuffer)pair->value);
909bc2
		
deef1d
		glEnable(GL_TEXTURE_2D);
deef1d
		glBindTexture(GL_TEXTURE_2D, texid);
deef1d
		glEnable(GL_TEXTURE_GEN_S);
deef1d
		glEnable(GL_TEXTURE_GEN_T);
b4b587
		if (state->fixed) {
b4b587
			unsigned int mode;
b4b587
			glGetIntegerv(GL_MATRIX_MODE, (int*)&mode);
b4b587
			glMatrixMode(GL_MODELVIEW);
b4b587
			glPushMatrix();
b4b587
			glLoadIdentity();
b4b587
			glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
b4b587
			glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
b4b587
			glTexGendv(GL_S, GL_EYE_PLANE, state->genx);
b4b587
			glTexGendv(GL_T, GL_EYE_PLANE, state->geny);
b4b587
			glPopMatrix();
b4b587
			glMatrixMode(mode);
b4b587
		} else {
b4b587
			glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
b4b587
			glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
b4b587
			glTexGendv(GL_S, GL_OBJECT_PLANE, state->genx);
b4b587
			glTexGendv(GL_T, GL_OBJECT_PLANE, state->geny);
b4b587
		}
deef1d
	}
deef1d
}
deef1d
deef1d
void heliDrawingResetTexture() {
deef1d
	glDisable(GL_TEXTURE_GEN_S);
deef1d
	glDisable(GL_TEXTURE_GEN_T);
b4b587
	glBindTexture(GL_TEXTURE_2D, 0);
deef1d
	glDisable(GL_TEXTURE_2D);
deef1d
	glColor4d(1, 1, 1, 1);
deef1d
}
deef1d
b4b587
static void initTextureState(HeliTextureState *s, Animation animation, double x, double y, double width, double height, int fixed) {
b4b587
	s->animation = animation;
b4b587
	
b4b587
	double kx = fabs(width) > HELI_PRECISION ? 1/width
b4b587
				: width < 0 ? -HELI_PRECISION : HELI_PRECISION;
b4b587
	double ky = fabs(height) > HELI_PRECISION ? 1/height
b4b587
				: height < 0 ? -HELI_PRECISION : HELI_PRECISION;
b4b587
	double genx[4] = {kx, 0, 0, -x*kx};
b4b587
	double geny[4] = {0, ky, 0, -y*ky};
b4b587
	
b4b587
	if (fixed) {
b4b587
		s->fixed = TRUE;
b4b587
		double m[4][4];
b4b587
		glGetDoublev(GL_MODELVIEW_MATRIX, *m);
b4b587
		for(int i = 0; i < 4; ++i) {
b4b587
			s->genx[i] = genx[0]*m[0][i] + genx[1]*m[1][i] + genx[2]*m[2][i] + genx[3]*m[3][i];
b4b587
			s->geny[i] = geny[0]*m[0][i] + geny[1]*m[1][i] + geny[2]*m[2][i] + geny[3]*m[3][i];
b4b587
		}
b4b587
	} else {
b4b587
		s->fixed = FALSE;
b4b587
		memcpy(s->genx, genx, sizeof(genx));
b4b587
		memcpy(s->geny, geny, sizeof(geny));
b4b587
	}
b4b587
}
b4b587
deef1d
d6f40c
void background(unsigned int colorCode) {
d6f40c
	double color[4];
d6f40c
	heliColorToDouble(colorCode, color);
d6f40c
	glClearColor(color[0], color[1], color[2], color[3]);
d6f40c
}
d6f40c
void clear()
d6f40c
	{ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); }
d6f40c
d4e89f
void fill(unsigned int colorCode)
d4e89f
	{ heliColorToDouble(colorCode, heliDrawingGetState()->fillColor); }
b4b587
void fillTexture(Animation animation, double x, double y, double width, double height, int fixed)
b4b587
	{ initTextureState(&heliDrawingGetState()->fillTexture, animation, x, y, width, height, fixed); }
3e7c5f
void noFillColor()
3e7c5f
	{ fill(0); }
3e7c5f
void noFillTexture()
3e7c5f
	{ fillTexture(NULL, 0, 0, 1, 1, FALSE); }
53e18e
void noFill()
3e7c5f
	{ noFillColor(); noFillTexture(); }
deef1d
d4e89f
void stroke(unsigned int colorCode)
d4e89f
	{ heliColorToDouble(colorCode, heliDrawingGetState()->strokeColor); }
b4b587
void strokeTexture(Animation animation, double x, double y, double width, double height, int fixed)
b4b587
	{ initTextureState(&heliDrawingGetState()->strokeTexture, animation, x, y, width, height, fixed); }
3e7c5f
void noStrokeColor()
3e7c5f
	{ stroke(0); }
3e7c5f
void noStrokeTexture()
3e7c5f
	{ strokeTexture(NULL, 0, 0, 1, 1, FALSE); }
53e18e
void noStroke()
3e7c5f
	{ noStrokeColor(); noStrokeTexture(); }
07b70f
deef1d
void strokeWidth(double width)
deef1d
	{ heliDrawingGetState()->strokeWidth = width; }
07b70f
b4b587
void enableAntialiasing()
b4b587
	{ heliDrawingGetState()->antialiasing = TRUE; }
b4b587
void disableAntialiasing()
b4b587
	{ heliDrawingGetState()->antialiasing = FALSE; }
b4b587
546e3a
const char* rgba(double r, double g, double b, double a) {
07b70f
	static char buf[1024];
07b70f
	snprintf(buf, sizeof(buf) - 1, "%f %f %f %f", r, g, b, a);
07b70f
	return buf;
07b70f
}
07b70f
546e3a
const char* rgb(double r, double g, double b)
07b70f
	{ return rgba(r, g, b, 1); }
07b70f
8bc1f1
void translate(double x, double y)
8bc1f1
	{ glTranslated(x, y, 0); }
8bc1f1
void rotate(double angle)
8bc1f1
	{ glRotated(angle, 0, 0, 1); }
8bc1f1
void scale(double x, double y)
8bc1f1
	{ glScaled(x, y, 1); }
8bc1f1
void zoom(double z)
8bc1f1
	{ scale(z, z); }
ba5c91
void noTransform()
ba5c91
	{ glLoadIdentity(); }
ba5c91
8bc1f1
8bc1f1
void cliprect(double x, double y, double width, double height) {
8bc1f1
	double eq[4][4] = {
8bc1f1
		{ 1, 0, 0, -x        },
8bc1f1
		{-1, 0, 0,  x+width  },
8bc1f1
		{ 0, 1, 0, -y        },
8bc1f1
		{ 0,-1, 0,  y+height } };
8bc1f1
	for(int i = 0; i < 4; ++i) {
8bc1f1
		glEnable(GL_CLIP_PLANE0 + i);
8bc1f1
		glClipPlane(GL_CLIP_PLANE0 + i, eq[i]);
8bc1f1
	}
8bc1f1
}
8bc1f1
8bc1f1
void noClip()
8bc1f1
	{ for(int i = 0; i < 4; ++i) glDisable(GL_CLIP_PLANE0 + i); }
8bc1f1
1d641c
1d641c
void projectionByViewport() {
89ec47
	unsigned int framebuffer_id = 0;
89ec47
	glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (int*)&framebuffer_id);
1d641c
	int vp[4] = {};
1d641c
	glGetIntegerv(GL_VIEWPORT, vp);
1d641c
	unsigned int mode;
1d641c
	glGetIntegerv(GL_MATRIX_MODE, (int*)&mode);
1d641c
	glMatrixMode(GL_PROJECTION);
1d641c
	glLoadIdentity();
89ec47
	if (framebuffer_id)
89ec47
		glOrtho(0, vp[2], 0, vp[3], -1.0, 1.0);
89ec47
	else
89ec47
		glOrtho(0, vp[2], vp[3], 0, -1.0, 1.0);
1d641c
	glMatrixMode(mode);
1d641c
}
1d641c
1d641c
1d641c
void viewportByFramebuffer(Framebuffer framebuffer) {
1d641c
	if (framebuffer)
1d641c
		glViewport(0, 0, framebufferGetWidth(framebuffer), framebufferGetHeight(framebuffer));
1d641c
	else
d7d433
		glViewport(0, 0, windowGetWidth(), windowGetHeight());
1d641c
}
1d641c
1d641c
1d641c
void viewportByWindow()
1d641c
	{ viewportByFramebuffer(NULL); }
1d641c
1d641c
1d641c
void targetEx(Framebuffer framebuffer, int updateViewport, int updateProjection) {
1d641c
	if (framebuffer) {
1d641c
		if (heliGLBindFramebufferPtr) {
1d641c
			unsigned int id = framebufferGetGLId(framebuffer);
1d641c
			heliGLBindFramebufferPtr(GL_READ_FRAMEBUFFER, id);
1d641c
			heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, id);
909bc2
			unsigned int texid = framebufferGetGLTexId(framebuffer);
909bc2
			if (texid) heliUIntAdd(&heliDrawingFramebuffersToFlush, texid, framebuffer, NULL);
1d641c
		}
1d641c
		if (updateViewport) {
1d641c
			viewportByFramebuffer(framebuffer);
1d641c
			if (updateProjection) projectionByViewport();
1d641c
		}
1d641c
	} else {
1d641c
		if (heliGLBindFramebufferPtr) {
1d641c
			heliGLBindFramebufferPtr(GL_READ_FRAMEBUFFER, heliGLWindowFramebufferReadId);
1d641c
			heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, heliGLWindowFramebufferDrawId);
1d641c
		}
1d641c
		if (updateViewport) {
1d641c
			viewportByWindow();
1d641c
			if (updateProjection) projectionByViewport();
1d641c
		}
1d641c
	}
1d641c
}
1d641c
void target(Framebuffer framebuffer)
1d641c
	{ targetEx(framebuffer, TRUE, TRUE); }
1d641c
void noTarget()
1d641c
	{ target(NULL); }
1d641c
1d641c
07b70f
void rect(double x, double y, double width, double height) {
07b70f
	resetPath();
b4b587
	HeliDrawingState *s = heliDrawingGetState();
b4b587
	double aaBorder = 0;
b4b587
	int antialiasing = s->antialiasing && !isStrokeSolid(s, &aaBorder);
b4b587
	heliFillRectSimple(x, y, x+width, y+height, aaBorder, antialiasing);
07b70f
	moveTo(x, y);
07b70f
	lineTo(x + width, y);
07b70f
	lineTo(x + width, y + height);
07b70f
	lineTo(x, y + height);
deef1d
	endPath(TRUE, TRUE, FALSE, FALSE);
07b70f
}
07b70f
20d197
void rectRounded(double x, double y, double width, double height, double radius) {
20d197
	radius = fabs(radius);
20d197
	if (radius < HELI_PRECISION)
20d197
		{ rect(x, y, width, height); return; }
20d197
	double x0 = x, y0 = y, x1 = x + width, y1 = y + height;
20d197
	if (width < 0) { x0 = x1; x1 = x; }
20d197
	if (height < 0) { y0 = y1; y1 = y; }
20d197
	
20d197
	double rw = 0.5*fabs(width);
20d197
	double rh = 0.5*fabs(height);
20d197
	if (radius > rw) radius = rw;
20d197
	if (radius > rh) radius = rh;
20d197
	int lw = fabs(rw - radius) > HELI_PRECISION;
20d197
	int lh = fabs(rh - radius) > HELI_PRECISION;
20d197
	double r2 = radius + radius;
20d197
	
20d197
	if (lw && lh) {
20d197
		resetPath();
20d197
		arcPath( x0   , y0   , r2, r2, -180, -90 );
20d197
		arcPath( x1-r2, y0   , r2, r2,  -90, 0   );
20d197
		arcPath( x1-r2, y1-r2, r2, r2,    0, 90  );
20d197
		arcPath( x0   , y1-r2, r2, r2,   90, 180 );
20d197
		closePath();
20d197
	} else
20d197
	if (lw) {
20d197
		resetPath();
20d197
		arcPath( x0   , y0, r2, y1-y0,  90, 270 );
20d197
		arcPath( x1-r2, y0, r2, y1-y0, -90, 90  );
20d197
		closePath();
20d197
	} else
20d197
	if (lh) {
20d197
		resetPath();
20d197
		arcPath( x0, y0   , x1-x0, y0+r2, -180, 0   );
20d197
		arcPath( x0, y1-r2, x1-x0, y1   ,    0, 180 );
20d197
		closePath();
20d197
	} else {
20d197
		circle(0.5*(x0 + x1), 0.5*(y0 + y1), radius);
20d197
	}
20d197
}
20d197
f8dca4
void rectTextured(Animation animation, double x, double y, double width, double height) {
f8dca4
	saveState();
f8dca4
	fillTexture(animation, x, y, width, height, FALSE);
f8dca4
	rect(x, y, width, height);
f8dca4
	restoreState();
f8dca4
}
f8dca4
07b70f
void line(double x1, double y1, double x2, double y2) {
07b70f
	resetPath();
07b70f
	moveTo(x1, y1);
07b70f
	lineTo(x2, y2);
07b70f
	strokePath();
07b70f
}
07b70f
07b70f
void ellipse(double x, double y, double width, double height) {
07b70f
	resetPath();
deef1d
	if (fabs(fabs(width) - fabs(height)) <= HELI_PRECISION) {
b4b587
		HeliDrawingState *s = heliDrawingGetState();
b4b587
		double aaBorder = 0;
b4b587
		int antialiasing = s->antialiasing && !isStrokeSolid(s, &aaBorder);
b4b587
		heliFillCircleSimple(x + 0.5*width, y + 0.5*height, 0.5*width, aaBorder, antialiasing);
deef1d
		arcPath(x, y, width, height, 0, 360);
deef1d
		endPath(TRUE, TRUE, FALSE, FALSE);
deef1d
	} else {
deef1d
		arcPath(x, y, width, height, 0, 360);
deef1d
		closePathSimple();
deef1d
	}
07b70f
}
07b70f
788bd9
void circle(double x, double y, double radius)
788bd9
	{ ellipse(x - radius, y - radius, 2*radius, 2*radius); }
788bd9
ba9f06
void point(double x, double y) {
deef1d
	saveStateEx(STATE_FILL | STATE_STROKE);
3954ba
	HeliDrawingState *s = heliDrawingGetState();
deef1d
	memcpy(&s->fillColor, &s->strokeColor, sizeof(s->fillColor));
deef1d
	memcpy(&s->fillTexture, &s->strokeTexture, sizeof(s->fillTexture));
deef1d
	noStroke();
deef1d
	if (s->strokeWidth > HELI_PRECISION)
deef1d
		ellipse(x - s->strokeWidth*0.5, y - s->strokeWidth*0.5, s->strokeWidth, s->strokeWidth);
deef1d
	restoreState();
ba9f06
}
07b70f
07b70f
void arcPath(double x, double y, double w, double h, double start, double stop) {
ba9f06
	w *= 0.5; x += w;
ba9f06
	h *= 0.5; y += h;
0b65e7
	if (fabs(w) < HELI_PRECISION || fabs(h) < HELI_PRECISION) return;
0b65e7
	double step = fabs(w) > fabs(h) ? 1/fabs(w) : 1/fabs(h);
0b65e7
	if (step > PI/180) step = PI/180;
07b70f
	start *= PI/180;
07b70f
	stop *= PI/180;
ba9f06
	double a = start;
ba9f06
	if (start < stop) {
ba9f06
		while(1) {
ba9f06
			if (a > stop) a = stop;
ba9f06
			lineTo(x + cos(a)*w, y + sin(a)*h);
ba9f06
			if (a == stop) break;
ba9f06
			a += step;
ba9f06
		}
ba9f06
	} else {
ba9f06
		while(1) {
ba9f06
			if (a < stop) a = stop;
ba9f06
			lineTo(x + cos(a)*w, y + sin(a)*h);
ba9f06
			if (a == stop) break;
ba9f06
			a -= step;
ba9f06
		}
07b70f
	}
07b70f
}
07b70f
07b70f
void arc(double x, double y, double w, double h, double start, double stop) {
07b70f
	resetPath();
07b70f
	arcPath(x, y, w, h, start, stop);
07b70f
	strokePath();
07b70f
}
07b70f
b4b587
void regularPolygon(double x, double y, int sides, double radius, double angle) {
b4b587
	angle *= PI/180.0;
07b70f
	resetPath();
b4b587
	moveTo(x + radius*cos(angle), y + radius*sin(angle));
07b70f
	for(int i = 1; i < sides; ++i) {
b4b587
		double a = i*2*PI/sides + angle;
b4b587
		lineTo(x + radius*cos(a), y + radius*sin(a));
07b70f
	}
ba9f06
	closePathSimple();
07b70f
}
07b70f
07b70f
07b70f
void resetPath()
07b70f
	{ pathSize = 0; }
07b70f
deef1d
b4b587
void heliFillCircleSimple(double x, double y, double r, double aaBorder, int antialiasing) {
deef1d
	r = fabs(r);
deef1d
	if (r < HELI_PRECISION) return;
deef1d
	
deef1d
	HeliDrawingState *s = heliDrawingGetState();
deef1d
	if (s->fillColor[3] <= HELI_PRECISION) return;
b4b587
	if (!s->antialiasing) antialiasing = FALSE;
deef1d
	
deef1d
	heliDrawingApplyTexture(s->fillColor, &s->fillTexture);
deef1d
	
deef1d
	double step = 1/r;
deef1d
	if (step > PI/180) step = PI/180;
b4b587
	if (!antialiasing || aaBorder < HELI_PRECISION) {
b4b587
		if (antialiasing) glEnable(GL_MULTISAMPLE);
deef1d
		glBegin(GL_TRIANGLE_FAN);
deef1d
		for(double a = 0; a < 2*PI; a += step) glVertex2d(x + cos(a)*r, y + sin(a)*r);
deef1d
		glEnd();
deef1d
		glDisable(GL_MULTISAMPLE);
deef1d
	} else {
deef1d
		double color[4] = { s->fillColor[0], s->fillColor[1], s->fillColor[2], s->fillColor[3] };
deef1d
		double sideColor[4] = { color[0], color[1], color[2], 0 };
deef1d
		double r1 = r + 0.5*aaBorder;
deef1d
		double r0 = r - 0.5*aaBorder;
deef1d
		if (r0 > HELI_PRECISION) {
deef1d
			glBegin(GL_TRIANGLE_FAN);
deef1d
			for(double a = 0; a < 2*PI; a += step) glVertex2d(x + cos(a)*r0, y + sin(a)*r0);
deef1d
			glEnd();
deef1d
		} else {
deef1d
			color[3] *= r/aaBorder;
deef1d
			r0 = 0;
deef1d
		}
deef1d
		glBegin(GL_TRIANGLE_STRIP);
deef1d
		for(double a = step; a < 2*PI; a += step) {
deef1d
			double s = sin(a), c = cos(a);
deef1d
			glColor4dv(color);
deef1d
			glVertex2d(x + c*r0, y + s*r0);
deef1d
			glColor4dv(sideColor);
deef1d
			glVertex2d(x + c*r1, y + s*r1);
deef1d
		}
deef1d
		glEnd();
deef1d
	}
deef1d
}
deef1d
deef1d
b4b587
void heliFillRectSimple(double x0, double y0, double x1, double y1, double aaBorder, int antialiasing) {
deef1d
	if (fabs(x1 - x0) < HELI_PRECISION || fabs(y1 - y0) < HELI_PRECISION) return;
deef1d
	
deef1d
	HeliDrawingState *s = heliDrawingGetState();
deef1d
	if (s->fillColor[3] <= HELI_PRECISION) return;
b4b587
	if (!s->antialiasing) antialiasing = FALSE;
deef1d
	
deef1d
	heliDrawingApplyTexture(s->fillColor, &s->fillTexture);
b4b587
	if (!antialiasing || aaBorder <= HELI_PRECISION) {
b4b587
		if (antialiasing) glEnable(GL_MULTISAMPLE);
deef1d
		glBegin(GL_QUADS);
deef1d
		glVertex2d(x0, y0);
deef1d
		glVertex2d(x1, y0);
deef1d
		glVertex2d(x1, y1);
deef1d
		glVertex2d(x0, y1);
deef1d
		glEnd();
deef1d
		glDisable(GL_MULTISAMPLE);
deef1d
	} else {
deef1d
		double color[4] = { s->fillColor[0], s->fillColor[1], s->fillColor[2], s->fillColor[3] };
deef1d
		double sideColor[4] = { color[0], color[1], color[2], 0 };
deef1d
		
deef1d
		if (x1 < x0) { double x = x0; x0 = x1; x1 = x; }
deef1d
		if (y1 < y0) { double y = y0; y0 = y1; y1 = y; }
deef1d
		double x  = (x1 + x0)*0.5;
deef1d
		double y  = (y1 + y0)*0.5;
deef1d
		double hw = (x1 - x0)*0.5;
deef1d
		double hh = (y1 - y0)*0.5;
deef1d
		
f8dca4
		double k0 = 0;
f8dca4
		double k1 = 0.5;
f8dca4
		double w0 = hw - k0*aaBorder;
f8dca4
		double w1 = hw + k1*aaBorder;
deef1d
		if (w0 < HELI_PRECISION) w0 = 0;
f8dca4
		double h0 = hh - k0*aaBorder;
f8dca4
		double h1 = hh + k1*aaBorder;
deef1d
		if (h0 < HELI_PRECISION) h0 = 0;
deef1d
		
f8dca4
		const double k = (w0 ? 1 : w1/((k0+k1)*aaBorder))
f8dca4
		               * (h0 ? 1 : h1/((k0+k1)*aaBorder));
deef1d
		color[3] *= k;
deef1d
		
deef1d
		const int vcnt = 14;
deef1d
		
deef1d
		const double vertices[][2] = {
f8dca4
			{ x-w1, y-h1 }, { x-w0, y-h0 },
f8dca4
			{ x+w1, y-h1 }, { x+w0, y-h0 },
f8dca4
			{ x+w1, y+h1 }, { x+w0, y+h0 },
f8dca4
			{ x-w1, y+h1 }, { x-w0, y+h0 },
f8dca4
			{ x-w1, y-h1 }, { x-w0, y-h0 },
f8dca4
			{ x-w0, y-h0 },
f8dca4
			{ x+w0, y-h0 },
f8dca4
			{ x-w0, y+h0 },
f8dca4
			{ x+w0, y+h0 } };
deef1d
		
deef1d
		const double *colors[] = {
deef1d
			sideColor, color,
deef1d
			sideColor, color,
deef1d
			sideColor, color,
deef1d
			sideColor, color,
deef1d
			sideColor, color,
deef1d
			color,
deef1d
			color,
deef1d
			color,
deef1d
			color };
deef1d
		
deef1d
		glBegin(GL_TRIANGLE_STRIP);
deef1d
		for(int i = 0; i < vcnt; ++i) {
deef1d
			glColor4dv(colors[i]);
deef1d
			glVertex2dv(vertices[i]);
deef1d
		}
deef1d
		glEnd();
deef1d
	}
deef1d
	heliDrawingResetTexture();
deef1d
}
deef1d
deef1d
b4b587
static void drawFillSimple(int antialiasing) {
3954ba
	HeliDrawingState *s = heliDrawingGetState();
deef1d
	if (s->fillColor[3] <= HELI_PRECISION) return;
ba9f06
	if (pathSize < 6) return;
ba9f06
	
deef1d
	heliDrawingApplyTexture(s->fillColor, &s->fillTexture);
b4b587
	if (antialiasing && s->antialiasing) glEnable(GL_MULTISAMPLE);
ba9f06
	glBegin(GL_TRIANGLE_FAN);
ba9f06
	for(int i = 0; i < pathSize; i += 2)
ba9f06
		glVertex2dv(&path[i]);
ba9f06
	glEnd();
ba9f06
	glDisable(GL_MULTISAMPLE);
deef1d
	heliDrawingResetTexture();
ba9f06
}
ba9f06
b4b587
static void drawFill(int antialiasing) {
3954ba
	HeliDrawingState *s = heliDrawingGetState();
deef1d
	if (s->fillColor[3] <= HELI_PRECISION) return;
ba9f06
	if (pathSize < 6) return;
ba9f06
	
ba9f06
	double l = path[0], r = l;
ba9f06
	double t = path[1], b = t;
ba9f06
	for(int i = 2; i < pathSize; i += 2) {
ba9f06
		if (l > path[i]) l = path[i];
ba9f06
		if (r < path[i]) r = path[i];
ba9f06
		if (t > path[i + 1]) t = path[i + 1];
ba9f06
		if (b < path[i + 1]) b = path[i + 1];
07b70f
	}
ba9f06
	if ( r - l <= HELI_PRECISION
ba9f06
	  || b - t <= HELI_PRECISION) return;
ba9f06
	l -= 1; r += 1;
ba9f06
	t -= 1; b += 1;
ba9f06
	
b4b587
	if (antialiasing && s->antialiasing) glEnable(GL_MULTISAMPLE);
ba9f06
	glEnable(GL_STENCIL_TEST);
ba9f06
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
ba9f06
ba9f06
	glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
ba9f06
	glBegin(GL_QUADS);
ba9f06
	glVertex2d(l, t);
ba9f06
	glVertex2d(r, t);
ba9f06
	glVertex2d(r, b);
ba9f06
	glVertex2d(l, b);
ba9f06
	glEnd();
ba9f06
	
1d641c
	if (heliGLStencilOpSeparatePtr) {
1d641c
		heliGLStencilOpSeparatePtr(GL_FRONT, GL_INCR_WRAP, GL_INCR_WRAP, GL_INCR_WRAP);
1d641c
		heliGLStencilOpSeparatePtr(GL_BACK, GL_DECR_WRAP, GL_DECR_WRAP, GL_DECR_WRAP);
87fe10
		glBegin(GL_TRIANGLE_FAN);
87fe10
		for(int i = 0; i < pathSize; i += 2)
87fe10
			glVertex2dv(&path[i]);
87fe10
		glEnd();
87fe10
	} else {
87fe10
		glEnable(GL_CULL_FACE);
87fe10
		
87fe10
		glStencilOp(GL_DECR_WRAP, GL_DECR_WRAP, GL_DECR_WRAP);
87fe10
		glCullFace(GL_FRONT);
87fe10
		glBegin(GL_TRIANGLE_FAN);
87fe10
		for(int i = 0; i < pathSize; i += 2)
87fe10
			glVertex2dv(&path[i]);
87fe10
		glEnd();
87fe10
		
87fe10
		glStencilOp(GL_INCR_WRAP, GL_INCR_WRAP, GL_INCR_WRAP);
87fe10
		glCullFace(GL_BACK);
87fe10
		glBegin(GL_TRIANGLE_FAN);
87fe10
		for(int i = 0; i < pathSize; i += 2)
87fe10
			glVertex2dv(&path[i]);
87fe10
		glEnd();
87fe10
		
87fe10
		glDisable(GL_CULL_FACE);
87fe10
	}
ba9f06
	
ba9f06
	glStencilFunc(GL_NOTEQUAL, 0, -1u);
ba9f06
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
ba9f06
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
deef1d
deef1d
	heliDrawingApplyTexture(s->fillColor, &s->fillTexture);
ba9f06
	glBegin(GL_QUADS);
ba9f06
	glVertex2d(l, t);
ba9f06
	glVertex2d(r, t);
ba9f06
	glVertex2d(r, b);
ba9f06
	glVertex2d(l, b);
ba9f06
	glEnd();
deef1d
	heliDrawingResetTexture();
ba9f06
	
ba9f06
	glStencilFunc(GL_ALWAYS, 0, -1u);
ba9f06
	glDisable(GL_STENCIL_TEST);
ba9f06
	glDisable(GL_MULTISAMPLE);
ba9f06
}
ba9f06
0b65e7
static void strokeAddPoint(HeliStroke *stroke, double x, double y, double dx, double dy) {
0b65e7
	if (stroke->allocatedCount < stroke->count + 1) {
0b65e7
		stroke->allocatedCount += stroke->allocatedCount/4 + 32;
0b65e7
		stroke->points = realloc(stroke->points, stroke->allocatedCount*4*sizeof(*stroke->points));
0b65e7
		memset(stroke->points + stroke->count, 0, (stroke->allocatedCount - stroke->count)*sizeof(*stroke->points));
0b65e7
	}
0b65e7
	HeliStrokePoint *p = &stroke->points[ stroke->count++ ];
0b65e7
	p->x = x;
0b65e7
	p->y = y;
0b65e7
	p->dx = dx;
0b65e7
	p->dy = dy;
0b65e7
}
0b65e7
0b65e7
static void strokeAddCornerSub(HeliStroke *stroke, double dx, double dy, double precisionSqr, int level) {
0b65e7
	assert(stroke->count > 0);
0b65e7
	HeliStrokePoint *prev = stroke->points + stroke->count - 1;
0b65e7
	if (level > 0) {
0b65e7
		double distx = dx - prev->dx;
0b65e7
		double disty = dy - prev->dy;
0b65e7
		if (distx*distx + disty*disty > precisionSqr) {
0b65e7
			double mx = prev->dx + dx;
0b65e7
			double my = prev->dy + dy;
0b65e7
			double lsqr = mx*mx + my*my;
0b65e7
			if (lsqr > HELI_PRECISION_SQR) {
0b65e7
				double kl = 1/sqrt(lsqr);
0b65e7
				mx *= kl;
0b65e7
				my *= kl;
0b65e7
				strokeAddCornerSub(stroke, mx, my, precisionSqr, level-1);
0b65e7
				strokeAddCornerSub(stroke, dx, dy, precisionSqr, level-1);
0b65e7
				return;
0b65e7
			}
0b65e7
		}
0b65e7
	}
0b65e7
	strokeAddPoint(stroke, prev->x, prev->y, dx, dy);
0b65e7
}
0b65e7
0b65e7
static void strokeAddCorner(
0b65e7
	HeliStroke *stroke,
0b65e7
	double xp, double yp,
0b65e7
	double x0, double y0,
0b65e7
	double x1, double y1,
0b65e7
	double precisionSqr )
0b65e7
{
0b65e7
	double dx0 = x0 - xp;
0b65e7
	double dy0 = y0 - yp;
0b65e7
	double dx1 = x1 - x0;
0b65e7
	double dy1 = y1 - y0;
0b65e7
	
0b65e7
	double l0 = sqrt(dx0*dx0 + dy0*dy0);
0b65e7
	double l1 = sqrt(dx1*dx1 + dy1*dy1);
0b65e7
	assert(l0 > HELI_PRECISION);
0b65e7
	assert(l1 > HELI_PRECISION);
0b65e7
	
0b65e7
	double kl0 = 1/l0, kl1 = 1/l1;
0b65e7
	double nx0 = dx0*kl0, ny0 = dy0*kl0;
0b65e7
	double nx1 = dx1*kl1, ny1 = dy1*kl1;
89ec47
	assert(fabs(sqrt(nx0*nx0 + ny0*ny0) - 1) <= HELI_PRECISION);
89ec47
	assert(fabs(sqrt(nx1*nx1 + ny1*ny1) - 1) <= HELI_PRECISION);
0b65e7
	
0b65e7
	double dot = nx0*ny1 - ny0*nx1;
0b65e7
	if (dot > HELI_PRECISION) {
0b65e7
		// round
0b65e7
		strokeAddPoint(stroke, x0, y0, ny0, -nx0);
0b65e7
		strokeAddCornerSub(stroke, ny1, -nx1, precisionSqr, 8);
0b65e7
	} else
89ec47
	if (dot < -HELI_PRECISION) {
0b65e7
		// corner
0b65e7
		double dx = nx1 - nx0;
0b65e7
		double dy = ny1 - ny0;
0b65e7
		double kl = 1/sqrt(dx*dx + dy*dy);
0b65e7
		double nx = dx*kl, ny = dy*kl;
0b65e7
		double c = nx*nx1 + ny*ny1;
0b65e7
		double cl = 1/sqrt(1 - c*c);
89ec47
		if (cl >  3) cl =  3;
0b65e7
		strokeAddPoint(stroke, x0, y0, nx*cl, ny*cl);
0b65e7
	} else {
0b65e7
		// flat
0b65e7
		strokeAddPoint(stroke, x0, y0, ny0, -nx0);
0b65e7
	}
0b65e7
}
0b65e7
b4b587
static void strokeDraw(HeliStroke *stroke, double w, double *color, int antialiasing) {
8bc1f1
	const double aaBorder = heliGLGetAAResolution();
b4b587
	if (antialiasing && aaBorder > HELI_PRECISION) {
0b65e7
		double c0[4] = { color[0], color[1], color[2], 0 };
0b65e7
		double c1[4] = { color[0], color[1], color[2], color[3] };
0b65e7
		double w0 = w + 0.5*aaBorder;
0b65e7
		double w1 = w - 0.5*aaBorder;
0b65e7
		if (w1 < HELI_PRECISION) {
0b65e7
			c1[3] *= w0/aaBorder;
0b65e7
			w1 = 0;
0b65e7
		}
0b65e7
		
0b65e7
		glBegin(GL_TRIANGLE_STRIP);
0b65e7
		for(int i = 0; i < stroke->count; ++i) {
0b65e7
			HeliStrokePoint *p = stroke->points + i;
0b65e7
			glColor4dv(c0);
0b65e7
			glVertex2d(p->x + p->dx*w0, p->y + p->dy*w0);
0b65e7
			glColor4dv(c1);
0b65e7
			glVertex2d(p->x + p->dx*w1, p->y + p->dy*w1);
0b65e7
		}
0b65e7
		glEnd();
0b65e7
		
0b65e7
		if (w1 > 0) {
0b65e7
			glColor4dv(c1);
0b65e7
			glBegin(GL_TRIANGLE_STRIP);
0b65e7
			for(int i = 0; i < stroke->count; ++i) {
0b65e7
				HeliStrokePoint *p = stroke->points + i;
0b65e7
				glVertex2d(p->x, p->y);
0b65e7
				glVertex2d(p->x + p->dx*w1, p->y + p->dy*w1);
0b65e7
			}
0b65e7
			glEnd();
0b65e7
		}
0b65e7
		
0b65e7
		glColor4d(1, 1, 1, 1);
0b65e7
	} else {
0b65e7
		glColor4dv(color);
b4b587
		if (antialiasing) glEnable(GL_MULTISAMPLE);
0b65e7
		glBegin(GL_TRIANGLE_STRIP);
0b65e7
		for(int i = 0; i < stroke->count; ++i) {
0b65e7
			HeliStrokePoint *p = stroke->points + i;
0b65e7
			glVertex2d(p->x, p->y);
0b65e7
			glVertex2d(p->x + p->dx*w, p->y + p->dy*w);
0b65e7
		}
0b65e7
		glEnd();
0b65e7
		glDisable(GL_MULTISAMPLE);
0b65e7
		glColor4d(1, 1, 1, 1);
0b65e7
	}
0b65e7
}
0b65e7
0b65e7
0b65e7
static void drawStroke(int close) {
3954ba
	HeliDrawingState *s = heliDrawingGetState();
deef1d
	if (s->strokeColor[3] <= HELI_PRECISION) return;
deef1d
	if (s->strokeWidth < HELI_PRECISION) return;
ba9f06
	if (pathSize < 4) return;
0b65e7
	
0b65e7
	HeliStroke stroke = {};
0b65e7
	stroke.allocatedCount = pathSize;
0b65e7
	stroke.points = calloc(stroke.allocatedCount, sizeof(*stroke.points));
0b65e7
	
deef1d
	double w = 0.5*s->strokeWidth;
b4b587
	double ps = heliGLGetPixelSize();
812e27
	double precisionSqr = ps > HELI_PRECISION ? ps : 0.5*w;
b4b587
	precisionSqr *= 0.5;
0b65e7
	precisionSqr *= precisionSqr;
0b65e7
	
deef1d
	heliDrawingApplyTexture(s->strokeColor, &s->strokeTexture);
deef1d
	
0b65e7
	if (close) {
0b65e7
		for(int i = 0; i < pathSize; i += 2) {
0b65e7
			int p = i > 0 ? i - 2 : pathSize - 2;
0b65e7
			int n = i < pathSize - 2 ? i + 2 : 0;
0b65e7
			strokeAddCorner(
0b65e7
				&stroke,
0b65e7
				path[p], path[p+1],
0b65e7
				path[i], path[i+1],
0b65e7
				path[n], path[n+1],
0b65e7
				precisionSqr );
0b65e7
		}
0b65e7
		strokeAddPoint(
0b65e7
			&stroke,
0b65e7
			stroke.points[0].x,
0b65e7
			stroke.points[0].y,
0b65e7
			stroke.points[0].dx,
0b65e7
			stroke.points[0].dy );
b4b587
		strokeDraw(&stroke, w, s->strokeColor, s->antialiasing);
0b65e7
		
0b65e7
		stroke.count = 0;
0b65e7
		
0b65e7
		for(int i = pathSize - 2; i >= 0; i -= 2) {
0b65e7
			int p = i < pathSize - 2 ? i + 2 : 0;
0b65e7
			int n = i > 0 ? i - 2 : pathSize - 2;
0b65e7
			strokeAddCorner(
0b65e7
				&stroke,
0b65e7
				path[p], path[p+1],
0b65e7
				path[i], path[i+1],
0b65e7
				path[n], path[n+1],
0b65e7
				precisionSqr );
0b65e7
		}
0b65e7
		strokeAddPoint(
0b65e7
			&stroke,
0b65e7
			stroke.points[0].x,
0b65e7
			stroke.points[0].y,
0b65e7
			stroke.points[0].dx,
0b65e7
			stroke.points[0].dy );
b4b587
		strokeDraw(&stroke, w, s->strokeColor, s->antialiasing);
0b65e7
	} else {
0b65e7
		double px = 0, py = 0, nx = 0, ny = 0;
0b65e7
		for(int i = 0; i < pathSize; i += 2) {
0b65e7
			double x = path[i], y = path[i+1];
0b65e7
			if (i > 0)            { px = path[i-2]; py = path[i-1]; }
0b65e7
			if (i < pathSize - 2) { nx = path[i+2]; ny = path[i+3]; }
0b65e7
			if (i == 0)            { px = x + y - ny; py = y + nx - x; }
0b65e7
			if (i == pathSize - 2) { nx = x + py - y; ny = y + x - px; }
0b65e7
			strokeAddCorner(&stroke, px, py, x, y, nx, ny, precisionSqr);
0b65e7
		}
0b65e7
		--stroke.count;
0b65e7
		for(int i = pathSize - 2; i >= 0; i -= 2) {
0b65e7
			double x = path[i], y = path[i+1];
0b65e7
			if (i > 0)            { nx = path[i-2]; ny = path[i-1]; }
0b65e7
			if (i < pathSize - 2) { px = path[i+2]; py = path[i+3]; }
0b65e7
			if (i == 0)            { nx = x + py - y; ny = y + x - px; }
0b65e7
			if (i == pathSize - 2) { px = x + y - ny; py = y + nx - x; }
0b65e7
			strokeAddCorner(&stroke, px, py, x, y, nx, ny, precisionSqr);
0b65e7
		}
0b65e7
		memcpy(stroke.points + stroke.count - 1, stroke.points, sizeof(*stroke.points));
b4b587
		strokeDraw(&stroke, w, s->strokeColor, s->antialiasing);
0b65e7
	}
0b65e7
	
deef1d
	heliDrawingResetTexture();
0b65e7
	free(stroke.points);
07b70f
}
07b70f
07b70f
static void pushPathPoint(double x, double y) {
07b70f
	if (pathAllocated < pathSize + 2) {
07b70f
		pathAllocated += pathAllocated/4 + 32;
a2530e
		path = realloc(path, pathAllocated*sizeof(*path));
07b70f
		memset(&path[pathSize], 0, (pathAllocated - pathSize)*sizeof(*path));
07b70f
	}
07b70f
	path[pathSize++] = x;
07b70f
	path[pathSize++] = y;
07b70f
}
07b70f
ba9f06
static void endPath(int close, int stroke, int fill, int fillSimple) {
89ec47
	int origPathSize = pathSize;
89ec47
	
0b65e7
	// remove duplicates
0b65e7
	int removed = 0;
0b65e7
	for(int i = 2; i < pathSize; i += 2) {
0b65e7
		if ( fabs(path[i-removed-2] - path[i]) <= 2*HELI_PRECISION
0b65e7
		  && fabs(path[i-removed-1] - path[i+1]) <= 2*HELI_PRECISION )
0b65e7
		{
0b65e7
			removed += 2;
0b65e7
		} else
0b65e7
		if (removed > 0) {
0b65e7
			path[i-removed] = path[i];
0b65e7
			path[i-removed+1] = path[i+1];
0b65e7
		}
0b65e7
	}
0b65e7
	
0b65e7
	// remove tails duplicates
0b65e7
	pathSize -= removed;
0b65e7
	if ( pathSize >= 4
0b65e7
	  && close
0b65e7
	  && fabs(path[pathSize-2] - path[0]) <= 2*HELI_PRECISION
0b65e7
	  && fabs(path[pathSize-1] - path[1]) <= 2*HELI_PRECISION )
0b65e7
		pathSize -= 2;
0b65e7
	
b4b587
	// if shape convex?
b4b587
	if (pathSize >= 6 && fill && !fillSimple) {
b4b587
		int convex = TRUE;
b4b587
		int d = 0;
b4b587
		double x0 = path[pathSize-4];
b4b587
		double y0 = path[pathSize-3];
b4b587
		double x1 = path[pathSize-2];
b4b587
		double y1 = path[pathSize-1];
b4b587
		for(int i = 0; i < pathSize; i += 2) {
b4b587
			double x2 = path[i];
b4b587
			double y2 = path[i+1];
b4b587
			double xx0 = x1 - x0;
b4b587
			double yy0 = y1 - y0;
b4b587
			double xx1 = y1 - y2;
b4b587
			double yy1 = x2 - x1;
b4b587
			double dot = xx0*xx1 + yy0*yy1;
b4b587
			double ll2 = (xx0*xx0 + yy0*yy0)*(xx1*xx1 + yy1*yy1);
b4b587
			double s2 = dot*dot/ll2;
b4b587
			if (s2 > HELI_PRECISION_SQR) {
b4b587
				int dd = dot > 0.0 ? 1 : -1;
b4b587
				if (!d) d = dd; else
b4b587
					if (d != dd) { convex = FALSE; break; }
b4b587
				x0 = x1; y0 = y1;
b4b587
				x1 = x2; y1 = y2;
b4b587
			}
b4b587
		}
b4b587
		if (convex) { fill = FALSE; fillSimple = TRUE; }
b4b587
	}
b4b587
		
0b65e7
	// draw
89ec47
	int isPoint = FALSE;
89ec47
	double px, py;
ba9f06
	if (pathSize >= 4) {
b4b587
		if (pathSize >= 6 && (fillSimple || fill)) {
b4b587
			HeliDrawingState *s = heliDrawingGetState();
b4b587
			int antialiasing = s->antialiasing && (!stroke || !isStrokeSolid(s, NULL));
b4b587
			if (fillSimple) drawFillSimple(antialiasing);
b4b587
			           else drawFill(antialiasing);
b4b587
		}
0b65e7
		if (stroke) drawStroke(close);
89ec47
	} else
89ec47
	if (origPathSize >= 4 && stroke) {
89ec47
		px = path[0];
89ec47
		py = path[1];
89ec47
		isPoint = TRUE;
ba9f06
	}
0b65e7
	
ba9f06
	resetPath();
89ec47
	
89ec47
	if (isPoint) point(px, py);
ba9f06
}
ba9f06
07b70f
void closePath()
ba9f06
	{ endPath(TRUE, TRUE, TRUE, FALSE); }
07b70f
void strokePath()
ba9f06
	{ endPath(FALSE, TRUE, FALSE, FALSE); }
07b70f
void lineTo(double x, double y)
07b70f
	{ pushPathPoint(x, y); }
07b70f
void moveTo(double x, double y)
07b70f
	{ resetPath(); lineTo(x, y); }
07b70f
07b70f
3954ba
HeliDrawingState *heliDrawingGetState()
3954ba
	{ return statesStack + statesStackIndex; }
3954ba
3954ba
HeliDrawingState *heliDrawingGetStateStack()
3954ba
	{ return statesStack; }
3954ba
3e7c5f
void resetState()
3e7c5f
	{ resetStateEx(STATE_ALL); }
3e7c5f
3e7c5f
void resetStateEx(unsigned int flags) {
7ac038
	if (flags & STATE_FILL_COLOR        ) fill(COLOR_WHITE);
1d641c
	if (flags & STATE_FILL_TEXTURE      ) noFillTexture();
7ac038
	if (flags & STATE_STROKE_COLOR      ) stroke(COLOR_BLACK);
1d641c
	if (flags & STATE_STROKE_TEXTURE    ) noStrokeTexture();
1d641c
	if (flags & STATE_STROKE_WIDTH      ) strokeWidth(1);
1d641c
	if (flags & STATE_TEXT_ALIGN        ) textAlign(HALIGN_LEFT, VALIGN_TOP);
1d641c
	if (flags & STATE_TEXT_SIZE         ) textSize(24);
20d197
	if (flags & STATE_TEXT_FONT         ) textFontDefault();
1d641c
	if (flags & STATE_TARGET_FRAMEBUFFER) targetEx(NULL, FALSE, FALSE);
1d641c
	if (flags & STATE_TARGET_VIEWPORT   ) viewportByWindow();
1d641c
	if (flags & STATE_TARGET_PROJECTION ) projectionByViewport();
7ac038
	if (flags & STATE_BACKGROUND        ) background(COLOR_WHITE);
d6f40c
	if (flags & STATE_TRANSFORM         ) noTransform();
d6f40c
	if (flags & STATE_CLIP              ) noClip();
b4b587
	if (flags & STATE_ANTIALIASING      ) enableAntialiasing();
3e7c5f
}
3e7c5f
3e7c5f
void saveState()
deef1d
	{ saveStateEx(STATE_ALL); }
deef1d
deef1d
void saveStateEx(unsigned int flags) {
3954ba
	if (statesStackIndex >= sizeof(statesStack)/sizeof(*statesStack) - 1) {
3954ba
		fprintf(stderr, "helianthus: drawing stack overflow\n");
3954ba
		return;
3954ba
	}
3954ba
	++statesStackIndex;
3954ba
	memcpy(statesStack + statesStackIndex, statesStack + statesStackIndex - 1, sizeof(*statesStack));
deef1d
	statesStack[statesStackIndex].flags = flags;
deef1d
	heliGLGetCommonState(&statesStack[statesStackIndex].glState, flags);
3954ba
}
3954ba
deef1d
void restoreState() {
3954ba
	assert(statesStackIndex > 0);
3954ba
	if (statesStackIndex <= 0) {
3954ba
		fprintf(stderr, "helianthus: drawing stack underflow\n");
3954ba
		return;
3954ba
	}
deef1d
	
deef1d
	HeliDrawingState *prev = &statesStack[statesStackIndex];
deef1d
	heliGLSetCommonState(&prev->glState);
3954ba
	--statesStackIndex;
deef1d
	HeliDrawingState *curr = &statesStack[statesStackIndex];
deef1d
	
deef1d
	unsigned int flags = ~prev->flags;
deef1d
	if (flags & STATE_FILL_COLOR)
deef1d
		memcpy(curr->fillColor, prev->fillColor, sizeof(curr->fillColor));
deef1d
	if (flags & STATE_FILL_TEXTURE)
deef1d
		memcpy(&curr->fillTexture, &prev->fillTexture, sizeof(curr->fillTexture));
deef1d
	if (flags & STATE_STROKE_COLOR)
deef1d
		memcpy(curr->strokeColor, prev->strokeColor, sizeof(curr->strokeColor));
deef1d
	if (flags & STATE_STROKE_TEXTURE)
deef1d
		memcpy(&curr->strokeTexture, &prev->strokeTexture, sizeof(curr->strokeTexture));
deef1d
	if (flags & STATE_STROKE_WIDTH)
deef1d
		curr->strokeWidth = prev->strokeWidth;
deef1d
	if (flags & STATE_TEXT_ALIGN)
deef1d
		{ curr->horAlign = prev->horAlign; curr->vertAlign = prev->vertAlign; }
deef1d
	if (flags & STATE_TEXT_SIZE)
deef1d
		curr->fontSize = prev->fontSize;
deef1d
	if (flags & STATE_TEXT_FONT)
deef1d
		curr->font = prev->font;
b4b587
	if (flags & STATE_ANTIALIASING)
b4b587
		curr->antialiasing = prev->antialiasing;
3954ba
}
3954ba
f63775
void heliDrawingClearFrame() {
1d641c
	unsigned int prev;
1d641c
	glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (int*)&prev);
d6f40c
	clear();
1d641c
	if (heliGLBindFramebufferPtr) heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, prev);
07b70f
}
07b70f
650f35
void heliDrawingPrepareFrame() {
650f35
	resetPath();
3954ba
	
f63775
	heliDrawingClearFrame();
a20939
	glDisable(GL_DEPTH_TEST);
ba9f06
	glDisable(GL_STENCIL_TEST);
a20939
	glDisable(GL_CULL_FACE);
a20939
	glDisable(GL_MULTISAMPLE);
ba9f06
	
ba9f06
	glEnable(GL_BLEND);
1d641c
	if (heliGLBlendFuncSeparatePtr)
89ec47
		heliGLBlendFuncSeparatePtr(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE);
1d641c
	else
1d641c
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
ba9f06
	glEnable(GL_LINE_SMOOTH);
ba9f06
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
650f35
}
650f35
07b70f
void heliDrawingFinish() {
07b70f
	resetPath();
07b70f
	free(path);
07b70f
	path = NULL;
07b70f
	pathAllocated = 0;
909bc2
	heliArrayDestroy(&heliDrawingFramebuffersToFlush);
07b70f
}