ShapePath.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. /**
  2. * @author zz85 / http://www.lab4games.net/zz85/blog
  3. * minimal class for proxing functions to Path. Replaces old "extractSubpaths()"
  4. **/
  5. import { Color } from '../../math/Color.js';
  6. import { Path } from './Path.js';
  7. import { Shape } from './Shape.js';
  8. import { ShapeUtils } from '../ShapeUtils.js';
  9. function ShapePath() {
  10. this.type = 'ShapePath';
  11. this.color = new Color();
  12. this.subPaths = [];
  13. this.currentPath = null;
  14. }
  15. Object.assign( ShapePath.prototype, {
  16. moveTo: function ( x, y ) {
  17. this.currentPath = new Path();
  18. this.subPaths.push( this.currentPath );
  19. this.currentPath.moveTo( x, y );
  20. return this;
  21. },
  22. lineTo: function ( x, y ) {
  23. this.currentPath.lineTo( x, y );
  24. return this;
  25. },
  26. quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
  27. this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY );
  28. return this;
  29. },
  30. bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
  31. this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY );
  32. return this;
  33. },
  34. splineThru: function ( pts ) {
  35. this.currentPath.splineThru( pts );
  36. return this;
  37. },
  38. toShapes: function ( isCCW, noHoles ) {
  39. function toShapesNoHoles( inSubpaths ) {
  40. var shapes = [];
  41. for ( var i = 0, l = inSubpaths.length; i < l; i ++ ) {
  42. var tmpPath = inSubpaths[ i ];
  43. var tmpShape = new Shape();
  44. tmpShape.curves = tmpPath.curves;
  45. shapes.push( tmpShape );
  46. }
  47. return shapes;
  48. }
  49. function isPointInsidePolygon( inPt, inPolygon ) {
  50. var polyLen = inPolygon.length;
  51. // inPt on polygon contour => immediate success or
  52. // toggling of inside/outside at every single! intersection point of an edge
  53. // with the horizontal line through inPt, left of inPt
  54. // not counting lowerY endpoints of edges and whole edges on that line
  55. var inside = false;
  56. for ( var p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) {
  57. var edgeLowPt = inPolygon[ p ];
  58. var edgeHighPt = inPolygon[ q ];
  59. var edgeDx = edgeHighPt.x - edgeLowPt.x;
  60. var edgeDy = edgeHighPt.y - edgeLowPt.y;
  61. if ( Math.abs( edgeDy ) > Number.EPSILON ) {
  62. // not parallel
  63. if ( edgeDy < 0 ) {
  64. edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx;
  65. edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy;
  66. }
  67. if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue;
  68. if ( inPt.y === edgeLowPt.y ) {
  69. if ( inPt.x === edgeLowPt.x ) return true; // inPt is on contour ?
  70. // continue; // no intersection or edgeLowPt => doesn't count !!!
  71. } else {
  72. var perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y );
  73. if ( perpEdge === 0 ) return true; // inPt is on contour ?
  74. if ( perpEdge < 0 ) continue;
  75. inside = ! inside; // true intersection left of inPt
  76. }
  77. } else {
  78. // parallel or collinear
  79. if ( inPt.y !== edgeLowPt.y ) continue; // parallel
  80. // edge lies on the same horizontal line as inPt
  81. if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) ||
  82. ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour !
  83. // continue;
  84. }
  85. }
  86. return inside;
  87. }
  88. var isClockWise = ShapeUtils.isClockWise;
  89. var subPaths = this.subPaths;
  90. if ( subPaths.length === 0 ) return [];
  91. if ( noHoles === true ) return toShapesNoHoles( subPaths );
  92. var solid, tmpPath, tmpShape, shapes = [];
  93. if ( subPaths.length === 1 ) {
  94. tmpPath = subPaths[ 0 ];
  95. tmpShape = new Shape();
  96. tmpShape.curves = tmpPath.curves;
  97. shapes.push( tmpShape );
  98. return shapes;
  99. }
  100. var holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() );
  101. holesFirst = isCCW ? ! holesFirst : holesFirst;
  102. // console.log("Holes first", holesFirst);
  103. var betterShapeHoles = [];
  104. var newShapes = [];
  105. var newShapeHoles = [];
  106. var mainIdx = 0;
  107. var tmpPoints;
  108. newShapes[ mainIdx ] = undefined;
  109. newShapeHoles[ mainIdx ] = [];
  110. for ( var i = 0, l = subPaths.length; i < l; i ++ ) {
  111. tmpPath = subPaths[ i ];
  112. tmpPoints = tmpPath.getPoints();
  113. solid = isClockWise( tmpPoints );
  114. solid = isCCW ? ! solid : solid;
  115. if ( solid ) {
  116. if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) ) mainIdx ++;
  117. newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints };
  118. newShapes[ mainIdx ].s.curves = tmpPath.curves;
  119. if ( holesFirst ) mainIdx ++;
  120. newShapeHoles[ mainIdx ] = [];
  121. //console.log('cw', i);
  122. } else {
  123. newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } );
  124. //console.log('ccw', i);
  125. }
  126. }
  127. // only Holes? -> probably all Shapes with wrong orientation
  128. if ( ! newShapes[ 0 ] ) return toShapesNoHoles( subPaths );
  129. if ( newShapes.length > 1 ) {
  130. var ambiguous = false;
  131. var toChange = [];
  132. for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
  133. betterShapeHoles[ sIdx ] = [];
  134. }
  135. for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
  136. var sho = newShapeHoles[ sIdx ];
  137. for ( var hIdx = 0; hIdx < sho.length; hIdx ++ ) {
  138. var ho = sho[ hIdx ];
  139. var hole_unassigned = true;
  140. for ( var s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) {
  141. if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) {
  142. if ( sIdx !== s2Idx ) toChange.push( { froms: sIdx, tos: s2Idx, hole: hIdx } );
  143. if ( hole_unassigned ) {
  144. hole_unassigned = false;
  145. betterShapeHoles[ s2Idx ].push( ho );
  146. } else {
  147. ambiguous = true;
  148. }
  149. }
  150. }
  151. if ( hole_unassigned ) {
  152. betterShapeHoles[ sIdx ].push( ho );
  153. }
  154. }
  155. }
  156. // console.log("ambiguous: ", ambiguous);
  157. if ( toChange.length > 0 ) {
  158. // console.log("to change: ", toChange);
  159. if ( ! ambiguous ) newShapeHoles = betterShapeHoles;
  160. }
  161. }
  162. var tmpHoles;
  163. for ( var i = 0, il = newShapes.length; i < il; i ++ ) {
  164. tmpShape = newShapes[ i ].s;
  165. shapes.push( tmpShape );
  166. tmpHoles = newShapeHoles[ i ];
  167. for ( var j = 0, jl = tmpHoles.length; j < jl; j ++ ) {
  168. tmpShape.holes.push( tmpHoles[ j ].h );
  169. }
  170. }
  171. //console.log("shape", shapes);
  172. return shapes;
  173. }
  174. } );
  175. export { ShapePath };