From 0361bff7c9547753841f352515e0a4c15af5c60c Mon Sep 17 00:00:00 2001 From: ketmar Date: Tue, 30 Nov 2021 04:50:02 +0000 Subject: [PATCH] egra: agg mini code cleanups FossilOrigin-Name: 8b603a8b1ef88ae422bac66f9247dcf71b28d45a301fbbf48f6aeaec80ffb057 --- egra/gfx/aggmini/core.d | 718 +++++++++++++++++++-------------------------- egra/gfx/aggmini/enums.d | 5 + egra/gfx/aggmini/stroker.d | 38 +-- egra/gfx/aggmini/supmath.d | 366 ++++++++++++++++++++++- egra/test.d | 8 +- 5 files changed, 671 insertions(+), 464 deletions(-) diff --git a/egra/gfx/aggmini/core.d b/egra/gfx/aggmini/core.d index 69ddc1a..bf5de77 100644 --- a/egra/gfx/aggmini/core.d +++ b/egra/gfx/aggmini/core.d @@ -55,34 +55,51 @@ private import iv.egra.gfx.base; //debug private import iv.cmdcon; +//version = egra_aggmini_debug_rasta; + // ////////////////////////////////////////////////////////////////////////// // // some "fastgfx" backend public struct AGGDrawer { protected: + // added vertex will contain "end poly" command only when `closePoly()`/`endPoly()` was called static struct Vertex { - float x, y; // fixed point - uint type; // 0: line; 1: move; 2: line and close + float x, y; + uint cmd; // path command - this (in float ax, in float ay, bool asMove) pure nothrow @safe @nogc { + this (in float ax, in float ay, in uint acmd) pure nothrow @safe @nogc { pragma(inline, true); x = ax; y = ay; - type = (asMove ? 1u : 0u); + cmd = acmd; } - - @property bool asmove () const pure nothrow @safe @nogc { pragma(inline, true); return (type == 1u); } - @property bool asline () const pure nothrow @safe @nogc { pragma(inline, true); return (type != 1u); } - @property bool closed () const pure nothrow @safe @nogc { pragma(inline, true); return (type == 2u); } } protected: Rasterizer rast; - Vertex* vtx; - uint vtxcount, vtxsize; - float lastStartX = 0.0f, lastStartY = 0.0f; + SimpleVector!Vertex vtx; // reset by `beginPath()` + + float lastStartX = 0.0f, lastStartY = 0.0f; // first point of the current poly + float lastX = 0.0f, lastY = 0.0f; + float utlastX = 0.0f, utlastY = 0.0f; + + // for vertex producer + uint vpIdx; + + // for rasterising vertex consumer + static struct ConsumerState { + float startX = 0.0f, startY = 0.0f; + float lastX = 0.0f, lastY = 0.0f; + uint lineCmdCount = 0; + // 0: poly just started + // 1: last command was "move to" + // 2 and more: number of sequential "line to" commands minus 1 + } + ConsumerState rcsm; + + AGGMatrix tmatrix; - bool notransform = true; + bool dotransform = false; public: float tessTol = 0.25f; @@ -98,10 +115,12 @@ public: mixin(DisableCopyingMixin); public: - ref AGGDrawer withTransform(DG) (in auto ref AGGMatrix tmt, scope DG dg) { + ref AGGDrawer withTransform(DG) (in auto ref AGGMatrix tmt, scope DG dg) + if (is(typeof((inout int=0) { DG vp = void; dg(); }))) + { immutable AGGMatrix old = tmatrix; tmatrix = tmt; - notransform = tmt.isIdentity; + dotransform = !tmt.isIdentity; scope(exit) tmatrix = old; dg(); return this; @@ -112,121 +131,120 @@ protected: enum distTol = cast(float)(1.0f/256.0f); enum KAPPA90 = 0.5522847493f; // length proportional to radius of a cubic bezier handle for 90deg arcs - static bool ptEquals (in float x0, in float y0, in float x1, in float y1) pure { - enum EPS = cast(float)(1.0f/256.0f); - static bool eq (in float a, in float b) pure nothrow @safe @nogc { immutable float df = a-b; return (df < 0.0f ? (-df < EPS) : (df < EPS)); } - return (eq(x0, x1) && eq(y0, y1)); - } - - static float distPtSeg (in float x, in float y, in float px, in float py, in float qx, in float qy) pure nothrow @safe @nogc { - immutable float pqx = qx-px; - immutable float pqy = qy-py; - float dx = x-px; - float dy = y-py; - immutable float d = pqx*pqx+pqy*pqy; - float t = pqx*dx+pqy*dy; - if (d > 0) t /= d; - if (t < 0) t = 0; else if (t > 1) t = 1; - dx = px+t*pqx-x; - dy = py+t*pqy-y; - return dx*dx+dy*dy; - } - - static float cross() (in float dx0, in float dy0, in float dx1, in float dy1) pure { pragma(inline, true); return (dx1*dy0-dx0*dy1); } - static T min(T) (in T a, in T b) pure { pragma(inline, true); return (a < b ? a : b); } - static T max(T) (in T a, in T b) pure { pragma(inline, true); return (a > b ? a : b); } - static T sign(T) (in T a) pure { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); } - - static float normalize (float* x, float* y) nothrow @safe @nogc { - import core.stdc.math : sqrtf; - float d = sqrtf((*x)*(*x)+(*y)*(*y)); - if (d > 1e-6f) { - immutable float id = 1.0f/d; - *x *= id; - *y *= id; - } - return d; - } + // this does some fancy logic + void addVtx (float fx, float fy, uint cmd) { + pragma(inline, true); + assert(isLineTo(cmd) || isMoveTo(cmd) || isEndPoly(cmd)); - void checkLastClose () { - if (!vtxcount) return; - if (vtx[vtxcount-1].asmove) return; - if (vtx[vtxcount-1].closed) return; - if (ptEquals(lastStartX, lastStartY, vtx[vtxcount-1].x, vtx[vtxcount-1].y)) { - assert(vtx[vtxcount-1].type == 0u); - vtx[vtxcount-1].type = 2u; // line and close; + if (isEndPoly(cmd) && (fx != fx || fy != fy)) { + fx = lastX; + fy = lastY; + } else { + assert(fx == fx && fy == fy); // reject infs and nans + if (!isEndPoly(cmd)) { + utlastX = fx; + utlastY = fy; + } + // transform coords + if (dotransform) { + tmatrix.point(&fx, &fy, fx, fy); + assert(fx == fx && fy == fy); // reject infs and nans + } } - } - void addVertexIntr (float fx, float fy, bool asMove) { - //static bool eq (in float a, in float b) pure nothrow @safe @nogc { import std.math : abs; return (abs(a-b) < EPS); } - // it is important to reject pixels that are too close! - /* - if (vtxcount > 0 && ptEquals(fx, fy, vtx[vtxcount-1].x, vtx[vtxcount-1].y)) { - if (asMove != vtx[vtxcount-1].asmove) return; - } - */ - if (asMove && vtxcount && vtx[vtxcount-1].asmove) { - // fix move vertex - vtx[vtxcount-1].x = fx; - vtx[vtxcount-1].y = fy; - lastStartX = fx; - lastStartY = fy; + // moveto? + if (isMoveTo(cmd)) { + // if we just end the poly, record new coords, but don't emit any command + if (vtx.isEmpty || isEndPoly(vtx[$-1u].cmd)) { + // postpone it + lastStartX = lastX = fx; + lastStartY = lastY = fy; + return; + } + // if prev was moveto, just fix it + if (isMoveTo(vtx[$-1u].cmd)) { + vtx[$-1u].x = lastX = fx; + vtx[$-1u].y = lastY = fy; + return; + } + // prev should be a line, emit move + assert(isLineTo(vtx[$-1u].cmd)); + vtx.add(Vertex(fx, fy, cmd)); + lastX = fx; + lastY = fy; return; } - // reject duplicate vertices - if (vtxcount && asMove == vtx[vtxcount-1].asmove && ptEquals(fx, fy, vtx[vtxcount-1].x, vtx[vtxcount-1].y)) { + + // lineto? + if (isLineTo(cmd)) { + // if we just end the poly, move to the new start + // (we may have postponed "move to" here, and this will "flush" it) + if (vtx.isEmpty || isEndPoly(vtx[$-1u].cmd)) { + vtx.add(Vertex(lastX, lastY, PathCommand.MoveTo)); + } + // if prev is "line to", reject duplicate points + // we cannot have empty vertex array here + if (isLineTo(vtx[$-1u].cmd) && ptEquals(fx, fy, vtx[$-1u].x, vtx[$-1u].y)) return; + vtx.add(Vertex(fx, fy, cmd)); + lastX = fx; + lastY = fy; return; } - if (vtxcount+1 > vtxsize) { - import core.stdc.stdlib : realloc; - uint newsz = (vtxsize|0xfff)+1; - vtx = cast(Vertex*)realloc(vtx, newsz*Vertex.sizeof); - if (vtx is null) assert(0, "out of memory"); - vtxsize = newsz; - } - //if (asMove && vtxcount > 0 && vtx[vtxcount-1].asmove) --vtxcount; - // detect closed contour - if (vtxcount) { - if (asMove) { - if (!vtx[vtxcount-1].asmove && !vtx[vtxcount-1].closed) { - if (ptEquals(lastStartX, lastStartY, vtx[vtxcount-1].x, vtx[vtxcount-1].y)) { - assert(vtx[vtxcount-1].type == 0u); - vtx[vtxcount-1].type = 2u; // line and close; - } + + // end poly + assert(isEndPoly(cmd)); + // do not emit if if we haven't any poly points yet + if (vtx.isEmpty || isEndPoly(vtx[$-1u].cmd)) return; + // we cannot have polys with "move only", no need to check for that + // need to close the poly? + if (isClose(cmd)) { + // we can have "move to" recorded, take it back + if (isMoveTo(vtx[$-1u].cmd)) { + immutable float mx = vtx[$-1u].x; + immutable float my = vtx[$-1u].y; + vtx.removeLast(); + assert(!vtx.isEmpty && isLineTo(vtx[$-1u].cmd)); + if (!ptEquals(lastStartX, lastStartY, vtx[$-1u].x, vtx[$-1u].y)) { + // close contour + vtx.add(Vertex(lastStartX, lastStartY, PathCommand.LineTo)); } + // no need to put close command back + vtx.add(Vertex(fx, fy, cmd)); + assert(lastX == mx && lastY == my); + lastStartX = mx; + lastStartY = my; } else { - // reset "closed" flag, if we're going to continue - // `closePath()` will insert dummy "move", so it's legal - if (!vtx[vtxcount-1].asmove && vtx[vtxcount-1].closed) { - vtx[vtxcount-1].type = 0u; // line + // we should have "line to" recorded + assert(!vtx.isEmpty && isLineTo(vtx[$-1u].cmd)); + if (!ptEquals(lastStartX, lastStartY, vtx[$-1u].x, vtx[$-1u].y)) { + // close contour + vtx.add(Vertex(lastStartX, lastStartY, PathCommand.LineTo)); } + vtx.add(Vertex(fx, fy, cmd)); + // fix current coords + lastStartX = lastX = fx; + lastStartY = lastY = fy; } + return; } - // append point - vtx[vtxcount++] = Vertex(fx, fy, asMove); - // fix last start - if (asMove) { - lastStartX = fx; - lastStartY = fy; - } + + // end poly w/o closing + // do nothing (do not take back last "move to") + assert(!vtx.isEmpty); + vtx.add(Vertex(fx, fy, cmd)); } - void addVertex (float fx, float fy, bool asMove=false) { + @property bool hasPolyPoints () const pure { pragma(inline, true); - if (!notransform) tmatrix.point(&fx, &fy, fx, fy); - if (vtxcount == 0 && !asMove) addVertexIntr(0, 0, true); - addVertexIntr(fx, fy, asMove); + return (!vtx.isEmpty && isMoveTo(vtx[$-1u].cmd)); } public: void dumpVertices () { - checkLastClose(); import core.stdc.stdio : stderr, fprintf; - fprintf(stderr, "=== VERTEX COUNT: %u ===\n", vtxcount); - foreach (immutable uint idx; 0..vtxcount) { - fprintf(stderr, " %u: (%g, %g) : %s%s\n", idx, vtx[idx].x, vtx[idx].y, (vtx[idx].asmove ? "move".ptr : "line".ptr), - (vtx[idx].closed ? " closed".ptr : "".ptr)); + fprintf(stderr, "=== VERTEX COUNT: %u ===\n", vtx.length); + foreach (immutable uint idx; 0..vtx.length) { + fprintf(stderr, " %u: (%g, %g) : 0x%02x\n", idx, vtx[idx].x, vtx[idx].y, vtx[idx].cmd); } fprintf(stderr, "-------\n"); } @@ -270,41 +288,142 @@ public: @property FillRule fillRule () const pure { pragma(inline, true); return rast.fillRule; } @property void fillRule (FillRule v) { pragma(inline, true); rast.fillRule = v; } - @property float currX () const pure { pragma(inline, true); return (vtxcount > 0 ? vtx[vtxcount-1].x : 0.0f); } - @property float currY () const pure { pragma(inline, true); return (vtxcount > 0 ? vtx[vtxcount-1].y : 0.0f); } + // untransformed + @property float currX () const pure { pragma(inline, true); return utlastX; } + @property float currY () const pure { pragma(inline, true); return utlastY; } + + // transformed + @property float realCurrX () const pure { pragma(inline, true); return lastX; } + @property float realCurrY () const pure { pragma(inline, true); return lastY; } - @property AGGMatrix getTransform () const pure { return tmatrix; } + @property AGGMatrix getTransform () const pure { pragma(inline, true); return tmatrix; } @property void transform() (in auto ref AGGMatrix tmt) { tmatrix = tmt; - notransform = tmt.isIdentity; + dotransform = !tmt.isIdentity; } void resetTransform () { - if (!notransform) { + if (dotransform) { tmatrix.identity(); - notransform = true; + dotransform = false; } } + // ////////////////////////////////////////////////////////////////////////// // + // Vetex Producer API + void rewind () { + pragma(inline, true); + vpIdx = 0; + } + + uint vertex (float* x, float* y) { + if (vpIdx >= vtx.length) return PathCommand.Stop; + *x = vtx[vpIdx].x; + *y = vtx[vpIdx].y; + return vtx[vpIdx++].cmd; + } + + + // ////////////////////////////////////////////////////////////////////////// // + // Vetex Consumer API does rasterising + void removeAll () { + pragma(inline, true); + rcsm = rcsm.init; + version(egra_aggmini_debug_rasta) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "=== DRAWER: RENDER RESET ===\n"); } + } + + void addVertex (float x, float y, uint cmd) { + // stop? + if (isStop(cmd)) { + //removeAll(); + version(egra_aggmini_debug_rasta) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "=== DRAWER: RENDER STOP ===\n"); } + rcsm = rcsm.init; + return; + } + + // line/move? + if (isLineTo(cmd) || isMoveTo(cmd)) { + version(egra_aggmini_debug_rasta) { + import core.stdc.stdio : stderr, fprintf; fprintf(stderr, " pre*: %s: (%g, %g) lineCmdCount=%u; start=(%g,%g); last=(%g,%g)\n", + (isLineTo(cmd) ? "lineto".ptr : "moveto".ptr), x, y, rcsm.lineCmdCount, rcsm.startX, rcsm.startY, rcsm.lastX, rcsm.lastY); + } + if (!rcsm.lineCmdCount) { rcsm.startX = x; rcsm.startY = y; rcsm.lineCmdCount = 1; } + if (isLineTo(cmd)) { + // do not add duplicate points + if (rcsm.lineCmdCount < 2 || !ptEquals(x, y, rcsm.lastX, rcsm.lastY)) rast.lineTo(x, y); + ++rcsm.lineCmdCount; + } else { + rast.moveTo(x, y); + rcsm.lineCmdCount = 1; + } + rcsm.lastX = x; + rcsm.lastY = y; + version(egra_aggmini_debug_rasta) { + import core.stdc.stdio : stderr, fprintf; fprintf(stderr, " post: %s: (%g, %g) lineCmdCount=%u; start=(%g,%g); last=(%g,%g)\n", + (isLineTo(cmd) ? "lineto".ptr : "moveto".ptr), x, y, rcsm.lineCmdCount, rcsm.startX, rcsm.startY, rcsm.lastX, rcsm.lastY); + } + return; + } + + // end poly? + if (isEndPoly(cmd)) { + version(egra_aggmini_debug_rasta) { + import core.stdc.stdio : stderr, fprintf; fprintf(stderr, " pre*: %s: (%g, %g) lineCmdCount=%u; start=(%g,%g); last=(%g,%g)\n", + (isClose(cmd) ? "close".ptr : "end".ptr), x, y, rcsm.lineCmdCount, rcsm.startX, rcsm.startY, rcsm.lastX, rcsm.lastY); + } + // close poly? + if (rcsm.lineCmdCount > 2 && isClose(cmd) && !ptEquals(rcsm.startX, rcsm.startY, rcsm.lastX, rcsm.lastY)) { + rast.lineTo(rcsm.startX, rcsm.startY); + rcsm.lastX = rcsm.startX; + rcsm.lastY = rcsm.startY; + } + rcsm.lineCmdCount = 0; + // don't do "move to" here, it breaks rendering + version(egra_aggmini_debug_rasta) { + import core.stdc.stdio : stderr, fprintf; fprintf(stderr, " post: %s: (%g, %g) lineCmdCount=%u; start=(%g,%g); last=(%g,%g)\n", + (isClose(cmd) ? "close".ptr : "end".ptr), x, y, rcsm.lineCmdCount, rcsm.startX, rcsm.startY, rcsm.lastX, rcsm.lastY); + } + return; + } + + assert(0, "invalid path command"); + } + + + // ////////////////////////////////////////////////////////////////////////// // + ref AGGDrawer resetParams () { + width = 1.5f; + miterLimit = 4.0f; + innerMiterLimit = 1.01f; + approximationScale = 1.0f; + lineCap = LineCap.Butt; + lineJoin = LineJoin.Miter; + innerJoin = InnerJoin.Miter; + return this; + } + ref AGGDrawer beginFrame () { resetTransform(); - vtxcount = 0; - //addVertex(0, 0, true); - mStroker.width = 1.5f; + vtx.removeAll(); + vpIdx = 0; + lastStartX = lastX = utlastX = 0.0f; + lastStartY = lastY = utlastY = 0.0f; rast.reset(); - return this; + return resetParams(); } ref AGGDrawer cancelFrame () { return beginFrame(); } + // this doesn't reset params or transform ref AGGDrawer resetFrame () { - resetTransform(); - vtxcount = 0; - //addVertex(0, 0, true); + vtx.removeAll(); + vpIdx = 0; + lastStartX = lastX = utlastX = 0.0f; + lastStartY = lastY = utlastY = 0.0f; rast.reset(); return this; } @@ -334,23 +453,25 @@ public: ref AGGDrawer beginPath () { pragma(inline, true); - vtxcount = 0; + vtx.removeAll(); return this; } - ref AGGDrawer closePath () { - if (vtxcount && !vtx[vtxcount-1].asmove) { - addVertex(lastStartX, lastStartY, false); // move to the start - addVertex(lastStartX, lastStartY, true); // and open new one - } + ref AGGDrawer endPoly () { + addVtx(lastX, lastY, PathCommand.EndPoly); + return this; + } + + ref AGGDrawer closePoly () { + addVtx(lastX, lastY, PathCommand.EndPoly|PathFlag.Close); return this; } // starts new sub-path with specified point as first point - ref AGGDrawer moveTo (in float x, in float y) { pragma(inline, true); addVertex(x, y, true); return this; } + ref AGGDrawer moveTo (in float x, in float y) { pragma(inline, true); addVtx(x, y, PathCommand.MoveTo); return this; } // adds line segment from the last point in the path to the specified point - ref AGGDrawer lineTo (in float x, in float y) { pragma(inline, true); addVertex(x, y, false); return this; } + ref AGGDrawer lineTo (in float x, in float y) { pragma(inline, true); addVtx(x, y, PathCommand.LineTo); return this; } // adds cubic bezier segment from last point in the path via two control points to the specified point ref AGGDrawer bezierTo (in float x2, in float y2, in float x3, in float y3, in float x4, in float y4) { @@ -381,7 +502,7 @@ public: // handle degenerate cases if (ptEquals(x0, y0, x1, y1) || ptEquals(x1, y1, x2, y2) || - distPtSeg(x1, y1, x0, y0, x2, y2) < distTol*distTol || + distPtSegSq(x1, y1, x0, y0, x2, y2) < distTol*distTol || radius < distTol) { lineTo(x1, y1); @@ -393,8 +514,8 @@ public: float dy0 = y0-y1; float dx1 = x2-x1; float dy1 = y2-y1; - normalize(&dx0, &dy0); - normalize(&dx1, &dy1); + normalize(ref dx0, ref dy0); + normalize(ref dx1, ref dy1); immutable float a = acosf(dx0*dx1+dy0*dy1); immutable float d = radius/tanf(a*0.5f); @@ -405,7 +526,7 @@ public: float cx = void, cy = void, a0 = void, a1 = void; Winding dir = void; - if (cross(dx0, dy0, dx1, dy1) > 0.0f) { + if (cross2(dx0, dy0, dx1, dy1) > 0.0f) { cx = x1+dx0*d+dy0*radius; cy = y1+dy0*d+-dx0*radius; a0 = atan2f(dx0, -dy0); @@ -426,226 +547,30 @@ public: // ////////////////////////////////////////////////////////////////////////// // - // fill pathes - ref AGGDrawer fill () { - foreach (const ref Vertex vt; vtx[0..vtxcount]) { - if (vt.asmove) rast.moveTo(vt.x, vt.y); else rast.lineTo(vt.x, vt.y); - } - return this; + protected void streamFromStroker () { + // no need to close polys, our consumer will do it + streamAllVertices!false(mStroker, this); + mStroker.removeAll(); } - private ref AGGDrawer renderAGGPath(VS) (ref VS vs) { - //debug conwriteln("=== RENDERPATH ==="); - float x, y; - vs.rewind(); - for (;;) { - auto cmd = vs.vertex(&x, &y); - if (isStop(cmd)) break; - //debug conwriteln(" (", x, ", ", y, "; ", cmd, ")"); - if (isMoveTo(cmd)) { rast.moveTo(x, y); continue; } - if (isLineTo(cmd)) { rast.lineTo(x, y); continue; } - //if (isEndPoly(cmd)) {} - } - vs.removeAll(); - return this; + protected void streamToStrokerAndRender(VS) (ref VS vs) if (IsGoodVertexProducer!VS) { + streamAllVertices(vs, mStroker, &streamFromStroker); } - // vertex source for drawer - private static struct SelfVertexSource { - Vertex* vtx; - uint vtxcount; - uint vidx; - int sendClose = 0; - - @trusted nothrow @nogc: - this (ref AGGDrawer drw) { - pragma(inline, true); - drw.checkLastClose(); - vtx = drw.vtx; - vtxcount = drw.vtxcount; - } - - void removeAll () { - vidx = 0; - sendClose = 0; - } - - // Vertex Source Interface - void rewind () { - vidx = 0; - sendClose = 0; - } - - // will inject `EndPoly` commands - uint vertex (float* x, float* y) { - // sendClose: - // 0: normal - // 1: send `EndPoly|Close`, reset `sendClose` flag and exit - // 2: resend current vertex without any checks, and reset `sendClose` flag - uint res = void; - // send "end closed polygon"? - if (sendClose == 1) { - sendClose = 0; - //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "SENDING CLOSED POLY! (vidx=%u)\n", vidx); } - return (PathCommand.EndPoly|PathFlag.Close); - } - if (vidx >= vtxcount) return PathCommand.Stop; - // resend vertex? - if (sendClose == 2) { - sendClose = 0; - *x = vtx[vidx].x; - *y = vtx[vidx].y; - return (vtx[vidx++].asmove ? PathCommand.MoveTo: PathCommand.LineTo); - } - // if moved, and previous not a close, send "end unclosed polygon" - if (vidx && vtx[vidx].asmove && !vtx[vidx-1].closed) { - // there cannot be two moves in a row - assert(!vtx[vidx-1].asmove); - sendClose = 2; // resend this move on the next step - // send "end unclosed polygon" command - //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "SENDING UNCLOSED POLY! (vidx=%u)\n", vidx); } - return PathCommand.EndPoly; - } - assert(sendClose == 0); - // if closed, end the poly - if (vtx[vidx].closed) { - assert(!vtx[vidx].asmove); - sendClose = 1; // send "end closed polygon" on the next step - } - *x = vtx[vidx].x; - *y = vtx[vidx].y; - return (vtx[vidx++].asmove ? PathCommand.MoveTo: PathCommand.LineTo); - } - } - - // vertex consumer for reasterizer - private static struct RenderVertexConsumer { - Rasterizer* rast; - bool wasVertex; - float startX = 0.0f, startY = 0.0f; - float lastX = 0.0f, lastY = 0.0f; - - @trusted nothrow @nogc: - this (ref AGGDrawer drw) { - pragma(inline, true); - rast = &drw.rast; - } - - void reset () { - removeAll(); - } - - // Generator interface - void removeAll () { - wasVertex = false; - startX = startY = lastX = lastY = 0.0f; - } - - void addVertex (float x, float y, uint cmd) { - if (isClose(cmd) || isStop(cmd)) { - // close path - if (wasVertex && isClose(cmd) && !ptEquals(lastX, lastY, startX, startY)) rast.lineTo(startX, startY); - wasVertex = false; - lastX = startX; - lastY = startY; - return; - } - //debug conwriteln(" (", x, ", ", y, "; ", cmd, ")"); - if (isMoveTo(cmd)) { - rast.moveTo(x, y); - startX = lastX = x; - startY = lastY = y; - wasVertex = false; - return; - } - if (isLineTo(cmd)) { - rast.lineTo(x, y); - lastX = x; - lastY = y; - wasVertex = true; - return; - } - } - } - - // calls `flushdg()` after each line - private static struct PolyVertexRouter(VS) { - VS *vs; - bool wasVertex; - float startX = 0.0f, startY = 0.0f; - float lastX = 0.0f, lastY = 0.0f; - void delegate (ref VS vs) @safe nothrow @nogc flushdg; - - @trusted nothrow @nogc: - this (ref VS avs, scope void delegate (ref VS vs) @safe nothrow @nogc aflushdg) { - pragma(inline, true); - vs = &avs; - flushdg = aflushdg; - } - - // Generator interface - void removeAll () { - vs.removeAll(); - wasVertex = false; - startX = startY = lastX = lastY = 0.0f; - } - void addVertex (float x, float y, uint cmd) { - if (isStop(cmd) || isClose(cmd)) { - if (wasVertex && isClose(cmd)) { - // close path - if (!ptEquals(lastX, lastY, startX, startY)) { - vs.addVertex(startX, startY, PathCommand.LineTo); - } - } - vs.addVertex(x, y, cmd); - flushdg(*vs); - wasVertex = false; - lastX = startX; - lastY = startY; - vs.removeAll(); - return; - } - //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, " (%g, %g) : %u\n", x, y, cmd); } - //debug conwriteln(" (", x, ", ", y, "; ", cmd, ")"); - if (isMoveTo(cmd)) { - if (wasVertex) flushdg(*vs); - wasVertex = false; - startX = lastX = x; - startY = lastY = y; - } else if (isLineTo(cmd)) { - lastX = x; - lastY = y; - wasVertex = true; - } - vs.addVertex(x, y, cmd); - } - } - - private ref AGGDrawer convertPathToPath(VS0, VS1) (ref VS0 vsrc, ref VS1 vdest) { - vdest.removeAll(); - float x = 0.0f, y = 0.0f; - vsrc.rewind(); - bool wasStop = false; - //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "===CVT===\n"); } - for (;;) { - auto cmd = vsrc.vertex(&x, &y); - vdest.addVertex(x, y, cmd); - //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, " (%g, %g) : %02x\n", x, y, cmd); } - if (isStop(cmd)) { wasStop = true; break; } - } - if (!wasStop) vdest.addVertex(x, y, PathCommand.Stop); - vsrc.removeAll(); + // ////////////////////////////////////////////////////////////////////////// // + // fill pathes + ref AGGDrawer fill () { + streamAllVertices!false(this, this); // no need to close polys, our consumer will do it + removeAll(); return this; } - // stroke pathes ref AGGDrawer stroke () { - if (!vtxcount) return this; - auto src = SelfVertexSource(this); - auto dest = PolyVertexRouter!(typeof(mStroker))(mStroker, (ref typeof(mStroker) vs) { renderAGGPath(vs); }); - return convertPathToPath(src, dest); + if (vtx.isEmpty) return this; + streamAllVertices(this, mStroker, &streamFromStroker); + return this; } // set parameters, and then call this @@ -663,55 +588,22 @@ public: // contour pathes ref AGGDrawer contour () { - if (!vtxcount) return this; - - auto src = SelfVertexSource(this); - auto cdest = PolyVertexRouter!(typeof(mContourer))(mContourer, (ref typeof(mContourer) vs) { - auto dest = PolyVertexRouter!(typeof(mStroker))(mStroker, (ref typeof(mStroker) vs) { renderAGGPath(vs); }); - convertPathToPath(vs, dest); - }); - - return convertPathToPath(src, cdest); + if (vtx.isEmpty) return this; + streamAllVertices(this, mContourer, &streamToStrokerAndRender!(typeof(mContourer))); + return this; } // dash pathes ref AGGDrawer dashStroke () { - if (!vtxcount) return this; + if (vtx.isEmpty) return this; if (!mDasher.hasDashes) return stroke(); - - mDasher.shorten = mStroker.shorten; - - auto src = SelfVertexSource(this); - auto ddest = PolyVertexRouter!(typeof(mDasher))(mDasher, (ref typeof(mDasher) vs) { - auto dest = PolyVertexRouter!(typeof(mStroker))(mStroker, (ref typeof(mStroker) vs) { renderAGGPath(vs); }); - convertPathToPath(vs, dest); - }); - - return convertPathToPath(src, ddest); - } - - ref AGGDrawer dashContour () { - if (!vtxcount) return this; - if (!mDasher.hasDashes) return contour(); - mDasher.shorten = mStroker.shorten; - - auto src = SelfVertexSource(this); - auto ddest = PolyVertexRouter!(typeof(mDasher))(mDasher, (ref typeof(mDasher) vs) { - auto cdest = PolyVertexRouter!(typeof(mContourer))(mContourer, (ref typeof(mContourer) vs) { - auto dest = PolyVertexRouter!(typeof(mStroker))(mStroker, (ref typeof(mStroker) vs) { renderAGGPath(vs); }); - convertPathToPath(vs, dest); - }); - convertPathToPath(vs, cdest); - }); - - return convertPathToPath(src, ddest); + streamAllVertices(this, mDasher, &streamToStrokerAndRender!(typeof(mDasher))); + return this; } // ////////////////////////////////////////////////////////////////////////// // - enum Winding { CW, CCW } - /* Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). * Angles are specified in radians. @@ -726,7 +618,7 @@ public: //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); static if (mode == "original") { - bool asMove = (vtxcount == 0); + bool asMove = !hasPolyPoints; } else static if (mode == "move") { enum asMove = true; } else static if (mode == "line") { @@ -769,7 +661,7 @@ public: immutable float tany = dx*r*kappa; if (i == 0) { - addVertex(x, y, asMove); + if (asMove) moveTo(x, y); else lineTo(x, y); } else { bezierTo(px+ptanx, py+ptany, x-tanx, y-tany, x, y); } @@ -812,7 +704,7 @@ public: bezierTo(x+rw*(1.0f-KAPPA90), y+h, x, y+h-rh*(1.0f-KAPPA90), x, y+h-rh); lineTo(x, y+rh); bezierTo(x, y+rh*(1.0f-KAPPA90), x+rw*(1.0f-KAPPA90), y, x+rw, y); - return closePath(); + return closePoly(); } // creates new rounded rectangle shaped sub-path @@ -838,7 +730,7 @@ public: bezierTo(x+w, y+ryTR*(1.0f-KAPPA90), x+w-rxTR*(1.0f-KAPPA90), y, x+w-rxTR, y); lineTo(x+rxTL, y); bezierTo(x+rxTL*(1.0f-KAPPA90), y, x, y+ryTL*(1.0f-KAPPA90), x, y+ryTL); - return closePath(); + return closePoly(); } // creates new ellipse shaped sub-path @@ -848,7 +740,7 @@ public: bezierTo(cx+rx*KAPPA90, cy+ry, cx+rx, cy+ry*KAPPA90, cx+rx, cy); bezierTo(cx+rx, cy-ry*KAPPA90, cx+rx*KAPPA90, cy-ry, cx, cy-ry); bezierTo(cx-rx*KAPPA90, cy-ry, cx-rx, cy-ry*KAPPA90, cx-rx, cy); - return closePath(); + return closePoly(); } // creates new circle shaped sub-path @@ -879,7 +771,7 @@ private: immutable float d3 = fabsf(((x3-x4)*dy-(y3-y4)*dx)); if ((d2+d3)*(d2+d3) < tessTol*(dx*dx+dy*dy)) { - addVertex(x4, y4); + lineTo(x4, y4); return; } @@ -890,7 +782,7 @@ private: // "taxicab" / "manhattan" check for flat curves if (fabsf(x1+x3-x2-x2)+fabsf(y1+y3-y2-y2)+fabsf(x2+x4-x3-x3)+fabsf(y2+y4-y3-y3) < tessTol*0.25f) { - addVertex(x1234, y1234); + lineTo(x1234, y1234); return; } @@ -918,9 +810,9 @@ private: /+ if (level == 0) { - addVertex(x1, y1/*, 0*/); + lineTo(x1, y1/*, 0*/); tesselateBezierMcSeem(x1, y1, x2, y2, x3, y3, x4, y4, 1/*, type*/); - addVertex(x4, y4/*, type*/); + lineTo(x4, y4/*, type*/); return; } +/ @@ -978,11 +870,11 @@ private: } if (d2 > d3) { if (d2 < tessTol) { - addVertex(x2, y2); + lineTo(x2, y2); return; } } if (d3 < tessTol) { - addVertex(x3, y3); + lineTo(x3, y3); return; } break; @@ -990,20 +882,20 @@ private: // p1,p2,p4 are collinear, p3 is significant if (d3*d3 <= tessTol*(dx*dx+dy*dy)) { if (angleTol < AngleTolEPS) { - addVertex(x23, y23); + lineTo(x23, y23); return; } else { // angle condition float da1 = fabsf(atan2f(y4-y3, x4-x3)-atan2f(y3-y2, x3-x2)); if (da1 >= FLT_PI) da1 = 2.0f*FLT_PI-da1; if (da1 < angleTol) { - addVertex(x2, y2); - addVertex(x3, y3); + lineTo(x2, y2); + lineTo(x3, y3); return; } if (cuspLimit != 0.0f) { if (da1 > cuspLimit) { - addVertex(x3, y3); + lineTo(x3, y3); return; } } @@ -1014,20 +906,20 @@ private: // p1,p3,p4 are collinear, p2 is significant if (d2*d2 <= tessTol*(dx*dx+dy*dy)) { if (angleTol < AngleTolEPS) { - addVertex(x23, y23); + lineTo(x23, y23); return; } else { // angle condition float da1 = fabsf(atan2f(y3-y2, x3-x2)-atan2f(y2-y1, x2-x1)); if (da1 >= FLT_PI) da1 = 2.0f*FLT_PI-da1; if (da1 < angleTol) { - addVertex(x2, y2); - addVertex(x3, y3); + lineTo(x2, y2); + lineTo(x3, y3); return; } if (cuspLimit != 0.0f) { if (da1 > cuspLimit) { - addVertex(x2, y2); + lineTo(x2, y2); return; } } @@ -1039,7 +931,7 @@ private: if ((d2+d3)*(d2+d3) <= tessTol*(dx*dx+dy*dy)) { // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions if (angleTol < AngleTolEPS) { - addVertex(x23, y23); + lineTo(x23, y23); return; } else { // angle and cusp condition @@ -1050,16 +942,16 @@ private: if (da2 >= FLT_PI) da2 = 2.0f*FLT_PI-da2; if (da1+da2 < angleTol) { // finally we can stop the recursion - addVertex(x23, y23); + lineTo(x23, y23); return; } if (cuspLimit != 0.0f) { if (da1 > cuspLimit) { - addVertex(x2, y2); + lineTo(x2, y2); return; } if (da2 > cuspLimit) { - addVertex(x3, y3); + lineTo(x3, y3); return; } } @@ -1165,7 +1057,7 @@ private: ddy += dddy; // Output a point. - addVertex(px, py/*, (t > 0 ? type : 0)*/); + lineTo(px, py/*, (t > 0 ? type : 0)*/); // Advance along the curve. t += dt; @@ -1187,9 +1079,9 @@ private: tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4); break; case DeCasteljauMcSeem: - addVertex(x1, y1); + lineTo(x1, y1); tesselateBezierMcSeem(x1, y1, x2, y2, x3, y3, x4, y4, 1); - addVertex(x4, y4); + lineTo(x4, y4); break; } } @@ -1304,10 +1196,6 @@ private: import core.stdc.stdio; printf("microsecs: %u\n", cast(uint)estt); } } - static if (baph_debug_time) { - immutable estt = clockMicro()-tstt; - import core.stdc.stdio; printf("total microsecs: %u\n", cast(uint)estt); - } if (doFill) { static if (baph_debug_time) { immutable fstt = clockMicro(); @@ -1328,6 +1216,10 @@ private: import core.stdc.stdio; printf("stroke microsecs: %u\n", cast(uint)t); } } + static if (baph_debug_time) { + immutable estt = clockMicro()-tstt; + import core.stdc.stdio; printf("total microsecs: %u\n", cast(uint)estt); + } beginPath(); } diff --git a/egra/gfx/aggmini/enums.d b/egra/gfx/aggmini/enums.d index f667708..b5c5830 100644 --- a/egra/gfx/aggmini/enums.d +++ b/egra/gfx/aggmini/enums.d @@ -65,3 +65,8 @@ public enum InnerJoin { Jag, Round, } + +public enum Winding { + CW, + CCW, +} diff --git a/egra/gfx/aggmini/stroker.d b/egra/gfx/aggmini/stroker.d index e71f078..92342cc 100644 --- a/egra/gfx/aggmini/stroker.d +++ b/egra/gfx/aggmini/stroker.d @@ -49,40 +49,6 @@ private import iv.egra.gfx.aggmini.enums; // ////////////////////////////////////////////////////////////////////////// // -enum PathCommand : ubyte { - Stop = 0, - MoveTo = 1, - LineTo = 2, - EndPoly = 0x0F, - Mask = 0x0F, -} - -enum PathFlag : ubyte { - None = 0x00, - CCW = 0x10, - CW = 0x20, - Close = 0x40, - Mask = 0xF0, -} - - -bool isVertex (in uint c) pure { pragma(inline, true); return (c >= PathCommand.MoveTo && c < PathCommand.EndPoly); } -bool isDrawing (in uint c) pure { pragma(inline, true); return (c >= PathCommand.LineTo && c < PathCommand.EndPoly); } -bool isStop (in uint c) pure { pragma(inline, true); return (c == PathCommand.Stop); } -bool isMoveTo (in uint c) pure { pragma(inline, true); return (c == PathCommand.MoveTo); } -bool isLineTo (in uint c) pure { pragma(inline, true); return (c == PathCommand.LineTo); } -bool isEndPoly (in uint c) pure { pragma(inline, true); return ((c&PathCommand.Mask) == PathCommand.EndPoly); } -bool isClose (in uint c) pure { pragma(inline, true); return (c&~cast(uint)(PathFlag.CW|PathFlag.CCW)) == (PathCommand.EndPoly|PathFlag.Close); } -bool isCW (in uint c) pure { pragma(inline, true); return ((c&PathFlag.CW) != 0); } -bool isCCW (in uint c) pure { pragma(inline, true); return ((c&PathFlag.CCW) != 0); } -bool isOriented (in uint c) pure { pragma(inline, true); return ((c&(PathFlag.CW|PathFlag.CCW)) != 0); } -bool isClosed (in uint c) pure { pragma(inline, true); return ((c&PathFlag.Close) != 0); } -uint getCloseFlag (in uint c) pure { pragma(inline, true); return (c&PathFlag.Close); } -uint clearOrientation (in uint c) pure { pragma(inline, true); return (c&~cast(uint)(PathFlag.CW|PathFlag.CCW)); } -uint getOrientation (in uint c) pure { pragma(inline, true); return (c&(PathFlag.CW|PathFlag.CCW)); } -uint setOrientation (in uint c, uint o) pure { pragma(inline, true); return (clearOrientation(c)|o); } - - /* * VertexConsumer API: * alias value_type = VertexType; // shoud support `VertexType(x, y)` @@ -191,7 +157,7 @@ public nothrow @trusted @nogc: vc.removeAll(); - float cp = cross(v0.x, v0.y, v1.x, v1.y, v2.x, v2.y); + float cp = cross3(v0.x, v0.y, v1.x, v1.y, v2.x, v2.y); if (cp != 0.0f && (cp > 0.0f) == (mWidth > 0.0f)) { // Inner join float limit = (len1 < len2 ? len1 : len2)/mWidthAbs; @@ -345,7 +311,7 @@ private: // the previous one or goes back. immutable float x2 = v1.x+dx1; immutable float y2 = v1.y-dy1; - if ((cross(v0.x, v0.y, v1.x, v1.y, x2, y2) < 0) == (cross(v1.x, v1.y, v2.x, v2.y, x2, y2) < 0)) { + if ((cross3(v0.x, v0.y, v1.x, v1.y, x2, y2) < 0.0f) == (cross3(v1.x, v1.y, v2.x, v2.y, x2, y2) < 0.0f)) { // This case means that the next segment continues // the previous one (straight line) addVertex(vc, v1.x+dx1, v1.y-dy1); diff --git a/egra/gfx/aggmini/supmath.d b/egra/gfx/aggmini/supmath.d index 8262377..de10e2e 100644 --- a/egra/gfx/aggmini/supmath.d +++ b/egra/gfx/aggmini/supmath.d @@ -82,6 +82,292 @@ public T rad2deg(T) (in T rad) if (__traits(isFloating, T) && (T.sizeof == 4 || // ////////////////////////////////////////////////////////////////////////// // +/* + vertex consumer: + void removeAll (); + void addVertex (float x, float y, uint cmd); +*/ +template IsGoodVertexConsumer(VS) { + enum IsGoodVertexConsumer = + is(typeof((inout int=0) { + VS vs = void; + vs.removeAll(); + vs.addVertex(0.666f, 0.666f, cast(uint)666u); + })); +} + +/* + vertex producer: + void rewind (); + uint vertex (float* x, float* y); +*/ +template IsGoodVertexProducer(VP) { + enum IsGoodVertexProducer = + is(typeof((inout int=0) { + VP vp = void; + vp.rewind(); + float x, y; + uint v = vp.vertex(&x, &y); + })); +} + + +// ////////////////////////////////////////////////////////////////////////// // +enum PathCommand : uint { + Stop = 0u, + MoveTo = 1u, + LineTo = 2u, + EndPoly = 0x0Fu, + Mask = 0x0Fu, +} + +enum PathFlag : uint { + None = 0x00u, + CCW = 0x10u, + CW = 0x20u, + Close = 0x40u, + Mask = 0xF0u, +} + +bool isVertex (in uint c) pure { pragma(inline, true); return (c >= PathCommand.MoveTo && c < PathCommand.EndPoly); } +bool isDrawing (in uint c) pure { pragma(inline, true); return (c >= PathCommand.LineTo && c < PathCommand.EndPoly); } +bool isStop (in uint c) pure { pragma(inline, true); return (c == PathCommand.Stop); } +bool isMoveTo (in uint c) pure { pragma(inline, true); return (c == PathCommand.MoveTo); } +bool isLineTo (in uint c) pure { pragma(inline, true); return (c == PathCommand.LineTo); } +bool isEndPoly (in uint c) pure { pragma(inline, true); return ((c&PathCommand.Mask) == PathCommand.EndPoly); } +bool isClose (in uint c) pure { pragma(inline, true); return (c&~cast(uint)(PathFlag.CW|PathFlag.CCW)) == (PathCommand.EndPoly|PathFlag.Close); } +bool isCW (in uint c) pure { pragma(inline, true); return ((c&PathFlag.CW) != 0); } +bool isCCW (in uint c) pure { pragma(inline, true); return ((c&PathFlag.CCW) != 0); } +bool isOriented (in uint c) pure { pragma(inline, true); return ((c&(PathFlag.CW|PathFlag.CCW)) != 0); } +//bool isClosed (in uint c) pure { pragma(inline, true); return ((c&PathFlag.Close) != 0); } +uint getCloseFlag (in uint c) pure { pragma(inline, true); return (c&PathFlag.Close); } +uint clearOrientation (in uint c) pure { pragma(inline, true); return (c&~cast(uint)(PathFlag.CW|PathFlag.CCW)); } +uint getOrientation (in uint c) pure { pragma(inline, true); return (c&(PathFlag.CW|PathFlag.CCW)); } +uint setOrientation (in uint c, in uint o) pure { pragma(inline, true); return (clearOrientation(c)|o); } + + +bool ptEquals (in float x0, in float y0, in float x1, in float y1) pure { + pragma(inline, true); + enum EPS = cast(float)(1.0f/256.0f); + static bool eq (in float a, in float b) pure nothrow @safe @nogc { + pragma(inline, true); + immutable float df = a-b; + return (df < 0.0f ? (-df < EPS) : (df < EPS)); + } + return (eq(x0, x1) && eq(y0, y1)); +} + + +// ////////////////////////////////////////////////////////////////////////// // +// this closes polys +void streamAllVertices(bool doClosePolys=true, VS, VD, DG) (ref VS vsrc, ref VD vdest, scope DG flushdg=null) +if (IsGoodVertexProducer!VS && IsGoodVertexConsumer!VD && + (is(DG == typeof(null)) || + is(typeof((inout int=0) { + DG dg = void; + VD vd = void; + dg(ref vd); + })) || + is(typeof((inout int=0) { + DG dg = void; + dg(); + })) + )) +{ + static enum IsNonEmptyDG(DG) = !is(DG == typeof(null)); + static enum IsDGWithArgs(DG) = is(typeof((inout int=0) { DG dg = void; VD vd = void; dg(ref vd); })); + + float startX = 0.0f, startY = 0.0f; + float lastX = 0.0f, lastY = 0.0f; + uint lineCmdCount = 0; + float x = 0.0f, y = 0.0f; + vsrc.rewind(); + vdest.removeAll(); + for (;;) { + auto cmd = vsrc.vertex(&x, &y); + // stop? + if (isStop(cmd)) break; + + // line/move? + if (isLineTo(cmd) || isMoveTo(cmd)) { + if (!lineCmdCount) { startX = x; startY = y; lineCmdCount = 1; } + if (isLineTo(cmd)) { + // do not add duplicate points + if (lineCmdCount < 2 || !ptEquals(x, y, lastX, lastY)) vdest.addVertex(x, y, cmd); + ++lineCmdCount; + } else { + vdest.addVertex(x, y, cmd); + lineCmdCount = 1; + } + lastX = x; + lastY = y; + continue; + } + + // end poly? + if (isEndPoly(cmd)) { + // close poly? + if (lineCmdCount > 2 && isClose(cmd) && !ptEquals(startX, startY, lastX, lastY)) { + static if (doClosePolys) vdest.addVertex(startX, startY, PathCommand.LineTo); + lastX = startX; + lastY = startY; + } + // don't do "move to" here, it breaks rendering + vdest.addVertex(x, y, cmd); + // call "flush" delegate + static if (IsNonEmptyDG!DG) { + static if (IsDGWithArgs!DG) flushdg(vdest); else flushdg(); + } + lineCmdCount = 0; + continue; + } + + // wtf?! + assert(0, "invalid path command"); + } + + // final stop + vdest.addVertex(x, y, PathCommand.Stop); + // call "flush" delegate + static if (IsNonEmptyDG!DG) { + if (lineCmdCount) { + static if (IsDGWithArgs!DG) flushdg(vdest); else flushdg(); + } + } +} + + +// ////////////////////////////////////////////////////////////////////////// // +static auto CreatePolyTrans(VS, VD) (ref VS avs, ref VD avd, in bool BreakOnMoveTo=true) +if (IsGoodVertexProducer!VS && IsGoodVertexConsumer!VD) +{ + return VertexStreamPolyTrans!(VS, VD)(avs, avd, BreakOnMoveTo); +} + + +// it routes vertices from VS to VD until "endpoly" or "stop" +struct VertexStreamPolyTrans(VS, VD) if (IsGoodVertexProducer!VS && IsGoodVertexConsumer!VD) { + VS *vsrc; + VD *vdest; + bool wasVertex; + float startX = 0.0f, startY = 0.0f; + float lastX = 0.0f, lastY = 0.0f; + uint lastCmd = uint.max; + bool breakOnMoveTo; + +@trusted nothrow @nogc: + this (ref VS avs, ref VD avd, in bool BreakOnMoveTo=true) { + pragma(inline, true); + avs.rewind(); + avd.removeAll(); + vsrc = &avs; + vdest = &avd; + breakOnMoveTo = BreakOnMoveTo; + } + + enum { + StopNoData = -1, + MoreData = 0, + StopWithData = 1, + } + + // route one poly from source to dest + // it routes both "end poly", and "stop" + // each routed "end poly" will be followed by "stop" + // returns: + // -1 if no data was transferred, and "stop" command read + // 0 if some data was transferred, and "stop" command read (i.e. `vdest` got some data, but `vsrc` isn't) + // +1 if some data was transferred, and "end poly" command read (i.e. `vdest` got some data, and `vsrc` is not empty yet) + int routeOnePoly () { + if (isStop(lastCmd)) return -1; // just in case + // clear destination + vdest.removeAll(); + float x = 0.0f, y = 0.0f; + readloop: for (;;) { + // initial read? + if (lastCmd == uint.max) { + for (;;) { + lastCmd = vsrc.vertex(&x, &y); + if (isStop(lastCmd)) { + // no more + lastCmd = PathCommand.Stop; + // route "stop" + vdest.addVertex(x, y, PathCommand.Stop); + return -1; + } + if (isMoveTo(lastCmd) || isLineTo(lastCmd)) { lastX = x; lastY = y; break; } + if (!isEndPoly(lastCmd)) assert(0, "invalid path command"); + } + } + // here, `lastCmd` must be valid move/line + assert(isMoveTo(lastCmd) || isLineTo(lastCmd)); + // if it is a move command, find next line command + while (isMoveTo(lastCmd)) { + // remember poly start + startX = lastX; + startY = lastY; + lastCmd = vsrc.vertex(&x, &y); + if (isStop(lastCmd)) { + // no more + // route "stop" + vdest.addVertex(x, y, lastCmd); + return -1; + } + if (isMoveTo(lastCmd) || isLineTo(lastCmd)) { lastX = x; lastY = y; continue; } + if (isEndPoly(lastCmd)) { lastCmd = uint.max; continue readloop; } + assert(0, "invalid path command"); + } + // here we should have start coords from the previous move, and lineto + assert(isLineTo(lastCmd)); + // move to the first point + vdest.addVertex(startX, startY, PathCommand.MoveTo); + for (;;) { + vdest.addVertex(lastX, lastY, lastCmd); + lastCmd = vsrc.vertex(&x, &y); + if (isLineTo(lastCmd) || isMoveTo(lastCmd)) { + lastX = x; + lastY = y; + if (isMoveTo(lastCmd)) { + // end of poly? + if (breakOnMoveTo) { + // push "stop" to dest + vdest.addVertex(x, y, PathCommand.Stop); + break; + } + // if we don't want to stop on "moveTo", keep going + } + // route lineto + vdest.addVertex(x, y, lastCmd); + continue; + } + if (isEndPoly(lastCmd)) { + // close if necessary + if (isClose(lastCmd) && !ptEquals(startX, startY, lastX, lastY)) { + // close it + vdest.addVertex(startX, startY, PathCommand.LineTo); + lastX = startX; + lastY = startY; + } + // route "end poly" + vdest.addVertex(x, y, lastCmd); + // and push "stop" to dest + vdest.addVertex(x, y, PathCommand.Stop); + lastCmd = uint.max; // look for the next poly + return 1; // was a transfer, has more data + } + if (isStop(lastCmd)) { + // route stop, and stop + vdest.addVertex(x, y, lastCmd); + return 0; + } + } + } + assert(0); + } +} + + +// ////////////////////////////////////////////////////////////////////////// // // Vertex (x, y) with the distance to the next one. The last vertex has // distance between the last and the first points if the polygon is closed // and 0.0 if it's a polyline. @@ -109,7 +395,7 @@ public nothrow @trusted @nogc: alias ValueType = T; private: T* pvec; - uint pvecAllot, pvecSize; + uint pvecSize, pvecAllot; public: mixin(DisableCopyingMixin); @@ -118,16 +404,36 @@ public: import core.stdc.stdlib : free; if (pvec !is null) free(pvec); pvec = null; - pvecAllot = pvecSize = 0; + pvecSize = pvecAllot = 0; } + @property bool isEmpty () const pure { pragma(inline, true); return (pvecSize == 0); } @property uint length () const pure { pragma(inline, true); return pvecSize; } + @property uint capacity () const pure { pragma(inline, true); return pvecAllot; } - ref inout(T) opIndex (in uint idx) inout pure { pragma(inline, true); return pvec[idx]; } + @property uint opDollar () const pure { pragma(inline, true); return pvecSize; } - ref inout(T) curr (in uint idx) inout pure { pragma(inline, true); return pvec[idx]; } - ref inout(T) prev (in uint idx) inout pure { pragma(inline, true); return pvec[(idx+pvecSize-1u)%pvecSize]; } - ref inout(T) next (in uint idx) inout pure { pragma(inline, true); return pvec[(idx+1u)%pvecSize]; } + ref inout(T) opIndex (in uint idx) inout pure { pragma(inline, true); assert(pvecSize && idx < pvecSize); return pvec[idx]; } + + inout(T)[] opSlice (in uint lo, uint hi) inout pure { + pragma(inline, true); + if (hi > pvecSize) hi = pvecSize; + return (lo < hi ? pvec[lo..hi] : null); + } + + ref inout(T) curr (in uint idx) inout pure { pragma(inline, true); assert(pvecSize && idx < pvecSize); return pvec[idx]; } + ref inout(T) prev (in uint idx) inout pure { pragma(inline, true); assert(pvecSize); return pvec[(idx+pvecSize-1u)%pvecSize]; } + ref inout(T) next (in uint idx) inout pure { pragma(inline, true); assert(pvecSize); return pvec[(idx+1u)%pvecSize]; } + + void clear () { + pragma(inline, true); + if (pvec !is null) { + import core.stdc.stdlib : free; + free(pvec); + } + pvec = null; + pvecSize = pvecAllot = 0; + } void removeAll () { pragma(inline, true); @@ -143,7 +449,7 @@ public: import core.stdc.stdlib : realloc; import core.stdc.string : memcpy; if (pvecSize == pvecAllot) { - uint newsz = (pvecAllot|0xff)+1; + uint newsz = (pvecAllot|0xffu)+1u; pvec = cast(T*)realloc(pvec, T.sizeof*newsz); if (pvec is null) assert(0, "out of memory"); pvecAllot = newsz; @@ -304,27 +610,34 @@ public nothrow @trusted @nogc: // ////////////////////////////////////////////////////////////////////////// // -bool intersection (in float ax, in float ay, in float bx, in float by, in float cx, in float cy, in float dx, in float dy, float* x, float* y) /*pure*/ nothrow @trusted @nogc { +bool intersection (in float ax, in float ay, in float bx, in float by, + in float cx, in float cy, in float dx, in float dy, float* x, float* y) pure nothrow @trusted @nogc +{ //enum IntersectionEPS = cast(double)1.0e-30; // See calc_intersection enum IntersectionEPS = cast(float)1.0e-16; // See calc_intersection //version(aliced) pragma(inline, true); //import std.math : abs; - import core.stdc.math : fabsf; + //import core.stdc.math : fabsf; immutable float num = (ay-cy)*(dx-cx)-(ax-cx)*(dy-cy); immutable float den = (bx-ax)*(dy-cy)-(by-ay)*(dx-cx); - if (fabsf(den) < IntersectionEPS) return false; - //if ((den < 0.0f ? (-den < IntersectionEPS) : (den < IntersectionEPS))) return false; + //if (fabsf(den) < IntersectionEPS) return false; + if ((den < 0.0f ? (-den < IntersectionEPS) : (den < IntersectionEPS))) return false; immutable float r = num/den; if (x !is null) *x = ax+r*(bx-ax); if (y !is null) *y = ay+r*(by-ay); return true; } -float cross (in float x1, in float y1, in float x2, in float y2, in float x, in float y) pure nothrow @safe @nogc { +float cross3 (in float x1, in float y1, in float x2, in float y2, in float x, in float y) pure nothrow @safe @nogc { pragma(inline, true); return (x-x2)*(y2-y1)-(y-y2)*(x2-x1); } +float cross2 (in float dx0, in float dy0, in float dx1, in float dy1) pure nothrow @safe @nogc { + pragma(inline, true); + return dx1*dy0-dx0*dy1; +} + float distance (in float x1, in float y1, in float x2, in float y2) /*pure*/ nothrow @safe @nogc { pragma(inline, true); import core.stdc.math : sqrtf; @@ -341,6 +654,35 @@ float distance() (in auto ref VertexDist v0, in auto ref VertexDist v1) /*pure*/ return sqrtf(dx*dx+dy*dy); } +float distPtSegSq (in float x, in float y, in float px, in float py, in float qx, in float qy) pure nothrow @safe @nogc { + immutable float pqx = qx-px; + immutable float pqy = qy-py; + float dx = x-px; + float dy = y-py; + immutable float d = pqx*pqx+pqy*pqy; + float t = pqx*dx+pqy*dy; + if (d > 0.0f) t /= d; + if (t < 0.0f) t = 0.0f; else if (t > 1.0f) t = 1.0f; + dx = px+t*pqx-x; + dy = py+t*pqy-y; + return dx*dx+dy*dy; +} + +T min(T) (in T a, in T b) pure nothrow @safe @nogc { pragma(inline, true); return (a < b ? a : b); } +T max(T) (in T a, in T b) pure nothrow @safe @nogc { pragma(inline, true); return (a > b ? a : b); } +T sign(T) (in T a) pure nothrow @safe @nogc { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); } + +void normalize (ref float x, ref float y) nothrow @safe @nogc { + import core.stdc.math : sqrtf; + immutable float d = sqrtf(x*x+y*y); + if (d > 1e-6f) { + immutable float id = 1.0f/d; + x *= id; + y *= id; + } + //return d; +} + // ////////////////////////////////////////////////////////////////////////// // // Matrices and Transformations diff --git a/egra/test.d b/egra/test.d index dab3490..8964203 100644 --- a/egra/test.d +++ b/egra/test.d @@ -423,8 +423,10 @@ final class MainPaneWindow : SubWindow { gxagg.withTransform(tmt, { gxagg.moveTo(50, 50); gxagg.lineTo(60, 60); + gxagg.endPoly(); gxagg.moveTo(100, 100); gxagg.lineTo(140, 120); + gxagg.endPoly(); version(all) { gxagg.moveTo(200, 200); gxagg.lineTo(200, 100); @@ -437,7 +439,7 @@ final class MainPaneWindow : SubWindow { gxagg.lineTo(200, 200); gxagg.lineTo(100, 200); } - //gxagg.closePath(); + gxagg.closePoly(); //gxagg.dumpVertices(); }); @@ -445,13 +447,13 @@ final class MainPaneWindow : SubWindow { gxagg.fill(); gxagg.render(gxrgba(0, 127, 0, 127)); } - version(none) { + version(all) { //gxagg.dumpVertices(); gxagg.stroke(); gxagg.render(gxrgba(255, 255, 0, 255)); } - version(all) { + version(none) { gxagg.resetDashes(); gxagg.addDash(4, 4); gxagg.addDash(8, 8); -- 2.11.4.GIT