diff --git a/src/path.js b/src/path.js index 2f9e74878f6..6b8ffe13595 100644 --- a/src/path.js +++ b/src/path.js @@ -2,7 +2,13 @@ import Attributes from "./attributes"; import Point from "./point"; import Bezier from "bezier-js"; import { round } from "./round"; -import { linesCross, curveCrossesLine, curveCrossesCurve } from "./utils"; +import { + linesCross, + curveCrossesLine, + curveCrossesCurve, + pointOnLine, + pointOnCurve +} from "./utils"; function Path() { this.render = true; @@ -655,4 +661,65 @@ function addIntersectionsToArray(candidates, intersections) { } } +/** Splits path on point, and retuns both halves */ +Path.prototype.split = function(point) { + let divided = this.divide(); + let firstHalf = false; + let secondHalf = false; + for (let pi in divided) { + let path = divided[pi]; + if (path.ops[1].type === "line") { + if (pointOnLine(path.ops[0].to, path.ops[1].to, point)) { + firstHalf = divided.slice(0, pi); + firstHalf.push(new Path().move(path.ops[0].to).line(point)); + pi++; + secondHalf = divided.slice(pi); + secondHalf.unshift(new Path().move(point).line(path.ops[1].to)); + } + } else if (path.ops[1].type === "curve") { + let t = pointOnCurve( + path.ops[0].to, + path.ops[1].cp1, + path.ops[1].cp2, + path.ops[1].to, + point + ); + if (t !== false) { + let curve = new Bezier( + { x: path.ops[0].to.x, y: path.ops[0].to.y }, + { x: path.ops[1].cp1.x, y: path.ops[1].cp1.y }, + { x: path.ops[1].cp2.x, y: path.ops[1].cp2.y }, + { x: path.ops[1].to.x, y: path.ops[1].to.y } + ); + let split = curve.split(t); + firstHalf = divided.slice(0, pi); + firstHalf.push( + new Path() + .move(new Point(split.left.points[0].x, split.left.points[0].y)) + .curve( + new Point(split.left.points[1].x, split.left.points[1].y), + new Point(split.left.points[2].x, split.left.points[2].y), + new Point(split.left.points[3].x, split.left.points[3].y) + ) + ); + pi++; + secondHalf = divided.slice(pi); + secondHalf.unshift( + new Path() + .move(new Point(split.right.points[0].x, split.right.points[0].y)) + .curve( + new Point(split.right.points[1].x, split.right.points[1].y), + new Point(split.right.points[2].x, split.right.points[2].y), + new Point(split.right.points[3].x, split.right.points[3].y) + ) + ); + } + } + } + if (firstHalf) firstHalf = joinPaths(firstHalf); + if (secondHalf) secondHalf = joinPaths(secondHalf); + + return [firstHalf, secondHalf]; +}; + export default Path; diff --git a/src/utils.js b/src/utils.js index 1fcc2d6bf9d..df54b070c3d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,4 +1,5 @@ import Point from "./point"; +import Path from "./path"; import Bezier from "bezier-js"; import { round } from "./round"; @@ -51,6 +52,43 @@ export function linesCross(a1, a2, b1, b2) { else return false; } +/** Finds out whether a point lies on an endless line */ +export function pointOnBeam(from, to, check) { + if (from.sitsOn(check)) return true; + if (to.sitsOn(check)) return true; + let angleA = from.angle(to); + let angleB = from.angle(check); + if (angleA === angleB || Math.abs(angleA - angleB) === 180) return true; + else return false; +} + +/** Finds out whether a point lies on a line segment */ +export function pointOnLine(from, to, check) { + if (!pointOnBeam(from, to, check)) return false; + let lenA = from.dist(to); + let lenB = from.dist(check) + check.dist(to); + if (Math.round(lenA) == Math.round(lenB)) return true; + else return false; +} + +/** Finds out whether a point lies on a curve */ +export function pointOnCurve(start, cp1, cp2, end, check) { + if (start.sitsOn(check)) return true; + if (end.sitsOn(check)) return true; + let curve = new Bezier( + { x: start.x, y: start.y }, + { x: cp1.x, y: cp1.y }, + { x: cp2.x, y: cp2.y }, + { x: end.x, y: end.y } + ); + let intersections = curve.intersects({ + p1: { x: check.x - 1, y: check.y }, + p2: { x: check.x + 1, y: check.y } + }); + if (intersections.length > 0) return intersections.shift(); + else return false; +} + /** Find where an (endless) line crosses a certain X-value */ export function beamCrossesX(from, to, x) { if (from.x === to.x) return false; // Vertical line diff --git a/tests/path.test.js b/tests/path.test.js index 9ca1d894f83..14dd6105917 100644 --- a/tests/path.test.js +++ b/tests/path.test.js @@ -562,6 +562,62 @@ it("Should divide a path", () => { expect(divided[3].ops[1].to.y).to.equal(60); }); +it("Should split a path on a curve", () => { + let pattern = new freesewing.Pattern(); + pattern.parts.a = new pattern.Part(); + let a = pattern.parts.a; + a.points.A = new a.Point(45, 60); + a.points.B = new a.Point(10, 30); + a.points.BCp2 = new a.Point(40, 20); + a.points.C = new a.Point(90, 30); + a.points.CCp1 = new a.Point(50, -30); + a.points.D = new a.Point(50, 130); + a.points.DCp1 = new a.Point(150, 30); + + a.paths.test = new a.Path() + .move(a.points.A) + .line(a.points.B) + .curve(a.points.BCp2, a.points.CCp1, a.points.C) + .curve(a.points.DCp1, a.points.DCp1, a.points.D); + + a.points.split = a.paths.test.shiftAlong(120); + let halves = a.paths.test.split(a.points.split); + let curve = halves[0].ops.pop(); + expect(curve.type).to.equal("curve"); + expect(curve.cp1.x).to.equal(35.2); + expect(curve.cp1.y).to.equal(21.6); + expect(curve.cp2.x).to.equal(46.29); + expect(curve.cp2.y).to.equal(-15.02); + expect(curve.to.x).to.equal(72.9); + expect(curve.to.y).to.equal(9.03); +}); + +it("Should split a path on a line", () => { + let pattern = new freesewing.Pattern(); + pattern.parts.a = new pattern.Part(); + let a = pattern.parts.a; + a.points.A = new a.Point(45, 60); + a.points.B = new a.Point(10, 30); + a.points.BCp2 = new a.Point(40, 20); + a.points.C = new a.Point(90, 30); + a.points.CCp1 = new a.Point(50, -30); + a.points.D = new a.Point(50, 130); + a.points.DCp1 = new a.Point(150, 30); + + a.paths.test = new a.Path() + .move(a.points.A) + .line(a.points.B) + .curve(a.points.BCp2, a.points.CCp1, a.points.C) + .curve(a.points.DCp1, a.points.DCp1, a.points.D); + + a.points.split = a.paths.test.shiftAlong(20); + let halves = a.paths.test.split(a.points.split); + let line = halves[0].ops.pop(); + expect(line.type).to.equal("line"); + expect(line.to.x).to.equal(29.81); + expect(line.to.y).to.equal(46.98); +}); + function round(value) { return Math.round(value * 1e2) / 1e2; } diff --git a/tests/utils.test.js b/tests/utils.test.js index e826e1469c5..8936fa67c58 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -187,3 +187,32 @@ 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"'); }); + +it("Should find a start or end point on beam", () => { + let A = new freesewing.Point(12, 34); + let B = new freesewing.Point(56, 78); + let checkA = new freesewing.Point(12, 34); + let checkB = new freesewing.Point(56, 78); + expect(freesewing.utils.pointOnBeam(A, B, checkA)).to.equal(true); + expect(freesewing.utils.pointOnBeam(A, B, checkB)).to.equal(true); +}); + +it("Should find whether a point lies on a line segment", () => { + let A = new freesewing.Point(12, 34); + let B = new freesewing.Point(56, 78); + let check1 = A.shiftTowards(B, 10); + let check2 = A.shiftTowards(B, 210); + expect(freesewing.utils.pointOnLine(A, B, check1)).to.equal(true); + expect(freesewing.utils.pointOnLine(A, B, check2)).to.equal(false); +}); + +it("Should find a start or end point on curve", () => { + let A = new freesewing.Point(12, 34); + let Acp = new freesewing.Point(123, 4); + let B = new freesewing.Point(56, 78); + let Bcp = new freesewing.Point(5, 678); + let checkA = new freesewing.Point(12, 34); + let checkB = new freesewing.Point(56, 78); + expect(freesewing.utils.pointOnCurve(A, Acp, Bcp, B, checkA)).to.equal(true); + expect(freesewing.utils.pointOnCurve(A, Acp, Bcp, B, checkB)).to.equal(true); +});