fireball/lib/extras/core/ShapePath.js
2018-12-25 17:29:22 +03:30

287 lines
6.0 KiB
JavaScript

/**
* @author zz85 / http://www.lab4games.net/zz85/blog
* minimal class for proxing functions to Path. Replaces old "extractSubpaths()"
**/
import { Color } from '../../math/Color.js';
import { Path } from './Path.js';
import { Shape } from './Shape.js';
import { ShapeUtils } from '../ShapeUtils.js';
function ShapePath() {
this.type = 'ShapePath';
this.color = new Color();
this.subPaths = [];
this.currentPath = null;
}
Object.assign( ShapePath.prototype, {
moveTo: function ( x, y ) {
this.currentPath = new Path();
this.subPaths.push( this.currentPath );
this.currentPath.moveTo( x, y );
},
lineTo: function ( x, y ) {
this.currentPath.lineTo( x, y );
},
quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY );
},
bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY );
},
splineThru: function ( pts ) {
this.currentPath.splineThru( pts );
},
toShapes: function ( isCCW, noHoles ) {
function toShapesNoHoles( inSubpaths ) {
var shapes = [];
for ( var i = 0, l = inSubpaths.length; i < l; i ++ ) {
var tmpPath = inSubpaths[ i ];
var tmpShape = new Shape();
tmpShape.curves = tmpPath.curves;
shapes.push( tmpShape );
}
return shapes;
}
function isPointInsidePolygon( inPt, inPolygon ) {
var 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
var inside = false;
for ( var p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) {
var edgeLowPt = inPolygon[ p ];
var edgeHighPt = inPolygon[ q ];
var edgeDx = edgeHighPt.x - edgeLowPt.x;
var 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 {
var 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;
}
var isClockWise = ShapeUtils.isClockWise;
var subPaths = this.subPaths;
if ( subPaths.length === 0 ) return [];
if ( noHoles === true ) return toShapesNoHoles( subPaths );
var solid, tmpPath, tmpShape, shapes = [];
if ( subPaths.length === 1 ) {
tmpPath = subPaths[ 0 ];
tmpShape = new Shape();
tmpShape.curves = tmpPath.curves;
shapes.push( tmpShape );
return shapes;
}
var holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() );
holesFirst = isCCW ? ! holesFirst : holesFirst;
// console.log("Holes first", holesFirst);
var betterShapeHoles = [];
var newShapes = [];
var newShapeHoles = [];
var mainIdx = 0;
var tmpPoints;
newShapes[ mainIdx ] = undefined;
newShapeHoles[ mainIdx ] = [];
for ( var 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 ) {
var ambiguous = false;
var toChange = [];
for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
betterShapeHoles[ sIdx ] = [];
}
for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
var sho = newShapeHoles[ sIdx ];
for ( var hIdx = 0; hIdx < sho.length; hIdx ++ ) {
var ho = sho[ hIdx ];
var hole_unassigned = true;
for ( var 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;
}
}
var tmpHoles;
for ( var i = 0, il = newShapes.length; i < il; i ++ ) {
tmpShape = newShapes[ i ].s;
shapes.push( tmpShape );
tmpHoles = newShapeHoles[ i ];
for ( var j = 0, jl = tmpHoles.length; j < jl; j ++ ) {
tmpShape.holes.push( tmpHoles[ j ].h );
}
}
//console.log("shape", shapes);
return shapes;
}
} );
export { ShapePath };