|
|
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 |
}
|