diff --git a/src/point.js b/src/point.js index 20504f7ff9d..372f63843e8 100644 --- a/src/point.js +++ b/src/point.js @@ -100,6 +100,16 @@ Point.prototype.sitsOn = function(that) { else return false; }; +/** Checks whether this has roughly the same coordinates as that */ +Point.prototype.sitsRoughlyOn = function(that) { + if ( + Math.round(this.x) === Math.round(that.x) && + Math.round(this.y) === Math.round(that.y) + ) + return true; + else return false; +}; + /** Shifts this point fraction of the distance towards that point */ Point.prototype.shiftFractionTowards = function(that, fraction) { return this.shiftTowards(that, this.dist(that) * fraction); diff --git a/src/utils.js b/src/utils.js index 77d73809359..afe63292cde 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,4 +1,5 @@ import Point from "./point"; +import Bezier from "bezier-js"; import { round } from "./round"; /** Returns internal hook name for a macro */ @@ -69,3 +70,72 @@ export function units(value, to = "metric") { if (to === "imperial") return round(value / 25.4) + '"'; else return round(value / 10) + "cm"; } + +/** Find where a curve crosses a line */ +export function curveCrossesLine(from, cp1, cp2, to, start, end) { + let intersections = []; + let bz = new Bezier( + { x: from.x, y: from.y }, + { x: cp1.x, y: cp1.y }, + { x: cp2.x, y: cp2.y }, + { x: to.x, y: to.y } + ); + let line = { + p1: { x: start.x, y: start.y }, + p2: { x: end.x, y: end.y } + }; + for (let t of bz.intersects(line)) { + let isect = bz.get(t); + intersections.push(new Point(isect.x, isect.y)); + } + + if (intersections.length === 0) return false; + else if (intersections.length === 1) return intersections[0]; + else return intersections; +} + +/** Find where a curve crosses another curve */ +export function curveCrossesCurve( + fromA, + cp1A, + cp2A, + toA, + fromB, + cp1B, + cp2B, + toB +) { + let precision = 0.005; // See https://github.com/Pomax/bezierjs/issues/99 + let intersections = []; + let curveA = new Bezier( + { x: fromA.x, y: fromA.y }, + { x: cp1A.x, y: cp1A.y }, + { x: cp2A.x, y: cp2A.y }, + { x: toA.x, y: toA.y } + ); + let curveB = new Bezier( + { x: fromB.x, y: fromB.y }, + { x: cp1B.x, y: cp1B.y }, + { x: cp2B.x, y: cp2B.y }, + { x: toB.x, y: toB.y } + ); + + for (let tvalues of curveA.intersects(curveB, precision)) { + let intersection = curveA.get(tvalues.substr(0, tvalues.indexOf("/"))); + intersections.push(new Point(intersection.x, intersection.y)); + } + + if (intersections.length === 0) return false; + else if (intersections.length === 1) return intersections.shift(); + else { + let unique = []; + for (let i of intersections) { + let dupe = false; + for (let u of unique) { + if (i.sitsRoughlyOn(u)) dupe = true; + } + if (!dupe) unique.push(i); + } + return unique; + } +} diff --git a/tests/utils.test.js b/tests/utils.test.js index 51abbf48340..bdd295f690b 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -93,6 +93,86 @@ it("Should detect horizontal lines never pass a give Y-value", () => { expect(freesewing.utils.beamCrossesY(a, b, 69)).to.equal(false); }); +it("Should find no intersections between a curve and a line", () => { + let A = new freesewing.Point(10, 10); + let Acp = new freesewing.Point(310, 40); + let B = new freesewing.Point(110, 70); + let Bcp = new freesewing.Point(-210, 40); + let E = new freesewing.Point(-20, -20); + let D = new freesewing.Point(30, -85); + + let hit = freesewing.utils.curveCrossesLine(A, Acp, Bcp, B, E, D); + expect(hit).to.equal(false); +}); + +it("Should find 1 intersection between a curve and a line", () => { + let A = new freesewing.Point(10, 10); + let Acp = new freesewing.Point(310, 40); + let B = new freesewing.Point(110, 70); + let Bcp = new freesewing.Point(-210, 40); + let E = new freesewing.Point(20, 20); + let D = new freesewing.Point(30, -85); + + let hit = freesewing.utils.curveCrossesLine(A, Acp, Bcp, B, E, D); + expect(hit.x).to.equal(20.85); + expect(hit.y).to.equal(11.11); +}); + +it("Should find 3 intersections between a curve and a line", () => { + let A = new freesewing.Point(10, 10); + let Acp = new freesewing.Point(310, 40); + let B = new freesewing.Point(110, 70); + let Bcp = new freesewing.Point(-210, 40); + let E = new freesewing.Point(20, -5); + let D = new freesewing.Point(100, 85); + + let hits = freesewing.utils.curveCrossesLine(A, Acp, Bcp, B, E, D); + expect(hits.length).to.equal(3); +}); + +it("Should find 9 intersections between two curves", () => { + let A = new freesewing.Point(10, 10); + let Acp = new freesewing.Point(310, 40); + let B = new freesewing.Point(110, 70); + let Bcp = new freesewing.Point(-210, 40); + let C = new freesewing.Point(20, -5); + let Ccp = new freesewing.Point(60, 300); + let D = new freesewing.Point(100, 85); + let Dcp = new freesewing.Point(70, -220); + + let hits = freesewing.utils.curveCrossesCurve(A, Acp, Bcp, B, C, Ccp, Dcp, D); + expect(hits.length).to.equal(9); +}); + +it("Should find 1 intersection between two curves", () => { + let A = new freesewing.Point(10, 10); + let Acp = new freesewing.Point(310, 40); + let B = new freesewing.Point(110, 70); + let Bcp = new freesewing.Point(-210, 40); + let C = new freesewing.Point(20, -5); + let Ccp = new freesewing.Point(-60, 300); + let D = new freesewing.Point(-200, 85); + let Dcp = new freesewing.Point(-270, -220); + + let hit = freesewing.utils.curveCrossesCurve(A, Acp, Bcp, B, C, Ccp, Dcp, D); + expect(hit.x).to.equal(15.58); + expect(hit.y).to.equal(10.56); +}); + +it("Should find no intersection between two curves", () => { + let A = new freesewing.Point(10, 10); + let Acp = new freesewing.Point(310, 40); + let B = new freesewing.Point(110, 70); + let Bcp = new freesewing.Point(-210, 40); + let C = new freesewing.Point(20, -5); + let Ccp = new freesewing.Point(-60, -300); + let D = new freesewing.Point(-200, 85); + let Dcp = new freesewing.Point(-270, -220); + + let hit = freesewing.utils.curveCrossesCurve(A, Acp, Bcp, B, C, Ccp, Dcp, D); + expect(hit).to.equal(false); +}); + it("Should correctly format units", () => { expect(freesewing.utils.units(123.456)).to.equal("12.35cm"); expect(freesewing.utils.units(123.456, "imperial")).to.equal('4.86"');