diff options
author | Sven Gothel <[email protected]> | 2023-02-17 12:17:57 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2023-02-17 12:17:57 +0100 |
commit | e72dbd286ba95913711ac812bc979204f2073b7c (patch) | |
tree | 46aa57a2bd1f87a3c9b0fdaea834a388833525e6 /src/jogl/classes/jogamp | |
parent | 4aca9d8252afbdc9e7dfd234c086f889623bb140 (diff) |
Graph: Fix Loop.initFromPolyline()'s Winding determination, document Winding rules for OutlineShape and add get/setWinding in Outline
Loop.initFromPolyline()'s Winding determination used a 3-point triangle-area method,
which is insufficent for complex shapes like serif 'g' or 'æ'.
Solved by using the whole area over the Outline shape.
Note: Loop.initFromPolyline()'s Winding determination is used to convert
the inner shape or holes to CW only.
Therefor the outter bondary shapes must be CCW.
This details has been documented within OutlineShape, anchor 'windingrules'.
Since the conversion of 'CCW -> CW' for inner shapes or holes is covered,
a safe user path would be to completely create CCW shapes.
However, this has not been hardcoded and is left to the user.
Impact: Fixes rendering serif 'g' or 'æ'.
The enhanced unit test TestTextRendererNEWT01 produces snapshots for all fonts within FontSet01.
While it shows proper rendering of the single Glyphs it exposes another Region/Curve Renderer bug,
i.e. sort-of a Region overflow crossing over from the box-end to the start.
Diffstat (limited to 'src/jogl/classes/jogamp')
3 files changed, 88 insertions, 58 deletions
diff --git a/src/jogl/classes/jogamp/graph/curve/tess/CDTriangulator2D.java b/src/jogl/classes/jogamp/graph/curve/tess/CDTriangulator2D.java index 369d0b493..331116f7e 100644 --- a/src/jogl/classes/jogamp/graph/curve/tess/CDTriangulator2D.java +++ b/src/jogl/classes/jogamp/graph/curve/tess/CDTriangulator2D.java @@ -80,20 +80,20 @@ public class CDTriangulator2D implements Triangulator { @Override public final void addCurve(final List<Triangle> sink, final Outline polyline, final float sharpness) { - Loop loop = null; - - if(!loops.isEmpty()) { - loop = getContainerLoop(polyline); - } + Loop loop = getContainerLoop(polyline); if(loop == null) { - final GraphOutline outline = new GraphOutline(polyline); + final Winding winding = Winding.CCW; // -> HEdge.BOUNDARY + // Too late: polyline.setWinding(winding); + final GraphOutline outline = new GraphOutline(polyline); // , winding); final GraphOutline innerPoly = extractBoundaryTriangles(sink, outline, false, sharpness); // vertices.addAll(polyline.getVertices()); - loop = new Loop(innerPoly, Winding.CCW); + loop = new Loop(innerPoly, winding); loops.add(loop); } else { - final GraphOutline outline = new GraphOutline(polyline); + // final Winding winding = Winding.CW; // -> HEdge.HOLE + // Not required, handled in Loop.initFromPolyline(): polyline.setWinding(winding); + final GraphOutline outline = new GraphOutline(polyline); // , winding); final GraphOutline innerPoly = extractBoundaryTriangles(sink, outline, true, sharpness); // vertices.addAll(innerPoly.getVertices()); loop.addConstraintCurve(innerPoly); @@ -221,12 +221,15 @@ public class CDTriangulator2D implements Triangulator { } private Loop getContainerLoop(final Outline polyline) { - final ArrayList<Vertex> vertices = polyline.getVertices(); - for(int i=0; i < loops.size(); i++) { - final Loop loop = loops.get(i); - for(int j=0; j < vertices.size(); j++) { - if( loop.checkInside( vertices.get(j) ) ) { - return loop; + final int count = loops.size(); + if( 0 < count ) { + final ArrayList<Vertex> vertices = polyline.getVertices(); + for(int i=0; i < count; i++) { + final Loop loop = loops.get(i); + for(int j=0; j < vertices.size(); j++) { + if( loop.checkInside( vertices.get(j) ) ) { + return loop; + } } } } diff --git a/src/jogl/classes/jogamp/graph/curve/tess/GraphOutline.java b/src/jogl/classes/jogamp/graph/curve/tess/GraphOutline.java index 81e6efdad..75192d45a 100644 --- a/src/jogl/classes/jogamp/graph/curve/tess/GraphOutline.java +++ b/src/jogl/classes/jogamp/graph/curve/tess/GraphOutline.java @@ -31,6 +31,7 @@ import java.util.ArrayList; import com.jogamp.graph.geom.Outline; import com.jogamp.graph.geom.Vertex; +import com.jogamp.graph.geom.plane.Winding; public class GraphOutline { final private Outline outline; @@ -40,11 +41,14 @@ public class GraphOutline { this.outline = new Outline(); } - /**Create a control polyline of control vertices + /** + * Create a control polyline of control vertices * the curve pieces can be identified by onCurve flag * of each cp the control polyline is open by default + * + * @param ol the source {@link Outline} */ - public GraphOutline(final Outline ol){ + public GraphOutline(final Outline ol) { this.outline = ol; final ArrayList<Vertex> vertices = this.outline.getVertices(); for(int i = 0; i< vertices.size(); i++){ diff --git a/src/jogl/classes/jogamp/graph/curve/tess/Loop.java b/src/jogl/classes/jogamp/graph/curve/tess/Loop.java index 5d1bc051f..1d8264607 100644 --- a/src/jogl/classes/jogamp/graph/curve/tess/Loop.java +++ b/src/jogl/classes/jogamp/graph/curve/tess/Loop.java @@ -34,6 +34,7 @@ import com.jogamp.graph.geom.Vertex; import com.jogamp.graph.geom.plane.Winding; import com.jogamp.graph.geom.Triangle; import com.jogamp.opengl.math.VectorUtil; +import com.jogamp.opengl.math.Vert2fImmutable; import com.jogamp.opengl.math.geom.AABBox; public class Loop { @@ -91,9 +92,24 @@ public class Loop { return (root.getNext().getNext().getNext() == root); } - /**Create a connected list of half edges (loop) + private static float area(final ArrayList<GraphVertex> vertices) { + final int n = vertices.size(); + float area = 0.0f; + for (int p = n - 1, q = 0; q < n; p = q++) { + final float[] pCoord = vertices.get(p).getCoord(); + final float[] qCoord = vertices.get(q).getCoord(); + area += pCoord[0] * qCoord[1] - qCoord[0] * pCoord[1]; + } + return area; + } + private static Winding getWinding(final ArrayList<GraphVertex> vertices) { + return area(vertices) >= 0 ? Winding.CCW : Winding.CW ; + } + + /** + * Create a connected list of half edges (loop) * from the boundary profile - * @param reqWinding requested winding of edges (CCW or CW) + * @param reqWinding requested winding of edges, either {@link Winding#CCW} for {@link HEdge#BOUNDARY} or {@link Winding#CW} for {@link HEdge#HOLE} */ private HEdge initFromPolyline(final GraphOutline outline, final Winding reqWinding){ final ArrayList<GraphVertex> vertices = outline.getGraphPoint(); @@ -101,57 +117,64 @@ public class Loop { if(vertices.size()<3) { throw new IllegalArgumentException("outline's vertices < 3: " + vertices.size()); } - final Winding hasWinding = VectorUtil.getWinding( - vertices.get(0).getPoint(), - vertices.get(1).getPoint(), - vertices.get(2).getPoint()); - //FIXME: handle case when vertices come inverted - Rami - // skips inversion CW -> CCW - final boolean invert = hasWinding != reqWinding && - reqWinding == Winding.CW; - - final int max; + final Winding hasWinding = getWinding( vertices ); // requires area-winding detection + final int edgeType = reqWinding == Winding.CCW ? HEdge.BOUNDARY : HEdge.HOLE ; - int index; HEdge firstEdge = null; HEdge lastEdge = null; - if(!invert) { - max = vertices.size(); - index = 0; - } else { - max = -1; - index = vertices.size() -1; - } - - while(index != max){ - final GraphVertex v1 = vertices.get(index); - box.resize(v1.getX(), v1.getY(), v1.getZ()); - - final HEdge edge = new HEdge(v1, edgeType); - - v1.addEdge(edge); - if(lastEdge != null) { - lastEdge.setNext(edge); - edge.setPrev(lastEdge); - } else { - firstEdge = edge; - } - - if(!invert) { - if(index == vertices.size()-1) { + /** + * The winding conversion CW -> CCW can't be resolved here (-> Rami?) + * Therefore we require outline boundaries to be in CCW, see API-doc comment in OutlineShape. + * + * Original comment: + * FIXME: handle case when vertices come inverted - Rami + * Skips inversion CW -> CCW + */ + if( hasWinding == reqWinding || reqWinding == Winding.CCW ) { + // Correct Winding or skipped CW -> CCW (no inversion possible here, too late ??) + final int max = vertices.size() - 1; + for(int index = 0; index <= max; ++index) { + final GraphVertex v1 = vertices.get(index); + box.resize(v1.getX(), v1.getY(), v1.getZ()); + + final HEdge edge = new HEdge(v1, edgeType); + + v1.addEdge(edge); + if(lastEdge != null) { + lastEdge.setNext(edge); + edge.setPrev(lastEdge); + } else { + firstEdge = edge; + } + if(index == max ) { edge.setNext(firstEdge); firstEdge.setPrev(edge); } - index++; - } else { + lastEdge = edge; + } + } else { // if( reqWinding == Winding.CW ) { + // CCW -> CW + for(int index = vertices.size() - 1; index >= 0; --index) { + final GraphVertex v1 = vertices.get(index); + box.resize(v1.getX(), v1.getY(), v1.getZ()); + + final HEdge edge = new HEdge(v1, edgeType); + + v1.addEdge(edge); + if(lastEdge != null) { + lastEdge.setNext(edge); + edge.setPrev(lastEdge); + } else { + firstEdge = edge; + } + if (index == 0) { edge.setNext(firstEdge); firstEdge.setPrev(edge); } - index--; + lastEdge = edge; } - lastEdge = edge; } return firstEdge; } @@ -159,7 +182,7 @@ public class Loop { public void addConstraintCurve(final GraphOutline polyline) { // GraphOutline outline = new GraphOutline(polyline); /**needed to generate vertex references.*/ - initFromPolyline(polyline, Winding.CW); + initFromPolyline(polyline, Winding.CW); // -> HEdge.HOLE final GraphVertex v3 = locateClosestVertex(polyline); final HEdge v3Edge = v3.findBoundEdge(); |