toShapes method
Converts the subPaths array into an array of Shapes. By default solid shapes are defined clockwise (CW) and holes are defined counterclockwise (CCW). If isCCW is set to true, then those are flipped.
Implementation
List<Shape> toShapes(bool isCCW, bool noHoles) {
toShapesNoHoles(inSubpaths) {
List<Shape> shapes = [];
for (int i = 0, l = inSubpaths.length; i < l; i++) {
final tmpPath = inSubpaths[i];
final tmpShape = Shape(null);
tmpShape.curves = tmpPath.curves;
shapes.add(tmpShape);
}
return shapes;
}
bool isPointInsidePolygon(Vector2 inPt, List<Vector2> inPolygon) {
final polyLen = inPolygon.length;
// inPt on polygon contour => immediate success or
// toggling of inside/outside at every single! intersection point of an edge
// with the horizontal line through inPt, left of inPt
// not counting lowerY endpoints of edges and whole edges on that line
bool inside = false;
for (int p = polyLen - 1, q = 0; q < polyLen; p = q++) {
Vector2 edgeLowPt = inPolygon[p];
Vector2 edgeHighPt = inPolygon[q];
double edgeDx = edgeHighPt.x - edgeLowPt.x;
double edgeDy = edgeHighPt.y - edgeLowPt.y;
if (edgeDy.abs() > MathUtils.epsilon) {
// not parallel
if (edgeDy < 0) {
edgeLowPt = inPolygon[q];
edgeDx = -edgeDx;
edgeHighPt = inPolygon[p];
edgeDy = -edgeDy;
}
if ((inPt.y < edgeLowPt.y) || (inPt.y > edgeHighPt.y)) continue;
if (inPt.y == edgeLowPt.y) {
if (inPt.x == edgeLowPt.x) return true; // inPt is on contour ?
// continue; // no intersection or edgeLowPt => doesn't count !!!
} else {
double perpEdge = edgeDy * (inPt.x - edgeLowPt.x) -
edgeDx * (inPt.y - edgeLowPt.y);
if (perpEdge == 0) return true; // inPt is on contour ?
if (perpEdge < 0) continue;
inside = !inside; // true intersection left of inPt
}
} else {
// parallel or collinear
if (inPt.y != edgeLowPt.y) continue; // parallel
// edge lies on the same horizontal line as inPt
if (((edgeHighPt.x <= inPt.x) && (inPt.x <= edgeLowPt.x)) ||
((edgeLowPt.x <= inPt.x) && (inPt.x <= edgeHighPt.x))) {
return true;
} // inPt: Point on contour !
// continue;
}
}
return inside;
}
const isClockWise = ShapeUtils.isClockWise;
final subPaths = this.subPaths;
if (subPaths.isEmpty) return [];
if (noHoles == true) return toShapesNoHoles(subPaths);
bool solid;
Path tmpPath;
Shape tmpShape;
List<Shape> shapes = [];
if (subPaths.length == 1) {
tmpPath = subPaths[0];
tmpShape = Shape();
tmpShape.curves = tmpPath.curves;
shapes.add(tmpShape);
return shapes;
}
bool holesFirst = !isClockWise(subPaths[0].getPoints());
holesFirst = isCCW ? !holesFirst : holesFirst;
// Console.log("Holes first", holesFirst);
dynamic betterShapeHoles = [];
final newShapes = [];
List newShapeHoles = [];
int mainIdx = 0;
List<Vector?> tmpPoints;
// newShapes[ mainIdx ] = null;
newShapes.listSetter(mainIdx, null);
// newShapeHoles[ mainIdx ] = [];
newShapeHoles.listSetter(mainIdx, []);
for (int i = 0, l = subPaths.length; i < l; i++) {
tmpPath = subPaths[i];
tmpPoints = tmpPath.getPoints();
solid = isClockWise(tmpPoints);
solid = isCCW ? !solid : solid;
if (solid) {
if ((!holesFirst) && (newShapes[mainIdx] != null)) mainIdx++;
// newShapes[ mainIdx ] = { "s": Shape(null), "p": tmpPoints };
newShapes.listSetter(mainIdx, {"s": Shape(null), "p": tmpPoints});
newShapes[mainIdx]["s"].curves = tmpPath.curves;
if (holesFirst) mainIdx++;
// newShapeHoles[ mainIdx ] = [];
newShapeHoles.listSetter(mainIdx, []);
//Console.log('cw', i);
} else {
newShapeHoles[mainIdx].add({"h": tmpPath, "p": tmpPoints[0]});
//Console.log('ccw', i);
}
}
// only Holes? -> probably all Shapes with wrong orientation
if (newShapes.isEmpty || newShapes[0] == null) {
return toShapesNoHoles(subPaths);
}
if (newShapes.length > 1) {
bool ambiguous = false;
int toChange = 0;
for (int sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx++) {
// betterShapeHoles[ sIdx ] = [];
(betterShapeHoles as List).listSetter(sIdx, []);
}
for (int sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx++) {
final sho = newShapeHoles[sIdx];
for (int hIdx = 0; hIdx < sho.length; hIdx++) {
final ho = sho[hIdx];
bool holeUnassigned = true;
for (int s2Idx = 0; s2Idx < newShapes.length; s2Idx++) {
if (isPointInsidePolygon(ho["p"], newShapes[s2Idx]["p"])) {
if ( sIdx != s2Idx ) toChange ++;
if (holeUnassigned) {
holeUnassigned = false;
betterShapeHoles[s2Idx].add(ho);
} else {
ambiguous = true;
}
}
}
if (holeUnassigned) {
betterShapeHoles[sIdx].add(ho);
}
}
}
if ( toChange > 0 && ambiguous == false ) {
newShapeHoles = betterShapeHoles;
}
}
dynamic tmpHoles;
for (int i = 0, il = newShapes.length; i < il; i++) {
tmpShape = newShapes[i]["s"];
shapes.add(tmpShape);
tmpHoles = newShapeHoles[i];
for (int j = 0, jl = tmpHoles.length; j < jl; j++) {
tmpShape.holes.add(tmpHoles[j]["h"]);
}
}
//Console.log("shape", shapes);
return shapes;
}