1
0
Fork 0

Added path intersection methods

This commit is contained in:
Joost De Cock 2018-08-21 16:30:51 +02:00
parent 4553f21843
commit 273a4a1d7b
3 changed files with 322 additions and 7 deletions

View file

@ -2,6 +2,7 @@ import Attributes from "./attributes";
import Point from "./point";
import Bezier from "bezier-js";
import { round } from "./round";
import { linesCross, curveCrossesLine, curveCrossesCurve } from "./utils";
function Path() {
this.render = true;
@ -450,7 +451,7 @@ Path.prototype.reverse = function() {
return rev;
};
/** Returns a reversed version of this */
/** Returns the point at an edge of this path */
Path.prototype.edge = function(side) {
this.boundary();
if (side === "topLeft") return this.topLeft;
@ -514,10 +515,144 @@ function edgeCurveAsBezier(op) {
{ x: op.to.x, y: op.to.y }
);
}
///* Returns the edge of a single path operation */
//function opEdge(op, side) {
// if(op.type === 'move' || op.type
//
//}
/** Divides a path into atomic paths */
Path.prototype.divide = function() {
let paths = [];
let current, start;
for (let i in this.ops) {
let op = this.ops[i];
if (op.type === "move") {
current = op.to;
start = op.to;
} else if (op.type === "line") {
paths.push(new Path().move(current).line(op.to));
} else if (op.type === "curve") {
paths.push(new Path().move(current).curve(op.cp1, op.cp2, op.to));
} else if (op.type === "close") {
paths.push(new Path().move(current).line(start));
}
if (op.to) current = op.to;
}
return paths;
};
/** Finds intersections between this path and an X value */
Path.prototype.crossesX = function(x) {
return this.crossesAxis(x, "x");
};
/** Finds intersections between this path and an Y value */
Path.prototype.crossesY = function(y) {
return this.crossesAxis(y, "y");
};
/** Finds intersections between this path and a X or Y value */
Path.prototype.crossesAxis = function(val = false, mode) {
if (val === false) throw "Path.crosses[X-Y] requires an value as parameter";
let intersections = [];
let lineStart =
mode === "x" ? new Point(val, -100000) : new Point(-10000, val);
let lineEnd = mode === "x" ? new Point(val, 100000) : new Point(100000, val);
for (let path of this.divide()) {
if (path.ops[1].type === "line") {
addIntersectionsToArray(
linesCross(path.ops[0].to, path.ops[1].to, lineStart, lineEnd),
intersections
);
} else if (path.ops[1].type === "curve") {
addIntersectionsToArray(
curveCrossesLine(
path.ops[0].to,
path.ops[1].cp1,
path.ops[1].cp2,
path.ops[1].to,
lineStart,
lineEnd
),
intersections
);
}
}
return intersections;
};
/** Finds intersections between this path and another path */
Path.prototype.intersects = function(path) {
if (this === path)
throw "Calculating intersections between two identical paths is bad idea";
let intersections = [];
for (let pathA of this.divide()) {
for (let pathB of path.divide()) {
if (pathA.ops[1].type === "line") {
if (pathB.ops[1].type === "line") {
addIntersectionsToArray(
linesCross(
pathA.ops[0].to,
pathA.ops[1].to,
pathB.ops[0].to,
pathB.ops[1].to
),
intersections
);
} else if (pathB.ops[1].type === "curve") {
addIntersectionsToArray(
curveCrossesLine(
pathB.ops[0].to,
pathB.ops[1].cp1,
pathB.ops[1].cp2,
pathB.ops[1].to,
pathA.ops[0].to,
pathA.ops[1].to
),
intersections
);
}
} else if (pathA.ops[1].type === "curve") {
if (pathB.ops[1].type === "line") {
addIntersectionsToArray(
curveCrossesLine(
pathA.ops[0].to,
pathA.ops[1].cp1,
pathA.ops[1].cp2,
pathA.ops[1].to,
pathB.ops[0].to,
pathB.ops[1].to
),
intersections
);
} else if (pathB.ops[1].type === "curve") {
addIntersectionsToArray(
curveCrossesCurve(
pathA.ops[0].to,
pathA.ops[1].cp1,
pathA.ops[1].cp2,
pathA.ops[1].to,
pathB.ops[0].to,
pathB.ops[1].cp1,
pathB.ops[1].cp2,
pathB.ops[1].to
),
intersections
);
}
}
}
}
return intersections;
};
function addIntersectionsToArray(candidates, intersections) {
if (!candidates) return;
if (typeof candidates === "object") {
if (typeof candidates.x === "number") intersections.push(candidates);
else {
for (let candidate of candidates) intersections.push(candidate);
}
}
}
export default Path;

View file

@ -392,6 +392,176 @@ it("Should find the edges of a path for corner cases", () => {
expect(round(a.paths.test.edge("right").y)).to.equal(60);
});
it("Should find where a path crosses an X value", () => {
let pattern = new freesewing.Pattern();
pattern.parts.a = new pattern.Part();
let a = pattern.parts.a;
a.points.A = new a.Point(95, 50);
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)
.close();
let intersections = a.paths.test.crossesX(60);
expect(intersections.length).to.equal(4);
expect(intersections[0].x).to.equal(60);
expect(intersections[0].y).to.equal(41.76);
expect(intersections[1].x).to.equal(60);
expect(intersections[1].y).to.equal(1.45);
expect(intersections[2].x).to.equal(60);
expect(intersections[2].y).to.equal(120);
expect(intersections[3].x).to.equal(60);
expect(intersections[3].y).to.equal(112.22);
});
it("Should find where a path crosses an Y value", () => {
let pattern = new freesewing.Pattern();
pattern.parts.a = new pattern.Part();
let a = pattern.parts.a;
a.points.A = new a.Point(95, 50);
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)
.close();
let intersections = a.paths.test.crossesY(60);
expect(intersections.length).to.equal(2);
expect(intersections[0].x).to.equal(117.83);
expect(intersections[0].y).to.equal(60);
expect(intersections[1].x).to.equal(89.38);
expect(intersections[1].y).to.equal(60);
});
it("Should throw an error when not passing a value to path.crossesX", () => {
let pattern = new freesewing.Pattern();
pattern.parts.a = new pattern.Part();
let a = pattern.parts.a;
a.paths.test = new a.Path();
expect(() => a.paths.test.crossesX()).to.throw();
expect(() => a.paths.test.crossesY()).to.throw();
});
it("Should find the intersections between two paths", () => {
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.points._A = new a.Point(55, 40);
a.points._B = new a.Point(0, 55);
a.points._BCp2 = new a.Point(40, -20);
a.points._C = new a.Point(90, 40);
a.points._CCp1 = new a.Point(50, -30);
a.points._D = new a.Point(40, 120);
a.points._DCp1 = new a.Point(180, 40);
a.paths.example1 = 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.paths.example2 = 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);
let intersections = a.paths.example1.intersects(a.paths.example2);
expect(intersections.length).to.equal(6);
expect(intersections[0].x).to.equal(29.71);
expect(intersections[0].y).to.equal(46.9);
expect(intersections[1].x).to.equal(12.48);
expect(intersections[1].y).to.equal(32.12);
expect(intersections[2].x).to.equal(14.84);
expect(intersections[2].y).to.equal(27.98);
expect(intersections[3].x).to.equal(66.33);
expect(intersections[3].y).to.equal(4.1);
expect(intersections[4].x).to.equal(130.65);
expect(intersections[4].y).to.equal(40.52);
expect(intersections[5].x).to.equal(86.52);
expect(intersections[5].y).to.equal(93.31);
});
it("Should throw an error when running path.intersect on an identical path", () => {
let pattern = new freesewing.Pattern();
pattern.parts.a = new pattern.Part();
let a = pattern.parts.a;
a.paths.test = new a.Path();
expect(() => a.paths.test.intersects(a.paths.test)).to.throw();
});
it("Should divide a path", () => {
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(-60, 90);
a.points.E = new a.Point(90, 190);
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.E, a.points.D, a.points.A)
.close();
let divided = a.paths.test.divide();
expect(divided.length).to.equal(4);
expect(divided[0].ops[0].type).to.equal("move");
expect(divided[0].ops[0].to.x).to.equal(45);
expect(divided[0].ops[0].to.y).to.equal(60);
expect(divided[0].ops[1].type).to.equal("line");
expect(divided[0].ops[1].to.x).to.equal(10);
expect(divided[0].ops[1].to.y).to.equal(30);
expect(divided[1].ops[0].type).to.equal("move");
expect(divided[1].ops[0].to.x).to.equal(10);
expect(divided[1].ops[0].to.y).to.equal(30);
expect(divided[1].ops[1].type).to.equal("curve");
expect(divided[1].ops[1].cp1.x).to.equal(40);
expect(divided[1].ops[1].cp1.y).to.equal(20);
expect(divided[1].ops[1].cp2.x).to.equal(50);
expect(divided[1].ops[1].cp2.y).to.equal(-30);
expect(divided[1].ops[1].to.x).to.equal(90);
expect(divided[1].ops[1].to.y).to.equal(30);
expect(divided[2].ops[0].type).to.equal("move");
expect(divided[2].ops[0].to.x).to.equal(90);
expect(divided[2].ops[0].to.y).to.equal(30);
expect(divided[2].ops[1].type).to.equal("curve");
expect(divided[2].ops[1].cp1.x).to.equal(90);
expect(divided[2].ops[1].cp1.y).to.equal(190);
expect(divided[2].ops[1].cp2.x).to.equal(-60);
expect(divided[2].ops[1].cp2.y).to.equal(90);
expect(divided[2].ops[1].to.x).to.equal(45);
expect(divided[2].ops[1].to.y).to.equal(60);
expect(divided[3].ops[0].type).to.equal("move");
expect(divided[3].ops[0].to.x).to.equal(45);
expect(divided[3].ops[0].to.y).to.equal(60);
expect(divided[3].ops[1].type).to.equal("line");
expect(divided[3].ops[1].to.x).to.equal(45);
expect(divided[3].ops[1].to.y).to.equal(60);
});
function round(value) {
return Math.round(value * 1e2) / 1e2;
}

View file

@ -60,11 +60,21 @@ it("Should find the intersection of two line segments", () => {
let b = new freesewing.Point(90, 74);
let c = new freesewing.Point(90, 19);
let d = new freesewing.Point(11, 70);
let X = freesewing.utils.beamsCross(a, b, c, d);
let X = freesewing.utils.linesCross(a, b, c, d);
expect(X.x).to.equal(51.95);
expect(X.y).to.equal(43.56);
});
it("Should find the intersection of two line segments - round() edge case", () => {
let a = new freesewing.Point(45, 60);
let b = new freesewing.Point(10, 30);
let c = new freesewing.Point(55, 40);
let d = new freesewing.Point(0, 55);
let X = freesewing.utils.linesCross(a, b, c, d);
expect(X.x).to.equal(29.71);
expect(X.y).to.equal(46.9);
});
it("Should find the intersection of an endles line and a give X-value", () => {
let a = new freesewing.Point(10, 10);
let b = new freesewing.Point(90, 74);