Blame src/drawing.c

07b70f
07b70f
#include "private.h"
ba9f06
#include "world.h"
07b70f
#include "drawing.h"
07b70f
3c0f7f
07b70f
static double colorBack[4] = {1, 1, 1, 1};
07b70f
static double *path;
07b70f
static size_t pathSize;
07b70f
static size_t pathAllocated;
07b70f
3954ba
static HeliDrawingState statesStack[1024] = {{
3954ba
	{0, 0, 0, 1},       // colorStroke
3954ba
	{0.5, 0.5, 0.5, 1}, // colorFill
3954ba
	1,                  // lineWidth
3954ba
	HALIGN_LEFT,        // horAlign
3954ba
	VALIGN_TOP,         // vertAlign
3954ba
	NULL,               // font
3954ba
	24                  // fontSize
3954ba
}};
3954ba
static int statesStackIndex = 0;
3954ba
3954ba
07b70f
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
ba9f06
07b70f
void background(const char *color)
07b70f
	{ heliParseColor(color, colorBack); }
07b70f
void fill(const char *color)
3954ba
	{ heliParseColor(color, heliDrawingGetState()->colorFill); }
07b70f
void noFill()
07b70f
	{ fill("transparent"); }
07b70f
void stroke(const char *color)
3954ba
	{ heliParseColor(color, heliDrawingGetState()->colorStroke); }
07b70f
void noStroke()
07b70f
	{ stroke("transparent"); }
07b70f
07b70f
void strokeWeight(double weight)
3954ba
	{ heliDrawingGetState()->lineWidth = weight; }
07b70f
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
07b70f
void rect(double x, double y, double width, double height) {
07b70f
	resetPath();
07b70f
	moveTo(x, y);
07b70f
	lineTo(x + width, y);
07b70f
	lineTo(x + width, y + height);
07b70f
	lineTo(x, y + height);
ba9f06
	closePathSimple();
07b70f
}
07b70f
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();
07b70f
	arcPath(x, y, width, height, 0, 360);
ba9f06
	closePathSimple();
07b70f
}
07b70f
ba9f06
void point(double x, double y) {
3954ba
	pushDrawingState();
3954ba
	HeliDrawingState *s = heliDrawingGetState();
3954ba
	memcpy(s->colorFill, s->colorStroke, sizeof(s->colorFill));
3954ba
	s->colorStroke[3] = 0;
3954ba
	ellipse(x - s->lineWidth*0.5, y - s->lineWidth*0.5, s->lineWidth, s->lineWidth);
3954ba
	popDrawingState();
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
07b70f
void regularPolygon(double x, double y, int sides, double size) {
07b70f
	resetPath();
07b70f
	size *= 0.5;
07b70f
	moveTo(x + size, y);
07b70f
	for(int i = 1; i < sides; ++i) {
ba9f06
		double a = i*2*PI/sides;
07b70f
		lineTo(x + size*cos(a), y + size*sin(a));
07b70f
	}
ba9f06
	closePathSimple();
07b70f
}
07b70f
07b70f
07b70f
void resetPath()
07b70f
	{ pathSize = 0; }
07b70f
ba9f06
static void drawFillSimple() {
3954ba
	HeliDrawingState *s = heliDrawingGetState();
3954ba
	if (s->colorFill[3] <= HELI_PRECISION) return;
ba9f06
	if (pathSize < 6) return;
ba9f06
	
3954ba
	glColor4dv(s->colorFill);
ba9f06
	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);
ba9f06
	glColor4d(1, 1, 1, 1);
ba9f06
}
ba9f06
ba9f06
static void drawFill() {
3954ba
	HeliDrawingState *s = heliDrawingGetState();
3954ba
	if (s->colorFill[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
	
ba9f06
	glEnable(GL_MULTISAMPLE);
3954ba
	glColor4dv(s->colorFill);
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
	
ba9f06
	glStencilOpSeparate(GL_FRONT, GL_INCR_WRAP, GL_INCR_WRAP, GL_INCR_WRAP);
ba9f06
	glStencilOpSeparate(GL_BACK, GL_DECR_WRAP, GL_DECR_WRAP, GL_DECR_WRAP);
ba9f06
	glBegin(GL_TRIANGLE_FAN);
ba9f06
	for(int i = 0; i < pathSize; i += 2)
ba9f06
		glVertex2dv(&path[i]);
ba9f06
	glEnd();
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);
ba9f06
	glBegin(GL_QUADS);
ba9f06
	glVertex2d(l, t);
ba9f06
	glVertex2d(r, t);
ba9f06
	glVertex2d(r, b);
ba9f06
	glVertex2d(l, b);
ba9f06
	glEnd();
ba9f06
	
ba9f06
	glStencilFunc(GL_ALWAYS, 0, -1u);
ba9f06
	glDisable(GL_STENCIL_TEST);
ba9f06
	glDisable(GL_MULTISAMPLE);
ba9f06
	glColor4d(1, 1, 1, 1);
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;
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
0b65e7
	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);
0b65e7
		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
0b65e7
static void strokeDraw(HeliStroke *stroke, double w, double *color) {
0b65e7
	const double aaBorder = 1;
0b65e7
	if (aaBorder > 0) {
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);
0b65e7
		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();
3954ba
	if (s->colorStroke[3] <= HELI_PRECISION) return;
3954ba
	if (s->lineWidth < 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
	
3954ba
	double w = 0.5*s->lineWidth;
0b65e7
	double precisionSqr = 1/(w*0.75);
0b65e7
	precisionSqr *= precisionSqr;
0b65e7
	
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 );
3954ba
		strokeDraw(&stroke, w, s->colorStroke);
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 );
3954ba
		strokeDraw(&stroke, w, s->colorStroke);
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));
3954ba
		strokeDraw(&stroke, w, s->colorStroke);
0b65e7
	}
0b65e7
	
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) {
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
	
0b65e7
	// draw
ba9f06
	if (pathSize >= 4) {
ba9f06
		if (fillSimple) drawFillSimple();
ba9f06
			else if (fill) drawFill();
0b65e7
		if (stroke) drawStroke(close);
ba9f06
	}
0b65e7
	
ba9f06
	resetPath();
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
3954ba
void pushDrawingState() {
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));
3954ba
}
3954ba
3954ba
void popDrawingState() {
3954ba
	assert(statesStackIndex > 0);
3954ba
	if (statesStackIndex <= 0) {
3954ba
		fprintf(stderr, "helianthus: drawing stack underflow\n");
3954ba
		return;
3954ba
	}
3954ba
	--statesStackIndex;
3954ba
}
3954ba
f63775
void heliDrawingClearFrame() {
a20939
	glClearColor(colorBack[0], colorBack[1], colorBack[2], colorBack[3]);
f63775
	glClear(GL_COLOR_BUFFER_BIT);
07b70f
}
07b70f
650f35
void heliDrawingPrepareFrame() {
650f35
	resetPath();
3954ba
	if (statesStackIndex > 0) {
3954ba
		//fprintf(stderr, "helianthus: drawing stack was not empty at end of frame\n");
3954ba
		while(statesStackIndex > 0) popDrawingState();
3954ba
	}
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);
ba9f06
	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;
07b70f
}