import { Color } from '../../math/Color.js'; import { Path } from './Path.js'; import { Shape } from './Shape.js'; import { ShapeUtils } from '../ShapeUtils.js'; class ShapePath { constructor() { this.type = 'ShapePath'; this.color = new Color(); this.subPaths = []; this.currentPath = null; } moveTo(x, y) { this.currentPath = new Path(); this.subPaths.push(this.currentPath); this.currentPath.moveTo(x, y); return this; } lineTo(x, y) { this.currentPath.lineTo(x, y); return this; } quadraticCurveTo(aCPx, aCPy, aX, aY) { this.currentPath.quadraticCurveTo(aCPx, aCPy, aX, aY); return this; } bezierCurveTo(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) { this.currentPath.bezierCurveTo(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY); return this; } splineThru(pts) { this.currentPath.splineThru(pts); return this; } toShapes(isCCW, noHoles) { function toShapesNoHoles(inSubpaths) { const shapes = []; for (let i = 0, l = inSubpaths.length; i < l; i++) { const tmpPath = inSubpaths[i]; const tmpShape = new Shape(); tmpShape.curves = tmpPath.curves; shapes.push(tmpShape); } return shapes; } function isPointInsidePolygon(inPt, inPolygon) { const 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 let inside = false; for (let p = polyLen - 1, q = 0; q < polyLen; p = q++) { let edgeLowPt = inPolygon[p]; let edgeHighPt = inPolygon[q]; let edgeDx = edgeHighPt.x - edgeLowPt.x; let edgeDy = edgeHighPt.y - edgeLowPt.y; if (Math.abs(edgeDy) > Number.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 { const 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; const subPaths = this.subPaths; if (subPaths.length === 0) return []; if (noHoles === true) return toShapesNoHoles(subPaths); let solid, tmpPath, tmpShape; const shapes = []; if (subPaths.length === 1) { tmpPath = subPaths[0]; tmpShape = new Shape(); tmpShape.curves = tmpPath.curves; shapes.push(tmpShape); return shapes; } let holesFirst = !isClockWise(subPaths[0].getPoints()); holesFirst = isCCW ? !holesFirst : holesFirst; // console.log("Holes first", holesFirst); const betterShapeHoles = []; const newShapes = []; let newShapeHoles = []; let mainIdx = 0; let tmpPoints; newShapes[mainIdx] = undefined; newShapeHoles[mainIdx] = []; for (let 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]) mainIdx++; newShapes[mainIdx] = { s: new Shape(), p: tmpPoints }; newShapes[mainIdx].s.curves = tmpPath.curves; if (holesFirst) mainIdx++; newShapeHoles[mainIdx] = []; //console.log('cw', i); } else { newShapeHoles[mainIdx].push({ h: tmpPath, p: tmpPoints[0] }); //console.log('ccw', i); } } // only Holes? -> probably all Shapes with wrong orientation if (!newShapes[0]) return toShapesNoHoles(subPaths); if (newShapes.length > 1) { let ambiguous = false; const toChange = []; for (let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx++) { betterShapeHoles[sIdx] = []; } for (let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx++) { const sho = newShapeHoles[sIdx]; for (let hIdx = 0; hIdx < sho.length; hIdx++) { const ho = sho[hIdx]; let hole_unassigned = true; for (let s2Idx = 0; s2Idx < newShapes.length; s2Idx++) { if (isPointInsidePolygon(ho.p, newShapes[s2Idx].p)) { if (sIdx !== s2Idx) toChange.push({ froms: sIdx, tos: s2Idx, hole: hIdx }); if (hole_unassigned) { hole_unassigned = false; betterShapeHoles[s2Idx].push(ho); } else { ambiguous = true; } } } if (hole_unassigned) { betterShapeHoles[sIdx].push(ho); } } } // console.log("ambiguous: ", ambiguous); if (toChange.length > 0) { // console.log("to change: ", toChange); if (!ambiguous) newShapeHoles = betterShapeHoles; } } let tmpHoles; for (let i = 0, il = newShapes.length; i < il; i++) { tmpShape = newShapes[i].s; shapes.push(tmpShape); tmpHoles = newShapeHoles[i]; for (let j = 0, jl = tmpHoles.length; j < jl; j++) { tmpShape.holes.push(tmpHoles[j].h); } } //console.log("shape", shapes); return shapes; } } export { ShapePath };