ShapeAlgorithms.js 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043
  1. /**
  2. * Copyright (c) 2011-2018 by Andrew Mustun. All rights reserved.
  3. *
  4. * This file is part of the QCAD project.
  5. *
  6. * QCAD is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * QCAD is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with QCAD.
  18. */
  19. /**
  20. * \class ShapeAlgorithms
  21. * Various shape based algorithms.
  22. */
  23. function ShapeAlgorithms() {
  24. }
  25. /**
  26. * \return Array with only the circle shapes from the given shapes.
  27. */
  28. ShapeAlgorithms.getCircleShapes = function(shapes) {
  29. if (isNull(shapes)) {
  30. return undefined;
  31. }
  32. var ret = [];
  33. for (var i=0; i<shapes.length; i++) {
  34. if (isCircleShape(shapes[i])) {
  35. ret.push(shapes[i]);
  36. }
  37. }
  38. return ret;
  39. };
  40. /**
  41. * \return The shape of the given shapes that is closest to the given position.
  42. */
  43. ShapeAlgorithms.getClosestShape = function(shapes, position) {
  44. if (isNull(shapes)) {
  45. return undefined;
  46. }
  47. var ret = undefined;
  48. var minDist = undefined;
  49. var circle = undefined;
  50. for (var i=0; i<shapes.length; i++) {
  51. var s = shapes[i];
  52. var dist = s.getDistanceTo(position);
  53. if (isNumber(dist) && (isNull(minDist) || dist<minDist)) {
  54. minDist = dist;
  55. ret = s;
  56. }
  57. }
  58. return ret;
  59. };
  60. ShapeAlgorithms.getTangents = function(circle1, circle2) {
  61. var offs1, offs2;
  62. var circleCenter1 = circle1.getCenter();
  63. var circleRadius1 = circle1.getRadius();
  64. var circleCenter2 = circle2.getCenter();
  65. var circleRadius2 = circle2.getRadius();
  66. // create all four possible tangents:
  67. var tangents = [];
  68. var angle1 = circleCenter1.getAngleTo(circleCenter2);
  69. var dist1 = circleCenter1.getDistanceTo(circleCenter2);
  70. if (dist1<1.0e-6) {
  71. return [];
  72. }
  73. // outer tangents:
  74. var dist2 = circleRadius2 - circleRadius1;
  75. if (dist1>dist2) {
  76. var angle2 = Math.asin(dist2/dist1);
  77. var angt1 = angle1 + angle2 + Math.PI/2.0;
  78. var angt2 = angle1 - angle2 - Math.PI/2.0;
  79. offs1 = new RVector();
  80. offs2 = new RVector();
  81. offs1.setPolar(circleRadius1, angt1);
  82. offs2.setPolar(circleRadius2, angt1);
  83. tangents.push(new RLine(circleCenter1.operator_add(offs1),
  84. circleCenter2.operator_add(offs2)));
  85. offs1.setPolar(circleRadius1, angt2);
  86. offs2.setPolar(circleRadius2, angt2);
  87. tangents.push(new RLine(circleCenter1.operator_add(offs1),
  88. circleCenter2.operator_add(offs2)));
  89. }
  90. else {
  91. tangents.push(undefined);
  92. tangents.push(undefined);
  93. }
  94. // inner tangents:
  95. var dist3 = circleRadius2 + circleRadius1;
  96. if (dist1>dist3) {
  97. var angle3 = Math.asin(dist3/dist1);
  98. var angt3 = angle1 + angle3 + Math.PI/2.0;
  99. var angt4 = angle1 - angle3 - Math.PI/2.0;
  100. offs1 = new RVector();
  101. offs2 = new RVector();
  102. offs1.setPolar(circleRadius1, angt3);
  103. offs2.setPolar(circleRadius2, angt3);
  104. tangents.push(new RLine(circleCenter1.operator_subtract(offs1),
  105. circleCenter2.operator_add(offs2)));
  106. offs1.setPolar(circleRadius1, angt4);
  107. offs2.setPolar(circleRadius2, angt4);
  108. tangents.push(new RLine(circleCenter1.operator_subtract(offs1),
  109. circleCenter2.operator_add(offs2)));
  110. }
  111. else {
  112. tangents.push(undefined);
  113. tangents.push(undefined);
  114. }
  115. return tangents;
  116. };
  117. /**
  118. * \return Line that is orthogonal to line and tangential to circle.
  119. */
  120. ShapeAlgorithms.getOrthogonalTangents = function(line, circle) {
  121. var ret = [];
  122. var auxLine1, auxLine2;
  123. var ips, ips1, ips2;
  124. var lineAngle = line.getAngle();
  125. if (isCircleShape(circle) || isArcShape(circle)) {
  126. // line parallel to line through center of circle:
  127. auxLine1 = new RLine(circle.getCenter(), lineAngle, 100.0);
  128. // intersections of parallel with circle:
  129. ips1 = circle.getIntersectionPoints(auxLine1, false);
  130. for (var i=0; i<ips1.length; i++) {
  131. // candidate:
  132. auxLine2 = new RLine(ips1[i], lineAngle+Math.PI/2, 100.0);
  133. ips2 = line.getIntersectionPoints(auxLine2, false);
  134. if (ips2.length===1) {
  135. ret.push(new RLine(ips1[i], ips2[0]));
  136. }
  137. }
  138. }
  139. else if (isEllipseShape(circle)) {
  140. var center = circle.getCenter();
  141. // circle around ellipse:
  142. var auxCircle = new RCircle(center, circle.getMajorRadius());
  143. var foci = circle.getFoci();
  144. auxLine1 = new RLine(foci[0], lineAngle, 100.0);
  145. auxLine2 = new RLine(foci[1], lineAngle, 100.0);
  146. ips1 = auxLine1.getIntersectionPoints(auxCircle, false);
  147. ips2 = auxLine2.getIntersectionPoints(auxCircle, false);
  148. var pointOfContact1 = undefined;
  149. var pointOfContact2 = undefined;
  150. if (ips1.length>=1 && ips2.length>=1) {
  151. if (ips1[0].equalsFuzzy(ips2[0])) {
  152. pointOfContact1 = ips1[0];
  153. }
  154. else {
  155. auxLine1 = new RLine(ips1[0], ips2[0]);
  156. ips = circle.getIntersectionPoints(auxLine1, false);
  157. if (ips.length>=1) {
  158. pointOfContact1 = ips[0];
  159. }
  160. }
  161. }
  162. if (ips1.length>=2 && ips2.length>=2) {
  163. if (ips1[1].equalsFuzzy(ips2[1])) {
  164. pointOfContact2 = ips1[1];
  165. }
  166. else {
  167. auxLine2 = new RLine(ips1[1], ips2[1]);
  168. ips = circle.getIntersectionPoints(auxLine2, false);
  169. if (ips.length>=1) {
  170. pointOfContact2 = ips[0];
  171. }
  172. }
  173. }
  174. if (!isNull(pointOfContact1)) {
  175. var pointOnLine1 = line.getClosestPointOnShape(pointOfContact1, false);
  176. ret.push(new RLine(pointOfContact1, pointOnLine1));
  177. }
  178. if (!isNull(pointOfContact2)) {
  179. var pointOnLine2 = line.getClosestPointOnShape(pointOfContact2, false);
  180. ret.push(new RLine(pointOfContact2, pointOnLine2));
  181. }
  182. }
  183. return ret;
  184. };
  185. /**
  186. * \return Parallels to this shape.
  187. * \param distance Distance of first parallel or concentric arc or circle.
  188. * \param number Number of offset shapes to generate.
  189. * \param sidePosition RVector indicating what side of the shape the parallels
  190. * should be RS.LeftHand or RS.RightHand or RS.BothSides.
  191. */
  192. ShapeAlgorithms.getOffsetShapes = function(shape, distance, number, sidePosition) {
  193. var side = isVector(sidePosition) ? RS.NoSide : sidePosition;
  194. var pos = isVector(sidePosition) ? sidePosition : RVector.invalid;
  195. return shape.getOffsetShapes(distance, number, side, pos);
  196. };
  197. ShapeAlgorithms.getOffsetLines = function(shape, distance, number, sidePosition) {
  198. var side = isVector(sidePosition) ? RS.NoSide : sidePosition;
  199. var pos = isVector(sidePosition) ? sidePosition : RVector.invalid;
  200. return RShape.getOffsetLines(shape, distance, number, side, pos);
  201. };
  202. ShapeAlgorithms.getOffsetArcs = function(shape, distance, number, sidePosition) {
  203. var side = isVector(sidePosition) ? RS.NoSide : sidePosition;
  204. var pos = isVector(sidePosition) ? sidePosition : RVector.invalid;
  205. return RShape.getOffsetArcs(shape, distance, number, side, pos);
  206. };
  207. /**
  208. * \return Array of spline shapes representing the parallel curves to the given ellipse shape.
  209. */
  210. ShapeAlgorithms.getOffsetEllipses = function(shape, distance, number, sidePosition) {
  211. var side = isVector(sidePosition) ? RS.NoSide : sidePosition;
  212. var pos = isVector(sidePosition) ? sidePosition : RVector.invalid;
  213. return RShape.getOffsetEllipses(shape, distance, number, side, pos);
  214. };
  215. /**
  216. * \return Intersection points between shape and other shapes.
  217. */
  218. ShapeAlgorithms.getIntersectionPoints = function(shape, otherShapes, onShape, onOtherShapes) {
  219. var intersections = [];
  220. var i, k;
  221. // treat start and end points as intersection points for open shapes:
  222. if (onShape &&
  223. !isCircleShape(shape) &&
  224. !isFullEllipseShape(shape) &&
  225. !isXLineShape(shape) &&
  226. (!isPolylineShape(shape) || !shape.isGeometricallyClosed()) &&
  227. (!isSplineShape(shape) || !shape.isClosed())) {
  228. var sp = shape.getStartPoint();
  229. sp.isStart = true;
  230. intersections.push(sp);
  231. if (!isRayShape(shape)) {
  232. var ep = shape.getEndPoint()
  233. ep.isEnd = true;
  234. intersections.push(ep);
  235. }
  236. }
  237. // find all intersection points:
  238. for (i=0; i<otherShapes.length; i++) {
  239. var otherShape = otherShapes[i];
  240. if (isFunction(otherShape.data)) {
  241. otherShape = otherShape.data();
  242. }
  243. var sol = shape.getIntersectionPoints(otherShape, onShape, false, true);
  244. for (k=0; k<sol.length; k++) {
  245. if (!onOtherShapes || otherShape.isOnShape(sol[k])) {
  246. intersections.push(sol[k]);
  247. }
  248. }
  249. }
  250. var selfIntersectionPoints = shape.getSelfIntersectionPoints();
  251. // add self intersection points to list:
  252. if (selfIntersectionPoints.length!==0) {
  253. intersections = intersections.concat(selfIntersectionPoints);
  254. }
  255. return intersections;
  256. };
  257. /**
  258. * \return Array of shapes to extend or trim to.
  259. *
  260. * \param doc RDocument
  261. * \param entityId ID of entity to exclude (typically clicked entity).
  262. * \param shape Shape of (clicked) entity.
  263. * \param extend True if entity is being extended.
  264. */
  265. ShapeAlgorithms.getIntersectingShapes = function(doc, entityId, shape, extend) {
  266. if (isNull(extend)) {
  267. extend = false;
  268. }
  269. if (isNull(shape)) {
  270. return [];
  271. }
  272. // find other shapes that potentially intersect with the chosen entity:
  273. var ret = [];
  274. // allow for error: especially for ellipse segments bordering to tangential lines this is needed:
  275. var otherEntityIds;
  276. if (extend===true) {
  277. // TODO: if we are extending, the 'rest' has to be queried instead
  278. //otherEntityIds = document.queryIntersectedEntitiesXY(document.getBoundingBox().growXY(1.0e-2), true);
  279. otherEntityIds = doc.queryAllVisibleEntities();
  280. }
  281. else {
  282. if (isXLineShape(shape) || isRayShape(shape)) {
  283. otherEntityIds = doc.queryAllEntities();
  284. }
  285. else {
  286. otherEntityIds = doc.queryIntersectedEntitiesXY(shape.getBoundingBox().growXY(1.0e-2));
  287. }
  288. }
  289. for (var i=0; i<otherEntityIds.length; i++) {
  290. var otherEntity = doc.queryEntityDirect(otherEntityIds[i]);
  291. if (isTextBasedEntity(otherEntity)) {
  292. continue;
  293. }
  294. // ignore intersection points of same entity
  295. // self intersection points are handled elsewhere
  296. var same = otherEntityIds[i]===entityId;
  297. if (same && !isBlockReferenceEntity(otherEntity)) {
  298. continue;
  299. }
  300. // TODO: if shape is arc, circle, ellipse or ellipse arc:
  301. // entities with full bounding box outside full circle or full ellipse
  302. // bounding box could be ignored.
  303. var s = otherEntity.getShapes();
  304. if (s.length!==0) {
  305. if (!same) {
  306. ret = ret.concat(s);
  307. }
  308. else {
  309. // ignore same shape for block reference entities:
  310. for (var k=0; k<s.length; k++) {
  311. if (!shape.equals(s[k].data())) {
  312. ret.push(s[k]);
  313. }
  314. }
  315. }
  316. }
  317. }
  318. return ret;
  319. };
  320. /**
  321. * Breaks the closest segment in shape to position between two intersections
  322. * with otherShapes or
  323. * extends a shape to the next two (imaginary) intersections with otherShapes.
  324. *
  325. * \param extend True: extending instead of breaking out.
  326. *
  327. * \return Array of three new shapes which each might be undefined if its
  328. * length would otherwise be 0.
  329. * The first shape is the rest at the start of the shape.
  330. * The second shape is the rest at the end of the shape.
  331. * The third shape is the segment self in its new shape.
  332. */
  333. ShapeAlgorithms.autoSplit = function(shape, otherShapes, position, extend) {
  334. if (isNull(extend)) {
  335. extend = false;
  336. }
  337. // get intersection points:
  338. var ips = ShapeAlgorithms.getIntersectionPoints(shape, otherShapes, !extend, extend);
  339. if (ips.length===0) {
  340. // no intersections with other shapes or self,
  341. // return whole shape as segment:
  342. return [undefined, undefined, shape.clone()];
  343. }
  344. // convert circle to arc:
  345. if (isCircleShape(shape)) {
  346. var ap = shape.getCenter().getAngleTo(position);
  347. var arc = new RArc(shape.getCenter(), shape.getRadius(), ap, ap, false);
  348. var maxD = undefined;
  349. var p = undefined;
  350. for (var i=0; i<ips.length; i++) {
  351. var ip = ips[i];
  352. var d = arc.getDistanceFromStart(ip);
  353. if (isNull(maxD) || d>maxD) {
  354. maxD = d;
  355. p = ip;
  356. }
  357. }
  358. // no intersections:
  359. if (isNull(p)) {
  360. return [undefined, undefined, shape.clone()];
  361. }
  362. // angle at intersection point closest to end of arc is where we split the circle:
  363. ap = shape.getCenter().getAngleTo(p);
  364. shape = new RArc(shape.getCenter(), shape.getRadius(), ap, ap, false);
  365. }
  366. // find intersection points closest to position:
  367. // array of two distances and two point vectors:
  368. var cutDistances = ShapeAlgorithms.getClosestIntersectionPointDistances(shape, ips, position);
  369. // distance along shape to clicked position:
  370. //var dPosition = ;
  371. // make sure direction of shape does not change in the process:
  372. //intersectionPointDistances.sort();
  373. var cutDist1 = undefined;
  374. var cutDist2 = undefined;
  375. var cutPos1 = undefined;
  376. var cutPos2 = undefined;
  377. if (!isNull(cutDistances) && cutDistances.length>1) {
  378. cutDist1 = cutDistances[0][0];
  379. cutDist2 = cutDistances[0][1];
  380. cutPos1 = cutDistances[1][0];
  381. cutPos2 = cutDistances[1][1];
  382. }
  383. // if we only have one cutting point (XLine, Ray), make it the first parameter:
  384. if (isNull(cutDist1)) {
  385. cutDist1 = cutDist2;
  386. cutPos1 = cutPos2;
  387. cutDist2 = undefined;
  388. cutPos2 = undefined;
  389. }
  390. return ShapeAlgorithms.autoSplitManual(shape, cutDist1, cutDist2, cutPos1, cutPos2, position, extend);
  391. };
  392. /**
  393. * Cut shape at given distances / positions.
  394. *
  395. * \return Array of three new shapes which each might be undefined if its
  396. * length would otherwise be 0.
  397. * The first shape is the rest at the start of the shape.
  398. * The second shape is the rest at the end of the shape.
  399. * The third shape is the segment self in its new shape.
  400. */
  401. ShapeAlgorithms.autoSplitManual = function(shape, cutDist1, cutDist2, cutPos1, cutPos2, position, extend) {
  402. if (isNull(extend)) {
  403. extend = false;
  404. }
  405. // if (!isCircleShape(shape) && !isFullEllipseShape(shape) &&
  406. // !isXLineShape(shape) && !isRayShape(shape)) {
  407. // if (isNull(cutDist1) || isNull(cutDist2)) {
  408. // // abort if shape requires two intersection points:
  409. // return undefined;
  410. // }
  411. // }
  412. // if (isNull(cutDist2)) {
  413. // cutDist2 = cutDist1;
  414. // }
  415. var dummy;
  416. var distSegment;
  417. // var cutPos1 = shape.getPointWithDistanceToStart(cutDist1);
  418. // var cutPos2 = undefined;
  419. // if (!isNull(cutDist2)) {
  420. // cutPos2 = shape.getPointWithDistanceToStart(cutDist2);
  421. // }
  422. if (isNull(cutDist1) && !isNull(cutPos1)) {
  423. cutDist1 = shape.getDistanceFromStart(cutPos1);
  424. }
  425. if (isNull(cutDist2) && !isNull(cutPos2)) {
  426. cutDist2 = shape.getDistanceFromStart(cutPos2);
  427. }
  428. if (isNull(cutDist2)) {
  429. // ray, nothing to cut:
  430. if (RMath.fuzzyCompare(cutDist1, 0.0) && shape.getStartPoint().equalsFuzzy(cutPos1)) {
  431. return [undefined, undefined, shape.clone()];
  432. }
  433. }
  434. else {
  435. if (RMath.fuzzyCompare(cutDist1, 0.0) && shape.getStartPoint().equalsFuzzy(cutPos1) &&
  436. RMath.fuzzyCompare(cutDist2, shape.getLength()) && shape.getEndPoint().equalsFuzzy(cutPos2)) {
  437. return [undefined, undefined, shape.clone()];
  438. }
  439. }
  440. var rest1 = undefined;
  441. var rest2 = undefined;
  442. var segment = undefined;
  443. // lines:
  444. if (isLineShape(shape)) {
  445. rest1 = shape.clone();
  446. rest2 = shape.clone();
  447. if (cutDist1 < cutDist2) {
  448. rest1.trimEndPoint(cutDist1);
  449. rest2.trimStartPoint(cutDist2);
  450. }
  451. else {
  452. rest1.trimEndPoint(cutDist2);
  453. rest2.trimStartPoint(cutDist1);
  454. }
  455. segment = shape.clone();
  456. segment.setStartPoint(cutPos1);
  457. segment.setEndPoint(cutPos2);
  458. if (rest1.getLength()<RS.PointTolerance) {
  459. rest1 = undefined;
  460. }
  461. if (rest2.getLength()<RS.PointTolerance) {
  462. rest2 = undefined;
  463. }
  464. }
  465. // xlines:
  466. else if (isXLineShape(shape)) {
  467. var line = shape.getLineShape();
  468. cutPos1 = line.getPointWithDistanceToStart(cutDist1);
  469. if (isNull(cutDist2)) {
  470. cutPos2 = undefined;
  471. }
  472. else {
  473. cutPos2 = line.getPointWithDistanceToStart(cutDist2);
  474. }
  475. rest1 = undefined;
  476. rest2 = undefined;
  477. if (!isNull(cutDist1) && !isNull(cutDist2) && cutDist1 > cutDist2) {
  478. dummy = cutDist1;
  479. cutDist1 = cutDist2;
  480. cutDist2 = dummy;
  481. }
  482. // if (!isNull(cutDist1) && !isNull(cutDist2)) {
  483. // cutDist1 = cutDist2;
  484. // cutDist2 = undefined;
  485. // }
  486. // <--------x---------------x--------->
  487. // rest2 cp2 segment cp1 rest1
  488. if (!isNull(cutDist1) && !isNull(cutDist2)) {
  489. rest1 = new RRay(cutPos1, RVector.createPolar(1.0, shape.getDirection2()));
  490. segment = new RLine(cutPos1, cutPos2);
  491. rest2 = new RRay(cutPos2, RVector.createPolar(1.0, shape.getDirection1()));
  492. }
  493. // <-o--------------x----------------->
  494. // pos segment cp1 rest1
  495. // <----------------x-------------o--->
  496. // rest1 cp1 segment pos
  497. else if (!isNull(cutDist1)) {
  498. rest1 = new RRay(cutPos1, RVector.createPolar(1.0, shape.getDirection2()));
  499. segment = new RRay(cutPos1, RVector.createPolar(1.0, shape.getDirection1()));
  500. distSegment = segment.getDistanceTo(position);
  501. if (isNaN(distSegment)) {
  502. dummy = rest1;
  503. rest1 = segment;
  504. segment = dummy;
  505. }
  506. rest2 = undefined;
  507. }
  508. }
  509. // rays:
  510. else if (isRayShape(shape)) {
  511. rest1 = undefined;
  512. rest2 = undefined;
  513. if (!isNull(cutDist1) && !isNull(cutDist2) && Math.sign(cutDist1) !== Math.sign(cutDist2)) {
  514. dummy = cutDist1;
  515. cutDist1 = cutDist2;
  516. cutDist2 = dummy;
  517. }
  518. // if (!cutDist1.isValid() && cutDist2.isValid()) {
  519. // cutDist1 = cutDist2;
  520. // cutDist2 = undefined;
  521. // }
  522. // <--------x-------o-------x---------
  523. // rest2 cp2 segment cp1 rest1
  524. if (isValidVector(cutPos1) && isValidVector(cutPos2)) {
  525. rest1 = new RLine(shape.getBasePoint(), cutPos1);
  526. segment = new RLine(cutPos1, cutPos2);
  527. rest2 = new RRay(cutPos2, RVector.createPolar(1.0, shape.getDirection1()));
  528. }
  529. // <-------o--------x-----------------
  530. // segment cp1 rest1
  531. // <----------------x--------o--------
  532. // rest1 cp1 segment
  533. else if (isValidVector(cutPos1)) {
  534. rest1 = new RLine(shape.getBasePoint(), cutPos1);
  535. segment = new RRay(cutPos1, RVector.createPolar(1.0, shape.getDirection1()));
  536. rest2 = undefined;
  537. distSegment = segment.getDistanceTo(position);
  538. if (isNaN(distSegment)) {
  539. dummy = rest1;
  540. rest1 = segment;
  541. segment = dummy;
  542. }
  543. }
  544. }
  545. // arcs:
  546. else if (isArcShape(shape)) {
  547. if (cutDist1 > cutDist2) {
  548. var dummy = cutDist1;
  549. cutDist1 = cutDist2;
  550. cutDist2 = dummy;
  551. }
  552. rest1 = shape.clone();
  553. rest2 = shape.clone();
  554. rest1.trimEndPoint(cutDist1);
  555. rest2.trimStartPoint(cutDist2);
  556. segment = shape.clone();
  557. //var l1 = segment.getLength();
  558. segment.setStartAngle(segment.getCenter().getAngleTo(cutPos1));
  559. //segment.trimStartPoint(cutDist1);
  560. //var l2 = segment.getLength();
  561. //segment.trimEndPoint(cutDist2 - (l1-l2));
  562. segment.setEndAngle(segment.getCenter().getAngleTo(cutPos2));
  563. if (!extend) {
  564. var angleLength1 = rest1.getAngleLength(true);
  565. var angleLength2 = rest2.getAngleLength(true);
  566. // rest1 is the same as the segment:
  567. var same1 = RMath.fuzzyAngleCompare(rest1.getStartAngle(), segment.getStartAngle()) &&
  568. RMath.fuzzyAngleCompare(rest1.getEndAngle(), segment.getEndAngle()) &&
  569. rest1.isReversed()===segment.isReversed();
  570. // catch common errors:
  571. if (angleLength1+angleLength2 > shape.getAngleLength() || same1) {
  572. rest1.trimEndPoint(cutDist2);
  573. rest2.trimStartPoint(cutDist1);
  574. segment.trimStartPoint(cutDist2);
  575. segment.trimEndPoint(cutDist1);
  576. angleLength1 = rest1.getAngleLength(true);
  577. angleLength2 = rest2.getAngleLength(true);
  578. }
  579. if (angleLength1<1.0e-5) {
  580. rest1 = undefined;
  581. }
  582. if (angleLength2<1.0e-5) {
  583. rest2 = undefined;
  584. }
  585. }
  586. }
  587. // circles:
  588. else if (isCircleShape(shape)) {
  589. if (isNull(cutDist1) || isNull(cutDist2)) {
  590. rest1 = undefined;
  591. rest2 = undefined;
  592. }
  593. else {
  594. var angle1 = shape.getCenter().getAngleTo(cutPos1);
  595. var angle2 = shape.getCenter().getAngleTo(cutPos2);
  596. rest1 = new RArc(
  597. shape.getCenter(),
  598. shape.getRadius(),
  599. angle1, angle2,
  600. false);
  601. rest2 = undefined;
  602. segment = new RArc(
  603. shape.getCenter(),
  604. shape.getRadius(),
  605. angle2, angle1,
  606. false);
  607. if (!isNull(position)) {
  608. var cursorAngle = shape.getCenter().getAngleTo(position);
  609. if (RMath.isAngleBetween(cursorAngle, angle1, angle2, false)) {
  610. rest1.setStartAngle(angle2);
  611. rest1.setEndAngle(angle1);
  612. segment.setStartAngle(angle1);
  613. segment.setEndAngle(angle2);
  614. }
  615. }
  616. var angleLength1 = rest1.getAngleLength(true);
  617. if (angleLength1<RS.AngleTolerance) {
  618. rest1 = undefined;
  619. }
  620. }
  621. }
  622. // ellipse arcs:
  623. else if (isEllipseArcShape(shape)) {
  624. rest1 = shape.clone();
  625. rest2 = shape.clone();
  626. rest1.trimEndPoint(cutPos1, cutPos1);
  627. rest2.trimStartPoint(cutPos2, cutPos2);
  628. segment = shape.clone();
  629. segment.trimStartPoint(cutPos1, cutPos1);
  630. segment.trimEndPoint(cutPos2, cutPos2);
  631. var angleLength1 = rest1.getAngleLength(true);
  632. var angleLength2 = rest2.getAngleLength(true);
  633. if (angleLength1+angleLength2 > shape.getAngleLength()) {
  634. rest1.trimEndPoint(cutPos2, cutPos2);
  635. rest2.trimStartPoint(cutPos1, cutPos1);
  636. segment.trimStartPoint(cutPos2, cutPos2);
  637. segment.trimEndPoint(cutPos1, cutPos1);
  638. angleLength1 = rest1.getAngleLength(true);
  639. angleLength2 = rest2.getAngleLength(true);
  640. }
  641. if (angleLength1<1.0e-5) {
  642. rest1 = undefined;
  643. }
  644. if (angleLength2<1.0e-5) {
  645. rest2 = undefined;
  646. }
  647. }
  648. // full ellipses:
  649. else if (isFullEllipseShape(shape)) {
  650. if (!isValidVector(cutPos1) || !isValidVector(cutPos2)) {
  651. rest1 = undefined;
  652. rest2 = undefined;
  653. }
  654. else {
  655. var angle1 = shape.getParamTo(cutPos1);
  656. var angle2 = shape.getParamTo(cutPos2);
  657. rest1 = new REllipse(
  658. shape.getCenter(),
  659. shape.getMajorPoint(),
  660. shape.getRatio(),
  661. angle1, angle2,
  662. false);
  663. rest2 = undefined;
  664. segment = new REllipse(
  665. shape.getCenter(),
  666. shape.getMajorPoint(),
  667. shape.getRatio(),
  668. angle2, angle1,
  669. false);
  670. if (!isNull(position)) {
  671. var cursorAngle = shape.getParamTo(position);
  672. if (RMath.isAngleBetween(cursorAngle, angle1, angle2, false)) {
  673. rest1.setStartParam(angle2);
  674. rest1.setEndParam(angle1);
  675. segment.setStartParam(angle1);
  676. segment.setEndParam(angle2);
  677. }
  678. }
  679. var angleLength1 = rest1.getAngleLength();
  680. if (angleLength1<RS.AngleTolerance) {
  681. rest1 = undefined;
  682. }
  683. }
  684. }
  685. // polyline:
  686. else if (isPolylineShape(shape)) {
  687. var closed = shape.isGeometricallyClosed();
  688. if (closed) {
  689. shape.relocateStartPoint(cutDist1);
  690. shape.convertToOpen();
  691. cutDist2 -= cutDist1;
  692. if (cutDist2<0.0) {
  693. cutDist2 = shape.getLength() + cutDist2;
  694. }
  695. cutDist1 = 0.0;
  696. }
  697. rest1 = shape.clone();
  698. rest2 = shape.clone();
  699. segment = shape.clone();
  700. if (closed) {
  701. rest1.trimEndPoint(cutDist2);
  702. segment = undefined;
  703. rest2.trimStartPoint(cutDist2);
  704. }
  705. else {
  706. // make sure point 1 is closer to the start of the polyline:
  707. if (cutDist1 > cutDist2) {
  708. var dummy = cutDist1;
  709. cutDist1 = cutDist2;
  710. cutDist2 = dummy;
  711. }
  712. rest1.trimEndPoint(cutDist1);
  713. var l1 = segment.getLength();
  714. segment.trimStartPoint(cutDist1);
  715. var l2 = segment.getLength();
  716. segment.trimEndPoint(cutDist2 - (l1-l2));
  717. rest2.trimStartPoint(cutDist2);
  718. }
  719. if (!isNull(segment)) {
  720. if (segment.getLength()<RS.PointTolerance || (closed && RMath.fuzzyCompare(segment.getLength(), shape.getLength()))) {
  721. segment = undefined;
  722. }
  723. }
  724. if (!isNull(rest1)) {
  725. if (rest1.getLength()<RS.PointTolerance || (closed && RMath.fuzzyCompare(rest1.getLength(), shape.getLength()))) {
  726. rest1 = undefined;
  727. }
  728. }
  729. if (!isNull(rest2)) {
  730. if (rest2.getLength()<RS.PointTolerance || (closed && RMath.fuzzyCompare(rest2.getLength(), shape.getLength()))) {
  731. rest2 = undefined;
  732. }
  733. }
  734. if (isNull(segment) && !isNull(rest1) && !isNull(rest2)) {
  735. var distRest1 = rest1.getDistanceTo(position);
  736. var distRest2 = rest2.getDistanceTo(position);
  737. if (distRest1<distRest2 || isNaN(distRest2)) {
  738. segment = rest1;
  739. rest1 = undefined;
  740. }
  741. else {
  742. segment = rest2;
  743. rest2 = undefined;
  744. }
  745. }
  746. }
  747. // spline:
  748. else if (isSplineShape(shape)) {
  749. rest1 = shape.clone();
  750. rest2 = shape.clone();
  751. segment = shape.clone();
  752. var tAtCutPos1 = shape.getTAtDistance(cutDist1);
  753. var tAtCutPos2 = shape.getTAtDistance(cutDist2);
  754. var tMax = shape.getTMax();
  755. if (shape.getStartPoint().equalsFuzzy(shape.getEndPoint())) {
  756. if (RMath.fuzzyCompare(tAtCutPos1, shape.getTMax())) {
  757. tAtCutPos1 = shape.getTMin();
  758. }
  759. }
  760. if (tAtCutPos1 < tAtCutPos2) {
  761. if (RMath.fuzzyCompare(tAtCutPos1, 0.0)) {
  762. rest1 = undefined;
  763. }
  764. else {
  765. rest1.trimEndPoint(cutDist1);
  766. // positions are more precise but
  767. // distances take into account possible self intersections:
  768. rest1.setEndPoint(cutPos1);
  769. }
  770. var l1 = segment.getLength();
  771. segment.trimStartPoint(cutDist1);
  772. segment.setStartPoint(cutPos1);
  773. var l2 = segment.getLength();
  774. segment.trimEndPoint(cutDist2 - (l1-l2));
  775. segment.setEndPoint(cutPos2);
  776. if (RMath.fuzzyCompare(tAtCutPos2, tMax)) {
  777. rest2 = undefined;
  778. }
  779. else {
  780. rest2.trimStartPoint(cutDist2);
  781. rest2.setStartPoint(cutPos2);
  782. }
  783. }
  784. else {
  785. if (RMath.fuzzyCompare(tAtCutPos1, 0.0)) {
  786. rest1 = undefined;
  787. }
  788. else {
  789. rest1.trimEndPoint(cutDist2);
  790. rest1.setEndPoint(cutPos2);
  791. }
  792. var l1 = segment.getLength();
  793. segment.trimStartPoint(cutDist2);
  794. segment.setStartPoint(cutPos2);
  795. var l2 = segment.getLength();
  796. segment.trimEndPoint(cutDist1 - (l1-l2));
  797. segment.setEndPoint(cutPos1);
  798. if (RMath.fuzzyCompare(tAtCutPos2, tMax)) {
  799. rest2 = undefined;
  800. }
  801. else {
  802. rest2.trimStartPoint(cutDist1);
  803. rest2.setStartPoint(cutPos1);
  804. }
  805. }
  806. if (!isNull(segment)) {
  807. if (!segment.isValid() || segment.getLength()<RS.PointTolerance) {
  808. segment = undefined;
  809. }
  810. }
  811. if (!isNull(rest1)) {
  812. if (!rest1.isValid() || rest1.getLength()<RS.PointTolerance) {
  813. rest1 = undefined;
  814. }
  815. }
  816. if (!isNull(rest2)) {
  817. if (!rest2.isValid() || rest2.getLength()<RS.PointTolerance) {
  818. rest2 = undefined;
  819. }
  820. }
  821. }
  822. var ret = [];
  823. // add new rest entities:
  824. ret.push(rest1);
  825. ret.push(rest2);
  826. ret.push(segment);
  827. return ret;
  828. };
  829. /**
  830. * \return The two distances along the given shape identifying the
  831. * intersections points closest to the given position along with the cut positions:
  832. * [ [cutDist1, cutDist2], [cutPos1, cutPos2] ]
  833. *
  834. * \param onShape True: only return intersections on the shape
  835. * (for trimming, breaking, default).
  836. * False: Also consider intersection points outside of shape (for extending).
  837. * \param onOtherShapes True only return intersections on one of the other
  838. * shapes (for extending).
  839. */
  840. ShapeAlgorithms.getClosestIntersectionPointDistances = function(shape, intersections, position) {
  841. // if (isNull(onShape)) {
  842. // onShape = true;
  843. // }
  844. // if (isNull(onOtherShapes)) {
  845. // onOtherShapes = false;
  846. // }
  847. // var i, k;
  848. // // treat start and end points as intersection points for open shapes:
  849. // if (onShape &&
  850. // !isCircleShape(shape) &&
  851. // !isFullEllipseShape(shape) &&
  852. // !isXLineShape(shape) &&
  853. // (!isPolylineShape(shape) || !shape.isGeometricallyClosed()) &&
  854. // (!isSplineShape(shape) || !shape.isClosed())) {
  855. // var sp = shape.getStartPoint();
  856. // sp.isStart = true;
  857. // intersections.push(sp);
  858. // if (!isRayShape(shape)) {
  859. // var ep = shape.getEndPoint()
  860. // ep.isEnd = true;
  861. // intersections.push(ep);
  862. // }
  863. // }
  864. // // find all intersection points:
  865. // for (i=0; i<otherShapes.length; i++) {
  866. // var otherShape = otherShapes[i];
  867. // if (isFunction(otherShape.data)) {
  868. // otherShape = otherShape.data();
  869. // }
  870. // var sol = shape.getIntersectionPoints(otherShape, onShape, false, true);
  871. // for (k=0; k<sol.length; k++) {
  872. // if (!onOtherShapes || otherShape.isOnShape(sol[k])) {
  873. // intersections.push(sol[k]);
  874. // }
  875. // }
  876. // }
  877. // // add self intersection points to list:
  878. // if (!isNull(selfIntersectionPoints) && selfIntersectionPoints.length!==0) {
  879. // intersections = intersections.concat(selfIntersectionPoints);
  880. // }
  881. var ip, dist;
  882. // closed circular shapes:
  883. var circular = false;
  884. if (isCircleShape(shape)) {
  885. var a = shape.getCenter().getAngleTo(position);
  886. shape = new RArc(shape.getCenter(), shape.getRadius(), a, a, false);
  887. circular = true;
  888. }
  889. if (isPolylineShape(shape) && shape.isGeometricallyClosed()) {
  890. circular = true;
  891. }
  892. if (isArcShape(shape)) {
  893. circular = true;
  894. }
  895. // if (isSplineShape(shape) && shape.isGeometricallyClosed()) {
  896. // circular = true;
  897. // }
  898. var pDist = shape.getDistanceFromStart(position);
  899. var orthoLine;
  900. var reversedShape = false;
  901. if (isEllipseShape(shape)) {
  902. orthoLine = new RLine(shape.getCenter(), position);
  903. if (isEllipseShape(shape)) {
  904. if (shape.isReversed()) {
  905. shape.reverse();
  906. reversedShape = true;
  907. }
  908. }
  909. }
  910. // find intersection points directly before and after clicked position:
  911. var cutDist1 = undefined;
  912. var cutDist2 = undefined;
  913. var cutPos1 = undefined;
  914. var cutPos2 = undefined;
  915. // for circular shapes, also find intersections closest to and furthest from start point:
  916. var cutDistMax = undefined;
  917. var cutDistMin = undefined;
  918. var cutPosMax = undefined;
  919. var cutPosMin = undefined;
  920. for (var i=0; i<intersections.length; i++) {
  921. ip = intersections[i];
  922. if (isEllipseShape(shape)) {
  923. dist = RMath.getAngleDifference(orthoLine.getAngle(), shape.getCenter().getAngleTo(ip));
  924. if (isNull(cutDist1) || dist<cutDist1) {
  925. cutPos1 = ip;
  926. cutDist1 = dist;
  927. }
  928. if (isNull(cutDist2) || dist>cutDist2) {
  929. cutPos2 = ip;
  930. cutDist2 = dist;
  931. }
  932. }
  933. else {
  934. var dists = shape.getDistancesFromStart(ip);
  935. for (var k=0; k<dists.length; k++) {
  936. dist = dists[k];
  937. // largest distance to start
  938. // but smaller than click point:
  939. if (dist<pDist) {
  940. if (isNull(cutDist1) || dist>cutDist1) {
  941. cutDist1 = dist;
  942. cutPos1 = ip;
  943. }
  944. }
  945. if (circular) {
  946. if (isNull(cutDistMax) || dist>cutDistMax) {
  947. cutDistMax = dist;
  948. cutPosMax = ip;
  949. }
  950. }
  951. // smallest distance to start
  952. // but larger than click point
  953. if (dist>pDist) {
  954. if (isNull(cutDist2) || dist<cutDist2) {
  955. cutDist2 = dist;
  956. cutPos2 = ip;
  957. }
  958. }
  959. if (circular) {
  960. if (isNull(cutDistMin) || (dist<cutDistMin && (dist>pDist || isPolylineShape(shape)))) {
  961. cutDistMin = dist;
  962. cutPosMin = ip;
  963. }
  964. }
  965. }
  966. }
  967. }
  968. if (circular) {
  969. if (isNull(cutDist1)) {
  970. cutDist1 = cutDistMax;
  971. cutPos1 = cutPosMax;
  972. }
  973. if (isNull(cutDist2)) {
  974. cutDist2 = cutDistMin;
  975. cutPos2 = cutPosMin;
  976. }
  977. }
  978. if (isEllipseShape(shape) && shape.isReversed()) {
  979. var dummy = cutPos1;
  980. cutPos1 = cutPos2;
  981. cutPos2 = dummy;
  982. dummy = cutDist1;
  983. cutDist1 = cutDist2;
  984. cutDist2 = dummy;
  985. }
  986. // open shape: cut to start or end point:
  987. if (!isCircleShape(shape) &&
  988. !isFullEllipseShape(shape) &&
  989. !isXLineShape(shape) &&
  990. !isRayShape(shape) &&
  991. (!isPolylineShape(shape) || !shape.isGeometricallyClosed()) &&
  992. (!isSplineShape(shape) || !shape.isClosed())) {
  993. if (!isValidVector(cutPos1)) {
  994. cutDist1 = 0.0;
  995. cutPos1 = shape.getStartPoint();
  996. }
  997. if (!isValidVector(cutPos2)) {
  998. cutDist2 = shape.getLength();
  999. cutPos2 = shape.getEndPoint();
  1000. }
  1001. }
  1002. if (reversedShape) {
  1003. shape.reverse();
  1004. }
  1005. return [ [cutDist1, cutDist2], [cutPos1, cutPos2] ];
  1006. /*
  1007. var orthoLine = undefined;
  1008. var reversedShape = false;
  1009. // auxiliary line othogonal to entity and through cursor:
  1010. var p = shape.getClosestPointOnShape(position, true);
  1011. if (isLineBasedShape(shape)) {
  1012. var orthoAngle = shape.getDirection1()+Math.PI/2.0;
  1013. var r = RVector.createPolar(1.0, orthoAngle);
  1014. orthoLine = new RLine(position, position.operator_add(r));
  1015. }
  1016. else if (isArcShape(shape) || isCircleShape(shape) || isEllipseShape(shape)) {
  1017. orthoLine = new RLine(shape.getCenter(), position);
  1018. if (isArcShape(shape) || isEllipseShape(shape)) {
  1019. if (shape.isReversed()) {
  1020. shape.reverse();
  1021. reversedShape = true;
  1022. }
  1023. }
  1024. }
  1025. if (isNull(orthoLine) && !isSplineShape(shape) && !isPolylineShape(shape)) {
  1026. return undefined;
  1027. }
  1028. var intersections = [];
  1029. // treat start and end points as intersection points:
  1030. if (onShape &&
  1031. !isCircleShape(shape) &&
  1032. !isFullEllipseShape(shape) &&
  1033. !isXLineShape(shape) &&
  1034. (!isPolylineShape(shape) || !shape.isGeometricallyClosed())) {
  1035. var sp = shape.getStartPoint();
  1036. sp.isStart = true;
  1037. intersections.push(sp);
  1038. if (!isRayShape(shape)) {
  1039. var ep = shape.getEndPoint()
  1040. ep.isEnd = true;
  1041. intersections.push(ep);
  1042. }
  1043. }
  1044. // find all intersection points:
  1045. for (var i=0; i<otherShapes.length; i++) {
  1046. var otherShape = otherShapes[i];
  1047. if (isFunction(otherShape.data)) {
  1048. otherShape = otherShape.data();
  1049. }
  1050. var sol = shape.getIntersectionPoints(otherShape, onShape, false, true);
  1051. for (var k=0; k<sol.length; k++) {
  1052. if (!onOtherShapes || otherShape.isOnShape(sol[k])) {
  1053. intersections.push(sol[k]);
  1054. }
  1055. }
  1056. }
  1057. // add self intersection points to list:
  1058. if (!isNull(selfIntersectionPoints)) {
  1059. intersections = intersections.concat(selfIntersectionPoints);
  1060. }
  1061. var cutPos1 = RVector.invalid;
  1062. var distRight = undefined;
  1063. var cutPos2 = RVector.invalid;
  1064. var distLeft = undefined;
  1065. // for closed polylines:
  1066. var cutPos3 = RVector.invalid;
  1067. var distRightMax = undefined;
  1068. var cutPos4 = RVector.invalid;
  1069. var distLeftMax = undefined;
  1070. // at least 2 intersection points are required to proceed:
  1071. if (intersections.length<2 && onShape && !isXLineShape(shape)) {
  1072. if (reversedShape) {
  1073. shape.reverse();
  1074. }
  1075. return undefined;
  1076. }
  1077. // find cutting point left and right of click point:
  1078. var tPos = undefined;
  1079. for (i=0; i<intersections.length; i++) {
  1080. var inters = intersections[i];
  1081. if (!inters.isValid()) {
  1082. continue;
  1083. }
  1084. var s;
  1085. var dist;
  1086. if (isLineBasedShape(shape)) {
  1087. s = orthoLine.getSideOfPoint(inters);
  1088. dist = inters.getDistanceTo(position);
  1089. if (s===RS.RightHand) {
  1090. if (isNull(distRight) || dist<distRight) {
  1091. cutPos1 = inters;
  1092. distRight = dist;
  1093. }
  1094. }
  1095. else if (s===RS.LeftHand) {
  1096. if (isNull(distLeft) || dist<distLeft) {
  1097. cutPos2 = inters;
  1098. distLeft = dist;
  1099. }
  1100. }
  1101. }
  1102. else if (isArcShape(shape) || isCircleShape(shape) || isEllipseShape(shape)) {
  1103. dist = RMath.getAngleDifference(orthoLine.getAngle(), shape.getCenter().getAngleTo(inters));
  1104. if (isNull(distRight) || dist>distRight) {
  1105. cutPos1 = inters;
  1106. distRight = dist;
  1107. }
  1108. if (isNull(distLeft) || dist<distLeft) {
  1109. cutPos2 = inters;
  1110. distLeft = dist;
  1111. }
  1112. }
  1113. else if (isSplineShape(shape)) {
  1114. if (isNull(tPos)) {
  1115. tPos = shape.getTAtPoint(position);
  1116. }
  1117. var tInters = shape.getTAtPoint(inters);
  1118. dist = tPos - tInters;
  1119. if (dist>0.0) {
  1120. if (isNull(distRight) || dist<distRight) {
  1121. cutPos2 = inters;
  1122. distRight = dist;
  1123. }
  1124. }
  1125. else if (dist<0.0) {
  1126. dist = Math.abs(dist);
  1127. if (isNull(distLeft) || dist<distLeft) {
  1128. cutPos1 = inters;
  1129. distLeft = dist;
  1130. }
  1131. }
  1132. }
  1133. else if (isPolylineShape(shape)) {
  1134. if (isNull(tPos)) {
  1135. tPos = shape.getLengthTo(position);
  1136. }
  1137. // shortest distance to intersection:
  1138. var tInters1 = shape.getLengthTo(inters, true, true);
  1139. if (inters.isEnd===true && tInters1<RS.PointTolerance) {
  1140. tInters1 = shape.getLength();
  1141. }
  1142. // longest distance to intersection:
  1143. var tInters2 = shape.getLengthTo(inters, true, false);
  1144. if (inters.isEnd===true && tInters2<RS.PointTolerance) {
  1145. tInters2 = shape.getLength();
  1146. }
  1147. var dists = [tPos - tInters1, tPos - tInters2];
  1148. for (var n=0; n<dists.length; n++) {
  1149. var dist = dists[n];
  1150. if (dist>0.0) {
  1151. if (isNull(distRight) || dist<distRight) {
  1152. cutPos2 = inters;
  1153. distRight = dist;
  1154. }
  1155. if (isNull(distRightMax) || dist>distRightMax) {
  1156. cutPos3 = inters;
  1157. distRightMax = dist;
  1158. }
  1159. }
  1160. else if (dist<0.0) {
  1161. dist = Math.abs(dist);
  1162. if (isNull(distLeft) || dist<distLeft) {
  1163. cutPos1 = inters;
  1164. distLeft = dist;
  1165. }
  1166. if (isNull(distLeftMax) || dist>distLeftMax) {
  1167. cutPos4 = inters;
  1168. distLeftMax = dist;
  1169. }
  1170. }
  1171. }
  1172. }
  1173. }
  1174. if (!isCircleShape(shape) &&
  1175. !isFullEllipseShape(shape) &&
  1176. !isXLineShape(shape) &&
  1177. !isRayShape(shape) &&
  1178. (!isPolylineShape(shape) || shape.isGeometricallyClosed())) {
  1179. if (!isValidVector(cutPos1)) {
  1180. cutPos1 = shape.getEndPoint();
  1181. }
  1182. if (!isValidVector(cutPos2)) {
  1183. cutPos2 = shape.getStartPoint();
  1184. }
  1185. }
  1186. if (isPolylineShape(shape)) {
  1187. if (isNull(distLeft)) {
  1188. cutPos1 = cutPos3;
  1189. cutPos2 = cutPos2;
  1190. }
  1191. if (isNull(distRight)) {
  1192. cutPos1 = cutPos1;
  1193. cutPos2 = cutPos4;
  1194. }
  1195. }
  1196. if (reversedShape) {
  1197. shape.reverse();
  1198. }
  1199. return [cutPos1, cutPos2];
  1200. */
  1201. };
  1202. /**
  1203. * \return RCircle through all three given points (RVector) or RLine if only
  1204. * two points are given.
  1205. */
  1206. ShapeAlgorithms.createCircleFrom3Points = function(point1, point2, point3) {
  1207. ShapeAlgorithms.error = undefined;
  1208. if (isNull(point1) || isNull(point2)) {
  1209. return undefined;
  1210. }
  1211. if (isNull(point3)) {
  1212. return new RLine(point1, point2);
  1213. }
  1214. if (point1.equalsFuzzy(point2) || point2.equalsFuzzy(point3) || point3.equalsFuzzy(point1)) {
  1215. ShapeAlgorithms.error = qsTr("At least two points are identical.");
  1216. return undefined;
  1217. }
  1218. var ret = RCircle.createFrom3Points(point1, point2, point3);
  1219. if (isNull(ret)) {
  1220. ShapeAlgorithms.error = qsTr("No circle possible.");
  1221. return undefined;
  1222. }
  1223. return ret;
  1224. };
  1225. /**
  1226. * \return RArc through all three given points (RVector) or RLine if only
  1227. * two points are given.
  1228. */
  1229. ShapeAlgorithms.createArcFrom3Points = function(point1, point2, point3) {
  1230. ShapeAlgorithms.error = undefined;
  1231. if (isNull(point1) || isNull(point2)) {
  1232. return undefined;
  1233. }
  1234. if (isNull(point3)) {
  1235. return new RLine(point1, point2);
  1236. }
  1237. if (point1.equalsFuzzy(point2) || point2.equalsFuzzy(point3) || point3.equalsFuzzy(point1)) {
  1238. ShapeAlgorithms.error = qsTr("At least two points are identical.");
  1239. return undefined;
  1240. }
  1241. var ret = RArc.createFrom3Points(point1, point2, point3);
  1242. if (!ret.isValid()) {
  1243. ShapeAlgorithms.error = qsTr("No arc possible.");
  1244. return undefined;
  1245. }
  1246. return ret;
  1247. };
  1248. /**
  1249. * Approximates the given ellipse with arc segments and returns a polyline with
  1250. * arc segments.
  1251. *
  1252. * \param segments Number of arc segments to generate.
  1253. * \return RPolyline object or undefined
  1254. */
  1255. ShapeAlgorithms.approximateEllipse = function(ellipse, segments) {
  1256. ShapeAlgorithms.error = undefined;
  1257. if (segments<=0) {
  1258. ShapeAlgorithms.error =
  1259. qsTr("Invalid number of segments: %1.").arg(segments);
  1260. return undefined;
  1261. }
  1262. if (ellipse.getMajorRadius()<RS.PointTolerance ||
  1263. ellipse.getMinorRadius()<RS.PointTolerance) {
  1264. ShapeAlgorithms.error = qsTr("Invalid ellipse major / minor: %1 / %2.")
  1265. .arg(ellipse.getMajorRadius()).arg(ellipse.getMinorRadius());
  1266. return undefined;
  1267. }
  1268. return ellipse.approximateWithArcs(segments);
  1269. };
  1270. ShapeAlgorithms.getCompleteQuadrilateralSegments = function(line1, line2, line3, line4) {
  1271. var ret = [];
  1272. // maps vertices to number of intersection points at that vertex:
  1273. var vertices = new Map(function(v1, v2) { return v1.equalsFuzzy(v2); });
  1274. var lines = ShapeAlgorithms.removeSharedPointer([ line1, line2, line3, line4 ]);
  1275. var i, k;
  1276. for (i=0; i<lines.length; i++) {
  1277. var angle = lines[i].getAngle();
  1278. var ips = [];
  1279. for (k=0; k<lines.length; k++) {
  1280. if (i===k) {
  1281. continue;
  1282. }
  1283. ips = ips.concat(lines[i].getIntersectionPoints(lines[k], false));
  1284. }
  1285. if (ips===0) {
  1286. return [];
  1287. }
  1288. var edge;
  1289. if (RMath.fuzzyCompare(angle, Math.PI/2, 0.1) || RMath.fuzzyCompare(angle, Math.PI/2*3, 0.1)) {
  1290. // line is (almost) vertical:
  1291. edge = RVector.getMinimumY(ips);
  1292. }
  1293. else {
  1294. // line is not vertical:
  1295. edge = RVector.getMinimumX(ips);
  1296. }
  1297. ips = RVector.getSortedByDistance(ips, edge);
  1298. for (k=0; k<ips.length-1; k++) {
  1299. ret.push(new RLine(ips[k], ips[k+1]));
  1300. }
  1301. }
  1302. for (i=0; i<ret.length; i++) {
  1303. var sp = ret[i].getStartPoint();
  1304. var ep = ret[i].getEndPoint();
  1305. vertices.put(sp, vertices.get(sp, 0) + 1);
  1306. vertices.put(ep, vertices.get(ep, 0) + 1);
  1307. }
  1308. ret["vertices"] = vertices;
  1309. return ret;
  1310. };
  1311. ShapeAlgorithms.getQuadrilateral = function(line1, line2, line3, line4) {
  1312. var segments = ShapeAlgorithms.getCompleteQuadrilateralSegments(line1, line2, line3, line4);
  1313. var vertices = segments.vertices;
  1314. var i, k;
  1315. // produce vs array:
  1316. // vs[order] = list of vertices
  1317. // where order is the number of intersections at those vertices
  1318. var vs = [];
  1319. for (var order=4; order>0; order--) {
  1320. vs[order] = [];
  1321. var startIndex = 0;
  1322. var keys = vertices.getKeys();
  1323. var values = vertices.getValues();
  1324. do {
  1325. i = values.indexOf(order, startIndex);
  1326. startIndex = i+1;
  1327. if (i!==-1) {
  1328. vs[order].push(keys[i]);
  1329. }
  1330. } while (i!==-1);
  1331. }
  1332. var vert = [];
  1333. // quadrilateral:
  1334. if (vs[4].length===1 && vs[3].length===2 && vs[2].length===3) {
  1335. vert = [vs[3][0], vs[4][0], vs[3][1]];
  1336. var l1 = new RLine(vs[3][0], vs[4][0]);
  1337. var l2 = new RLine(vs[4][0], vs[3][1]);
  1338. // ret.push(new RLine(vs[3][0], vs[4][0]));
  1339. // ret.push(new RLine(vs[4][0], vs[3][1]));
  1340. for (i=0; i<vs[2].length; i++) {
  1341. if (l1.isOnShape(vs[2][i], false) || l2.isOnShape(vs[2][i], false)) {
  1342. continue;
  1343. }
  1344. vert.push(vs[2][i]);
  1345. // ret.push(new RLine(vs[3][1], vs[2][i]));
  1346. // ret.push(new RLine(vs[2][i], vs[3][0]));
  1347. break;
  1348. }
  1349. // return ret;
  1350. }
  1351. // trapezoid:
  1352. else if (vs[4].length===0 && vs[3].length===2 && vs[2].length===3) {
  1353. vert = [vs[3][0], vs[3][1]];
  1354. // ret.push(new RLine(vs[3][0], vs[3][1]));
  1355. for (i=0; i<vs[2].length; i++) {
  1356. for (k=0; k<vs[2].length; k++) {
  1357. if (i===k) {
  1358. continue;
  1359. }
  1360. var l = new RLine(vs[2][k], vs[2][i]);
  1361. if (!l.isOnShape(vs[3][0], true) && !l.isOnShape(vs[3][1], true)) {
  1362. vert.push(vs[2][i]);
  1363. vert.push(vs[2][k]);
  1364. // ret.push(new RLine(vs[3][1], vs[2][k]));
  1365. // ret.push(l);
  1366. // ret.push(new RLine(vs[2][i], vs[3][0]));
  1367. break;
  1368. }
  1369. }
  1370. if (vert.length===4) {
  1371. break;
  1372. }
  1373. }
  1374. }
  1375. // parallelogram:
  1376. if (vs[4].length===0 && vs[3].length===0 && vs[2].length===4) {
  1377. vert = vs[2];
  1378. }
  1379. if (vert.length!==4) {
  1380. return undefined;
  1381. }
  1382. var ret = [];
  1383. var cursor = vert[0];
  1384. for (k=0; k<4; k++) {
  1385. for (i=0; i<segments.length; i++) {
  1386. var segment = segments[i];
  1387. if (segment.getEndPoint().equalsFuzzy(cursor)) {
  1388. segment.reverse();
  1389. }
  1390. if (segment.getStartPoint().equalsFuzzy(cursor)) {
  1391. for (var c=0; c<4; c++) {
  1392. if (segment.getEndPoint().equalsFuzzy(vert[c])) {
  1393. ret.push(segment.getStartPoint());
  1394. cursor = segment.getEndPoint();
  1395. segments.splice(i, 1);
  1396. break;
  1397. }
  1398. }
  1399. }
  1400. if (ret.length===k+1) {
  1401. break;
  1402. }
  1403. }
  1404. }
  1405. if (ret.length!==4) {
  1406. return undefined;
  1407. }
  1408. return ret;
  1409. };
  1410. /**
  1411. * Produces an ellipse inscribed in the quadrilateral defined by the
  1412. * four given unordered / untrimmed edges (RLine shapes).
  1413. */
  1414. ShapeAlgorithms.createEllipseInscribedFromLines = function(line1, line2, line3, line4) {
  1415. var quad = ShapeAlgorithms.getQuadrilateral(line1, line2, line3, line4);
  1416. if (isNull(quad)) {
  1417. return undefined;
  1418. }
  1419. return REllipse.createInscribed(quad[0], quad[1], quad[2], quad[3]);
  1420. };
  1421. /**
  1422. * Produces an ellipse inscribed in the quadrilateral defined by the
  1423. * four given ordered vertices (RVector).
  1424. */
  1425. ShapeAlgorithms.createEllipseInscribedFromVertices = function(v1, v2, v3, v4) {
  1426. return REllipse.createInscribed(v1, v2, v3, v4);
  1427. };
  1428. /**
  1429. * Tries to convert the given spline into one line or arc.
  1430. * \return RArc, RLine or the original RSpline.
  1431. */
  1432. ShapeAlgorithms.splineToLineOrArc = function(spline, tolerance, linesOnly) {
  1433. if (isNull(linesOnly)) {
  1434. linesOnly = false;
  1435. }
  1436. var startPoint = spline.getStartPoint();
  1437. var endPoint = spline.getEndPoint();
  1438. var middlePoint = spline.getMiddlePoint();
  1439. var i, point;
  1440. if (!linesOnly) {
  1441. var arc = RArc.createFrom3Points(startPoint, middlePoint, endPoint);
  1442. if (arc.isValid()) {
  1443. var splineIsArc = true;
  1444. for (i=0.0; i<1.0; i+=0.1) {
  1445. point = spline.getPointAt(i);
  1446. if (!arc.isOnShape(point, true, tolerance)) {
  1447. splineIsArc = false;
  1448. break;
  1449. }
  1450. }
  1451. // if (splineIsArc && arc.getRadius()>1000.0) {
  1452. // return new RLine(startPoint, endPoint);
  1453. // }
  1454. if (splineIsArc) {
  1455. return arc;
  1456. }
  1457. }
  1458. }
  1459. var line = new RLine(startPoint, endPoint);
  1460. var splineIsLine = true;
  1461. for (i=0.0; i<1.0; i+=0.1) {
  1462. point = spline.getPointAt(i);
  1463. if (!line.isOnShape(point, true, tolerance)) {
  1464. splineIsLine = false;
  1465. break;
  1466. }
  1467. }
  1468. if (splineIsLine) {
  1469. return line;
  1470. }
  1471. return spline;
  1472. };
  1473. /**
  1474. * Converts the given circle into an arc with the given start angle or 0.
  1475. */
  1476. ShapeAlgorithms.circleToArc = function(circle, startAngle) {
  1477. if (isNull(startAngle)) {
  1478. startAngle = 0.0;
  1479. }
  1480. return circle.toArc(startAngle);
  1481. };
  1482. /**
  1483. * Converts the given line or arc into a polyline with numSegments segments.
  1484. */
  1485. ShapeAlgorithms.lineOrArcToPolyline = function(shape, numSegments) {
  1486. var ret = new RPolyline();
  1487. var l = shape.getLength();
  1488. var cursor;
  1489. for (var i=0; i<=numSegments; i++) {
  1490. if (i===0) {
  1491. cursor = shape.getStartPoint();
  1492. }
  1493. else if (i===numSegments) {
  1494. cursor = shape.getEndPoint();
  1495. }
  1496. else {
  1497. cursor = shape.getPointsWithDistanceToEnd(l/numSegments*i, RS.FromStart)[0];
  1498. }
  1499. ret.appendVertex(cursor);
  1500. }
  1501. return ret;
  1502. };
  1503. ShapeAlgorithms.removeSharedPointer = function(shape) {
  1504. if (isArray(shape)) {
  1505. var ret = [];
  1506. for (var i=0; i<shape.length; i++) {
  1507. ret.push(ShapeAlgorithms.removeSharedPointer(shape[i]));
  1508. }
  1509. return ret;
  1510. }
  1511. if (isFunction(shape.data)) {
  1512. return shape.data().clone();
  1513. }
  1514. else {
  1515. return shape;
  1516. }
  1517. };
  1518. /**
  1519. * \return Array of shapes of type point, line, arc, circle, ellipse.
  1520. */
  1521. ShapeAlgorithms.explodeToTrimmable = function(shape) {
  1522. if (isSplineShape(shape) && !RSpline.hasProxy()) {
  1523. return ShapeAlgorithms.removeSharedPointer(shape.getExploded());
  1524. }
  1525. if (isPolylineShape(shape)) {
  1526. return ShapeAlgorithms.removeSharedPointer(shape.getExploded());
  1527. }
  1528. if (isTriangleShape(shape)) {
  1529. return ShapeAlgorithms.removeSharedPointer(shape.getExploded());
  1530. }
  1531. return [ shape ];
  1532. };
  1533. /**
  1534. * \return Array of shapes which represent the given shape
  1535. * split up at the given points.
  1536. *
  1537. * \param points Array of RVector, assumed to be on shape.
  1538. */
  1539. ShapeAlgorithms.splitAt = function(shape, points) {
  1540. return shape.splitAt(points);
  1541. };
  1542. /**
  1543. * \return An arc, circle or ellipse, whichever can be used to represent
  1544. * the given ellipse best.
  1545. */
  1546. ShapeAlgorithms.ellipseToArcCircleEllipse = function(ellipse) {
  1547. return RShape.ellipseToArcCircleEllipse(ellipse);
  1548. };
  1549. /**
  1550. * Transforms the given arc, circle or ellipse into an ellipse
  1551. * using the given function which projects an RVector to another RVector.
  1552. *
  1553. * \returns An REllipse or in special cases an RArc or RCircle.
  1554. *
  1555. * OBSOLETE use RShape::transformArc instead
  1556. */
  1557. ShapeAlgorithms.transformArc = function(arc, fun) {
  1558. var r1, r2;
  1559. if (isEllipseShape(arc)) {
  1560. r1 = arc.getMajorPoint();
  1561. r2 = arc.getMinorPoint();
  1562. }
  1563. else {
  1564. r1 = new RVector(arc.getRadius(), 0);
  1565. r2 = new RVector(0, arc.getRadius());
  1566. }
  1567. // get vertices at extremities:
  1568. var v1 = arc.getCenter().operator_add(r1).operator_add(r2);
  1569. var v2 = arc.getCenter().operator_add(r1).operator_subtract(r2);
  1570. var v3 = arc.getCenter().operator_subtract(r1).operator_subtract(r2);
  1571. var v4 = arc.getCenter().operator_subtract(r1).operator_add(r2);
  1572. // project those vertices:
  1573. fun(v1);
  1574. fun(v2);
  1575. fun(v3);
  1576. fun(v4);
  1577. //var ret = [];
  1578. // inscribe ellipse into vertices:
  1579. var ellipse = ShapeAlgorithms.createEllipseInscribedFromVertices(v1, v2, v3, v4);
  1580. //ret.push(ellipse.copy());
  1581. if (isArcShape(arc) || isEllipseShape(arc)) {
  1582. var sp = arc.getStartPoint();
  1583. var ep = arc.getEndPoint();
  1584. var mp = arc.getMiddlePoint();
  1585. fun(sp);
  1586. fun(ep);
  1587. fun(mp);
  1588. ellipse.setStartParam(ellipse.getParamTo(sp));
  1589. ellipse.setEndParam(ellipse.getParamTo(ep));
  1590. var d1 = ellipse.getMiddlePoint().getDistanceTo(mp);
  1591. ellipse.setReversed(true);
  1592. var d2 = ellipse.getMiddlePoint().getDistanceTo(mp);
  1593. if (d1<d2) {
  1594. ellipse.setReversed(false);
  1595. }
  1596. }
  1597. //ret.push(ShapeAlgorithms.ellipseToArcCircleEllipse(ellipse));
  1598. //ret = ret.concat([new RLine(v1, v2), new RLine(v2, v3), new RLine(v3, v4), new RLine(v4, v1)]);
  1599. return ShapeAlgorithms.ellipseToArcCircleEllipse(ellipse);
  1600. };
  1601. /**
  1602. * \return List of points (RVector) which all lay on the given shape with
  1603. * the given maximum distance.
  1604. */
  1605. ShapeAlgorithms.getPointsOnShape = function(shape, distance, from) {
  1606. if (isNull(from)) {
  1607. from = RS.FromStart;
  1608. }
  1609. var ret = [];
  1610. if (isLineShape(shape) || isArcShape(shape)) {
  1611. var n = Math.floor(shape.getLength() / distance);
  1612. for (var i=1; i<=n; i++) {
  1613. var p = shape.getPointsWithDistanceToEnd(distance*i, from);
  1614. if (p.length===1) {
  1615. ret.push(p[0]);
  1616. }
  1617. }
  1618. }
  1619. return ret;
  1620. };
  1621. ShapeAlgorithms.appendShapeToPolylineAuto = function(pl, shape) {
  1622. if (!isFunction(shape.getEndPoint) || !isFunction(shape.reverse)) {
  1623. return;
  1624. }
  1625. if (pl.countVertices()>0 && pl.getEndPoint().equalsFuzzy(shape.getEndPoint())) {
  1626. shape.reverse();
  1627. }
  1628. pl.appendShape(shape);
  1629. };
  1630. /**
  1631. * Round or bevel between given polylines.
  1632. *
  1633. * \param trimmedShape1 Polyline segment or polyline 1, trimmed to cornerShape.
  1634. * \param ending1 RS::Ending which end was trimmed.
  1635. * \param segmentIndex1 Index of polyline segment that was trimmed.
  1636. * \param trimmedShape2 Polyline segment or polyline 2, trimmed to cornerShape.
  1637. * \param ending2 RS::Ending which end was trimmed.
  1638. * \param segmentIndex2 Index of polyline segment that was trimmed.
  1639. * \param cornerShape Shape to add at corner (arc, line, ...).
  1640. */
  1641. ShapeAlgorithms.modifyPolylineCorner = function(polyline1, trimmedShape1, ending1, segmentIndex1,
  1642. polyline2, trimmedShape2, ending2, segmentIndex2, cornerShape) {
  1643. return polyline1.modifyPolylineCorner(trimmedShape1, ending1, segmentIndex1,
  1644. trimmedShape2, ending2, segmentIndex2, cornerShape);
  1645. };
  1646. /**
  1647. * Finds the next segment in CCW direction from the cursor position.
  1648. *
  1649. * \param cursor Current position
  1650. * \param dir Direction backwards to last traced segment
  1651. */
  1652. //ShapeAlgorithms.getShapeSegment = function(cursor, dir) {
  1653. //};
  1654. ShapeAlgorithms.divideShape = function(shape, pos1, pos2) {
  1655. if (isNull(pos1) || isNull(shape)) {
  1656. return undefined;
  1657. }
  1658. // circles and ellipses require two cut points:
  1659. if (isNull(pos2)) {
  1660. if (isCircleShape(shape) || (isEllipseShape(shape) && shape.isFullEllipse())) {
  1661. return undefined;
  1662. }
  1663. }
  1664. var center;
  1665. var angle = undefined;
  1666. var angle2 = undefined;
  1667. var e;
  1668. var cutPos1 = undefined;
  1669. var cutPos2 = undefined;
  1670. if (isCircleShape(shape)) {
  1671. center = shape.getCenter();
  1672. var radius = shape.getRadius();
  1673. angle = center.getAngleTo(pos1);
  1674. angle2 = center.getAngleTo(pos2);
  1675. cutPos1 = center.operator_add(RVector.createPolar(radius, angle));
  1676. cutPos2 = center.operator_add(RVector.createPolar(radius, angle2));
  1677. // introduce tiny gap to make sure full arc is still rendered correctly
  1678. // in other CAD systems:
  1679. var arc1 = new RArc(
  1680. shape.getCenter(),
  1681. shape.getRadius(),
  1682. angle,
  1683. angle2,
  1684. false);
  1685. var arc2 = new RArc(
  1686. shape.getCenter(),
  1687. shape.getRadius(),
  1688. angle2,
  1689. angle,
  1690. false);
  1691. return [ [ arc1, arc2 ], [ cutPos1, cutPos2 ] ];
  1692. }
  1693. else if (isEllipseShape(shape) && shape.isFullEllipse()) {
  1694. center = shape.getCenter();
  1695. var ellipseAngle = shape.getAngle();
  1696. angle = center.getAngleTo(pos1) - ellipseAngle;
  1697. angle2 = center.getAngleTo(pos2) - ellipseAngle;
  1698. cutPos1 = shape.getPointAt(angle);
  1699. cutPos2 = shape.getPointAt(angle2);
  1700. var ellipse1 = shape.clone();
  1701. ellipse1.setStartParam(ellipse1.angleToParam(angle));
  1702. ellipse1.setEndParam(ellipse1.angleToParam(angle2));
  1703. var ellipse2 = shape.clone();
  1704. ellipse2.setStartParam(ellipse2.angleToParam(angle2));
  1705. ellipse2.setEndParam(ellipse2.angleToParam(angle));
  1706. return [ [ ellipse1, ellipse2 ], [ cutPos1, cutPos2 ] ];
  1707. }
  1708. else if (isPolylineShape(shape) && shape.isClosed()) {
  1709. shape.relocateStartPoint(pos1);
  1710. shape.convertToOpen();
  1711. cutPos1 = pos1;
  1712. if (!isNull(pos2)) {
  1713. return Divide.divideShape(pos2, undefined, shape);
  1714. }
  1715. return [ [ shape, undefined ], [ pos1, undefined ] ];
  1716. }
  1717. else {
  1718. var shape1 = shape.clone();
  1719. var shape2 = shape.clone();
  1720. shape1 = trimEndPoint(shape1, pos1, pos1);
  1721. if (isRayShape(shape1)) {
  1722. // ray points in opposite direction:
  1723. cutPos1 = shape1.getStartPoint();
  1724. }
  1725. else {
  1726. cutPos1 = shape1.getEndPoint();
  1727. }
  1728. shape2 = trimStartPoint(shape2, pos1, pos1);
  1729. return [ [ shape1, shape2 ], [ cutPos1, cutPos2 ] ];
  1730. }
  1731. };