Apollonius.js 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995
  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. include("scripts/library.js");
  20. include("scripts/ShapeAlgorithms.js");
  21. /**
  22. * \class Apollonius implementation for fining tangent circles to
  23. * three shapes (points, lines, circles).
  24. */
  25. function Apollonius() {
  26. }
  27. Apollonius.constructionShapes = [];
  28. /**
  29. * \return Solutions for circles (<=8) that are tangential to the three given shapes.
  30. */
  31. Apollonius.getSolutions = function(shape1, shape2, shape3) {
  32. if (isArray(shape1) && isNull(shape2) && isNull(shape3)) {
  33. if (shape1.length!==3) {
  34. debugger;
  35. return [];
  36. }
  37. return Apollonius.getSolutions(shape1[0], shape1[1], shape1[2]);
  38. }
  39. var points = [];
  40. var lines = [];
  41. var circles = [];
  42. var shapes = [ shape1, shape2, shape3 ];
  43. for (var i=0; i<shapes.length; i++) {
  44. var s = shapes[i];
  45. if (isPointShape(s)) {
  46. points.push(s);
  47. continue;
  48. }
  49. if (isLineBasedShape(s)) {
  50. lines.push(s);
  51. continue;
  52. }
  53. if (isArcShape(s)) {
  54. circles.push(new RCircle(s.center, s.radius));
  55. continue;
  56. }
  57. if (isCircleShape(s)) {
  58. circles.push(s);
  59. continue;
  60. }
  61. }
  62. if (points.length===3) {
  63. return Apollonius.getSolutionsPPP(points[0], points[1], points[2]);
  64. }
  65. else if (points.length===2) {
  66. if (circles.length===1) {
  67. return Apollonius.getSolutionsPPC(points[0], points[1], circles[0]);
  68. }
  69. else if (lines.length===1) {
  70. return Apollonius.getSolutionsPPL(points[0], points[1], lines[0]);
  71. }
  72. }
  73. else if (points.length===1) {
  74. if (circles.length===2) {
  75. return Apollonius.getSolutionsPCC(points[0], circles[0], circles[1]);
  76. }
  77. else if (lines.length===2) {
  78. return Apollonius.getSolutionsPLL(points[0], lines[0], lines[1]);
  79. }
  80. else if (circles.length===1 && lines.length===1) {
  81. return Apollonius.getSolutionsPLC(points[0], lines[0], circles[0]);
  82. }
  83. }
  84. else if (points.length===0) {
  85. if (lines.length===3) {
  86. return Apollonius.getSolutionsLLL(lines[0], lines[1], lines[2]);
  87. }
  88. else if (lines.length===2 && circles.length===1) {
  89. return Apollonius.getSolutionsLLC(lines[0], lines[1], circles[0]);
  90. }
  91. else if (lines.length===1 && circles.length===2) {
  92. return Apollonius.getSolutionsLCC(lines[0], circles[0], circles[1]);
  93. }
  94. else if (circles.length===3) {
  95. return Apollonius.getSolutionsCCC(circles[0], circles[1], circles[2]);
  96. }
  97. }
  98. return [];
  99. };
  100. /**
  101. * \return Solutions for circles that are tangential to the three given points.
  102. */
  103. Apollonius.getSolutionsPPP = function(point1, point2, point3) {
  104. return RCircle.createFrom3Points(point1.position, point2.position, point3.position);
  105. };
  106. /**
  107. * \return Solutions for circles that are tangential to the three given circles.
  108. */
  109. Apollonius.getSolutionsCCC = function(c1, c2, c3, intersect) {
  110. if (!isCircleShape(c1) ||
  111. !isCircleShape(c2) ||
  112. !isCircleShape(c3)) {
  113. return [];
  114. }
  115. var ret = [];
  116. var circle1 = c1;
  117. var circle2 = c2;
  118. var circle3 = c3;
  119. // special case: at least two circles are concentric: no solution:
  120. if (c1.center.equalsFuzzy(c2.center) ||
  121. c1.center.equalsFuzzy(c3.center) ||
  122. c2.center.equalsFuzzy(c3.center)) {
  123. return [];
  124. }
  125. // special case: three circles of equal size:
  126. if (RMath.fuzzyCompare(c1.radius, c2.radius) && RMath.fuzzyCompare(c1.radius, c3.radius)) {
  127. // add outer and inner circles to result:
  128. var sol = RCircle.createFrom3Points(c1.center, c2.center, c3.center);
  129. if (sol.isValid()) {
  130. var sol1 = sol.clone();
  131. var sol2 = sol.clone();
  132. sol1.radius = sol1.radius + c1.radius;
  133. sol2.radius = Math.abs(sol2.radius - c1.radius);
  134. ret.push(sol1);
  135. ret.push(sol2);
  136. }
  137. }
  138. // circle1 is always the smallest:
  139. else {
  140. if (c2.radius <= c1.radius && c2.radius <= c3.radius) {
  141. circle1 = c2;
  142. circle2 = c1;
  143. circle3 = c3;
  144. }
  145. if (c3.radius <= c1.radius && c3.radius <= c2.radius) {
  146. circle1 = c3;
  147. circle2 = c1;
  148. circle3 = c2;
  149. }
  150. }
  151. // qDebug("circle1: ", circle1);
  152. // qDebug("circle2: ", circle2);
  153. // qDebug("circle3: ", circle3);
  154. // special case: three circles intersect in one point:
  155. var commonIP = Apollonius.getCommonIntersectionPoint(circle1, circle2, circle3);
  156. if (!isNull(commonIP)) {
  157. var inversionCircle = new RCircle(commonIP, 10);
  158. var shapesInverse = Apollonius.getInverseShapes([circle1, circle2, circle3], inversionCircle);
  159. if (isLineBasedShape(shapesInverse[0]) &&
  160. isLineBasedShape(shapesInverse[1]) &&
  161. isLineBasedShape(shapesInverse[2])) {
  162. var circlesTouching = Apollonius.getSolutions(shapesInverse);
  163. ret = Apollonius.getInverseShapes(circlesTouching, inversionCircle);
  164. }
  165. return ret;
  166. }
  167. // special case: each circle intersects the other two,
  168. // at least one intersects through two points:
  169. var nIps12 = circle1.getIntersectionPoints(circle2).length;
  170. var nIps13 = circle1.getIntersectionPoints(circle3).length;
  171. var nIps23 = circle2.getIntersectionPoints(circle3).length;
  172. if (!intersect && nIps12>0 && nIps13>0 && nIps23>0 &&
  173. (nIps12===2 || nIps13===2 || nIps23===2)) {
  174. var ips12 = circle1.getIntersectionPoints(circle2);
  175. var ips13 = circle1.getIntersectionPoints(circle3);
  176. var ips23 = circle2.getIntersectionPoints(circle3);
  177. var inversionCircles = [];
  178. var r;
  179. if (ips12.length===2) {
  180. r = ips12[0].getDistanceTo(ips12[1]);
  181. inversionCircles.push(new RCircle(ips12[0], r));
  182. inversionCircles.push(new RCircle(ips12[1], r));
  183. }
  184. if (ips13.length===2) {
  185. r = ips13[0].getDistanceTo(ips13[1]);
  186. inversionCircles.push(new RCircle(ips13[0], r));
  187. inversionCircles.push(new RCircle(ips13[1], r));
  188. }
  189. if (ips23.length===2) {
  190. r = ips23[0].getDistanceTo(ips23[1]);
  191. inversionCircles.push(new RCircle(ips23[0], r));
  192. inversionCircles.push(new RCircle(ips23[1], r));
  193. }
  194. for (var i=0; i<inversionCircles.length; i++) {
  195. var circle1Inverse = Apollonius.getInverseShape(circle1, inversionCircles[i]);
  196. var circle2Inverse = Apollonius.getInverseShape(circle2, inversionCircles[i]);
  197. var circle3Inverse = Apollonius.getInverseShape(circle3, inversionCircles[i]);
  198. var iSol = Apollonius.getSolutions(circle1Inverse, circle2Inverse, circle3Inverse);
  199. var sol = Apollonius.getInverseShapes(iSol, inversionCircles[i]);
  200. ret = ret.concat(sol);
  201. }
  202. ret = Apollonius.removeDuplicates(ret);
  203. return ret;
  204. }
  205. var powerCenter = Apollonius.getPowerCenter(circle1, circle2, circle3);
  206. //Apollonius.constructionShapes.push(new RPoint(powerCenter));
  207. if (isNull(powerCenter)) {
  208. return ret;
  209. }
  210. var similarityAxes = Apollonius.getSimilarityAxes(circle1, circle2, circle3);
  211. for (var i=0; i<similarityAxes.length; i++) {
  212. // array may contain 'null' items to guarantee index for
  213. // alpha, beta, gamma tests:
  214. if (isNull(similarityAxes[i])) {
  215. continue;
  216. }
  217. //Apollonius.constructionShapes.push(similarityAxes[i]);
  218. var p, pp, q, qq, r, rr;
  219. var pole1 = Apollonius.getPole(circle1, similarityAxes[i]);
  220. var pole2 = Apollonius.getPole(circle2, similarityAxes[i]);
  221. var pole3 = Apollonius.getPole(circle3, similarityAxes[i]);
  222. if (isNull(pole1) || isNull(pole2) || isNull(pole3)) {
  223. continue;
  224. }
  225. // Apollonius.constructionShapes.push(new RPoint(pole1));
  226. // Apollonius.constructionShapes.push(new RPoint(pole2));
  227. // Apollonius.constructionShapes.push(new RPoint(pole3));
  228. var ray1 = new RLine(powerCenter, pole1);
  229. var ray2 = new RLine(powerCenter, pole2);
  230. var ray3 = new RLine(powerCenter, pole3);
  231. var ips1 = ray1.getIntersectionPoints(circle1, false);
  232. var ips2 = ray2.getIntersectionPoints(circle2, false);
  233. var ips3 = ray3.getIntersectionPoints(circle3, false);
  234. var gotPoints = false;
  235. if (circle1.contains(powerCenter) || circle2.contains(powerCenter) || circle3.contains(powerCenter)) {
  236. var ipsRight = [];
  237. var ipsLeft = [];
  238. var ipss = [ips1, ips2, ips3];
  239. for (var k=0; k<ipss.length; k++) {
  240. var ips = ipss[k];
  241. for (var n=0; n<ips.length; n++) {
  242. var ip = ips[n];
  243. if (similarityAxes[i].getSideOfPoint(ip)===RS.RightHand) {
  244. ipsRight.push(ip);
  245. }
  246. else {
  247. ipsLeft.push(ip);
  248. }
  249. }
  250. }
  251. if (ipsRight.length===3 && ipsLeft.length===3) {
  252. p = ipsRight[0];
  253. q = ipsRight[1];
  254. r = ipsRight[2];
  255. pp = ipsLeft[0];
  256. qq = ipsLeft[1];
  257. rr = ipsLeft[2];
  258. gotPoints = true;
  259. }
  260. }
  261. if (!gotPoints) {
  262. ips1 = RVector.getSortedByDistance(ips1, powerCenter);
  263. ips2 = RVector.getSortedByDistance(ips2, powerCenter);
  264. ips3 = RVector.getSortedByDistance(ips3, powerCenter);
  265. if (ips1.length!==2 || ips2.length!==2 || ips3.length!==2) {
  266. continue;
  267. }
  268. // alpha: +
  269. if (i==0 || i==3) {
  270. p = ips1[0];
  271. pp = ips1[1];
  272. }
  273. // alpha: -
  274. else {
  275. p = ips1[1];
  276. pp = ips1[0];
  277. }
  278. // beta: +
  279. if (i==0 || i==2) {
  280. q = ips2[0];
  281. qq = ips2[1];
  282. }
  283. // beta: -
  284. else {
  285. q = ips2[1];
  286. qq = ips2[0];
  287. }
  288. // gamma: +
  289. if (i==0 || i==1) {
  290. r = ips3[0];
  291. rr = ips3[1];
  292. }
  293. // gamma: -
  294. else {
  295. r = ips3[1];
  296. rr = ips3[0];
  297. }
  298. }
  299. if (!isNull(p) && !isNull(q) && !isNull(r)) {
  300. ret.push(RCircle.createFrom3Points(p,q,r));
  301. }
  302. if (!isNull(pp) && !isNull(qq) && !isNull(rr)) {
  303. ret.push(RCircle.createFrom3Points(pp,qq,rr));
  304. }
  305. }
  306. ret = ret.concat(Apollonius.getSolutionsCCCAlt(c1, c2, c3));
  307. ret = Apollonius.removeDuplicates(ret);
  308. ret = Apollonius.verify(ret, c1, c2, c3);
  309. return ret;
  310. }
  311. Apollonius.getSolutionsCCCAlt = function(c1, c2, c3) {
  312. var circle1 = c1;
  313. var circle2 = c2;
  314. var circle3 = c3;
  315. var allEqualSizes = RMath.fuzzyCompare(circle1.radius, circle2.radius) &&
  316. RMath.fuzzyCompare(circle1.radius, circle3.radius);
  317. if (!allEqualSizes
  318. //&& !intersect
  319. ) {
  320. // make sure that circle1 has the smallest radius:
  321. if (c2.radius <= c1.radius && c2.radius <= c3.radius) {
  322. circle1 = c2;
  323. circle2 = c1;
  324. circle3 = c3;
  325. }
  326. if (c3.radius <= c1.radius && c3.radius <= c2.radius) {
  327. circle1 = c3;
  328. circle2 = c1;
  329. circle3 = c2;
  330. }
  331. }
  332. // build arrays of three shapes each: either PCC or PPC:
  333. var shapes1 = [];
  334. var shapes2 = [];
  335. var shapes3 = [];
  336. var shapes4 = [];
  337. shapes1.push(new RPoint(circle1.center));
  338. shapes2.push(new RPoint(circle1.center));
  339. shapes3.push(new RPoint(circle1.center));
  340. shapes4.push(new RPoint(circle1.center));
  341. var circle21 = circle2.clone();
  342. var circle22 = circle2.clone();
  343. circle21.radius = Math.abs(circle21.radius - circle1.radius);
  344. circle22.radius += circle1.radius;
  345. // Apollonius.constructionShapes.push(circle21);
  346. // Apollonius.constructionShapes.push(circle22);
  347. if (RMath.fuzzyCompare(circle21.radius, 0.0)) {
  348. circle21 = new RPoint(circle21.center);
  349. }
  350. shapes1.push(circle21.clone());
  351. shapes2.push(circle21.clone());
  352. shapes3.push(circle22.clone());
  353. shapes4.push(circle22.clone());
  354. var circle31 = circle3.clone();
  355. var circle32 = circle3.clone();
  356. circle31.radius = Math.abs(circle31.radius - circle1.radius);
  357. circle32.radius += circle1.radius;
  358. // Apollonius.constructionShapes.push(circle31);
  359. // Apollonius.constructionShapes.push(circle32);
  360. if (RMath.fuzzyCompare(circle31.radius, 0.0)) {
  361. circle31 = new RPoint(circle31.center);
  362. }
  363. shapes1.push(circle31.clone());
  364. shapes3.push(circle31.clone());
  365. shapes2.push(circle32.clone());
  366. shapes4.push(circle32.clone());
  367. // intermediate solutions for PCC / PPC cases:
  368. var iSol1 = Apollonius.getSolutions(shapes1[0],shapes1[1],shapes1[2]);
  369. var iSol2 = Apollonius.getSolutions(shapes2[0],shapes2[1],shapes2[2]);
  370. var iSol3 = Apollonius.getSolutions(shapes3[0],shapes3[1],shapes3[2]);
  371. var iSol4 = Apollonius.getSolutions(shapes4[0],shapes4[1],shapes4[2]);
  372. // for (var i=0; i<iSol1.length; i++) {
  373. // Apollonius.constructionShapes.push(iSol1[i]);
  374. // }
  375. // for (var i=0; i<shapes2.length; i++) {
  376. // Apollonius.constructionShapes.push(shapes2[i]);
  377. // }
  378. // for (var i=0; i<shapes3.length; i++) {
  379. // Apollonius.constructionShapes.push(shapes3[i]);
  380. // }
  381. // for (var i=0; i<shapes4.length; i++) {
  382. // if (!isNull(shapes4[i])) {
  383. // Apollonius.constructionShapes.push(shapes4[i]);
  384. // }
  385. // }
  386. var iSols = [ iSol1, iSol2, iSol3, iSol4 ];
  387. var candidates = [];
  388. for (var i=0; i<iSols.length; i++) {
  389. if (isNull(iSols[i])) {
  390. continue;
  391. }
  392. for (var k=0; k<iSols[i].length; k++) {
  393. var obj = iSols[i][k];
  394. candidates = candidates.concat(ShapeAlgorithms.getOffsetShapes(obj, circle1.radius, 1, RS.BothSides));
  395. }
  396. }
  397. // innermost and outermost solutions for equal sized circles:
  398. if (allEqualSizes) {
  399. var sol = RCircle.createFrom3Points(circle1.center, circle2.center, circle3.center);
  400. var sol1 = sol.clone();
  401. var sol2 = sol.clone();
  402. sol1.radius = sol1.radius + circle1.radius;
  403. sol2.radius = Math.abs(sol2.radius - circle1.radius);
  404. candidates.push(sol1);
  405. candidates.push(sol2);
  406. }
  407. // filter out non-results:
  408. return Apollonius.verify(candidates, circle1, circle2, circle3);
  409. };
  410. Apollonius.removeDuplicates = function(shapes) {
  411. var ret = [];
  412. for (var i=0; i<shapes.length; i++) {
  413. var add = true;
  414. for (var k=0; k<ret.length; k++) {
  415. if (Apollonius.compareShapes(shapes[i], ret[k])) {
  416. add = false;
  417. }
  418. }
  419. if (add) {
  420. ret.push(shapes[i]);
  421. }
  422. }
  423. return ret;
  424. };
  425. Apollonius.compareShapes = function(shape1, shape2) {
  426. if (isLineBasedShape(shape1)) {
  427. if (!isLineBasedShape(shape2)) {
  428. return false;
  429. }
  430. return shape1.startPoint.equalsFuzzy(shape2.startPoint) &&
  431. shape1.endPoint.equalsFuzzy(shape2.endPoint);
  432. }
  433. if (isCircleShape(shape1)) {
  434. if (!isCircleShape(shape2)) {
  435. return false;
  436. }
  437. return shape1.center.equalsFuzzy(shape2.center) &&
  438. RMath.fuzzyCompare(shape1.radius, shape2.radius);
  439. }
  440. debugger;
  441. return false;
  442. };
  443. Apollonius.verify = function(candidates, shape1, shape2, shape3) {
  444. var ret = [];
  445. var shapes = [shape1, shape2, shape3];
  446. for (var i=0; i<candidates.length; i++) {
  447. var candidate = candidates[i];
  448. var pass = true;
  449. for (var k=0; k<shapes.length; k++) {
  450. var shape = shapes[k];
  451. if (!Apollonius.shapesTouch(shape, candidate)) {
  452. pass = false;
  453. }
  454. }
  455. if (pass) {
  456. ret.push(candidate);
  457. }
  458. }
  459. return ret;
  460. }
  461. Apollonius.shapesTouch = function(shape1, shape2) {
  462. if (isNull(shape1) || isNull(shape2)) {
  463. return false;
  464. }
  465. if (isPointShape(shape1)) {
  466. if (isPointShape(shape2)) {
  467. return false;
  468. }
  469. else {
  470. return RMath.fuzzyCompare(shape2.getDistanceTo(shape1.position, false), 0.0);
  471. }
  472. }
  473. else if (isLineBasedShape(shape1)) {
  474. if (isPointShape(shape2)) {
  475. return Apollonius.shapesTouch(shape2, shape1);
  476. }
  477. else if (isLineBasedShape(shape2)) {
  478. return false;
  479. }
  480. else if (isCircleShape(shape2)) {
  481. return RMath.fuzzyCompare(shape1.getDistanceTo(shape2.center, false), shape2.radius);
  482. }
  483. }
  484. else if (isCircleShape(shape1)) {
  485. if (isPointShape(shape2)) {
  486. return Apollonius.shapesTouch(shape2, shape1);
  487. }
  488. else if (isLineBasedShape(shape2)) {
  489. return Apollonius.shapesTouch(shape2, shape1);
  490. }
  491. else if (isCircleShape(shape2)) {
  492. var d = shape1.center.getDistanceTo(shape2.center);
  493. return RMath.fuzzyCompare(d, shape1.radius + shape2.radius) ||
  494. RMath.fuzzyCompare(d, Math.abs(shape1.radius - shape2.radius));
  495. }
  496. }
  497. return false;
  498. };
  499. /**
  500. * \return Pole point of the polar line with respect to circle.
  501. */
  502. Apollonius.getPole = function(circle, polarLine) {
  503. var r = circle.getRadius();
  504. var center = circle.getCenter();
  505. var p = polarLine.getClosestPointOnShape(center, false);
  506. var op = center.getDistanceTo(p);
  507. if (Math.abs(op)<RS.PointTolerance) {
  508. return undefined;
  509. }
  510. var opInverse = (r * r) / op;
  511. var v = p.operator_subtract(center);
  512. v.setMagnitude2D(opInverse);
  513. return center.operator_add(v);
  514. };
  515. /**
  516. * \return Similarity axes of the tree given circles.
  517. */
  518. Apollonius.getSimilarityAxes = function(c1, c2, c3) {
  519. var ret = [];
  520. var tangents12 = ShapeAlgorithms.getTangents(c1, c2);
  521. var tangents13 = ShapeAlgorithms.getTangents(c1, c3);
  522. var tangents23 = ShapeAlgorithms.getTangents(c2, c3);
  523. //Apollonius.constructionShapes = Apollonius.constructionShapes.concat(tangents12);
  524. //Apollonius.constructionShapes = Apollonius.constructionShapes.concat(tangents13);
  525. //Apollonius.constructionShapes = Apollonius.constructionShapes.concat(tangents23);
  526. var ips12o = [];
  527. var ips13o = [];
  528. var ips23o = [];
  529. var ips12i = [];
  530. var ips13i = [];
  531. var ips23i = [];
  532. // intersection of outer tangents circles 1,2:
  533. if (!isNull(tangents12[0]) && !isNull(tangents12[1])) {
  534. ips12o = tangents12[0].getIntersectionPoints(tangents12[1], false);
  535. }
  536. // intersection of outer tangents circles 1,3:
  537. if (!isNull(tangents13[0]) && !isNull(tangents13[1])) {
  538. ips13o = tangents13[0].getIntersectionPoints(tangents13[1], false);
  539. }
  540. // intersection of outer tangents circles 2,3:
  541. if (!isNull(tangents23[0]) && !isNull(tangents23[1])) {
  542. ips23o = tangents23[0].getIntersectionPoints(tangents23[1], false);
  543. }
  544. // intersection of inner tangents circles 1,2:
  545. if (!isNull(tangents12[2]) && !isNull(tangents12[3])) {
  546. ips12i = tangents12[2].getIntersectionPoints(tangents12[3], false);
  547. }
  548. else if (ips12o.length===0) {
  549. // two intersecting circles with same radius:
  550. ips12i = [RVector.getAverage(c1.center, c2.center)];
  551. }
  552. // intersection of inner tangents circles 1,3:
  553. if (!isNull(tangents13[2]) && !isNull(tangents13[3])) {
  554. ips13i = tangents13[2].getIntersectionPoints(tangents13[3], false);
  555. }
  556. else if (ips13o.length===0) {
  557. // two intersecting circles with same radius:
  558. ips13i = [RVector.getAverage(c1.center, c3.center)];
  559. }
  560. // intersection of inner tangents circles 2,3:
  561. if (!isNull(tangents23[2]) && !isNull(tangents23[3])) {
  562. ips23i = tangents23[2].getIntersectionPoints(tangents23[3], false);
  563. }
  564. else if (ips23o.length===0) {
  565. // two intersecting circles with same radius:
  566. ips23i = [RVector.getAverage(c2.center, c3.center)];
  567. }
  568. // alpha: - for inner / + for outer tangents of circles 2 and 3
  569. // beta: - for inner / + for outer tangents of circles 1 and 3
  570. // gamma: - for inner / + for outer tangents of circles 1 and 2
  571. var l;
  572. // outer, outer, outer:
  573. // alpha: +, beta: +, gamma: +
  574. l = Apollonius.getLine(ips12o, ips13o, ips23o);
  575. ret.push(l);
  576. // outer, inner, inner:
  577. // alpha: -, beta: -, gamma: +
  578. l = Apollonius.getLine(ips12o, ips13i, ips23i);
  579. ret.push(l);
  580. // inner, outer, inner:
  581. // alpha: -, beta: +, gamma: -
  582. l = Apollonius.getLine(ips12i, ips13o, ips23i);
  583. ret.push(l);
  584. // inner, inner, outer:
  585. // alpha: +, beta: -, gamma: -
  586. l = Apollonius.getLine(ips12i, ips13i, ips23o);
  587. ret.push(l);
  588. return ret;
  589. };
  590. /**
  591. * \return Line that connectes first points contained in each array.
  592. * Helper function. No check is performmed if the three points are indeed on
  593. * one line.
  594. */
  595. Apollonius.getLine = function(arr1, arr2, arr3) {
  596. var ps = [];
  597. if (arr1.length>0) {
  598. ps.push(arr1[0]);
  599. }
  600. if (arr2.length>0) {
  601. ps.push(arr2[0]);
  602. }
  603. if (arr3.length>0) {
  604. ps.push(arr3[0]);
  605. }
  606. if (ps.length>=2) {
  607. return new RLine(ps[0], ps[1]);
  608. }
  609. return undefined;
  610. };
  611. /**
  612. * \return Power center of the three given circles.
  613. */
  614. Apollonius.getPowerCenter = function(c1, c2, c3) {
  615. var radicalAxis1 = Apollonius.getRadicalAxis(c1, c2);
  616. //Apollonius.constructionShapes.push(radicalAxis1);
  617. var radicalAxis2 = Apollonius.getRadicalAxis(c1, c3);
  618. //Apollonius.constructionShapes.push(radicalAxis2);
  619. var ips = radicalAxis1.getIntersectionPoints(radicalAxis2, false);
  620. if (ips.length===0) {
  621. return undefined;
  622. }
  623. return ips[0];
  624. };
  625. /**
  626. * \return Radical axis of circles c1 and c2 with given length.
  627. */
  628. Apollonius.getRadicalAxis = function(c1, c2, length) {
  629. if (isNull(length)) {
  630. length = 100.0;
  631. }
  632. var c2c = new RLine(c1.center, c2.center);
  633. var dir = c2c.getAngle() + Math.PI/2;
  634. var center;
  635. var ips = c1.getIntersectionPoints(c2, false);
  636. if (ips.length===2) {
  637. center = RVector.getAverage(ips[0], ips[1]);
  638. }
  639. else {
  640. //Apollonius.constructionShapes.push(c2c);
  641. var v = RVector.createPolar(c1.radius, c2c.getAngle() + Math.PI/2);
  642. var p1 = c1.center.operator_add(v);
  643. v = RVector.createPolar(c2.radius, c2c.getAngle() + Math.PI/2);
  644. var p3 = c2.center.operator_add(v);
  645. var p2 = c2c.getMiddlePoint();
  646. var helperCircle = RCircle.createFrom3Points(p1, p2, p3);
  647. //Apollonius.constructionShapes.push(helperCircle);
  648. var ra1 = Apollonius.getRadicalAxis(c1, helperCircle);
  649. //Apollonius.constructionShapes.push(ra1);
  650. var ra2 = Apollonius.getRadicalAxis(c2, helperCircle);
  651. //Apollonius.constructionShapes.push(ra2);
  652. var rips = ra1.getIntersectionPoints(ra2, false);
  653. if (rips.length===0) {
  654. debugger;
  655. }
  656. center = c2c.getClosestPointOnShape(rips[0], false);
  657. }
  658. var dirV = RVector.createPolar(length*0.5, dir);
  659. return new RLine(center.operator_subtract(dirV), center.operator_add(dirV));
  660. };
  661. /**
  662. * \return Solutions for circles (<=4) that are tangential to the three given lines.
  663. */
  664. Apollonius.getSolutionsLLL = function(line1, line2, line3) {
  665. if (!isLineBasedShape(line1) ||
  666. !isLineBasedShape(line2) ||
  667. !isLineBasedShape(line3)) {
  668. return [];
  669. }
  670. /*
  671. situations:
  672. 0: all lines are parallel (no solutions)
  673. 3: lines 2 and 3 are parallel
  674. 5: lines 1 and 3 are parallel
  675. 6: lines 1 and 2 are parallel
  676. 7: none of the lines are parallel (4 solutions)
  677. */
  678. var situation = 0;
  679. var angleBisectors1, angleBisectors2, angleBisectors3;
  680. if (line1.intersectsWith(line2, false)) {
  681. situation+=1;
  682. }
  683. if (line1.intersectsWith(line3, false)) {
  684. situation+=2;
  685. }
  686. if (line2.intersectsWith(line3, false)) {
  687. situation+=4;
  688. }
  689. if (situation === 3 || situation === 5 || situation === 7) {
  690. angleBisectors1 = Apollonius.getAngleBisectors(line1, line2);
  691. }
  692. if (situation == 3 || situation == 6 || situation == 7) {
  693. angleBisectors2 = Apollonius.getAngleBisectors(line1, line3);
  694. }
  695. if (situation == 5 || situation == 6) {
  696. angleBisectors3 = Apollonius.getAngleBisectors(line2, line3);
  697. }
  698. var centerPoints = [];
  699. if (situation == 3 || situation == 7) {
  700. centerPoints = centerPoints.concat(angleBisectors1[0].getIntersectionPoints(angleBisectors2[0], false));
  701. centerPoints = centerPoints.concat(angleBisectors1[0].getIntersectionPoints(angleBisectors2[1], false));
  702. centerPoints = centerPoints.concat(angleBisectors1[1].getIntersectionPoints(angleBisectors2[0], false));
  703. centerPoints = centerPoints.concat(angleBisectors1[1].getIntersectionPoints(angleBisectors2[1], false));
  704. }
  705. else if (situation == 5) {
  706. centerPoints = centerPoints.concat(angleBisectors1[0].getIntersectionPoints(angleBisectors3[0], false));
  707. centerPoints = centerPoints.concat(angleBisectors1[0].getIntersectionPoints(angleBisectors3[1], false));
  708. centerPoints = centerPoints.concat(angleBisectors1[1].getIntersectionPoints(angleBisectors3[0], false));
  709. centerPoints = centerPoints.concat(angleBisectors1[1].getIntersectionPoints(angleBisectors3[1], false));
  710. }
  711. else if (situation == 6) {
  712. centerPoints = centerPoints.concat(angleBisectors2[0].getIntersectionPoints(angleBisectors3[0], false));
  713. centerPoints = centerPoints.concat(angleBisectors2[0].getIntersectionPoints(angleBisectors3[1], false));
  714. centerPoints = centerPoints.concat(angleBisectors2[1].getIntersectionPoints(angleBisectors3[0], false));
  715. centerPoints = centerPoints.concat(angleBisectors2[1].getIntersectionPoints(angleBisectors3[1], false));
  716. }
  717. var ret = [];
  718. var radius;
  719. var c;
  720. for (var i=0; i<centerPoints.length; i++) {
  721. var cp = centerPoints[i];
  722. radius = line1.getDistanceTo(cp, false);
  723. c = new RCircle(cp, radius);
  724. ret.push(c);
  725. }
  726. return ret;
  727. };
  728. /**
  729. * \return Solutions for circles that are tangential to the two given circles
  730. * and the given point.
  731. */
  732. Apollonius.getSolutionsPCC = function(point, circle1, circle2) {
  733. var ret = [];
  734. //Apollonius.constructionShapes = [];
  735. if (!isPointShape(point) ||
  736. !isCircleShape(circle1) ||
  737. !isCircleShape(circle2)) {
  738. return ret;
  739. }
  740. var p = point.getPosition();
  741. // reduce PCC to PPC case:
  742. // find second point [P']:
  743. // find two tangents of two circles:
  744. var tangents = ShapeAlgorithms.getTangents(circle1, circle2);
  745. if (tangents.length!==4) {
  746. return ret;
  747. }
  748. //Apollonius.constructionShapes.push(tangents[0]);
  749. //Apollonius.constructionShapes.push(tangents[1]);
  750. // circle from 3P through two tangent points and P:
  751. var p1 = tangents[0].getClosestPointOnShape(circle1.getCenter(), false);
  752. var p2 = tangents[0].getClosestPointOnShape(circle2.getCenter(), false);
  753. var c = RCircle.createFrom3Points(p1, p2, p);
  754. // find intersection of tangents:
  755. var l;
  756. var ips = tangents[0].getIntersectionPoints(tangents[1], false);
  757. if (ips.length===1) {
  758. // line from intersection to point [L]:
  759. l = new RLine(ips[0], p);
  760. //Apollonius.constructionShapes.push(l);
  761. //Apollonius.constructionShapes.push(c);
  762. }
  763. else {
  764. // circles of equal size:
  765. l = new RLine(p, tangents[0].getAngle(), 1.0);
  766. }
  767. // intersections of circle with L are P and P':
  768. ips = c.getIntersectionPoints(l, false);
  769. if (ips.length===1) {
  770. // point on the symetry axis between two circles of same size:
  771. var l1 = new RLine(p, circle1.getCenter());
  772. var l2 = new RLine(p, circle2.getCenter());
  773. var ips1 = l1.getIntersectionPoints(circle1);
  774. var ips2 = l2.getIntersectionPoints(circle2);
  775. if (ips1.length===1 && ips2.length===1) {
  776. ret.push(RCircle.createFrom3Points(ips1[0], ips2[0], p));
  777. }
  778. }
  779. else if (ips.length===2) {
  780. // solve PPC case for P, P', one of the circles:
  781. ret = Apollonius.getSolutionsPPC(ips[0], ips[1], circle1);
  782. ret = ret.concat(Apollonius.getSolutionsPPC(new RPoint(ips[0]), new RPoint(ips[1]), circle2));
  783. }
  784. if (!circle1.isOnShape(point.position, false) &&
  785. !circle2.isOnShape(point.position, false)) {
  786. var inversionCircle = new RCircle(point.position, 10);
  787. //Apollonius.constructionShapes.push(inversionCircle);
  788. var circles = [];
  789. circles.push(circle1.clone());
  790. circles.push(circle2.clone());
  791. var circlesInverse = Apollonius.getInverseShapes(circles, inversionCircle);
  792. //Apollonius.constructionShapes = Apollonius.constructionShapes.concat(circlesInverse);
  793. var tangents = Apollonius.getCommonTangents(circlesInverse[0], circlesInverse[1]);
  794. ret = ret.concat(Apollonius.getInverseShapes(tangents, inversionCircle));
  795. }
  796. ret = Apollonius.removeDuplicates(ret);
  797. ret = Apollonius.verify(ret, point, circle1, circle2);
  798. return ret;
  799. };
  800. /**
  801. * \return Solutions for circles that are tangential to the two given points
  802. * and the given circle.
  803. */
  804. Apollonius.getSolutionsPPC = function(point1, point2, circle) {
  805. if (!isPointShape(point1) ||
  806. !isPointShape(point2) ||
  807. !isCircleShape(circle)) {
  808. return [];
  809. }
  810. // both points are on the circle line:
  811. if (circle.isOnShape(point1.position) && circle.isOnShape(point2.position)) {
  812. return [ circle ];
  813. }
  814. var pOnCircle = undefined;
  815. var pOther = undefined;
  816. if (circle.isOnShape(point1.position)) {
  817. pOnCircle = point1.position;
  818. pOther = point2.position;
  819. }
  820. if (circle.isOnShape(point2.position)) {
  821. pOnCircle = point2.position;
  822. pOther = point1.position;
  823. }
  824. // one point is on circle:
  825. if (!isNull(pOnCircle) && !isNull(pOther)) {
  826. //Apollonius.constructionShapes = [];
  827. // line from circle center to point on circle:
  828. var l = new RLine(circle.getCenter(), pOnCircle);
  829. //Apollonius.constructionShapes.push(l);
  830. // middle orthogonal between points:
  831. var m = RVector.getAverage(pOnCircle, pOther);
  832. var lOrtho = new RLine(m, pOther.getAngleTo(pOnCircle) + Math.PI/2, 1.0);
  833. //Apollonius.constructionShapes.push(lOrtho);
  834. var ips = l.getIntersectionPoints(lOrtho, false);
  835. if (ips.length!==1) {
  836. return [];
  837. }
  838. return [ new RCircle(ips[0], ips[0].getDistanceTo(pOnCircle)) ];
  839. }
  840. var inversionCircle = new RCircle(point1.position, 10);
  841. var circleInverse = Apollonius.getInverseShape(circle, inversionCircle);
  842. var point2Inverse = Apollonius.getInverseShape(point2, inversionCircle);
  843. var tangents = Apollonius.getTangentsThroughPoint(circleInverse, point2Inverse.position);
  844. return Apollonius.getInverseShapes(tangents, inversionCircle);
  845. };
  846. /**
  847. * \return Solutions for circles that are tangential to the two given points
  848. * and the given line.
  849. */
  850. Apollonius.getSolutionsPPL = function(point1, point2, line) {
  851. if (!isPointShape(point1) ||
  852. !isPointShape(point2) ||
  853. !isLineBasedShape(line)) {
  854. return [];
  855. }
  856. if (line.isOnShape(point1.position)) {
  857. var p = point1.clone();
  858. point1 = point2.clone();
  859. point2 = p.clone();
  860. if (line.isOnShape(point1.position)) {
  861. return [];
  862. }
  863. }
  864. var inversionCircle = new RCircle(point1.position, 10);
  865. var lineInverse = Apollonius.getInverseShape(line, inversionCircle);
  866. var point2Inverse = Apollonius.getInverseShape(point2, inversionCircle);
  867. var tangents = Apollonius.getTangentsThroughPoint(lineInverse, point2Inverse.position);
  868. return Apollonius.getInverseShapes(tangents, inversionCircle);
  869. };
  870. /**
  871. * \return Solutions for circles that are tangential to the two given lines
  872. * and the given circle.
  873. */
  874. Apollonius.getSolutionsLLC = function(line1, line2, circle) {
  875. if (!isLineBasedShape(line1) ||
  876. !isLineBasedShape(line2) ||
  877. !isCircleShape(circle)) {
  878. return [];
  879. }
  880. var parallels1 = ShapeAlgorithms.getOffsetShapes(line1, circle.radius, 1, RS.BothSides);
  881. var parallels2 = ShapeAlgorithms.getOffsetShapes(line2, circle.radius, 1, RS.BothSides);
  882. var arr1 = [];
  883. var arr2 = [];
  884. var arr3 = [];
  885. var arr4 = [];
  886. arr1.push(new RPoint(circle.center));
  887. arr2.push(new RPoint(circle.center));
  888. arr3.push(new RPoint(circle.center));
  889. arr4.push(new RPoint(circle.center));
  890. arr1.push(parallels1[0]);
  891. arr2.push(parallels1[0]);
  892. arr3.push(parallels1[1]);
  893. arr4.push(parallels1[1]);
  894. arr1.push(parallels2[0]);
  895. arr2.push(parallels2[1]);
  896. arr3.push(parallels2[0]);
  897. arr4.push(parallels2[1]);
  898. var cArr1 = Apollonius.getSolutions(arr1[0], arr1[1], arr1[2]);
  899. var cArr2 = Apollonius.getSolutions(arr2[0], arr2[1], arr2[2]);
  900. var cArr3 = Apollonius.getSolutions(arr3[0], arr3[1], arr3[2]);
  901. var cArr4 = Apollonius.getSolutions(arr4[0], arr4[1], arr4[2]);
  902. var cArrs = [ cArr1, cArr2, cArr3, cArr4 ];
  903. var ret = [];
  904. var tmpCircle, obj1, obj2;
  905. for (var i=0; i<cArrs.length; i++) {
  906. var cArr = cArrs[i];
  907. for (var k=0; k<cArr.length; k++) {
  908. var obj = cArr[k];
  909. if (!isCircleShape(obj)) {
  910. continue;
  911. }
  912. tmpCircle = obj.clone();
  913. obj1 = obj.clone();
  914. obj2 = obj.clone();
  915. obj1.radius += circle.radius;
  916. obj2.radius = Math.abs(obj2.radius - circle.radius);
  917. var d1 = obj1.center.getDistanceTo(circle.center);
  918. if ((RMath.fuzzyCompare(d1, obj1.radius+circle.radius) ||
  919. RMath.fuzzyCompare(d1, Math.abs(obj1.radius-circle.radius))) &&
  920. RMath.fuzzyCompare(line1.getDistanceTo(obj1.center, false), obj1.radius) &&
  921. RMath.fuzzyCompare(line2.getDistanceTo(obj1.center, false), obj1.radius)) {
  922. ret.push(obj1);
  923. }
  924. var d2 = obj2.center.getDistanceTo(circle.center);
  925. if ((RMath.fuzzyCompare(d2, obj2.radius+circle.radius) ||
  926. RMath.fuzzyCompare(d2, Math.abs(obj2.radius-circle.radius))) &&
  927. RMath.fuzzyCompare(line1.getDistanceTo(obj2.center, false), obj2.radius) &&
  928. RMath.fuzzyCompare(line2.getDistanceTo(obj2.center, false), obj2.radius)) {
  929. ret.push(obj2);
  930. }
  931. }
  932. }
  933. return ret;
  934. };
  935. /**
  936. * \return Solutions for circles that are tangential to the given line and the
  937. * two given circles.
  938. */
  939. Apollonius.getSolutionsLCC = function(line, circle1, circle2) {
  940. if (!isLineBasedShape(line) ||
  941. !isCircleShape(circle1) ||
  942. !isCircleShape(circle2)) {
  943. return [];
  944. }
  945. if (circle1.radius > circle2.radius) {
  946. var tmp = circle1;
  947. circle1 = circle2;
  948. circle2 = tmp;
  949. }
  950. // find solutions for tangent to:
  951. // center point of smaller circle,
  952. // concentric circles for larger circle with distance = radius of smaller circle
  953. // parallels to line with distance = radius of smaller circle
  954. var arr1 = [];
  955. var arr2 = [];
  956. var arr3 = [];
  957. var arr4 = [];
  958. arr1.push(new RPoint(circle1.center));
  959. arr2.push(new RPoint(circle1.center));
  960. arr3.push(new RPoint(circle1.center));
  961. arr4.push(new RPoint(circle1.center));
  962. var circle21 = circle2.clone();
  963. circle21.radius += circle1.radius;
  964. arr1.push(circle21);
  965. arr3.push(circle21);
  966. if (RMath.fuzzyCompare(circle1.radius, circle2.radius)) {
  967. arr2.push(new RPoint(circle2.center));
  968. arr3.push(new RPoint(circle2.center));
  969. }
  970. else {
  971. var circle22 = circle2.clone();
  972. circle22.radius = Math.abs(circle22.radius - circle1.radius);
  973. arr2.push(circle22);
  974. arr4.push(circle22);
  975. }
  976. var parallels = ShapeAlgorithms.getOffsetShapes(line, circle1.radius, 1, RS.BothSides);
  977. arr1.push(parallels[0]);
  978. arr2.push(parallels[0]);
  979. arr3.push(parallels[1]);
  980. arr4.push(parallels[1]);
  981. var cArr1 = Apollonius.getSolutions(arr1[0], arr1[1], arr1[2]);
  982. var cArr2 = Apollonius.getSolutions(arr2[0], arr2[1], arr2[2]);
  983. var cArr3 = Apollonius.getSolutions(arr3[0], arr3[1], arr3[2]);
  984. var cArr4 = Apollonius.getSolutions(arr4[0], arr4[1], arr4[2]);
  985. // qDebug("cArr1:\n" + cArr1 + "\n\n");
  986. // qDebug("cArr2:\n" + cArr2 + "\n\n");
  987. // qDebug("cArr3:\n" + cArr3 + "\n\n");
  988. // qDebug("cArr4:\n" + cArr4 + "\n\n");
  989. var cArrs = [ cArr1, cArr2, cArr3, cArr4 ];
  990. var ret = [];
  991. var tmpCircle, obj1, obj2;
  992. for (var i=0; i<cArrs.length; i++) {
  993. var cArr = cArrs[i];
  994. for (var k=0; k<cArr.length; k++) {
  995. var obj = cArr[k];
  996. if (!isCircleShape(obj)) {
  997. continue;
  998. }
  999. tmpCircle = obj.clone();
  1000. obj1 = obj.clone();
  1001. obj2 = obj.clone();
  1002. obj1.radius += circle1.radius;
  1003. obj2.radius = Math.abs(obj2.radius - circle1.radius);
  1004. var obj1c1 = obj1.center.getDistanceTo(circle1.center);
  1005. var obj1c2 = obj1.center.getDistanceTo(circle2.center);
  1006. if ((RMath.fuzzyCompare(obj1c1, circle1.radius+obj1.radius) ||
  1007. RMath.fuzzyCompare(obj1c1, Math.abs(circle1.radius-obj1.radius))) &&
  1008. (RMath.fuzzyCompare(obj1c2, circle2.radius+obj1.radius) ||
  1009. RMath.fuzzyCompare(obj1c2, Math.abs(circle2.radius-obj1.radius))) &&
  1010. RMath.fuzzyCompare(line.getDistanceTo(obj1.center, false), obj1.radius)) {
  1011. ret.push(obj1);
  1012. }
  1013. var obj2c1 = obj2.center.getDistanceTo(circle1.center);
  1014. var obj2c2 = obj2.center.getDistanceTo(circle2.center);
  1015. if ((RMath.fuzzyCompare(obj2c1, circle1.radius+obj2.radius) ||
  1016. RMath.fuzzyCompare(obj2c1, Math.abs(circle1.radius-obj2.radius))) &&
  1017. (RMath.fuzzyCompare(obj2c2, circle2.radius+obj2.radius) ||
  1018. RMath.fuzzyCompare(obj2c2, Math.abs(circle2.radius-obj2.radius))) &&
  1019. RMath.fuzzyCompare(line.getDistanceTo(obj2.center, false), obj2.radius)) {
  1020. ret.push(obj2);
  1021. }
  1022. }
  1023. }
  1024. return ret;
  1025. };
  1026. /**
  1027. * \return Solutions for circles that are tangential to the given point, line
  1028. * and circle.
  1029. */
  1030. Apollonius.getSolutionsPLC = function(point, line, circle) {
  1031. if (!isPointShape(point) ||
  1032. !isLineBasedShape(line) ||
  1033. !isCircleShape(circle)) {
  1034. return [];
  1035. }
  1036. var lData = line;
  1037. if (isFunction(line.data)) {
  1038. lData = line.data();
  1039. }
  1040. // if (point.position.equalsFuzzy(new RVector(190,90))) {
  1041. // debugger;
  1042. // }
  1043. // var inversionCircle = new RCircle(point.position, 10);
  1044. // if (line.isOnShape(point.position) || circle.isOnShape(point.position)) {
  1045. // // TODO: there is still a solution here:
  1046. // return [];
  1047. // }
  1048. // var shapes = [];
  1049. // shapes.push(circle);
  1050. // shapes.push(line);
  1051. // var shapesInverse = Apollonius.getInverseShapes(shapes, inversionCircle);
  1052. // var tangents = Apollonius.getCommonTangents(shapesInverse[0], shapesInverse[1]);
  1053. // return Apollonius.getInverseShapes(tangents, inversionCircle);
  1054. var a = point.getPosition();
  1055. var c = circle.getCenter();
  1056. var f = line.getClosestPointOnShape(c, false);
  1057. var ortho = new RLine(c, f);
  1058. var cf = f.operator_subtract(c);
  1059. var ce = cf.copy();
  1060. ce.setMagnitude2D(circle.getRadius());
  1061. // intersections of orthogonal through c to line with circle:
  1062. var d1 = c.operator_add(ce);
  1063. var e1 = c.operator_subtract(ce);
  1064. var ds = [d1, e1];
  1065. var es = [e1, d1];
  1066. var centerCandidates = [];
  1067. var ips;
  1068. var p;
  1069. for (var i=0; i<2; i++) {
  1070. var d = ds[i];
  1071. var e = es[i];
  1072. // special case:
  1073. // a is on orthogonal cf:
  1074. if (ortho.getDistanceTo(a, false)<RS.PointTolerance) {
  1075. var da_ = new RLine(d, f);
  1076. da_.rotate(Math.PI/4, d);
  1077. var par = line.clone();
  1078. par.moveTo(a);
  1079. ips = da_.getIntersectionPoints(par, false);
  1080. if (ips.length!==1) {
  1081. continue;
  1082. }
  1083. var a_ = ips[0];
  1084. ips = da_.getIntersectionPoints(lData, false);
  1085. if (ips.length!==1) {
  1086. continue;
  1087. }
  1088. var f_ = ips[0];
  1089. var a_e = new RLine(a_, e);
  1090. var f_p = a_e.clone();
  1091. f_p.moveTo(f_);
  1092. ips = f_p.getIntersectionPoints(ortho, false);
  1093. if (ips.length!==1) {
  1094. continue;
  1095. }
  1096. p = ips[0];
  1097. var apm = RVector.getAverage(a, p);
  1098. var m = line.getDistanceTo(apm, false);
  1099. var circ = new RCircle(a, m);
  1100. var par2 = line.clone();
  1101. par2.moveTo(apm);
  1102. ips = par2.getIntersectionPoints(circ, false);
  1103. centerCandidates = centerCandidates.concat(ips);
  1104. }
  1105. // special case:
  1106. // point is on line:
  1107. else if (line.isOnShape(a, false)) {
  1108. // similarity axis:
  1109. var ea = new RLine(e, a);
  1110. ips = circle.getIntersectionPoints(ea, false);
  1111. if (ips.length!==2) {
  1112. continue;
  1113. }
  1114. var i1 = ips[0];
  1115. if (i1.equalsFuzzy(e)) {
  1116. i1 = ips[1];
  1117. }
  1118. var ci1 = new RLine(c, i1);
  1119. // ortho through a:
  1120. var orthoA = new RLine(a, line.getAngle() + Math.PI/2, 1.0)
  1121. ips = orthoA.getIntersectionPoints(ci1, false);
  1122. if (ips.length!==1) {
  1123. continue;
  1124. }
  1125. centerCandidates.push(ips[0]);
  1126. }
  1127. else {
  1128. var da = new RLine(d, a);
  1129. ips = da.getIntersectionPoints(lData, false);
  1130. if (ips.length!==1) {
  1131. continue;
  1132. }
  1133. var m = ips[0];
  1134. var efa = RCircle.createFrom3Points(e, f, a);
  1135. var ps = da.getIntersectionPoints(efa, false);
  1136. if (ps.length<1) {
  1137. continue;
  1138. }
  1139. p = ps[0];
  1140. if (p.equalsFuzzy(a) && ps.length>1) {
  1141. p = ps[1];
  1142. }
  1143. var ap = new RLine(a, p);
  1144. var apOrtho = ap.copy();
  1145. apOrtho.rotate(Math.PI/2, ap.getMiddlePoint());
  1146. var tangents = Apollonius.getTangentsThroughPoint(efa, m);
  1147. for (var k=0; k<tangents.length; k++) {
  1148. var tangent = tangents[k];
  1149. if (isNull(tangent)) {
  1150. continue;
  1151. }
  1152. var mt = tangent.getLength();
  1153. var mu = line.endPoint.operator_subtract(line.startPoint);
  1154. mu.setMagnitude2D(mt);
  1155. var u = m.operator_add(mu);
  1156. var v = m.operator_subtract(mu);
  1157. var orthU = line.clone();
  1158. orthU.rotate(Math.PI/2, u);
  1159. var orthV = line.clone();
  1160. orthV.rotate(Math.PI/2, v);
  1161. var pps = apOrtho.getIntersectionPoints(orthU, false);
  1162. centerCandidates = centerCandidates.concat(pps);
  1163. pps = apOrtho.getIntersectionPoints(orthV, false);
  1164. centerCandidates = centerCandidates.concat(pps);
  1165. }
  1166. }
  1167. }
  1168. var res = [];
  1169. for (var n=0; n<centerCandidates.length; n++) {
  1170. var centerCandidate = centerCandidates[n];
  1171. res.push(new RCircle(centerCandidate, centerCandidate.getDistanceTo(a)));
  1172. }
  1173. return res;
  1174. };
  1175. /**
  1176. * \return Solutions for circles that are tangential to the given point
  1177. * and the given lines.
  1178. */
  1179. Apollonius.getSolutionsPLL = function(point, line1, line2) {
  1180. if (!isPointShape(point) ||
  1181. !isLineBasedShape(line1) ||
  1182. !isLineBasedShape(line2)) {
  1183. return [];
  1184. }
  1185. var line2Data;
  1186. if (isFunction(line2.data)) {
  1187. line2Data = line2.data();
  1188. }
  1189. else {
  1190. line2Data = line2;
  1191. }
  1192. // intersection between two lines line1, line2:
  1193. var ipsLL = line1.getIntersectionPoints(line2Data, false);
  1194. var ips;
  1195. var i, k, c;
  1196. var circles, circle;
  1197. var centers = [];
  1198. var bisectorLines = Apollonius.getAngleBisectors(line1, line2);
  1199. var bisectorLine;
  1200. var onLine1 = line1.isOnShape(point.position, false);
  1201. var onLine2 = line2.isOnShape(point.position, false);
  1202. var onBisector = false;
  1203. for (k=0; k<bisectorLines.length; k++) {
  1204. if (bisectorLines[k].isOnShape(point.position, false)) {
  1205. onBisector = true;
  1206. break;
  1207. }
  1208. }
  1209. // lines are parallel:
  1210. if (ipsLL.length===0) {
  1211. // middle line:
  1212. var s = line1.getStartPoint();
  1213. var p = line2.getClosestPointOnShape(s, false);
  1214. var center = RVector.getAverage(s, p);
  1215. var middleLine = line1.clone();
  1216. middleLine.move(center.operator_subtract(middleLine.getStartPoint()));
  1217. // circle with radius c-s around point:
  1218. circle = new RCircle(point.position, center.getDistanceTo(s));
  1219. // intersections between circle and middle line are candidates:
  1220. centers = circle.getIntersectionPoints(middleLine, false);
  1221. }
  1222. // point is on line1 or line2:
  1223. else if (onLine1 || onLine2) {
  1224. var line = onLine1 ? line1 : line2;
  1225. var orthoLine = new RLine(point.position, line.getAngle() + Math.PI/2, 1.0);
  1226. for (k=0; k<bisectorLines.length; k++) {
  1227. bisectorLine = bisectorLines[k];
  1228. ips = bisectorLine.getIntersectionPoints(orthoLine, false);
  1229. if (ips.length!==1) {
  1230. continue;
  1231. }
  1232. centers.push(ips[0]);
  1233. }
  1234. }
  1235. else {
  1236. var centerCandidates = [];
  1237. // point on bisector:
  1238. if (onBisector) {
  1239. if (ipsLL.length!==1) {
  1240. return [];
  1241. }
  1242. // distance from point to line1 (radius of circle around point, tangential to lines):
  1243. var rp = line1.getDistanceTo(point.position, false);
  1244. // distance from intersection line1/line2 to point:
  1245. var dp = ipsLL[0].getDistanceTo(point.position);
  1246. // distances from intersection line1/line2 to intersection of bisector line with circle around point, touching line1, line2:
  1247. var dc1 = dp + rp;
  1248. var dc2 = dp - rp;
  1249. // factors to scale circle to reach results:
  1250. var f1 = dp / dc1;
  1251. var f2 = dp / dc2;
  1252. // radius of solution
  1253. var r1 = rp * f1;
  1254. var r2 = rp * f2;
  1255. for (k=0; k<bisectorLines.length; k++) {
  1256. bisectorLine = bisectorLines[k];
  1257. var a = bisectorLine.getAngle();
  1258. centerCandidates.push(ipsLL[0].operator_add(RVector.createPolar(dp + r2, a)));
  1259. centerCandidates.push(ipsLL[0].operator_add(RVector.createPolar(dp - r1, a)));
  1260. centerCandidates.push(ipsLL[0].operator_add(RVector.createPolar(dp + r2, a + Math.PI)));
  1261. centerCandidates.push(ipsLL[0].operator_add(RVector.createPolar(dp - r1, a + Math.PI)));
  1262. }
  1263. }
  1264. // circle C tangential to two lines with center E, radius 10:
  1265. else {
  1266. circles = Apollonius.getCircles2TR(line1, line2, 10.0);
  1267. if (isNull(circles) || circles.length===0) {
  1268. return [];
  1269. }
  1270. for (i=0; i<circles.length; i++) {
  1271. circle = circles[i];
  1272. var e = circle.getCenter();
  1273. // line L from intersection between the two lines to the point:
  1274. // center of solution is on this line
  1275. var line;
  1276. if (ipsLL.length===0) {
  1277. // lines parallel:
  1278. line = line1.clone();
  1279. line.move(point.position.operator_subtract(line1.getStartPoint()));
  1280. }
  1281. else {
  1282. line = new RLine(ipsLL[0], point.position);
  1283. }
  1284. // intersections between line L and circle C -> G, H:
  1285. var ipsLC = line.getIntersectionPoints(circle, false);
  1286. if (ipsLC.length!==2) {
  1287. continue;
  1288. }
  1289. var g = ipsLC[0];
  1290. var h = ipsLC[1];
  1291. // two lines L1, L2 with same angle as EG, EH through point:
  1292. var l1 = new RLine(e, g);
  1293. var l2 = new RLine(e, h);
  1294. l1.move(point.position.operator_subtract(l1.getStartPoint()));
  1295. l2.move(point.position.operator_subtract(l2.getStartPoint()));
  1296. // intersection of angle bisector and lines L1, L2 are centers of candidates:
  1297. for (k=0; k<bisectorLines.length; k++) {
  1298. bisectorLine = bisectorLines[k];
  1299. centerCandidates = centerCandidates.concat(l1.getIntersectionPoints(bisectorLine, false));
  1300. centerCandidates = centerCandidates.concat(l2.getIntersectionPoints(bisectorLine, false));
  1301. }
  1302. }
  1303. }
  1304. for (c=0; c<centerCandidates.length; c++) {
  1305. var centerCandidate = centerCandidates[c];
  1306. var dLine1 = line1.getDistanceTo(centerCandidate, false);
  1307. var dLine2 = line1.getDistanceTo(centerCandidate, false);
  1308. var dPoint = point.getDistanceTo(centerCandidate, false);
  1309. if (RMath.fuzzyCompare(dLine1, dPoint) && RMath.fuzzyCompare(dLine2, dPoint)) {
  1310. centers.push(centerCandidate);
  1311. }
  1312. }
  1313. centers = RVector.getUnique(centers);
  1314. }
  1315. var ret = [];
  1316. for (c=0; c<centers.length; c++) {
  1317. var r = centers[c].getDistanceTo(point.position);
  1318. if (RMath.fuzzyCompare(r, 0.0)) {
  1319. continue;
  1320. }
  1321. ret.push(new RCircle(centers[c], r));
  1322. }
  1323. return ret;
  1324. };
  1325. /**
  1326. * \return Given shapes inversed by given inversion circle.
  1327. */
  1328. Apollonius.getInverseShapes = function(shapes, inversionCircle) {
  1329. var shapesInverse = [];
  1330. for (var i=0; i<shapes.length; i++) {
  1331. if (isNull(shapes[i])) {
  1332. continue;
  1333. }
  1334. var shapeInverse = Apollonius.getInverseShape(shapes[i], inversionCircle);
  1335. if (!isNull(shapeInverse)) {
  1336. shapesInverse.push(shapeInverse);
  1337. }
  1338. }
  1339. return shapesInverse;
  1340. }
  1341. /**
  1342. * \return Given shape inversed by given inversion circle.
  1343. */
  1344. Apollonius.getInverseShape = function(shape, inversionCircle) {
  1345. if (isPointShape(shape)) {
  1346. var r = inversionCircle.radius;
  1347. var center = inversionCircle.center;
  1348. var d = shape.position.getDistanceTo(center);
  1349. if (Math.abs(d)<RS.PointTolerance) {
  1350. return shape;
  1351. }
  1352. // d' = r^2 / d
  1353. var dInverse = Math.pow(r, 2) / d;
  1354. var x = center.x + (shape.position.x - center.x) * dInverse / d;
  1355. var y = center.y + (shape.position.y - center.y) * dInverse / d;
  1356. return new RPoint(new RVector(x, y));
  1357. }
  1358. if (isLineBasedShape(shape)) {
  1359. var center = inversionCircle.center;
  1360. if (shape.isOnShape(center, false)) {
  1361. return shape;
  1362. }
  1363. else {
  1364. var s = Apollonius.getVerticalToPoint(shape, center);
  1365. var p = shape.getIntersectionPoints(s, false)[0];
  1366. var pInverse = Apollonius.getInverseShape(new RPoint(p), inversionCircle);
  1367. if (!pInverse.position.isValid()) {
  1368. debugger;
  1369. }
  1370. return RCircle.createFrom2Points(center, pInverse.position);
  1371. }
  1372. }
  1373. if (isCircleShape(shape)) {
  1374. var circle = shape;
  1375. var ips;
  1376. if (circle.center.equalsFuzzy(inversionCircle.center)) {
  1377. // create Point on circle and invert it with the circle
  1378. var radiusHelperPoint = new RPoint(new RVector(circle.center.x + circle.radius, circle.center.y));
  1379. var radius = circle.center.x - Apollonius.getInverseShape(radiusHelperPoint, inversionCircle).position.x;
  1380. if (radius<0) {
  1381. //debugger;
  1382. radius = Math.abs(radius);
  1383. }
  1384. return new RCircle(circle.center, radius);
  1385. }
  1386. else if (circle.isOnShape(inversionCircle.center)) {
  1387. var s = new RLine(inversionCircle.center, circle.center);
  1388. ips = s.getIntersectionPoints(circle, false);
  1389. if (ips.length<1) {
  1390. debugger;
  1391. return undefined;
  1392. }
  1393. var p = ips[0];
  1394. if (p.equalsFuzzy(inversionCircle.center)) {
  1395. if (ips.length<2) {
  1396. debugger;
  1397. return undefined;
  1398. }
  1399. p = ips[1];
  1400. }
  1401. var pInverse = Apollonius.getInverseShape(new RPoint(p), inversionCircle);
  1402. if (!pInverse.position.isValid()) {
  1403. debugger;
  1404. }
  1405. return new RLine(pInverse.position, s.getAngle() + Math.PI/2, 1.0);
  1406. }
  1407. else {
  1408. var l = new RLine(inversionCircle.center, circle.center);
  1409. ips = l.getIntersectionPoints(circle, false);
  1410. if (ips.length<2) {
  1411. return undefined;
  1412. }
  1413. var p1 = ips[0];
  1414. var p2 = ips[1];
  1415. var p1Inverse = Apollonius.getInverseShape(new RPoint(p1), inversionCircle);
  1416. var p2Inverse = Apollonius.getInverseShape(new RPoint(p2), inversionCircle);
  1417. if (!p1Inverse.position.isValid()) {
  1418. debugger;
  1419. }
  1420. if (!p2Inverse.position.isValid()) {
  1421. debugger;
  1422. }
  1423. return RCircle.createFrom2Points(p1Inverse.position, p2Inverse.position);
  1424. }
  1425. }
  1426. debugger;
  1427. };
  1428. /**
  1429. * \return Tangents from circle1 to circle 2
  1430. */
  1431. Apollonius.getCommonTangents = function(circle1, circle2) {
  1432. // one shape might be a line:
  1433. if (!isCircleShape(circle1) || !isCircleShape(circle2)) {
  1434. return [];
  1435. }
  1436. // concentric:
  1437. if (circle1.center.equalsFuzzy(circle2.center)) {
  1438. return [];
  1439. }
  1440. if (circle1.radius < RS.PointTolerance) {
  1441. return Apollonius.getTangentsThroughPoint(circle2, circle1.center);
  1442. }
  1443. if (circle2.radius < RS.PointTolerance) {
  1444. var tangentsInWrongOrder = Apollonius.getTangentsThroughPoint(circle1, circle2.center);
  1445. var tangents = [];
  1446. tangents[0] = tangentsInWrongOrder[1];
  1447. tangents[1] = tangentsInWrongOrder[0];
  1448. return tangents;
  1449. }
  1450. var circle1IsSmaller = circle1.radius < circle2.radius;
  1451. var c1, c2;
  1452. if (circle1IsSmaller) {
  1453. c1 = circle1;
  1454. c2 = circle2;
  1455. }
  1456. else {
  1457. c1 = circle2;
  1458. c2 = circle1;
  1459. }
  1460. var c21 = c2.clone();
  1461. c21.radius = Math.abs(c21.radius - c1.radius);
  1462. var c22 = c2.clone();
  1463. c22.radius += c1.radius;
  1464. var tangents1 = Apollonius.getTangentsThroughPoint(c21, c1.center);
  1465. var tangents2 = Apollonius.getTangentsThroughPoint(c22, c1.center);
  1466. var ret = [];
  1467. if (tangents1.length === 2) {
  1468. ret[0] = Apollonius.getParallelLinesWithDistance(tangents1[0], c1.radius)[0];
  1469. ret[3] = Apollonius.getParallelLinesWithDistance(tangents1[1], c1.radius)[1];
  1470. }
  1471. if (tangents2.length > 1) {
  1472. ret[1] = Apollonius.getParallelLinesWithDistance(tangents2[1], c1.radius)[0];
  1473. ret[2] = Apollonius.getParallelLinesWithDistance(tangents2[0], c1.radius)[1];
  1474. }
  1475. //Apollonius.constructionShapes = Apollonius.constructionShapes.concat(ret);
  1476. return ret;
  1477. };
  1478. Apollonius.getTangentsThroughPoint = function(circle, p) {
  1479. if (!isCircleShape(circle)) {
  1480. return [];
  1481. }
  1482. // used when creating tangential circles to two parallel lines and point:
  1483. if (Math.abs(circle.radius)<RS.PointTolerance) {
  1484. var lines = [];
  1485. lines[0] = lines[1] = new RLine(p, circle.center);
  1486. return lines;
  1487. }
  1488. // point on the circle line (produces error):
  1489. else if (circle.isOnShape(p)) {
  1490. var s = new RLine(p, circle.center);
  1491. var lines = [];
  1492. lines[0] = new RLine(p, s.getAngle() + Math.PI/2, 1.0);
  1493. lines[1] = undefined;
  1494. return lines;
  1495. // return [];
  1496. }
  1497. // point is inside the circle:
  1498. else if (circle.contains(p)) {
  1499. return [];
  1500. }
  1501. // point outside circle:
  1502. else {
  1503. var circle2 = RCircle.createFrom2Points(p, circle.center);
  1504. var touchingPoints = circle2.getIntersectionPoints(circle, false);
  1505. var lines = [];
  1506. if (!isNull(touchingPoints[1])) {
  1507. lines[0] = new RLine(p, touchingPoints[1]);
  1508. }
  1509. else {
  1510. lines[0] = undefined;
  1511. }
  1512. if (!isNull(touchingPoints[0])) {
  1513. lines[1] = new RLine(p, touchingPoints[0]);
  1514. }
  1515. else {
  1516. lines[1] = undefined;
  1517. }
  1518. return lines;
  1519. }
  1520. };
  1521. Apollonius.getParallelLinesWithDistance = function(line, distance) {
  1522. if (isNull(line)) {
  1523. return [];
  1524. }
  1525. if (!isLineBasedShape(line)) {
  1526. return [];
  1527. }
  1528. var point1 = (new RLine(line.startPoint, line.getAngle() + Math.PI/2, distance)).endPoint;
  1529. var point2 = (new RLine(line.startPoint, line.getAngle() - Math.PI/2, distance)).endPoint;
  1530. var line1 = new RLine(point1, line.getAngle(), 1.0);
  1531. var line2 = new RLine(point2, line.getAngle(), 1.0);
  1532. var arr = [];
  1533. arr[0] = line1;
  1534. arr[1] = line2;
  1535. return arr;
  1536. };
  1537. /**
  1538. * \return Common intersection point of all three circles or undefined.
  1539. */
  1540. Apollonius.getCommonIntersectionPoint = function(c1, c2, c3) {
  1541. if (!isCircleShape(c1) ||
  1542. !isCircleShape(c2) ||
  1543. !isCircleShape(c3)) {
  1544. return undefined;
  1545. }
  1546. var ips1 = c1.getIntersectionPoints(c2, false);
  1547. var ips2 = c1.getIntersectionPoints(c3, false);
  1548. if (ips1.length!==2 || ips2.length!==2) {
  1549. return undefined;
  1550. }
  1551. var p1_1 = ips1[0];
  1552. var p1_2 = ips1[1];
  1553. var p2_1 = ips2[0];
  1554. var p2_2 = ips2[1];
  1555. if (p1_1.equalsFuzzy(p2_1) || p1_1.equalsFuzzy(p2_2)) {
  1556. return p1_1;
  1557. }
  1558. else if (p1_2.equalsFuzzy(p2_1) || p1_2.equalsFuzzy(p2_2)) {
  1559. return p1_2;
  1560. }
  1561. else {
  1562. return undefined;
  1563. }
  1564. };
  1565. /**
  1566. * \return Angle bisectors of the two given lines or empty array if
  1567. * lines are parallel.
  1568. */
  1569. Apollonius.getAngleBisectors = function(line1, line2) {
  1570. if (!isLineBasedShape(line1) ||
  1571. !isLineBasedShape(line2)) {
  1572. return [];
  1573. }
  1574. var angle1 = (line1.getAngle() + line2.getAngle()) / 2;
  1575. var angle2 = angle1 + Math.PI/2;
  1576. var line2Data;
  1577. if (isFunction(line2.data)) {
  1578. line2Data = line2.data();
  1579. }
  1580. else {
  1581. line2Data = line2;
  1582. }
  1583. var points = line1.getIntersectionPoints(line2Data, false);
  1584. if (points.length===0) {
  1585. // lines are parallel:
  1586. return [];
  1587. }
  1588. var point = points[0];
  1589. return [
  1590. new RLine(point, angle1, 1.0),
  1591. new RLine(point, angle2, 1.0)
  1592. ];
  1593. };
  1594. Apollonius.getVerticalToPoint = function(line, p) {
  1595. if (!isLineBasedShape(line)) {
  1596. return undefined;
  1597. }
  1598. var p1 = line.getClosestPointOnShape(p, false);
  1599. return new RLine(p, p1);
  1600. };
  1601. /**
  1602. * \return Array of circles tangential to shape1 and shape2 with given radius.
  1603. *
  1604. * \param pos Only return circle closest to given pos or undefined to return all circles
  1605. * \param candidates Empty array. Filled with all candidates or undefined if caller is not
  1606. * interested in all solution.
  1607. * \param preview True for preview mode
  1608. */
  1609. Apollonius.getCircles2TR = function(shape1, shape2, radius, pos, candidates, preview) {
  1610. Apollonius.error = "";
  1611. if (isNull(shape1) || isNull(shape2) || !isNumber(radius)) {
  1612. return undefined;
  1613. }
  1614. if (isNull(preview)) {
  1615. preview = false;
  1616. }
  1617. if (radius <= 0.0 || radius > 1.0e6) {
  1618. if (!preview) {
  1619. Apollonius.error = qsTr("Invalid radius");
  1620. }
  1621. return undefined;
  1622. }
  1623. var i,k,ips,s;
  1624. if (isNull(candidates)) {
  1625. candidates = [];
  1626. }
  1627. if (candidates.length===0) {
  1628. var offset1 = ShapeAlgorithms.getOffsetShapes(shape1, radius, 1, RS.BothSides);
  1629. var offset2 = ShapeAlgorithms.getOffsetShapes(shape2, radius, 1, RS.BothSides);
  1630. if (isCircleShape(shape1) || isArcShape(shape1)) {
  1631. if (radius>shape1.getRadius()) {
  1632. s = shape1.clone();
  1633. s.setRadius(radius - shape1.getRadius());
  1634. offset1.push(s);
  1635. }
  1636. }
  1637. if (isCircleShape(shape2) || isArcShape(shape2)) {
  1638. if (radius>shape2.getRadius()) {
  1639. s = shape2.clone();
  1640. s.setRadius(radius - shape2.getRadius());
  1641. offset2.push(s);
  1642. }
  1643. }
  1644. var centerPoints = [];
  1645. for (i=0; i<offset1.length; i++) {
  1646. for (k=0; k<offset2.length; k++) {
  1647. s = offset2[k];
  1648. if (isFunction(s.data)) {
  1649. s = s.data();
  1650. }
  1651. ips = offset1[i].getIntersectionPoints(s, false);
  1652. centerPoints = centerPoints.concat(ips);
  1653. }
  1654. }
  1655. //var circle = undefined;
  1656. for (i=0; i<centerPoints.length; i++) {
  1657. var c = new RCircle(centerPoints[i], radius);
  1658. candidates.push(c);
  1659. }
  1660. }
  1661. if (candidates.length===0) {
  1662. if (!preview) {
  1663. Apollonius.error = qsTr("No solution");
  1664. }
  1665. return undefined;
  1666. }
  1667. // no position yet: return all candidates for preview:
  1668. if (isNull(pos)) {
  1669. return candidates;
  1670. }
  1671. return [ ShapeAlgorithms.getClosestShape(candidates, pos) ];
  1672. };