Source: model/Matching.js

  1. /**
  2. * @file Class Matching
  3. * @version March 26, 2017
  4. *
  5. * @author Olivier Pirson --- http://www.opimedia.be/
  6. * @license GPLv3 --- Copyright (C) 2017 Olivier Pirson
  7. */
  8. /**
  9. * A geometric matching:
  10. * - a "set" of points
  11. * - a "set" of segments on these points
  12. *
  13. * The set of points may be shared with other linked matching.
  14. */
  15. class Matching {
  16. /**
  17. * Construct a Matching.
  18. *
  19. * If linkedMatching !== null
  20. * then shared points with it.
  21. *
  22. * @param {null|Matching} linkedMatching
  23. */
  24. constructor(linkedMatching=null) {
  25. assert((linkedMatching === null) || (linkedMatching instanceof Matching), linkedMatching);
  26. if (linkedMatching === null) {
  27. this._points = []; // ordonned sequence of Point
  28. this._linkedMatchings = [this]; // ordonned sequence of Matching
  29. }
  30. else {
  31. this._points = linkedMatching.points;
  32. linkedMatching.linkedMatchings.push(this);
  33. this._linkedMatchings = linkedMatching.linkedMatchings;
  34. }
  35. this._segments = []; // ordonned sequence of Segment (for each segment, the two points are ordonned)
  36. this._numberPerfectMatchingsIfCalculated = null; // null or (if already calculated) the exact number of perfect matchings for theses points
  37. }
  38. /**
  39. * Returns the ordonned sequence of linked matchings.
  40. *
  41. * @returns {Array} Array of Matching
  42. */
  43. get linkedMatchings() { return this._linkedMatchings; }
  44. /**
  45. * If is already calculated or if it easy to calculate
  46. * then returns the exact number of perfect matchings for theses points,
  47. * else returns null.
  48. *
  49. * @returns {null|Number}
  50. */
  51. get numberPerfectMatchingsIfCalculated() {
  52. if (this.points.length % 2 !== 0) {
  53. this._numberPerfectMatchingsIfCalculated = 0;
  54. }
  55. else if (this.points.length <= 2) {
  56. this._numberPerfectMatchingsIfCalculated = this.points.length/2;
  57. }
  58. return this._numberPerfectMatchingsIfCalculated;
  59. }
  60. /**
  61. * Returns the ordonned sequence of points.
  62. *
  63. * @returns {Array} Array of Point
  64. */
  65. get points() { return this._points; }
  66. /**
  67. * Returns the sequence of segments.
  68. *
  69. * @returns {Array} Array of Segment (for each segment, the two points are ordonned)
  70. */
  71. get segments() { return this._segments; }
  72. /**
  73. * Returns all possible perfect matchings
  74. * with the points of this matching.
  75. *
  76. * If disjointMatching
  77. * then keep only perfect matching disjoint with this matching.
  78. *
  79. * If compatibleMatching
  80. * then keep only perfect matching disjoint with this matching.
  81. *
  82. * FIXME! Naive algorithm. Implement iterative building?
  83. *
  84. * @param {Boolean} disjointMatching
  85. * @param {Boolean} compatibleMatching
  86. *
  87. * @returns {Array} Array of Matching
  88. */
  89. allPerfectMatchings(disjointMatching, compatibleMatching) {
  90. assert(typeof disjointMatching === "boolean", disjointMatching);
  91. assert(typeof compatibleMatching === "boolean", compatibleMatching);
  92. const matchings = [];
  93. if (this.points.length % 2 !== 0) {
  94. return matchings;
  95. }
  96. const thisMatching = this;
  97. const points = this.points;
  98. const nbPoints = points.length;
  99. const assigns = [];
  100. // Value in assigns:
  101. // false: not assigned
  102. // number: index of the first point of a segment
  103. // true: index of the second point of a segment
  104. // Fill with first assignation
  105. for (let i = 0; i < nbPoints; ++i) {
  106. assigns.push(i % 2 === 0
  107. ? i + 1
  108. : true);
  109. }
  110. var available = new Set(); // available indices
  111. const backtrackPos = function () { // return previous position with assignation
  112. for (let i = nbPoints - 1; i >= 0; --i) {
  113. if (typeof assigns[i] === "number") {
  114. return i;
  115. }
  116. }
  117. return null;
  118. };
  119. const nextPointIndex = function (pos, first=0) { // return next point index
  120. for (let i = Math.max(first, pos + 1); i < nbPoints; ++i) {
  121. if (available.has(i)) {
  122. return i;
  123. }
  124. }
  125. return null;
  126. };
  127. const assign = function (pos, pointIndex) { // assign two related indices
  128. available.delete(pos);
  129. available.delete(pointIndex);
  130. assigns[pos] = pointIndex;
  131. assigns[pointIndex] = true;
  132. };
  133. const unassign = function (pos) { // unassign two related indices
  134. available.add(pos);
  135. available.add(assigns[pos]);
  136. assigns[assigns[pos]] = false;
  137. assigns[pos] = false;
  138. };
  139. const toMatching = function () { // return a new perfect matching corresponding to assignations, or null
  140. const matching = new Matching();
  141. matching._points = thisMatching._points;
  142. for (let i = 0; i < nbPoints; ++i) {
  143. if (typeof assigns[i] === "number") {
  144. const segment = new Segment(points[i], points[assigns[i]]);
  145. if (matching.segmentIsIntersect(segment)) {
  146. return null;
  147. }
  148. matching.segmentAdd(segment);
  149. }
  150. }
  151. return matching;
  152. };
  153. while (true) {
  154. // One possible matching
  155. const matching = toMatching();
  156. if (matching !== null) {
  157. matchings.push(matching);
  158. }
  159. // Search first position to modify
  160. var pos;
  161. var next;
  162. do {
  163. pos = backtrackPos();
  164. if (pos === null) {
  165. this._numberPerfectMatchingsIfCalculated = matchings.length;
  166. if (disjointMatching || compatibleMatching) { // keep only "good" matchings
  167. for (let i = 0; i < matchings.length; ++i) {
  168. if ((disjointMatching && !this.isDisjoint(matchings[i]))
  169. || (compatibleMatching && !this.isCompatible(matchings[i]))) {
  170. matchings.splice(i, 1);
  171. --i;
  172. }
  173. }
  174. }
  175. return matchings;
  176. }
  177. next = nextPointIndex(pos, assigns[pos] + 1);
  178. unassign(pos);
  179. } while (next === null);
  180. // Set values from pos
  181. assign(pos, next);
  182. for (let i = pos + 1; i < nbPoints; ++i) {
  183. if ((next !== null) && (assigns[i] === false)) {
  184. next = nextPointIndex(i);
  185. assign(i, next);
  186. }
  187. }
  188. }
  189. }
  190. /**
  191. * Returns an upper bound of number
  192. * of all possible perfect matchings with the points of this matching.
  193. *
  194. * @returns {Number}
  195. */
  196. allPerfectMatchingsUppedBound() {
  197. if ((this.points.length === 0) || (this.points.length % 2 !== 0)) {
  198. return 0;
  199. }
  200. var number = 1;
  201. for (let i = 3; i < this._points.length; i += 2) {
  202. number *= i;
  203. }
  204. return number;
  205. }
  206. /**
  207. * Returns the canonical perfect matching
  208. * corresponding to these points.
  209. *
  210. * @returns {Matching}
  211. */
  212. canonical() {
  213. assert(this._points.length % 2 === 0, this._points.length);
  214. const matching = new Matching();
  215. matching._points = this._points;
  216. for (let i = 0; i < this._points.length; i += 2) {
  217. const segment = new Segment(matching._points[i], matching._points[i + 1]);
  218. matching.segmentAdd(segment);
  219. }
  220. return matching;
  221. }
  222. /**
  223. * Empty sets of points and segments,
  224. * and intermediary linked matchings.
  225. */
  226. clear() {
  227. this.clearIntermediaryLinkedMatchings();
  228. this._points.splice(0, this._points.length);
  229. this._segments.splice(0, this._segments.length);
  230. this.clearIfModifyPoints();
  231. }
  232. /**
  233. * Reset some data when add or remove point.
  234. */
  235. clearIfModifyPoints() {
  236. this._numberPerfectMatchingsIfCalculated = null;
  237. for (let matching of this._linkedMatchings) {
  238. matching._numberPerfectMatchingsIfCalculated = null;
  239. }
  240. }
  241. /**
  242. * Remove all intermediary linked matchings.
  243. */
  244. clearIntermediaryLinkedMatchings() {
  245. assert(this._linkedMatchings.length >= 2, this._linkedMatchings);
  246. for (let i = 1; i < this._linkedMatchings.length - 1; ++i) {
  247. this._linkedMatchings[i]._linkedMatchings = null;
  248. }
  249. this._linkedMatchings.splice(1, this._linkedMatchings.length - 2);
  250. this._linkedMatchings[1]._linkedMatchings = this._linkedMatchings;
  251. }
  252. /**
  253. * Returns a set of segments in common between this matching and other.
  254. *
  255. * @param {Matching} other
  256. *
  257. * @returns {Set} Set of Segment
  258. */
  259. commonSegments(other) {
  260. assert(other instanceof Matching, other);
  261. const segments = new Set();
  262. for (let segment of this._segments) {
  263. for (let otherSegment of other._segments) {
  264. if (segment.isEquals(otherSegment)) {
  265. segments.add(segment);
  266. }
  267. }
  268. }
  269. return segments;
  270. }
  271. /**
  272. * Returns a set of segments in common between this matching
  273. * and previous or next one.
  274. *
  275. * @returns {Set} Set of Segment
  276. */
  277. commonSegmentsWithConsecutiveMatchings() {
  278. const i = this.linkedMatchingsIndex();
  279. const left = (i > 0
  280. ? this.commonSegments(this._linkedMatchings[i - 1])
  281. : new Set());
  282. const right = (i < this._linkedMatchings.length - 1
  283. ? this.commonSegments(this._linkedMatchings[i + 1])
  284. : new Set());
  285. return setUnion(left, right);
  286. }
  287. /**
  288. * Returns true iff the matching is the canonical perfect matching.
  289. *
  290. * @returns {boolean}
  291. */
  292. isCanonical() {
  293. if ((this._points.length % 2 !== 0) || (2*this._segments.length < this._points.length)) {
  294. // Odd number of points or not enough segments, impossible to be perfect
  295. return false;
  296. }
  297. const canonical = this.canonical();
  298. return this.isEquals(canonical);
  299. }
  300. /**
  301. * Returns true iff matching and other are compatible
  302. * (union have no intersection segments, without their endpoints).
  303. *
  304. * @param {Matching} other
  305. *
  306. * @returns {boolean}
  307. */
  308. isCompatible(other) {
  309. assert(other instanceof Matching, other);
  310. const segments = new Set();
  311. for (let segment of this._segments) {
  312. for (let otherSegment of other._segments) {
  313. if (!segment.isEquals(otherSegment) && segment.isProperIntersect(otherSegment)) {
  314. return false;
  315. }
  316. }
  317. }
  318. return true;
  319. }
  320. /**
  321. * Returns true iff matching and other are disjoint (no common segments).
  322. *
  323. * @param {Matching} other
  324. *
  325. * @returns {boolean}
  326. */
  327. isDisjoint(other) {
  328. assert(other instanceof Matching, other);
  329. if (this._segments === other._segments) {
  330. return false;
  331. }
  332. for (let segment of this._segments) {
  333. for (let otherSegment of other._segments) {
  334. if (segment.isEquals(otherSegment)) {
  335. return false;
  336. }
  337. }
  338. }
  339. return true;
  340. }
  341. /**
  342. * Returns true iff are equals matchings (same points and same segments).
  343. *
  344. * @param {Matching} other
  345. *
  346. * @returns {boolean}
  347. */
  348. isEquals(other) {
  349. assert(other instanceof Matching, other);
  350. if ((this._points === other._points)
  351. && (this._segments === other._segments)) {
  352. return true;
  353. }
  354. if ((this._points.length !== other._points.length)
  355. || (this._segments.length !== other._segments.length)
  356. || !arrayIsEquals(this._points, other._points,
  357. function (p, q) { return p.compare(q); })) {
  358. return false;
  359. }
  360. for (let i = 0; i < this._segments.length; ++i) {
  361. if (!this._segments[i].isEquals(other._segments[i])) {
  362. return false;
  363. }
  364. }
  365. return true;
  366. }
  367. /**
  368. * Returns a set of all points without segment.
  369. *
  370. * @returns {Set} Set of points
  371. */
  372. isolatedPoints() {
  373. const points = new Set(this._points);
  374. for (let segment of this._segments) {
  375. points.delete(segment.a);
  376. points.delete(segment.b);
  377. }
  378. return points;
  379. }
  380. /**
  381. * Returns true iff the matching is perfect (each point is degree 1)
  382. *
  383. * @returns {boolean}
  384. */
  385. isPerfect() {
  386. if ((this._points.length % 2 !== 0) || (2*this._segments.length < this._points.length)) {
  387. // Odd number of points or not enough segments, impossible to be perfect
  388. return false;
  389. }
  390. const points = new Set();
  391. for (let segment of this._segments) {
  392. if (points.has(segment.a) || points.has(segment.b)) {
  393. // Point in at least two segments
  394. return false;
  395. }
  396. points.add(segment.a);
  397. points.add(segment.b);
  398. }
  399. if (points.size !== this._points.length) {
  400. // There is at least one isolated point
  401. return false;
  402. }
  403. return true;
  404. }
  405. /**
  406. * Returns true iff all segment are vertical or horizontal.
  407. *
  408. * @returns {boolean}
  409. */
  410. isVerticalHorizontal() {
  411. for (let segment of this._segments) {
  412. if (!(segment.isVertical() || segment.isHorizontal())) {
  413. return false;
  414. }
  415. }
  416. return true;
  417. }
  418. /**
  419. * Returns the index of this matching in the sequence of linked matchings.
  420. *
  421. * @returns {integer} >= 0
  422. */
  423. linkedMatchingsIndex() {
  424. for (let i = 0; i < this._linkedMatchings.length; ++i) {
  425. if (this._linkedMatchings[i] === this) {
  426. return i;
  427. }
  428. }
  429. assert(false);
  430. }
  431. /**
  432. * Returns all informations of all linked matchings in a string :
  433. * # points
  434. * x_0, y_0
  435. * ...
  436. * x_{n-1}, y_{n-1}
  437. *
  438. * # matching
  439. *
  440. * # segment for first matching
  441. * index point a - index point b
  442. * ...
  443. *
  444. * ...
  445. *
  446. * # segment for last matching
  447. * index point a - index point b
  448. * ...
  449. *
  450. * Warning! All point coordinates are rounded to integers.
  451. *
  452. * @returns {String}
  453. */
  454. matchingsToString() {
  455. const lines = [this._points.length + " points"];
  456. for (let point of this._points) {
  457. lines.push(Math.round(point.x) + ", " + Math.round(point.y));
  458. }
  459. lines.push("");
  460. lines.push(this._linkedMatchings.length + " matchings");
  461. const indices = this.pointsIndices();
  462. for (let matching of this._linkedMatchings) {
  463. lines.push("");
  464. lines.push(matching._segments.length + " segments");
  465. for (let segment of matching._segments) {
  466. lines.push(indices[segment.a] + " - " + indices[segment.b]);
  467. }
  468. }
  469. return lines.join("\n");
  470. }
  471. /**
  472. * Returns the nearest point of the matching
  473. * or null if no nearest point.
  474. *
  475. * @param {Point} point
  476. *
  477. * @returns {Point|null}
  478. */
  479. nearestPoint(point) {
  480. assert(point instanceof Point, point);
  481. var minDistSqr = Number.POSITIVE_INFINITY;
  482. var minP = null;
  483. for (let p of this._points) {
  484. const distSqr = point.distanceSqr(p);
  485. if (minDistSqr > distSqr) {
  486. minDistSqr = distSqr;
  487. minP = p;
  488. }
  489. }
  490. return (minDistSqr < 10*10
  491. ? minP
  492. : null);
  493. }
  494. /**
  495. * Add a point.
  496. *
  497. * @param {Point} point (not already in the matching)
  498. */
  499. pointAdd(point) {
  500. assert(point instanceof Point, point);
  501. assert(!this.pointIsInMatching(point), point);
  502. sortedArrayInsert(this._points, point, function (p, q) { return p.compare(q); } );
  503. this.clearIfModifyPoints();
  504. }
  505. /**
  506. * Returns an associative table Point: (it index in the sequence of points).
  507. *
  508. * @returns {Map} Point:(integer >= 0)
  509. */
  510. pointsIndices() {
  511. const indices = {};
  512. for (let i = 0; i < this._points.length; ++i) {
  513. indices[this._points[i]] = i;
  514. }
  515. return indices;
  516. }
  517. /**
  518. * Returns true iff point is a point of the matching.
  519. *
  520. * @param {Point} point
  521. *
  522. * @returns {boolean}
  523. */
  524. pointIsInMatching(point) {
  525. assert(point instanceof Point, point);
  526. for (let p of this._points) {
  527. if (point.isEquals(p)) {
  528. return true;
  529. }
  530. }
  531. return false;
  532. }
  533. /**
  534. * Remove the existing point.
  535. *
  536. * If there is a segment with this point
  537. * then remove this segment.
  538. *
  539. * If removeInLinkedMatchings
  540. * then remove also segments in all linked matchings.
  541. *
  542. * @param {Point} point (must be in the matching)
  543. * @param {boolean} removeInLinkedMatchings
  544. */
  545. pointRemove(point, removeInLinkedMatchings=true) {
  546. assert(point instanceof Point, point);
  547. assert(this.pointIsInMatching(point), point);
  548. assert(typeof removeInLinkedMatchings === "boolean", removeInLinkedMatchings);
  549. // Remove all segment on this point
  550. for (let i = 0; i < this._segments.length; ++i) {
  551. if (this._segments[i].isExtremityPoint(point)) {
  552. this._segments.splice(i, 1);
  553. --i;
  554. }
  555. }
  556. if (removeInLinkedMatchings) {
  557. // For each other linked matchings, remove all segment on this point
  558. for (let linkedMatching of this._linkedMatchings) {
  559. if (linkedMatching !== this) {
  560. linkedMatching.pointRemove(point, false);
  561. }
  562. }
  563. }
  564. // Remove point
  565. arrayRemoveFirst(this._points, point,
  566. function (a, b) { return a.compare(b); });
  567. this.clearIfModifyPoints();
  568. }
  569. /**
  570. * Returns a set of segments that intersect (without their endpoints) one of other.
  571. *
  572. * @param {Matching} other
  573. *
  574. * @returns {Set} Set of Segment
  575. */
  576. properIntersectSegments(other) {
  577. assert(other instanceof Matching, other);
  578. const segments = new Set();
  579. for (let segment of this._segments) {
  580. for (let otherSegment of other._segments) {
  581. if (!segment.isEquals(otherSegment) && segment.isProperIntersect(otherSegment)) {
  582. segments.add(segment);
  583. }
  584. }
  585. }
  586. return segments;
  587. }
  588. /**
  589. * Returns a set of segments that intersect (without their endpoints)
  590. * one of previous or next matching.
  591. *
  592. * @returns {Set} Set of Segment
  593. */
  594. properIntersectSegmentsWithConsecutiveMatchings() {
  595. const i = this.linkedMatchingsIndex();
  596. const left = (i > 0
  597. ? this.properIntersectSegments(this._linkedMatchings[i - 1])
  598. : new Set());
  599. const right = (i < this._linkedMatchings.length - 1
  600. ? this.properIntersectSegments(this._linkedMatchings[i + 1])
  601. : new Set());
  602. return setUnion(left, right);
  603. }
  604. /**
  605. * Add a segment.
  606. *
  607. * @param {Segment} segment (not already in the matching)
  608. */
  609. segmentAdd(segment) {
  610. assert(segment instanceof Segment, segment);
  611. assert(!this.segmentIsInMatching(segment), segment);
  612. segment = segment.ordonned(); // segment with point a <= point b
  613. sortedArrayInsert(this._segments, segment, function (a, b) { return a.compare(b); } );
  614. }
  615. /**
  616. * Returns true iff segment is a segment of the matching.
  617. *
  618. * @param {Segment} segment
  619. *
  620. * @returns {boolean}
  621. */
  622. segmentIsInMatching(segment) {
  623. assert(segment instanceof Segment, segment);
  624. for (let s of this._segments) {
  625. if (segment.isEquals(s)) {
  626. return true;
  627. }
  628. }
  629. return false;
  630. }
  631. /**
  632. * Returns true iff segment intersect (*with* their endpoints) a segment of the matching.
  633. *
  634. * @param {Segment} segment
  635. *
  636. * @returns {boolean}
  637. */
  638. segmentIsIntersect(segment) {
  639. assert(segment instanceof Segment, segment);
  640. for (let s of this._segments) {
  641. if (segment.isIntersect(s)) {
  642. return true;
  643. }
  644. }
  645. return false;
  646. }
  647. /**
  648. * Remove the existing segment.
  649. *
  650. * @param {Segment} segment (must be in the matching)
  651. */
  652. segmentRemove(segment) {
  653. assert(segment instanceof Segment, segment);
  654. assert(this.segmentIsInMatching(segment), segment);
  655. arrayRemoveFirst(this._segments, segment,
  656. function (a, b) { return a.compare(b); });
  657. }
  658. /**
  659. * Set segments to be the canonical perfect matching
  660. * corresponding to these points.
  661. *
  662. * @returns {Matching}
  663. */
  664. setCanonical() {
  665. this._segments = this.canonical().segments;
  666. }
  667. /**
  668. * Calculate all possible perfect matchings
  669. * with the points of this matching
  670. * and set this list as the linked matchings.
  671. *
  672. * If disjointMatching
  673. * then keep only perfect matching disjoint with this matching.
  674. *
  675. * If compatibleMatching
  676. * then keep only perfect matching disjoint with this matching.
  677. *
  678. * @param {Boolean} disjointMatching
  679. * @param {Boolean} compatibleMatching
  680. */
  681. setPerfectMatchings(disjointMatching, compatibleMatching) {
  682. assert(typeof disjointMatching === "boolean", disjointMatching);
  683. assert(typeof compatibleMatching === "boolean", compatibleMatching);
  684. assert(this._linkedMatchings.length >= 2, this._linkedMatchings[0].length);
  685. this.clearIntermediaryLinkedMatchings();
  686. const matchings = this.allPerfectMatchings(disjointMatching, compatibleMatching);
  687. if (matchings.length === 0) {
  688. return;
  689. }
  690. const left = this._linkedMatchings[0];
  691. if (!left.isPerfect()) {
  692. // Set left matching with first perfect matching
  693. left._segments = matchings[0]._segments;
  694. }
  695. const right = this._linkedMatchings[this._linkedMatchings.length - 1];
  696. if (right.isEquals(left) || !right.isPerfect()) {
  697. // Set right matching with last perfect matching
  698. right._segments = matchings[matchings.length - 1]._segments;
  699. }
  700. if ((matchings.length > 1) && right.isEquals(left)) {
  701. // Set right matching with previous perfect matching
  702. right._segments = matchings[matchings.length - 2]._segments;
  703. }
  704. if (right.isEquals(left)) {
  705. // In the case where there is only one perfect matching,
  706. // then set right with a copy of segments.
  707. right._segments = right._segments.slice(0);
  708. }
  709. this._linkedMatchings.splice(1, 1); // remove right from the list of linked matchings
  710. // Add all perfect matchings different to left and right
  711. for (let matching of matchings) {
  712. if (!matching.isEquals(left) && !matching.isEquals(right)) {
  713. this._linkedMatchings.push(matching);
  714. }
  715. }
  716. // Add right perfect matching
  717. this._linkedMatchings.push(right);
  718. // Set all list of linked matchings
  719. for (let matching of this._linkedMatchings) {
  720. matching._linkedMatchings = this._linkedMatchings;
  721. }
  722. }
  723. /**
  724. * Set the list of linked matchings
  725. * to be a transformation (a list of perfect matchings two to two compatible)
  726. * from this matching to the last linked matching.
  727. *
  728. * If disjointTransformation
  729. * then search a disjoint transformation.
  730. *
  731. * Returns true if a transformation was found,
  732. * else return false.
  733. *
  734. * The two utmost matchings must be perfect and different.
  735. *
  736. * Warning! Only transformations of length 2 and 3 are implemented.
  737. *
  738. * FIXME! Naive algorithm that build before all perfect matchings and try possibilities.
  739. *
  740. *
  741. * @param {Boolean} disjointTransformation
  742. *
  743. * @returns {boolean}
  744. */
  745. setTransformation(disjointTransformation) {
  746. assert(typeof disjointTransformation === "boolean", disjointTransformation);
  747. assert(this._linkedMatchings.length >= 2, this._linkedMatchings[0].length);
  748. assert(this.points.length % 2 === 0, this.points.length);
  749. const matchings = this.allPerfectMatchings(false, false);
  750. assert(matchings.length >= 2, matchings.length);
  751. const left = this._linkedMatchings[0];
  752. const right = this._linkedMatchings[this._linkedMatchings.length - 1];
  753. assert(left.isPerfect(), left);
  754. assert(right.isPerfect(), right);
  755. assert(!left.isEquals(right));
  756. this.clearIntermediaryLinkedMatchings();
  757. this._linkedMatchings.splice(1, 1);
  758. // Remove left and right matchings from the matchings list
  759. for (let i = 0; i < matchings.length; ++i) {
  760. const matching = matchings[0];
  761. if (matching.isEquals(left) || matching.isEquals(right)) {
  762. matchings.splice(i, 1);
  763. }
  764. }
  765. // Try with only 1 intermediary matching
  766. for (let matching of matchings) {
  767. if ((!disjointTransformation
  768. || (matching.isDisjoint(left) && matching.isDisjoint(right)))
  769. && matching.isCompatible(left) && matching.isCompatible(right)) {
  770. this._linkedMatchings.push(matching);
  771. break;
  772. }
  773. }
  774. if (this._linkedMatchings.length === 1) {
  775. // Try with with 2 intermediary matchings
  776. var found = false;
  777. for (let matching1 of matchings) {
  778. if ((!disjointTransformation || matching1.isDisjoint(left))
  779. && matching1.isCompatible(left)) {
  780. for (let matching2 of matchings) {
  781. if ((!disjointTransformation
  782. || (matching2.isDisjoint(matching1) && matching2.isDisjoint(right)))
  783. && matching2.isCompatible(matching1) && matching2.isCompatible(right)) {
  784. this._linkedMatchings.push(matching1);
  785. this._linkedMatchings.push(matching2);
  786. found = true;
  787. break;
  788. }
  789. }
  790. if (found) {
  791. break;
  792. }
  793. }
  794. }
  795. }
  796. // Add the right matching
  797. this._linkedMatchings.push(right);
  798. // Set all list of linked matchings
  799. for (let matching of this._linkedMatchings) {
  800. matching._linkedMatchings = this._linkedMatchings;
  801. }
  802. return (this._linkedMatchings.length > 2);
  803. }
  804. /**
  805. * Set segments at random to be a perfect matching
  806. * corresponding to these points.
  807. *
  808. * @returns {Matching}
  809. */
  810. shuffleSegments() {
  811. if (this._segments.length <= 1) {
  812. return;
  813. }
  814. const maxNb = getRandomInteger(3, 5);
  815. for (let nb = 0; nb < maxNb; ++nb) {
  816. for (let i = 0; i < this._segments.length; ++i) {
  817. const j = getRandomInteger(0, this._segments.length);
  818. if (i === j) {
  819. continue;
  820. }
  821. const segmentI = this._segments[i];
  822. const segmentJ = this._segments[j];
  823. this.segmentRemove(segmentI);
  824. this.segmentRemove(segmentJ);
  825. const segmentA = new Segment(segmentI.a, segmentJ.a);
  826. const segmentB = new Segment(segmentI.b, segmentJ.b);
  827. var keep = false;
  828. if (!this.segmentIsIntersect(segmentA)) {
  829. this.segmentAdd(segmentA);
  830. if (!this.segmentIsIntersect(segmentB)) {
  831. this.segmentAdd(segmentB);
  832. keep = true;
  833. }
  834. else {
  835. this.segmentRemove(segmentA);
  836. }
  837. }
  838. if (!keep) {
  839. this.segmentAdd(segmentI);
  840. this.segmentAdd(segmentJ);
  841. }
  842. }
  843. }
  844. }
  845. }