From 0b65e73329e53b283e67da96e81205f310749df6 Mon Sep 17 00:00:00 2001 From: Ivan Mahonin Date: Jul 25 2020 05:15:57 +0000 Subject: opengl stroke --- diff --git a/demo/src/drawing.c b/demo/src/drawing.c index 1e9220e..de2c855 100644 --- a/demo/src/drawing.c +++ b/demo/src/drawing.c @@ -16,13 +16,13 @@ void drawingDraw() { double x = 512; double y = 512 - 64; - strokeWeight(5); + strokeWeight(20); fill("red"); stroke("blue"); rect(x, y, 48*2, 48); x += 48*2 + 16; - fill("#C71585AA"); stroke("0.5 0.5 0.5 0.5"); + fill("#C71585AA"); stroke("0 0 1 0.5"); ellipse(x, y, 48*2, 48); x += 48*2 + 16; diff --git a/src/drawing.c b/src/drawing.c index b700145..3b8deb9 100644 --- a/src/drawing.c +++ b/src/drawing.c @@ -15,6 +15,17 @@ static size_t pathSize; static size_t pathAllocated; +typedef struct _HeliStrokePoint { + double x, y, dx, dy; +} HeliStrokePoint; + +typedef struct _HeliStroke { + HeliStrokePoint *points; + int count; + int allocatedCount; +} HeliStroke; + + static void endPath(int close, int stroke, int fill, int fillSimple); @@ -78,9 +89,11 @@ void point(double x, double y) { } void arcPath(double x, double y, double w, double h, double start, double stop) { - const double step = PI/180; w *= 0.5; x += w; h *= 0.5; y += h; + if (fabs(w) < HELI_PRECISION || fabs(h) < HELI_PRECISION) return; + double step = fabs(w) > fabs(h) ? 1/fabs(w) : 1/fabs(h); + if (step > PI/180) step = PI/180; start *= PI/180; stop *= PI/180; double a = start; @@ -189,17 +202,210 @@ static void drawFill() { glColor4d(1, 1, 1, 1); } -static void drawStroke() { +static void strokeAddPoint(HeliStroke *stroke, double x, double y, double dx, double dy) { + if (stroke->allocatedCount < stroke->count + 1) { + stroke->allocatedCount += stroke->allocatedCount/4 + 32; + stroke->points = realloc(stroke->points, stroke->allocatedCount*4*sizeof(*stroke->points)); + memset(stroke->points + stroke->count, 0, (stroke->allocatedCount - stroke->count)*sizeof(*stroke->points)); + } + HeliStrokePoint *p = &stroke->points[ stroke->count++ ]; + p->x = x; + p->y = y; + p->dx = dx; + p->dy = dy; +} + +static void strokeAddCornerSub(HeliStroke *stroke, double dx, double dy, double precisionSqr, int level) { + assert(stroke->count > 0); + HeliStrokePoint *prev = stroke->points + stroke->count - 1; + if (level > 0) { + double distx = dx - prev->dx; + double disty = dy - prev->dy; + if (distx*distx + disty*disty > precisionSqr) { + double mx = prev->dx + dx; + double my = prev->dy + dy; + double lsqr = mx*mx + my*my; + if (lsqr > HELI_PRECISION_SQR) { + double kl = 1/sqrt(lsqr); + mx *= kl; + my *= kl; + strokeAddCornerSub(stroke, mx, my, precisionSqr, level-1); + strokeAddCornerSub(stroke, dx, dy, precisionSqr, level-1); + return; + } + } + } + strokeAddPoint(stroke, prev->x, prev->y, dx, dy); +} + +static void strokeAddCorner( + HeliStroke *stroke, + double xp, double yp, + double x0, double y0, + double x1, double y1, + double precisionSqr ) +{ + double dx0 = x0 - xp; + double dy0 = y0 - yp; + double dx1 = x1 - x0; + double dy1 = y1 - y0; + + double l0 = sqrt(dx0*dx0 + dy0*dy0); + double l1 = sqrt(dx1*dx1 + dy1*dy1); + assert(l0 > HELI_PRECISION); + assert(l1 > HELI_PRECISION); + + double kl0 = 1/l0, kl1 = 1/l1; + double nx0 = dx0*kl0, ny0 = dy0*kl0; + double nx1 = dx1*kl1, ny1 = dy1*kl1; + + double dot = nx0*ny1 - ny0*nx1; + if (dot > HELI_PRECISION) { + // round + strokeAddPoint(stroke, x0, y0, ny0, -nx0); + strokeAddCornerSub(stroke, ny1, -nx1, precisionSqr, 8); + } else + if (dot < HELI_PRECISION) { + // corner + double dx = nx1 - nx0; + double dy = ny1 - ny0; + double kl = 1/sqrt(dx*dx + dy*dy); + double nx = dx*kl, ny = dy*kl; + double c = nx*nx1 + ny*ny1; + double cl = 1/sqrt(1 - c*c); + if (cl > 3) cl = 3; + strokeAddPoint(stroke, x0, y0, nx*cl, ny*cl); + } else { + // flat + strokeAddPoint(stroke, x0, y0, ny0, -nx0); + } +} + +static void strokeDraw(HeliStroke *stroke, double w, double *color) { + const double aaBorder = 1; + if (aaBorder > 0) { + double c0[4] = { color[0], color[1], color[2], 0 }; + double c1[4] = { color[0], color[1], color[2], color[3] }; + double w0 = w + 0.5*aaBorder; + double w1 = w - 0.5*aaBorder; + if (w1 < HELI_PRECISION) { + c1[3] *= w0/aaBorder; + w1 = 0; + } + + glBegin(GL_TRIANGLE_STRIP); + for(int i = 0; i < stroke->count; ++i) { + HeliStrokePoint *p = stroke->points + i; + glColor4dv(c0); + glVertex2d(p->x + p->dx*w0, p->y + p->dy*w0); + glColor4dv(c1); + glVertex2d(p->x + p->dx*w1, p->y + p->dy*w1); + } + glEnd(); + + if (w1 > 0) { + glColor4dv(c1); + glBegin(GL_TRIANGLE_STRIP); + for(int i = 0; i < stroke->count; ++i) { + HeliStrokePoint *p = stroke->points + i; + glVertex2d(p->x, p->y); + glVertex2d(p->x + p->dx*w1, p->y + p->dy*w1); + } + glEnd(); + } + + glColor4d(1, 1, 1, 1); + } else { + glColor4dv(color); + glEnable(GL_MULTISAMPLE); + glBegin(GL_TRIANGLE_STRIP); + for(int i = 0; i < stroke->count; ++i) { + HeliStrokePoint *p = stroke->points + i; + glVertex2d(p->x, p->y); + glVertex2d(p->x + p->dx*w, p->y + p->dy*w); + } + glEnd(); + glDisable(GL_MULTISAMPLE); + glColor4d(1, 1, 1, 1); + } +} + + +static void drawStroke(int close) { if (colorStroke[3] <= HELI_PRECISION) return; + if (lineWidth < HELI_PRECISION) return; if (pathSize < 4) return; - - glColor4dv(colorStroke); - glLineWidth(lineWidth); - glBegin(GL_LINE_STRIP); - for(int i = 0; i < pathSize; i += 2) - glVertex2dv(&path[i]); - glEnd(); - glColor4d(1, 1, 1, 1); + + HeliStroke stroke = {}; + stroke.allocatedCount = pathSize; + stroke.points = calloc(stroke.allocatedCount, sizeof(*stroke.points)); + + double w = 0.5*lineWidth; + double precisionSqr = 1/(w*0.75); + precisionSqr *= precisionSqr; + + if (close) { + for(int i = 0; i < pathSize; i += 2) { + int p = i > 0 ? i - 2 : pathSize - 2; + int n = i < pathSize - 2 ? i + 2 : 0; + strokeAddCorner( + &stroke, + path[p], path[p+1], + path[i], path[i+1], + path[n], path[n+1], + precisionSqr ); + } + strokeAddPoint( + &stroke, + stroke.points[0].x, + stroke.points[0].y, + stroke.points[0].dx, + stroke.points[0].dy ); + strokeDraw(&stroke, w, colorStroke); + + stroke.count = 0; + + for(int i = pathSize - 2; i >= 0; i -= 2) { + int p = i < pathSize - 2 ? i + 2 : 0; + int n = i > 0 ? i - 2 : pathSize - 2; + strokeAddCorner( + &stroke, + path[p], path[p+1], + path[i], path[i+1], + path[n], path[n+1], + precisionSqr ); + } + strokeAddPoint( + &stroke, + stroke.points[0].x, + stroke.points[0].y, + stroke.points[0].dx, + stroke.points[0].dy ); + strokeDraw(&stroke, w, colorStroke); + } else { + double px = 0, py = 0, nx = 0, ny = 0; + for(int i = 0; i < pathSize; i += 2) { + double x = path[i], y = path[i+1]; + if (i > 0) { px = path[i-2]; py = path[i-1]; } + if (i < pathSize - 2) { nx = path[i+2]; ny = path[i+3]; } + if (i == 0) { px = x + y - ny; py = y + nx - x; } + if (i == pathSize - 2) { nx = x + py - y; ny = y + x - px; } + strokeAddCorner(&stroke, px, py, x, y, nx, ny, precisionSqr); + } + --stroke.count; + for(int i = pathSize - 2; i >= 0; i -= 2) { + double x = path[i], y = path[i+1]; + if (i > 0) { nx = path[i-2]; ny = path[i-1]; } + if (i < pathSize - 2) { px = path[i+2]; py = path[i+3]; } + if (i == 0) { nx = x + py - y; ny = y + x - px; } + if (i == pathSize - 2) { px = x + y - ny; py = y + nx - x; } + strokeAddCorner(&stroke, px, py, x, y, nx, ny, precisionSqr); + } + memcpy(stroke.points + stroke.count - 1, stroke.points, sizeof(*stroke.points)); + strokeDraw(&stroke, w, colorStroke); + } + + free(stroke.points); } static void pushPathPoint(double x, double y) { @@ -213,19 +419,35 @@ static void pushPathPoint(double x, double y) { } static void endPath(int close, int stroke, int fill, int fillSimple) { + // remove duplicates + int removed = 0; + for(int i = 2; i < pathSize; i += 2) { + if ( fabs(path[i-removed-2] - path[i]) <= 2*HELI_PRECISION + && fabs(path[i-removed-1] - path[i+1]) <= 2*HELI_PRECISION ) + { + removed += 2; + } else + if (removed > 0) { + path[i-removed] = path[i]; + path[i-removed+1] = path[i+1]; + } + } + + // remove tails duplicates + pathSize -= removed; + if ( pathSize >= 4 + && close + && fabs(path[pathSize-2] - path[0]) <= 2*HELI_PRECISION + && fabs(path[pathSize-1] - path[1]) <= 2*HELI_PRECISION ) + pathSize -= 2; + + // draw if (pathSize >= 4) { if (fillSimple) drawFillSimple(); else if (fill) drawFill(); - if (stroke) { - if ( close - && ( fabs(path[0] - path[pathSize - 2]) > HELI_PRECISION - || fabs(path[1] - path[pathSize - 1]) > HELI_PRECISION )) - { - pushPathPoint(path[0], path[1]); - } - drawStroke(); - } + if (stroke) drawStroke(close); } + resetPath(); }