Spaces:
Running
Running
| 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 }; | |