diff --git a/dist/main.cjs b/dist/main.cjs
index b9be8df..741c310 100644
--- a/dist/main.cjs
+++ b/dist/main.cjs
@@ -399,6 +399,271 @@ class LinkedList {
}
}
+const defaultAttributes = {
+ stroke: "black"
+};
+
+class SVGAttributes {
+ constructor(args = defaultAttributes) {
+ for(const property in args) {
+ this[property] = args[property];
+ }
+ this.stroke = args.stroke ?? defaultAttributes.stroke;
+ }
+
+ toAttributesString() {
+ return Object.keys(this)
+ .reduce( (acc, key) =>
+ acc + (this[key] !== undefined ? this.toAttrString(key, this[key]) : "")
+ , ``)
+ }
+
+ toAttrString(key, value) {
+ const SVGKey = key === "className" ? "class" : this.convertCamelToKebabCase(key);
+ return value === null ? `${SVGKey} ` : `${SVGKey}="${value.toString()}" `
+ }
+
+ convertCamelToKebabCase(str) {
+ return str
+ .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
+ .join('-')
+ .toLowerCase();
+ }
+}
+
+function convertToString(attrs) {
+ return new SVGAttributes(attrs).toAttributesString()
+}
+
+/**
+ * Class Multiline represent connected path of [edges]{@link Flatten.Edge}, where each edge may be
+ * [segment]{@link Flatten.Segment}, [arc]{@link Flatten.Arc}, [line]{@link Flatten.Line} or [ray]{@link Flatten.Ray}
+ */
+class Multiline extends LinkedList {
+ constructor(...args) {
+ super();
+
+ if (args.length === 0) {
+ return;
+ }
+
+ if (args.length === 1) {
+ if (args[0] instanceof Array) {
+ let shapes = args[0];
+ if (shapes.length === 0)
+ return;
+
+ // TODO: more strict validation:
+ // there may be only one line
+ // only first and last may be rays
+ shapes.every((shape) => {
+ return shape instanceof Flatten.Segment ||
+ shape instanceof Flatten.Arc ||
+ shape instanceof Flatten.Ray ||
+ shape instanceof Flatten.Line
+ });
+
+ for (let shape of shapes) {
+ let edge = new Flatten.Edge(shape);
+ this.append(edge);
+ }
+
+ this.setArcLength();
+ }
+ }
+ }
+
+ /**
+ * (Getter) Return array of edges
+ * @returns {Edge[]}
+ */
+ get edges() {
+ return [...this];
+ }
+
+ /**
+ * (Getter) Return bounding box of the multiline
+ * @returns {Box}
+ */
+ get box() {
+ return this.edges.reduce( (acc,edge) => acc.merge(edge.box), new Flatten.Box() );
+ }
+
+ /**
+ * (Getter) Returns array of vertices
+ * @returns {Point[]}
+ */
+ get vertices() {
+ let v = this.edges.map(edge => edge.start);
+ v.push(this.last.end);
+ return v;
+ }
+
+ /**
+ * Return new cloned instance of Multiline
+ * @returns {Multiline}
+ */
+ clone() {
+ return new Multiline(this.toShapes());
+ }
+
+ /**
+ * Set arc_length property for each of the edges in the face.
+ * Arc_length of the edge it the arc length from the first edge of the face
+ */
+ setArcLength() {
+ for (let edge of this) {
+ this.setOneEdgeArcLength(edge);
+ }
+ }
+
+ setOneEdgeArcLength(edge) {
+ if (edge === this.first) {
+ edge.arc_length = 0.0;
+ } else {
+ edge.arc_length = edge.prev.arc_length + edge.prev.length;
+ }
+ }
+
+ /**
+ * Split edge and add new vertex, return new edge inserted
+ * @param {Point} pt - point on edge that will be added as new vertex
+ * @param {Edge} edge - edge to split
+ * @returns {Edge}
+ */
+ addVertex(pt, edge) {
+ let shapes = edge.shape.split(pt);
+ // if (shapes.length < 2) return;
+
+ if (shapes[0] === null) // point incident to edge start vertex, return previous edge
+ return edge.prev;
+
+ if (shapes[1] === null) // point incident to edge end vertex, return edge itself
+ return edge;
+
+ let newEdge = new Flatten.Edge(shapes[0]);
+ let edgeBefore = edge.prev;
+
+ /* Insert first split edge into linked list after edgeBefore */
+ this.insert(newEdge, edgeBefore); // edge.face ?
+
+ // Update edge shape with second split edge keeping links
+ edge.shape = shapes[1];
+
+ return newEdge;
+ }
+
+ getChain(edgeFrom, edgeTo) {
+ let edges = [];
+ for (let edge = edgeFrom; edge !== edgeTo.next; edge = edge.next) {
+ edges.push(edge);
+ }
+ return edges
+ }
+
+ /**
+ * Split edges of multiline with intersection points and return mutated multiline
+ * @param {Point[]} ip - array of points to be added as new vertices
+ * @returns {Multiline}
+ */
+ split(ip) {
+ for (let pt of ip) {
+ let edge = this.findEdgeByPoint(pt);
+ this.addVertex(pt, edge);
+ }
+ return this;
+ }
+
+ /**
+ * Returns edge which contains given point
+ * @param {Point} pt
+ * @returns {Edge}
+ */
+ findEdgeByPoint(pt) {
+ let edgeFound;
+ for (let edge of this) {
+ if (edge.shape.contains(pt)) {
+ edgeFound = edge;
+ break;
+ }
+ }
+ return edgeFound;
+ }
+
+ /**
+ * Returns new multiline translated by vector vec
+ * @param {Vector} vec
+ * @returns {Multiline}
+ */
+ translate(vec) {
+ return new Multiline(this.edges.map( edge => edge.shape.translate(vec)));
+ }
+
+ /**
+ * Return new multiline rotated by given angle around given point
+ * If point omitted, rotate around origin (0,0)
+ * Positive value of angle defines rotation counterclockwise, negative - clockwise
+ * @param {number} angle - rotation angle in radians
+ * @param {Point} center - rotation center, default is (0,0)
+ * @returns {Multiline} - new rotated polygon
+ */
+ rotate(angle = 0, center = new Flatten.Point()) {
+ return new Multiline(this.edges.map( edge => edge.shape.rotate(angle, center) ));
+ }
+
+ /**
+ * Return new multiline transformed using affine transformation matrix
+ * Method does not support unbounded shapes
+ * @param {Matrix} matrix - affine transformation matrix
+ * @returns {Multiline} - new multiline
+ */
+ transform(matrix = new Flatten.Matrix()) {
+ return new Multiline(this.edges.map( edge => edge.shape.transform(matrix)));
+ }
+
+ /**
+ * Transform multiline into array of shapes
+ * @returns {Shape[]}
+ */
+ toShapes() {
+ return this.edges.map(edge => edge.shape.clone())
+ }
+
+ /**
+ * This method returns an object that defines how data will be
+ * serialized when called JSON.stringify() method
+ * @returns {Object}
+ */
+ toJSON() {
+ return this.edges.map(edge => edge.toJSON());
+ }
+
+ /**
+ * Return string to draw multiline in svg
+ * @param attrs - an object with attributes for svg path element
+ * TODO: support semi-infinite Ray and infinite Line
+ * @returns {string}
+ */
+ svg(attrs = {}) {
+ let svgStr = `\n\n`;
+ return svgStr;
+ }
+}
+
+Flatten.Multiline = Multiline;
+
+/**
+ * Shortcut function to create multiline
+ * @param args
+ */
+const multiline = (...args) => new Flatten.Multiline(...args);
+Flatten.multiline = multiline;
+
/*
Smart intersections describe intersection points that refers to the edges they intersect
This function are supposed for internal usage by morphing and relation methods between
@@ -454,11 +719,7 @@ function addToIntPoints(edge, pt, int_points)
function sortIntersections(intersections)
{
- // if (intersections.int_points1.length === 0) return;
-
// augment intersections with new sorted arrays
- // intersections.int_points1_sorted = intersections.int_points1.slice().sort(compareFn);
- // intersections.int_points2_sorted = intersections.int_points2.slice().sort(compareFn);
intersections.int_points1_sorted = getSortedArray(intersections.int_points1);
intersections.int_points2_sorted = getSortedArray(intersections.int_points2);
}
@@ -502,18 +763,6 @@ function compareFn(ip1, ip2)
return 0;
}
-// export function getSortedArrayOnLine(line, int_points) {
-// return int_points.slice().sort( (int_point1, int_point2) => {
-// if (line.coord(int_point1.pt) < line.coord(int_point2.pt)) {
-// return -1;
-// }
-// if (line.coord(int_point1.pt) > line.coord(int_point2.pt)) {
-// return 1;
-// }
-// return 0;
-// })
-// }
-
function filterDuplicatedIntersections(intersections)
{
if (intersections.int_points1.length < 2) return;
@@ -745,14 +994,10 @@ function splitByIntersections(polygon, int_points)
int_point.is_vertex |= END_VERTEX$1;
}
- if (int_point.is_vertex & START_VERTEX$1) { // nothing to split
+ if (int_point.is_vertex & START_VERTEX$1) { // nothing to split
+ int_point.edge_before = edge.prev;
if (edge.prev) {
- int_point.edge_before = edge.prev; // polygon
- int_point.is_vertex = END_VERTEX$1;
- }
- else { // multiline start vertex
- int_point.edge_after = int_point.edge_before;
- int_point.edge_before = edge.prev;
+ int_point.is_vertex = END_VERTEX$1; // polygon
}
continue;
}
@@ -768,6 +1013,11 @@ function splitByIntersections(polygon, int_points)
if (int_point.edge_before) {
int_point.edge_after = int_point.edge_before.next;
}
+ else {
+ if (polygon instanceof Multiline && int_point.is_vertex & START_VERTEX$1) {
+ int_point.edge_after = polygon.first;
+ }
+ }
}
}
@@ -2026,547 +2276,282 @@ function intersectCircle2Circle(circle1, circle2) {
return ip;
}
-
-function intersectCircle2Box(circle, box) {
- let ips = [];
- for (let seg of box.toSegments()) {
- let ips_tmp = intersectSegment2Circle(seg, circle);
- for (let ip of ips_tmp) {
- ips.push(ip);
- }
- }
- return ips;
-}
-
-function intersectArc2Arc(arc1, arc2) {
- let ip = [];
-
- if (arc1.box.not_intersect(arc2.box)) {
- return ip;
- }
-
- // Special case: overlapping arcs
- // May return up to 4 intersection points
- if (arc1.pc.equalTo(arc2.pc) && Flatten.Utils.EQ(arc1.r, arc2.r)) {
- let pt;
-
- pt = arc1.start;
- if (pt.on(arc2))
- ip.push(pt);
-
- pt = arc1.end;
- if (pt.on(arc2))
- ip.push(pt);
-
- pt = arc2.start;
- if (pt.on(arc1)) ip.push(pt);
-
- pt = arc2.end;
- if (pt.on(arc1)) ip.push(pt);
-
- return ip;
- }
-
- // Common case
- let circle1 = new Flatten.Circle(arc1.pc, arc1.r);
- let circle2 = new Flatten.Circle(arc2.pc, arc2.r);
- let ip_tmp = circle1.intersect(circle2);
- for (let pt of ip_tmp) {
- if (pt.on(arc1) && pt.on(arc2)) {
- ip.push(pt);
- }
- }
- return ip;
-}
-
-function intersectArc2Circle(arc, circle) {
- let ip = [];
-
- if (arc.box.not_intersect(circle.box)) {
- return ip;
- }
-
- // Case when arc center incident to circle center
- // Return arc's end points as 2 intersection points
- if (circle.pc.equalTo(arc.pc) && Flatten.Utils.EQ(circle.r, arc.r)) {
- ip.push(arc.start);
- ip.push(arc.end);
- return ip;
- }
-
- // Common case
- let circle1 = circle;
- let circle2 = new Flatten.Circle(arc.pc, arc.r);
- let ip_tmp = intersectCircle2Circle(circle1, circle2);
- for (let pt of ip_tmp) {
- if (pt.on(arc)) {
- ip.push(pt);
- }
- }
- return ip;
-}
-
-function intersectArc2Box(arc, box) {
- let ips = [];
- for (let seg of box.toSegments()) {
- let ips_tmp = intersectSegment2Arc(seg, arc);
- for (let ip of ips_tmp) {
- ips.push(ip);
- }
- }
- return ips;
-}
-
-function intersectEdge2Segment(edge, segment) {
- return edge.isSegment ? intersectSegment2Segment(edge.shape, segment) : intersectSegment2Arc(segment, edge.shape);
-}
-
-function intersectEdge2Arc(edge, arc) {
- return edge.isSegment ? intersectSegment2Arc(edge.shape, arc) : intersectArc2Arc(edge.shape, arc);
-}
-
-function intersectEdge2Line(edge, line) {
- return edge.isSegment ? intersectSegment2Line(edge.shape, line) : intersectLine2Arc(line, edge.shape);
-}
-
-function intersectEdge2Ray(edge, ray) {
- return edge.isSegment ? intersectRay2Segment(ray, edge.shape) : intersectRay2Arc(ray, edge.shape);
-}
-
-function intersectEdge2Circle(edge, circle) {
- return edge.isSegment ? intersectSegment2Circle(edge.shape, circle) : intersectArc2Circle(edge.shape, circle);
-}
-
-function intersectSegment2Polygon(segment, polygon) {
- let ip = [];
-
- for (let edge of polygon.edges) {
- for (let pt of intersectEdge2Segment(edge, segment)) {
- ip.push(pt);
- }
- }
-
- return ip;
-}
-
-function intersectArc2Polygon(arc, polygon) {
- let ip = [];
-
- for (let edge of polygon.edges) {
- for (let pt of intersectEdge2Arc(edge, arc)) {
- ip.push(pt);
- }
- }
-
- return ip;
-}
-
-function intersectLine2Polygon(line, polygon) {
- let ip = [];
-
- if (polygon.isEmpty()) {
- return ip;
- }
-
- for (let edge of polygon.edges) {
- for (let pt of intersectEdge2Line(edge, line)) {
- if (!ptInIntPoints(pt, ip)) {
- ip.push(pt);
- }
+
+function intersectCircle2Box(circle, box) {
+ let ips = [];
+ for (let seg of box.toSegments()) {
+ let ips_tmp = intersectSegment2Circle(seg, circle);
+ for (let ip of ips_tmp) {
+ ips.push(ip);
}
}
-
- return line.sortPoints(ip);
+ return ips;
}
-function intersectCircle2Polygon(circle, polygon) {
+function intersectArc2Arc(arc1, arc2) {
let ip = [];
- if (polygon.isEmpty()) {
+ if (arc1.box.not_intersect(arc2.box)) {
return ip;
}
- for (let edge of polygon.edges) {
- for (let pt of intersectEdge2Circle(edge, circle)) {
+ // Special case: overlapping arcs
+ // May return up to 4 intersection points
+ if (arc1.pc.equalTo(arc2.pc) && Flatten.Utils.EQ(arc1.r, arc2.r)) {
+ let pt;
+
+ pt = arc1.start;
+ if (pt.on(arc2))
ip.push(pt);
- }
- }
- return ip;
-}
+ pt = arc1.end;
+ if (pt.on(arc2))
+ ip.push(pt);
-function intersectEdge2Edge(edge1, edge2) {
- if (edge1.isSegment) {
- return intersectEdge2Segment(edge2, edge1.shape)
- }
- else if (edge1.isArc) {
- return intersectEdge2Arc(edge2, edge1.shape)
- }
- else if (edge1.isLine) {
- return intersectEdge2Line(edge2, edge1.shape)
- }
- else if (edge1.isRay) {
- return intersectEdge2Ray(edge2, edge1.shape)
- }
- return []
-}
+ pt = arc2.start;
+ if (pt.on(arc1)) ip.push(pt);
-function intersectEdge2Polygon(edge, polygon) {
- let ip = [];
+ pt = arc2.end;
+ if (pt.on(arc1)) ip.push(pt);
- if (polygon.isEmpty() || edge.shape.box.not_intersect(polygon.box)) {
return ip;
}
- let resp_edges = polygon.edges.search(edge.shape.box);
-
- for (let resp_edge of resp_edges) {
- ip = [...ip, ...intersectEdge2Edge(edge, resp_edge)];
+ // Common case
+ let circle1 = new Flatten.Circle(arc1.pc, arc1.r);
+ let circle2 = new Flatten.Circle(arc2.pc, arc2.r);
+ let ip_tmp = circle1.intersect(circle2);
+ for (let pt of ip_tmp) {
+ if (pt.on(arc1) && pt.on(arc2)) {
+ ip.push(pt);
+ }
}
-
return ip;
}
-function intersectPolygon2Polygon(polygon1, polygon2) {
+function intersectArc2Circle(arc, circle) {
let ip = [];
- if (polygon1.isEmpty() || polygon2.isEmpty()) {
+ if (arc.box.not_intersect(circle.box)) {
return ip;
}
- if (polygon1.box.not_intersect(polygon2.box)) {
+ // Case when arc center incident to circle center
+ // Return arc's end points as 2 intersection points
+ if (circle.pc.equalTo(arc.pc) && Flatten.Utils.EQ(circle.r, arc.r)) {
+ ip.push(arc.start);
+ ip.push(arc.end);
return ip;
}
- for (let edge1 of polygon1.edges) {
- ip = [...ip, ...intersectEdge2Polygon(edge1, polygon2)];
+ // Common case
+ let circle1 = circle;
+ let circle2 = new Flatten.Circle(arc.pc, arc.r);
+ let ip_tmp = intersectCircle2Circle(circle1, circle2);
+ for (let pt of ip_tmp) {
+ if (pt.on(arc)) {
+ ip.push(pt);
+ }
}
-
return ip;
}
-function intersectShape2Polygon(shape, polygon) {
- if (shape instanceof Flatten.Line) {
- return intersectLine2Polygon(shape, polygon);
- }
- else if (shape instanceof Flatten.Segment) {
- return intersectSegment2Polygon(shape, polygon);
- }
- else if (shape instanceof Flatten.Arc) {
- return intersectArc2Polygon(shape, polygon);
- }
- else {
- return [];
+function intersectArc2Box(arc, box) {
+ let ips = [];
+ for (let seg of box.toSegments()) {
+ let ips_tmp = intersectSegment2Arc(seg, arc);
+ for (let ip of ips_tmp) {
+ ips.push(ip);
+ }
}
+ return ips;
}
-function ptInIntPoints(new_pt, ip) {
- return ip.some( pt => pt.equalTo(new_pt) )
-}
-
-function createLineFromRay(ray) {
- return new Flatten.Line(ray.start, ray.norm)
-}
-function intersectRay2Segment(ray, segment) {
- return intersectSegment2Line(segment, createLineFromRay(ray))
- .filter(pt => ray.contains(pt));
-}
-
-function intersectRay2Arc(ray, arc) {
- return intersectLine2Arc(createLineFromRay(ray), arc)
- .filter(pt => ray.contains(pt))
-}
-
-function intersectRay2Circle(ray, circle) {
- return intersectLine2Circle(createLineFromRay(ray), circle)
- .filter(pt => ray.contains(pt))
-}
-
-function intersectRay2Box(ray, box) {
- return intersectLine2Box(createLineFromRay(ray), box)
- .filter(pt => ray.contains(pt))
-}
-
-function intersectRay2Line(ray, line) {
- return intersectLine2Line(createLineFromRay(ray), line)
- .filter(pt => ray.contains(pt))
-}
-
-function intersectRay2Ray(ray1, ray2) {
- return intersectLine2Line(createLineFromRay(ray1), createLineFromRay(ray2))
- .filter(pt => ray1.contains(pt))
- .filter(pt => ray2.contains(pt))
+function intersectEdge2Segment(edge, segment) {
+ return edge.isSegment ? intersectSegment2Segment(edge.shape, segment) : intersectSegment2Arc(segment, edge.shape);
}
-function intersectRay2Polygon(ray, polygon) {
- return intersectLine2Polygon(createLineFromRay(ray), polygon)
- .filter(pt => ray.contains(pt))
+function intersectEdge2Arc(edge, arc) {
+ return edge.isSegment ? intersectSegment2Arc(edge.shape, arc) : intersectArc2Arc(edge.shape, arc);
}
-const defaultAttributes = {
- stroke: "black"
-};
-
-class SVGAttributes {
- constructor(args = defaultAttributes) {
- for(const property in args) {
- this[property] = args[property];
- }
- this.stroke = args.stroke ?? defaultAttributes.stroke;
- }
-
- toAttributesString() {
- return Object.keys(this)
- .reduce( (acc, key) =>
- acc + (this[key] !== undefined ? this.toAttrString(key, this[key]) : "")
- , ``)
- }
-
- toAttrString(key, value) {
- const SVGKey = key === "className" ? "class" : this.convertCamelToKebabCase(key);
- return value === null ? `${SVGKey} ` : `${SVGKey}="${value.toString()}" `
- }
-
- convertCamelToKebabCase(str) {
- return str
- .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
- .join('-')
- .toLowerCase();
- }
+function intersectEdge2Line(edge, line) {
+ return edge.isSegment ? intersectSegment2Line(edge.shape, line) : intersectLine2Arc(line, edge.shape);
}
-function convertToString(attrs) {
- return new SVGAttributes(attrs).toAttributesString()
+function intersectEdge2Ray(edge, ray) {
+ return edge.isSegment ? intersectRay2Segment(ray, edge.shape) : intersectRay2Arc(ray, edge.shape);
}
-/**
- * Class Multiline represent connected path of [edges]{@link Flatten.Edge}, where each edge may be
- * [segment]{@link Flatten.Segment}, [arc]{@link Flatten.Arc}, [line]{@link Flatten.Line} or [ray]{@link Flatten.Ray}
- */
-class Multiline extends LinkedList {
- constructor(...args) {
- super();
-
- if (args.length === 0) {
- return;
- }
-
- if (args.length === 1) {
- if (args[0] instanceof Array) {
- let shapes = args[0];
- if (shapes.length === 0)
- return;
-
- // TODO: more strict validation:
- // there may be only one line
- // only first and last may be rays
- shapes.every((shape) => {
- return shape instanceof Flatten.Segment ||
- shape instanceof Flatten.Arc ||
- shape instanceof Flatten.Ray ||
- shape instanceof Flatten.Line
- });
-
- for (let shape of shapes) {
- let edge = new Flatten.Edge(shape);
- this.append(edge);
- }
-
- this.setArcLength();
- }
- }
- }
-
- /**
- * (Getter) Return array of edges
- * @returns {Edge[]}
- */
- get edges() {
- return [...this];
- }
-
- /**
- * (Getter) Return bounding box of the multiline
- * @returns {Box}
- */
- get box() {
- return this.edges.reduce( (acc,edge) => acc.merge(edge.box), new Flatten.Box() );
- }
-
- /**
- * (Getter) Returns array of vertices
- * @returns {Point[]}
- */
- get vertices() {
- let v = this.edges.map(edge => edge.start);
- v.push(this.last.end);
- return v;
- }
-
- /**
- * Return new cloned instance of Multiline
- * @returns {Multiline}
- */
- clone() {
- return new Multiline(this.toShapes());
- }
+function intersectEdge2Circle(edge, circle) {
+ return edge.isSegment ? intersectSegment2Circle(edge.shape, circle) : intersectArc2Circle(edge.shape, circle);
+}
- /**
- * Set arc_length property for each of the edges in the face.
- * Arc_length of the edge it the arc length from the first edge of the face
- */
- setArcLength() {
- for (let edge of this) {
- this.setOneEdgeArcLength(edge);
+function intersectSegment2Polygon(segment, polygon) {
+ let ip = [];
+
+ for (let edge of polygon.edges) {
+ for (let pt of intersectEdge2Segment(edge, segment)) {
+ ip.push(pt);
}
}
- setOneEdgeArcLength(edge) {
- if (edge === this.first) {
- edge.arc_length = 0.0;
- } else {
- edge.arc_length = edge.prev.arc_length + edge.prev.length;
+ return ip;
+}
+
+function intersectArc2Polygon(arc, polygon) {
+ let ip = [];
+
+ for (let edge of polygon.edges) {
+ for (let pt of intersectEdge2Arc(edge, arc)) {
+ ip.push(pt);
}
}
- /**
- * Split edge and add new vertex, return new edge inserted
- * @param {Point} pt - point on edge that will be added as new vertex
- * @param {Edge} edge - edge to split
- * @returns {Edge}
- */
- addVertex(pt, edge) {
- let shapes = edge.shape.split(pt);
- // if (shapes.length < 2) return;
+ return ip;
+}
- if (shapes[0] === null) // point incident to edge start vertex, return previous edge
- return edge.prev;
+function intersectLine2Polygon(line, polygon) {
+ let ip = [];
- if (shapes[1] === null) // point incident to edge end vertex, return edge itself
- return edge;
+ if (polygon.isEmpty()) {
+ return ip;
+ }
- let newEdge = new Flatten.Edge(shapes[0]);
- let edgeBefore = edge.prev;
+ for (let edge of polygon.edges) {
+ for (let pt of intersectEdge2Line(edge, line)) {
+ if (!ptInIntPoints(pt, ip)) {
+ ip.push(pt);
+ }
+ }
+ }
- /* Insert first split edge into linked list after edgeBefore */
- this.insert(newEdge, edgeBefore); // edge.face ?
+ return line.sortPoints(ip);
+}
- // Update edge shape with second split edge keeping links
- edge.shape = shapes[1];
+function intersectCircle2Polygon(circle, polygon) {
+ let ip = [];
- return newEdge;
+ if (polygon.isEmpty()) {
+ return ip;
}
- getChain(edgeFrom, edgeTo) {
- let edges = [];
- for (let edge = edgeFrom; edge !== edgeTo.next; edge = edge.next) {
- edges.push(edge);
+ for (let edge of polygon.edges) {
+ for (let pt of intersectEdge2Circle(edge, circle)) {
+ ip.push(pt);
}
- return edges
}
- /**
- * Split edges of multiline with intersection points and return mutated multiline
- * @param {Point[]} ip - array of points to be added as new vertices
- * @returns {Multiline}
- */
- split(ip) {
- for (let pt of ip) {
- let edge = this.findEdgeByPoint(pt);
- this.addVertex(pt, edge);
- }
- return this;
- }
+ return ip;
+}
- /**
- * Returns edge which contains given point
- * @param {Point} pt
- * @returns {Edge}
- */
- findEdgeByPoint(pt) {
- let edgeFound;
- for (let edge of this) {
- if (edge.shape.contains(pt)) {
- edgeFound = edge;
- break;
- }
- }
- return edgeFound;
+function intersectEdge2Edge(edge1, edge2) {
+ if (edge1.isSegment) {
+ return intersectEdge2Segment(edge2, edge1.shape)
+ }
+ else if (edge1.isArc) {
+ return intersectEdge2Arc(edge2, edge1.shape)
}
+ else if (edge1.isLine) {
+ return intersectEdge2Line(edge2, edge1.shape)
+ }
+ else if (edge1.isRay) {
+ return intersectEdge2Ray(edge2, edge1.shape)
+ }
+ return []
+}
- /**
- * Returns new multiline translated by vector vec
- * @param {Vector} vec
- * @returns {Multiline}
- */
- translate(vec) {
- return new Multiline(this.edges.map( edge => edge.shape.translate(vec)));
+function intersectEdge2Polygon(edge, polygon) {
+ let ip = [];
+
+ if (polygon.isEmpty() || edge.shape.box.not_intersect(polygon.box)) {
+ return ip;
}
- /**
- * Return new multiline rotated by given angle around given point
- * If point omitted, rotate around origin (0,0)
- * Positive value of angle defines rotation counterclockwise, negative - clockwise
- * @param {number} angle - rotation angle in radians
- * @param {Point} center - rotation center, default is (0,0)
- * @returns {Multiline} - new rotated polygon
- */
- rotate(angle = 0, center = new Flatten.Point()) {
- return new Multiline(this.edges.map( edge => edge.shape.rotate(angle, center) ));
+ let resp_edges = polygon.edges.search(edge.shape.box);
+
+ for (let resp_edge of resp_edges) {
+ ip = [...ip, ...intersectEdge2Edge(edge, resp_edge)];
}
- /**
- * Return new multiline transformed using affine transformation matrix
- * Method does not support unbounded shapes
- * @param {Matrix} matrix - affine transformation matrix
- * @returns {Multiline} - new multiline
- */
- transform(matrix = new Flatten.Matrix()) {
- return new Multiline(this.edges.map( edge => edge.shape.transform(matrix)));
+ return ip;
+}
+
+function intersectPolygon2Polygon(polygon1, polygon2) {
+ let ip = [];
+
+ if (polygon1.isEmpty() || polygon2.isEmpty()) {
+ return ip;
}
- /**
- * Transform multiline into array of shapes
- * @returns {Shape[]}
- */
- toShapes() {
- return this.edges.map(edge => edge.shape.clone())
+ if (polygon1.box.not_intersect(polygon2.box)) {
+ return ip;
}
- /**
- * This method returns an object that defines how data will be
- * serialized when called JSON.stringify() method
- * @returns {Object}
- */
- toJSON() {
- return this.edges.map(edge => edge.toJSON());
+ for (let edge1 of polygon1.edges) {
+ ip = [...ip, ...intersectEdge2Polygon(edge1, polygon2)];
}
- /**
- * Return string to draw multiline in svg
- * @param attrs - an object with attributes for svg path element
- * TODO: support semi-infinite Ray and infinite Line
- * @returns {string}
- */
- svg(attrs = {}) {
- let svgStr = `\n\n`;
- return svgStr;
+ return ip;
+}
+
+function intersectShape2Polygon(shape, polygon) {
+ if (shape instanceof Flatten.Line) {
+ return intersectLine2Polygon(shape, polygon);
+ }
+ else if (shape instanceof Flatten.Segment) {
+ return intersectSegment2Polygon(shape, polygon);
+ }
+ else if (shape instanceof Flatten.Arc) {
+ return intersectArc2Polygon(shape, polygon);
+ }
+ else {
+ return [];
}
}
-Flatten.Multiline = Multiline;
+function ptInIntPoints(new_pt, ip) {
+ return ip.some( pt => pt.equalTo(new_pt) )
+}
-/**
- * Shortcut function to create multiline
- * @param args
- */
-const multiline = (...args) => new Flatten.Multiline(...args);
-Flatten.multiline = multiline;
+function createLineFromRay(ray) {
+ return new Flatten.Line(ray.start, ray.norm)
+}
+function intersectRay2Segment(ray, segment) {
+ return intersectSegment2Line(segment, createLineFromRay(ray))
+ .filter(pt => ray.contains(pt));
+}
+
+function intersectRay2Arc(ray, arc) {
+ return intersectLine2Arc(createLineFromRay(ray), arc)
+ .filter(pt => ray.contains(pt))
+}
+
+function intersectRay2Circle(ray, circle) {
+ return intersectLine2Circle(createLineFromRay(ray), circle)
+ .filter(pt => ray.contains(pt))
+}
+
+function intersectRay2Box(ray, box) {
+ return intersectLine2Box(createLineFromRay(ray), box)
+ .filter(pt => ray.contains(pt))
+}
+
+function intersectRay2Line(ray, line) {
+ return intersectLine2Line(createLineFromRay(ray), line)
+ .filter(pt => ray.contains(pt))
+}
+
+function intersectRay2Ray(ray1, ray2) {
+ return intersectLine2Line(createLineFromRay(ray1), createLineFromRay(ray2))
+ .filter(pt => ray1.contains(pt))
+ .filter(pt => ray2.contains(pt))
+}
+
+function intersectRay2Polygon(ray, polygon) {
+ return intersectLine2Polygon(createLineFromRay(ray), polygon)
+ .filter(pt => ray.contains(pt))
+}
/**
* @module RayShoot
diff --git a/dist/main.mjs b/dist/main.mjs
index 3ba0f91..a30c8a8 100644
--- a/dist/main.mjs
+++ b/dist/main.mjs
@@ -395,6 +395,271 @@ class LinkedList {
}
}
+const defaultAttributes = {
+ stroke: "black"
+};
+
+class SVGAttributes {
+ constructor(args = defaultAttributes) {
+ for(const property in args) {
+ this[property] = args[property];
+ }
+ this.stroke = args.stroke ?? defaultAttributes.stroke;
+ }
+
+ toAttributesString() {
+ return Object.keys(this)
+ .reduce( (acc, key) =>
+ acc + (this[key] !== undefined ? this.toAttrString(key, this[key]) : "")
+ , ``)
+ }
+
+ toAttrString(key, value) {
+ const SVGKey = key === "className" ? "class" : this.convertCamelToKebabCase(key);
+ return value === null ? `${SVGKey} ` : `${SVGKey}="${value.toString()}" `
+ }
+
+ convertCamelToKebabCase(str) {
+ return str
+ .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
+ .join('-')
+ .toLowerCase();
+ }
+}
+
+function convertToString(attrs) {
+ return new SVGAttributes(attrs).toAttributesString()
+}
+
+/**
+ * Class Multiline represent connected path of [edges]{@link Flatten.Edge}, where each edge may be
+ * [segment]{@link Flatten.Segment}, [arc]{@link Flatten.Arc}, [line]{@link Flatten.Line} or [ray]{@link Flatten.Ray}
+ */
+class Multiline extends LinkedList {
+ constructor(...args) {
+ super();
+
+ if (args.length === 0) {
+ return;
+ }
+
+ if (args.length === 1) {
+ if (args[0] instanceof Array) {
+ let shapes = args[0];
+ if (shapes.length === 0)
+ return;
+
+ // TODO: more strict validation:
+ // there may be only one line
+ // only first and last may be rays
+ shapes.every((shape) => {
+ return shape instanceof Flatten.Segment ||
+ shape instanceof Flatten.Arc ||
+ shape instanceof Flatten.Ray ||
+ shape instanceof Flatten.Line
+ });
+
+ for (let shape of shapes) {
+ let edge = new Flatten.Edge(shape);
+ this.append(edge);
+ }
+
+ this.setArcLength();
+ }
+ }
+ }
+
+ /**
+ * (Getter) Return array of edges
+ * @returns {Edge[]}
+ */
+ get edges() {
+ return [...this];
+ }
+
+ /**
+ * (Getter) Return bounding box of the multiline
+ * @returns {Box}
+ */
+ get box() {
+ return this.edges.reduce( (acc,edge) => acc.merge(edge.box), new Flatten.Box() );
+ }
+
+ /**
+ * (Getter) Returns array of vertices
+ * @returns {Point[]}
+ */
+ get vertices() {
+ let v = this.edges.map(edge => edge.start);
+ v.push(this.last.end);
+ return v;
+ }
+
+ /**
+ * Return new cloned instance of Multiline
+ * @returns {Multiline}
+ */
+ clone() {
+ return new Multiline(this.toShapes());
+ }
+
+ /**
+ * Set arc_length property for each of the edges in the face.
+ * Arc_length of the edge it the arc length from the first edge of the face
+ */
+ setArcLength() {
+ for (let edge of this) {
+ this.setOneEdgeArcLength(edge);
+ }
+ }
+
+ setOneEdgeArcLength(edge) {
+ if (edge === this.first) {
+ edge.arc_length = 0.0;
+ } else {
+ edge.arc_length = edge.prev.arc_length + edge.prev.length;
+ }
+ }
+
+ /**
+ * Split edge and add new vertex, return new edge inserted
+ * @param {Point} pt - point on edge that will be added as new vertex
+ * @param {Edge} edge - edge to split
+ * @returns {Edge}
+ */
+ addVertex(pt, edge) {
+ let shapes = edge.shape.split(pt);
+ // if (shapes.length < 2) return;
+
+ if (shapes[0] === null) // point incident to edge start vertex, return previous edge
+ return edge.prev;
+
+ if (shapes[1] === null) // point incident to edge end vertex, return edge itself
+ return edge;
+
+ let newEdge = new Flatten.Edge(shapes[0]);
+ let edgeBefore = edge.prev;
+
+ /* Insert first split edge into linked list after edgeBefore */
+ this.insert(newEdge, edgeBefore); // edge.face ?
+
+ // Update edge shape with second split edge keeping links
+ edge.shape = shapes[1];
+
+ return newEdge;
+ }
+
+ getChain(edgeFrom, edgeTo) {
+ let edges = [];
+ for (let edge = edgeFrom; edge !== edgeTo.next; edge = edge.next) {
+ edges.push(edge);
+ }
+ return edges
+ }
+
+ /**
+ * Split edges of multiline with intersection points and return mutated multiline
+ * @param {Point[]} ip - array of points to be added as new vertices
+ * @returns {Multiline}
+ */
+ split(ip) {
+ for (let pt of ip) {
+ let edge = this.findEdgeByPoint(pt);
+ this.addVertex(pt, edge);
+ }
+ return this;
+ }
+
+ /**
+ * Returns edge which contains given point
+ * @param {Point} pt
+ * @returns {Edge}
+ */
+ findEdgeByPoint(pt) {
+ let edgeFound;
+ for (let edge of this) {
+ if (edge.shape.contains(pt)) {
+ edgeFound = edge;
+ break;
+ }
+ }
+ return edgeFound;
+ }
+
+ /**
+ * Returns new multiline translated by vector vec
+ * @param {Vector} vec
+ * @returns {Multiline}
+ */
+ translate(vec) {
+ return new Multiline(this.edges.map( edge => edge.shape.translate(vec)));
+ }
+
+ /**
+ * Return new multiline rotated by given angle around given point
+ * If point omitted, rotate around origin (0,0)
+ * Positive value of angle defines rotation counterclockwise, negative - clockwise
+ * @param {number} angle - rotation angle in radians
+ * @param {Point} center - rotation center, default is (0,0)
+ * @returns {Multiline} - new rotated polygon
+ */
+ rotate(angle = 0, center = new Flatten.Point()) {
+ return new Multiline(this.edges.map( edge => edge.shape.rotate(angle, center) ));
+ }
+
+ /**
+ * Return new multiline transformed using affine transformation matrix
+ * Method does not support unbounded shapes
+ * @param {Matrix} matrix - affine transformation matrix
+ * @returns {Multiline} - new multiline
+ */
+ transform(matrix = new Flatten.Matrix()) {
+ return new Multiline(this.edges.map( edge => edge.shape.transform(matrix)));
+ }
+
+ /**
+ * Transform multiline into array of shapes
+ * @returns {Shape[]}
+ */
+ toShapes() {
+ return this.edges.map(edge => edge.shape.clone())
+ }
+
+ /**
+ * This method returns an object that defines how data will be
+ * serialized when called JSON.stringify() method
+ * @returns {Object}
+ */
+ toJSON() {
+ return this.edges.map(edge => edge.toJSON());
+ }
+
+ /**
+ * Return string to draw multiline in svg
+ * @param attrs - an object with attributes for svg path element
+ * TODO: support semi-infinite Ray and infinite Line
+ * @returns {string}
+ */
+ svg(attrs = {}) {
+ let svgStr = `\n\n`;
+ return svgStr;
+ }
+}
+
+Flatten.Multiline = Multiline;
+
+/**
+ * Shortcut function to create multiline
+ * @param args
+ */
+const multiline = (...args) => new Flatten.Multiline(...args);
+Flatten.multiline = multiline;
+
/*
Smart intersections describe intersection points that refers to the edges they intersect
This function are supposed for internal usage by morphing and relation methods between
@@ -450,11 +715,7 @@ function addToIntPoints(edge, pt, int_points)
function sortIntersections(intersections)
{
- // if (intersections.int_points1.length === 0) return;
-
// augment intersections with new sorted arrays
- // intersections.int_points1_sorted = intersections.int_points1.slice().sort(compareFn);
- // intersections.int_points2_sorted = intersections.int_points2.slice().sort(compareFn);
intersections.int_points1_sorted = getSortedArray(intersections.int_points1);
intersections.int_points2_sorted = getSortedArray(intersections.int_points2);
}
@@ -498,18 +759,6 @@ function compareFn(ip1, ip2)
return 0;
}
-// export function getSortedArrayOnLine(line, int_points) {
-// return int_points.slice().sort( (int_point1, int_point2) => {
-// if (line.coord(int_point1.pt) < line.coord(int_point2.pt)) {
-// return -1;
-// }
-// if (line.coord(int_point1.pt) > line.coord(int_point2.pt)) {
-// return 1;
-// }
-// return 0;
-// })
-// }
-
function filterDuplicatedIntersections(intersections)
{
if (intersections.int_points1.length < 2) return;
@@ -741,14 +990,10 @@ function splitByIntersections(polygon, int_points)
int_point.is_vertex |= END_VERTEX$1;
}
- if (int_point.is_vertex & START_VERTEX$1) { // nothing to split
+ if (int_point.is_vertex & START_VERTEX$1) { // nothing to split
+ int_point.edge_before = edge.prev;
if (edge.prev) {
- int_point.edge_before = edge.prev; // polygon
- int_point.is_vertex = END_VERTEX$1;
- }
- else { // multiline start vertex
- int_point.edge_after = int_point.edge_before;
- int_point.edge_before = edge.prev;
+ int_point.is_vertex = END_VERTEX$1; // polygon
}
continue;
}
@@ -764,6 +1009,11 @@ function splitByIntersections(polygon, int_points)
if (int_point.edge_before) {
int_point.edge_after = int_point.edge_before.next;
}
+ else {
+ if (polygon instanceof Multiline && int_point.is_vertex & START_VERTEX$1) {
+ int_point.edge_after = polygon.first;
+ }
+ }
}
}
@@ -2022,547 +2272,282 @@ function intersectCircle2Circle(circle1, circle2) {
return ip;
}
-
-function intersectCircle2Box(circle, box) {
- let ips = [];
- for (let seg of box.toSegments()) {
- let ips_tmp = intersectSegment2Circle(seg, circle);
- for (let ip of ips_tmp) {
- ips.push(ip);
- }
- }
- return ips;
-}
-
-function intersectArc2Arc(arc1, arc2) {
- let ip = [];
-
- if (arc1.box.not_intersect(arc2.box)) {
- return ip;
- }
-
- // Special case: overlapping arcs
- // May return up to 4 intersection points
- if (arc1.pc.equalTo(arc2.pc) && Flatten.Utils.EQ(arc1.r, arc2.r)) {
- let pt;
-
- pt = arc1.start;
- if (pt.on(arc2))
- ip.push(pt);
-
- pt = arc1.end;
- if (pt.on(arc2))
- ip.push(pt);
-
- pt = arc2.start;
- if (pt.on(arc1)) ip.push(pt);
-
- pt = arc2.end;
- if (pt.on(arc1)) ip.push(pt);
-
- return ip;
- }
-
- // Common case
- let circle1 = new Flatten.Circle(arc1.pc, arc1.r);
- let circle2 = new Flatten.Circle(arc2.pc, arc2.r);
- let ip_tmp = circle1.intersect(circle2);
- for (let pt of ip_tmp) {
- if (pt.on(arc1) && pt.on(arc2)) {
- ip.push(pt);
- }
- }
- return ip;
-}
-
-function intersectArc2Circle(arc, circle) {
- let ip = [];
-
- if (arc.box.not_intersect(circle.box)) {
- return ip;
- }
-
- // Case when arc center incident to circle center
- // Return arc's end points as 2 intersection points
- if (circle.pc.equalTo(arc.pc) && Flatten.Utils.EQ(circle.r, arc.r)) {
- ip.push(arc.start);
- ip.push(arc.end);
- return ip;
- }
-
- // Common case
- let circle1 = circle;
- let circle2 = new Flatten.Circle(arc.pc, arc.r);
- let ip_tmp = intersectCircle2Circle(circle1, circle2);
- for (let pt of ip_tmp) {
- if (pt.on(arc)) {
- ip.push(pt);
- }
- }
- return ip;
-}
-
-function intersectArc2Box(arc, box) {
- let ips = [];
- for (let seg of box.toSegments()) {
- let ips_tmp = intersectSegment2Arc(seg, arc);
- for (let ip of ips_tmp) {
- ips.push(ip);
- }
- }
- return ips;
-}
-
-function intersectEdge2Segment(edge, segment) {
- return edge.isSegment ? intersectSegment2Segment(edge.shape, segment) : intersectSegment2Arc(segment, edge.shape);
-}
-
-function intersectEdge2Arc(edge, arc) {
- return edge.isSegment ? intersectSegment2Arc(edge.shape, arc) : intersectArc2Arc(edge.shape, arc);
-}
-
-function intersectEdge2Line(edge, line) {
- return edge.isSegment ? intersectSegment2Line(edge.shape, line) : intersectLine2Arc(line, edge.shape);
-}
-
-function intersectEdge2Ray(edge, ray) {
- return edge.isSegment ? intersectRay2Segment(ray, edge.shape) : intersectRay2Arc(ray, edge.shape);
-}
-
-function intersectEdge2Circle(edge, circle) {
- return edge.isSegment ? intersectSegment2Circle(edge.shape, circle) : intersectArc2Circle(edge.shape, circle);
-}
-
-function intersectSegment2Polygon(segment, polygon) {
- let ip = [];
-
- for (let edge of polygon.edges) {
- for (let pt of intersectEdge2Segment(edge, segment)) {
- ip.push(pt);
- }
- }
-
- return ip;
-}
-
-function intersectArc2Polygon(arc, polygon) {
- let ip = [];
-
- for (let edge of polygon.edges) {
- for (let pt of intersectEdge2Arc(edge, arc)) {
- ip.push(pt);
- }
- }
-
- return ip;
-}
-
-function intersectLine2Polygon(line, polygon) {
- let ip = [];
-
- if (polygon.isEmpty()) {
- return ip;
- }
-
- for (let edge of polygon.edges) {
- for (let pt of intersectEdge2Line(edge, line)) {
- if (!ptInIntPoints(pt, ip)) {
- ip.push(pt);
- }
+
+function intersectCircle2Box(circle, box) {
+ let ips = [];
+ for (let seg of box.toSegments()) {
+ let ips_tmp = intersectSegment2Circle(seg, circle);
+ for (let ip of ips_tmp) {
+ ips.push(ip);
}
}
-
- return line.sortPoints(ip);
+ return ips;
}
-function intersectCircle2Polygon(circle, polygon) {
+function intersectArc2Arc(arc1, arc2) {
let ip = [];
- if (polygon.isEmpty()) {
+ if (arc1.box.not_intersect(arc2.box)) {
return ip;
}
- for (let edge of polygon.edges) {
- for (let pt of intersectEdge2Circle(edge, circle)) {
+ // Special case: overlapping arcs
+ // May return up to 4 intersection points
+ if (arc1.pc.equalTo(arc2.pc) && Flatten.Utils.EQ(arc1.r, arc2.r)) {
+ let pt;
+
+ pt = arc1.start;
+ if (pt.on(arc2))
ip.push(pt);
- }
- }
- return ip;
-}
+ pt = arc1.end;
+ if (pt.on(arc2))
+ ip.push(pt);
-function intersectEdge2Edge(edge1, edge2) {
- if (edge1.isSegment) {
- return intersectEdge2Segment(edge2, edge1.shape)
- }
- else if (edge1.isArc) {
- return intersectEdge2Arc(edge2, edge1.shape)
- }
- else if (edge1.isLine) {
- return intersectEdge2Line(edge2, edge1.shape)
- }
- else if (edge1.isRay) {
- return intersectEdge2Ray(edge2, edge1.shape)
- }
- return []
-}
+ pt = arc2.start;
+ if (pt.on(arc1)) ip.push(pt);
-function intersectEdge2Polygon(edge, polygon) {
- let ip = [];
+ pt = arc2.end;
+ if (pt.on(arc1)) ip.push(pt);
- if (polygon.isEmpty() || edge.shape.box.not_intersect(polygon.box)) {
return ip;
}
- let resp_edges = polygon.edges.search(edge.shape.box);
-
- for (let resp_edge of resp_edges) {
- ip = [...ip, ...intersectEdge2Edge(edge, resp_edge)];
+ // Common case
+ let circle1 = new Flatten.Circle(arc1.pc, arc1.r);
+ let circle2 = new Flatten.Circle(arc2.pc, arc2.r);
+ let ip_tmp = circle1.intersect(circle2);
+ for (let pt of ip_tmp) {
+ if (pt.on(arc1) && pt.on(arc2)) {
+ ip.push(pt);
+ }
}
-
return ip;
}
-function intersectPolygon2Polygon(polygon1, polygon2) {
+function intersectArc2Circle(arc, circle) {
let ip = [];
- if (polygon1.isEmpty() || polygon2.isEmpty()) {
+ if (arc.box.not_intersect(circle.box)) {
return ip;
}
- if (polygon1.box.not_intersect(polygon2.box)) {
+ // Case when arc center incident to circle center
+ // Return arc's end points as 2 intersection points
+ if (circle.pc.equalTo(arc.pc) && Flatten.Utils.EQ(circle.r, arc.r)) {
+ ip.push(arc.start);
+ ip.push(arc.end);
return ip;
}
- for (let edge1 of polygon1.edges) {
- ip = [...ip, ...intersectEdge2Polygon(edge1, polygon2)];
+ // Common case
+ let circle1 = circle;
+ let circle2 = new Flatten.Circle(arc.pc, arc.r);
+ let ip_tmp = intersectCircle2Circle(circle1, circle2);
+ for (let pt of ip_tmp) {
+ if (pt.on(arc)) {
+ ip.push(pt);
+ }
}
-
return ip;
}
-function intersectShape2Polygon(shape, polygon) {
- if (shape instanceof Flatten.Line) {
- return intersectLine2Polygon(shape, polygon);
- }
- else if (shape instanceof Flatten.Segment) {
- return intersectSegment2Polygon(shape, polygon);
- }
- else if (shape instanceof Flatten.Arc) {
- return intersectArc2Polygon(shape, polygon);
- }
- else {
- return [];
+function intersectArc2Box(arc, box) {
+ let ips = [];
+ for (let seg of box.toSegments()) {
+ let ips_tmp = intersectSegment2Arc(seg, arc);
+ for (let ip of ips_tmp) {
+ ips.push(ip);
+ }
}
+ return ips;
}
-function ptInIntPoints(new_pt, ip) {
- return ip.some( pt => pt.equalTo(new_pt) )
-}
-
-function createLineFromRay(ray) {
- return new Flatten.Line(ray.start, ray.norm)
-}
-function intersectRay2Segment(ray, segment) {
- return intersectSegment2Line(segment, createLineFromRay(ray))
- .filter(pt => ray.contains(pt));
-}
-
-function intersectRay2Arc(ray, arc) {
- return intersectLine2Arc(createLineFromRay(ray), arc)
- .filter(pt => ray.contains(pt))
-}
-
-function intersectRay2Circle(ray, circle) {
- return intersectLine2Circle(createLineFromRay(ray), circle)
- .filter(pt => ray.contains(pt))
-}
-
-function intersectRay2Box(ray, box) {
- return intersectLine2Box(createLineFromRay(ray), box)
- .filter(pt => ray.contains(pt))
-}
-
-function intersectRay2Line(ray, line) {
- return intersectLine2Line(createLineFromRay(ray), line)
- .filter(pt => ray.contains(pt))
-}
-
-function intersectRay2Ray(ray1, ray2) {
- return intersectLine2Line(createLineFromRay(ray1), createLineFromRay(ray2))
- .filter(pt => ray1.contains(pt))
- .filter(pt => ray2.contains(pt))
+function intersectEdge2Segment(edge, segment) {
+ return edge.isSegment ? intersectSegment2Segment(edge.shape, segment) : intersectSegment2Arc(segment, edge.shape);
}
-function intersectRay2Polygon(ray, polygon) {
- return intersectLine2Polygon(createLineFromRay(ray), polygon)
- .filter(pt => ray.contains(pt))
+function intersectEdge2Arc(edge, arc) {
+ return edge.isSegment ? intersectSegment2Arc(edge.shape, arc) : intersectArc2Arc(edge.shape, arc);
}
-const defaultAttributes = {
- stroke: "black"
-};
-
-class SVGAttributes {
- constructor(args = defaultAttributes) {
- for(const property in args) {
- this[property] = args[property];
- }
- this.stroke = args.stroke ?? defaultAttributes.stroke;
- }
-
- toAttributesString() {
- return Object.keys(this)
- .reduce( (acc, key) =>
- acc + (this[key] !== undefined ? this.toAttrString(key, this[key]) : "")
- , ``)
- }
-
- toAttrString(key, value) {
- const SVGKey = key === "className" ? "class" : this.convertCamelToKebabCase(key);
- return value === null ? `${SVGKey} ` : `${SVGKey}="${value.toString()}" `
- }
-
- convertCamelToKebabCase(str) {
- return str
- .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
- .join('-')
- .toLowerCase();
- }
+function intersectEdge2Line(edge, line) {
+ return edge.isSegment ? intersectSegment2Line(edge.shape, line) : intersectLine2Arc(line, edge.shape);
}
-function convertToString(attrs) {
- return new SVGAttributes(attrs).toAttributesString()
+function intersectEdge2Ray(edge, ray) {
+ return edge.isSegment ? intersectRay2Segment(ray, edge.shape) : intersectRay2Arc(ray, edge.shape);
}
-/**
- * Class Multiline represent connected path of [edges]{@link Flatten.Edge}, where each edge may be
- * [segment]{@link Flatten.Segment}, [arc]{@link Flatten.Arc}, [line]{@link Flatten.Line} or [ray]{@link Flatten.Ray}
- */
-class Multiline extends LinkedList {
- constructor(...args) {
- super();
-
- if (args.length === 0) {
- return;
- }
-
- if (args.length === 1) {
- if (args[0] instanceof Array) {
- let shapes = args[0];
- if (shapes.length === 0)
- return;
-
- // TODO: more strict validation:
- // there may be only one line
- // only first and last may be rays
- shapes.every((shape) => {
- return shape instanceof Flatten.Segment ||
- shape instanceof Flatten.Arc ||
- shape instanceof Flatten.Ray ||
- shape instanceof Flatten.Line
- });
-
- for (let shape of shapes) {
- let edge = new Flatten.Edge(shape);
- this.append(edge);
- }
-
- this.setArcLength();
- }
- }
- }
-
- /**
- * (Getter) Return array of edges
- * @returns {Edge[]}
- */
- get edges() {
- return [...this];
- }
-
- /**
- * (Getter) Return bounding box of the multiline
- * @returns {Box}
- */
- get box() {
- return this.edges.reduce( (acc,edge) => acc.merge(edge.box), new Flatten.Box() );
- }
-
- /**
- * (Getter) Returns array of vertices
- * @returns {Point[]}
- */
- get vertices() {
- let v = this.edges.map(edge => edge.start);
- v.push(this.last.end);
- return v;
- }
-
- /**
- * Return new cloned instance of Multiline
- * @returns {Multiline}
- */
- clone() {
- return new Multiline(this.toShapes());
- }
+function intersectEdge2Circle(edge, circle) {
+ return edge.isSegment ? intersectSegment2Circle(edge.shape, circle) : intersectArc2Circle(edge.shape, circle);
+}
- /**
- * Set arc_length property for each of the edges in the face.
- * Arc_length of the edge it the arc length from the first edge of the face
- */
- setArcLength() {
- for (let edge of this) {
- this.setOneEdgeArcLength(edge);
+function intersectSegment2Polygon(segment, polygon) {
+ let ip = [];
+
+ for (let edge of polygon.edges) {
+ for (let pt of intersectEdge2Segment(edge, segment)) {
+ ip.push(pt);
}
}
- setOneEdgeArcLength(edge) {
- if (edge === this.first) {
- edge.arc_length = 0.0;
- } else {
- edge.arc_length = edge.prev.arc_length + edge.prev.length;
+ return ip;
+}
+
+function intersectArc2Polygon(arc, polygon) {
+ let ip = [];
+
+ for (let edge of polygon.edges) {
+ for (let pt of intersectEdge2Arc(edge, arc)) {
+ ip.push(pt);
}
}
- /**
- * Split edge and add new vertex, return new edge inserted
- * @param {Point} pt - point on edge that will be added as new vertex
- * @param {Edge} edge - edge to split
- * @returns {Edge}
- */
- addVertex(pt, edge) {
- let shapes = edge.shape.split(pt);
- // if (shapes.length < 2) return;
+ return ip;
+}
- if (shapes[0] === null) // point incident to edge start vertex, return previous edge
- return edge.prev;
+function intersectLine2Polygon(line, polygon) {
+ let ip = [];
- if (shapes[1] === null) // point incident to edge end vertex, return edge itself
- return edge;
+ if (polygon.isEmpty()) {
+ return ip;
+ }
- let newEdge = new Flatten.Edge(shapes[0]);
- let edgeBefore = edge.prev;
+ for (let edge of polygon.edges) {
+ for (let pt of intersectEdge2Line(edge, line)) {
+ if (!ptInIntPoints(pt, ip)) {
+ ip.push(pt);
+ }
+ }
+ }
- /* Insert first split edge into linked list after edgeBefore */
- this.insert(newEdge, edgeBefore); // edge.face ?
+ return line.sortPoints(ip);
+}
- // Update edge shape with second split edge keeping links
- edge.shape = shapes[1];
+function intersectCircle2Polygon(circle, polygon) {
+ let ip = [];
- return newEdge;
+ if (polygon.isEmpty()) {
+ return ip;
}
- getChain(edgeFrom, edgeTo) {
- let edges = [];
- for (let edge = edgeFrom; edge !== edgeTo.next; edge = edge.next) {
- edges.push(edge);
+ for (let edge of polygon.edges) {
+ for (let pt of intersectEdge2Circle(edge, circle)) {
+ ip.push(pt);
}
- return edges
}
- /**
- * Split edges of multiline with intersection points and return mutated multiline
- * @param {Point[]} ip - array of points to be added as new vertices
- * @returns {Multiline}
- */
- split(ip) {
- for (let pt of ip) {
- let edge = this.findEdgeByPoint(pt);
- this.addVertex(pt, edge);
- }
- return this;
- }
+ return ip;
+}
- /**
- * Returns edge which contains given point
- * @param {Point} pt
- * @returns {Edge}
- */
- findEdgeByPoint(pt) {
- let edgeFound;
- for (let edge of this) {
- if (edge.shape.contains(pt)) {
- edgeFound = edge;
- break;
- }
- }
- return edgeFound;
+function intersectEdge2Edge(edge1, edge2) {
+ if (edge1.isSegment) {
+ return intersectEdge2Segment(edge2, edge1.shape)
+ }
+ else if (edge1.isArc) {
+ return intersectEdge2Arc(edge2, edge1.shape)
}
+ else if (edge1.isLine) {
+ return intersectEdge2Line(edge2, edge1.shape)
+ }
+ else if (edge1.isRay) {
+ return intersectEdge2Ray(edge2, edge1.shape)
+ }
+ return []
+}
- /**
- * Returns new multiline translated by vector vec
- * @param {Vector} vec
- * @returns {Multiline}
- */
- translate(vec) {
- return new Multiline(this.edges.map( edge => edge.shape.translate(vec)));
+function intersectEdge2Polygon(edge, polygon) {
+ let ip = [];
+
+ if (polygon.isEmpty() || edge.shape.box.not_intersect(polygon.box)) {
+ return ip;
}
- /**
- * Return new multiline rotated by given angle around given point
- * If point omitted, rotate around origin (0,0)
- * Positive value of angle defines rotation counterclockwise, negative - clockwise
- * @param {number} angle - rotation angle in radians
- * @param {Point} center - rotation center, default is (0,0)
- * @returns {Multiline} - new rotated polygon
- */
- rotate(angle = 0, center = new Flatten.Point()) {
- return new Multiline(this.edges.map( edge => edge.shape.rotate(angle, center) ));
+ let resp_edges = polygon.edges.search(edge.shape.box);
+
+ for (let resp_edge of resp_edges) {
+ ip = [...ip, ...intersectEdge2Edge(edge, resp_edge)];
}
- /**
- * Return new multiline transformed using affine transformation matrix
- * Method does not support unbounded shapes
- * @param {Matrix} matrix - affine transformation matrix
- * @returns {Multiline} - new multiline
- */
- transform(matrix = new Flatten.Matrix()) {
- return new Multiline(this.edges.map( edge => edge.shape.transform(matrix)));
+ return ip;
+}
+
+function intersectPolygon2Polygon(polygon1, polygon2) {
+ let ip = [];
+
+ if (polygon1.isEmpty() || polygon2.isEmpty()) {
+ return ip;
}
- /**
- * Transform multiline into array of shapes
- * @returns {Shape[]}
- */
- toShapes() {
- return this.edges.map(edge => edge.shape.clone())
+ if (polygon1.box.not_intersect(polygon2.box)) {
+ return ip;
}
- /**
- * This method returns an object that defines how data will be
- * serialized when called JSON.stringify() method
- * @returns {Object}
- */
- toJSON() {
- return this.edges.map(edge => edge.toJSON());
+ for (let edge1 of polygon1.edges) {
+ ip = [...ip, ...intersectEdge2Polygon(edge1, polygon2)];
}
- /**
- * Return string to draw multiline in svg
- * @param attrs - an object with attributes for svg path element
- * TODO: support semi-infinite Ray and infinite Line
- * @returns {string}
- */
- svg(attrs = {}) {
- let svgStr = `\n\n`;
- return svgStr;
+ return ip;
+}
+
+function intersectShape2Polygon(shape, polygon) {
+ if (shape instanceof Flatten.Line) {
+ return intersectLine2Polygon(shape, polygon);
+ }
+ else if (shape instanceof Flatten.Segment) {
+ return intersectSegment2Polygon(shape, polygon);
+ }
+ else if (shape instanceof Flatten.Arc) {
+ return intersectArc2Polygon(shape, polygon);
+ }
+ else {
+ return [];
}
}
-Flatten.Multiline = Multiline;
+function ptInIntPoints(new_pt, ip) {
+ return ip.some( pt => pt.equalTo(new_pt) )
+}
-/**
- * Shortcut function to create multiline
- * @param args
- */
-const multiline = (...args) => new Flatten.Multiline(...args);
-Flatten.multiline = multiline;
+function createLineFromRay(ray) {
+ return new Flatten.Line(ray.start, ray.norm)
+}
+function intersectRay2Segment(ray, segment) {
+ return intersectSegment2Line(segment, createLineFromRay(ray))
+ .filter(pt => ray.contains(pt));
+}
+
+function intersectRay2Arc(ray, arc) {
+ return intersectLine2Arc(createLineFromRay(ray), arc)
+ .filter(pt => ray.contains(pt))
+}
+
+function intersectRay2Circle(ray, circle) {
+ return intersectLine2Circle(createLineFromRay(ray), circle)
+ .filter(pt => ray.contains(pt))
+}
+
+function intersectRay2Box(ray, box) {
+ return intersectLine2Box(createLineFromRay(ray), box)
+ .filter(pt => ray.contains(pt))
+}
+
+function intersectRay2Line(ray, line) {
+ return intersectLine2Line(createLineFromRay(ray), line)
+ .filter(pt => ray.contains(pt))
+}
+
+function intersectRay2Ray(ray1, ray2) {
+ return intersectLine2Line(createLineFromRay(ray1), createLineFromRay(ray2))
+ .filter(pt => ray1.contains(pt))
+ .filter(pt => ray2.contains(pt))
+}
+
+function intersectRay2Polygon(ray, polygon) {
+ return intersectLine2Polygon(createLineFromRay(ray), polygon)
+ .filter(pt => ray.contains(pt))
+}
/**
* @module RayShoot
diff --git a/dist/main.umd.js b/dist/main.umd.js
index d07c6ea..841cedd 100644
--- a/dist/main.umd.js
+++ b/dist/main.umd.js
@@ -401,6 +401,271 @@
}
}
+ const defaultAttributes = {
+ stroke: "black"
+ };
+
+ class SVGAttributes {
+ constructor(args = defaultAttributes) {
+ for(const property in args) {
+ this[property] = args[property];
+ }
+ this.stroke = args.stroke ?? defaultAttributes.stroke;
+ }
+
+ toAttributesString() {
+ return Object.keys(this)
+ .reduce( (acc, key) =>
+ acc + (this[key] !== undefined ? this.toAttrString(key, this[key]) : "")
+ , ``)
+ }
+
+ toAttrString(key, value) {
+ const SVGKey = key === "className" ? "class" : this.convertCamelToKebabCase(key);
+ return value === null ? `${SVGKey} ` : `${SVGKey}="${value.toString()}" `
+ }
+
+ convertCamelToKebabCase(str) {
+ return str
+ .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
+ .join('-')
+ .toLowerCase();
+ }
+ }
+
+ function convertToString(attrs) {
+ return new SVGAttributes(attrs).toAttributesString()
+ }
+
+ /**
+ * Class Multiline represent connected path of [edges]{@link Flatten.Edge}, where each edge may be
+ * [segment]{@link Flatten.Segment}, [arc]{@link Flatten.Arc}, [line]{@link Flatten.Line} or [ray]{@link Flatten.Ray}
+ */
+ class Multiline extends LinkedList {
+ constructor(...args) {
+ super();
+
+ if (args.length === 0) {
+ return;
+ }
+
+ if (args.length === 1) {
+ if (args[0] instanceof Array) {
+ let shapes = args[0];
+ if (shapes.length === 0)
+ return;
+
+ // TODO: more strict validation:
+ // there may be only one line
+ // only first and last may be rays
+ shapes.every((shape) => {
+ return shape instanceof Flatten.Segment ||
+ shape instanceof Flatten.Arc ||
+ shape instanceof Flatten.Ray ||
+ shape instanceof Flatten.Line
+ });
+
+ for (let shape of shapes) {
+ let edge = new Flatten.Edge(shape);
+ this.append(edge);
+ }
+
+ this.setArcLength();
+ }
+ }
+ }
+
+ /**
+ * (Getter) Return array of edges
+ * @returns {Edge[]}
+ */
+ get edges() {
+ return [...this];
+ }
+
+ /**
+ * (Getter) Return bounding box of the multiline
+ * @returns {Box}
+ */
+ get box() {
+ return this.edges.reduce( (acc,edge) => acc.merge(edge.box), new Flatten.Box() );
+ }
+
+ /**
+ * (Getter) Returns array of vertices
+ * @returns {Point[]}
+ */
+ get vertices() {
+ let v = this.edges.map(edge => edge.start);
+ v.push(this.last.end);
+ return v;
+ }
+
+ /**
+ * Return new cloned instance of Multiline
+ * @returns {Multiline}
+ */
+ clone() {
+ return new Multiline(this.toShapes());
+ }
+
+ /**
+ * Set arc_length property for each of the edges in the face.
+ * Arc_length of the edge it the arc length from the first edge of the face
+ */
+ setArcLength() {
+ for (let edge of this) {
+ this.setOneEdgeArcLength(edge);
+ }
+ }
+
+ setOneEdgeArcLength(edge) {
+ if (edge === this.first) {
+ edge.arc_length = 0.0;
+ } else {
+ edge.arc_length = edge.prev.arc_length + edge.prev.length;
+ }
+ }
+
+ /**
+ * Split edge and add new vertex, return new edge inserted
+ * @param {Point} pt - point on edge that will be added as new vertex
+ * @param {Edge} edge - edge to split
+ * @returns {Edge}
+ */
+ addVertex(pt, edge) {
+ let shapes = edge.shape.split(pt);
+ // if (shapes.length < 2) return;
+
+ if (shapes[0] === null) // point incident to edge start vertex, return previous edge
+ return edge.prev;
+
+ if (shapes[1] === null) // point incident to edge end vertex, return edge itself
+ return edge;
+
+ let newEdge = new Flatten.Edge(shapes[0]);
+ let edgeBefore = edge.prev;
+
+ /* Insert first split edge into linked list after edgeBefore */
+ this.insert(newEdge, edgeBefore); // edge.face ?
+
+ // Update edge shape with second split edge keeping links
+ edge.shape = shapes[1];
+
+ return newEdge;
+ }
+
+ getChain(edgeFrom, edgeTo) {
+ let edges = [];
+ for (let edge = edgeFrom; edge !== edgeTo.next; edge = edge.next) {
+ edges.push(edge);
+ }
+ return edges
+ }
+
+ /**
+ * Split edges of multiline with intersection points and return mutated multiline
+ * @param {Point[]} ip - array of points to be added as new vertices
+ * @returns {Multiline}
+ */
+ split(ip) {
+ for (let pt of ip) {
+ let edge = this.findEdgeByPoint(pt);
+ this.addVertex(pt, edge);
+ }
+ return this;
+ }
+
+ /**
+ * Returns edge which contains given point
+ * @param {Point} pt
+ * @returns {Edge}
+ */
+ findEdgeByPoint(pt) {
+ let edgeFound;
+ for (let edge of this) {
+ if (edge.shape.contains(pt)) {
+ edgeFound = edge;
+ break;
+ }
+ }
+ return edgeFound;
+ }
+
+ /**
+ * Returns new multiline translated by vector vec
+ * @param {Vector} vec
+ * @returns {Multiline}
+ */
+ translate(vec) {
+ return new Multiline(this.edges.map( edge => edge.shape.translate(vec)));
+ }
+
+ /**
+ * Return new multiline rotated by given angle around given point
+ * If point omitted, rotate around origin (0,0)
+ * Positive value of angle defines rotation counterclockwise, negative - clockwise
+ * @param {number} angle - rotation angle in radians
+ * @param {Point} center - rotation center, default is (0,0)
+ * @returns {Multiline} - new rotated polygon
+ */
+ rotate(angle = 0, center = new Flatten.Point()) {
+ return new Multiline(this.edges.map( edge => edge.shape.rotate(angle, center) ));
+ }
+
+ /**
+ * Return new multiline transformed using affine transformation matrix
+ * Method does not support unbounded shapes
+ * @param {Matrix} matrix - affine transformation matrix
+ * @returns {Multiline} - new multiline
+ */
+ transform(matrix = new Flatten.Matrix()) {
+ return new Multiline(this.edges.map( edge => edge.shape.transform(matrix)));
+ }
+
+ /**
+ * Transform multiline into array of shapes
+ * @returns {Shape[]}
+ */
+ toShapes() {
+ return this.edges.map(edge => edge.shape.clone())
+ }
+
+ /**
+ * This method returns an object that defines how data will be
+ * serialized when called JSON.stringify() method
+ * @returns {Object}
+ */
+ toJSON() {
+ return this.edges.map(edge => edge.toJSON());
+ }
+
+ /**
+ * Return string to draw multiline in svg
+ * @param attrs - an object with attributes for svg path element
+ * TODO: support semi-infinite Ray and infinite Line
+ * @returns {string}
+ */
+ svg(attrs = {}) {
+ let svgStr = `\n\n`;
+ return svgStr;
+ }
+ }
+
+ Flatten.Multiline = Multiline;
+
+ /**
+ * Shortcut function to create multiline
+ * @param args
+ */
+ const multiline = (...args) => new Flatten.Multiline(...args);
+ Flatten.multiline = multiline;
+
/*
Smart intersections describe intersection points that refers to the edges they intersect
This function are supposed for internal usage by morphing and relation methods between
@@ -456,11 +721,7 @@
function sortIntersections(intersections)
{
- // if (intersections.int_points1.length === 0) return;
-
// augment intersections with new sorted arrays
- // intersections.int_points1_sorted = intersections.int_points1.slice().sort(compareFn);
- // intersections.int_points2_sorted = intersections.int_points2.slice().sort(compareFn);
intersections.int_points1_sorted = getSortedArray(intersections.int_points1);
intersections.int_points2_sorted = getSortedArray(intersections.int_points2);
}
@@ -504,18 +765,6 @@
return 0;
}
- // export function getSortedArrayOnLine(line, int_points) {
- // return int_points.slice().sort( (int_point1, int_point2) => {
- // if (line.coord(int_point1.pt) < line.coord(int_point2.pt)) {
- // return -1;
- // }
- // if (line.coord(int_point1.pt) > line.coord(int_point2.pt)) {
- // return 1;
- // }
- // return 0;
- // })
- // }
-
function filterDuplicatedIntersections(intersections)
{
if (intersections.int_points1.length < 2) return;
@@ -747,14 +996,10 @@
int_point.is_vertex |= END_VERTEX$1;
}
- if (int_point.is_vertex & START_VERTEX$1) { // nothing to split
+ if (int_point.is_vertex & START_VERTEX$1) { // nothing to split
+ int_point.edge_before = edge.prev;
if (edge.prev) {
- int_point.edge_before = edge.prev; // polygon
- int_point.is_vertex = END_VERTEX$1;
- }
- else { // multiline start vertex
- int_point.edge_after = int_point.edge_before;
- int_point.edge_before = edge.prev;
+ int_point.is_vertex = END_VERTEX$1; // polygon
}
continue;
}
@@ -770,6 +1015,11 @@
if (int_point.edge_before) {
int_point.edge_after = int_point.edge_before.next;
}
+ else {
+ if (polygon instanceof Multiline && int_point.is_vertex & START_VERTEX$1) {
+ int_point.edge_after = polygon.first;
+ }
+ }
}
}
@@ -2028,547 +2278,282 @@
return ip;
}
-
- function intersectCircle2Box(circle, box) {
- let ips = [];
- for (let seg of box.toSegments()) {
- let ips_tmp = intersectSegment2Circle(seg, circle);
- for (let ip of ips_tmp) {
- ips.push(ip);
- }
- }
- return ips;
- }
-
- function intersectArc2Arc(arc1, arc2) {
- let ip = [];
-
- if (arc1.box.not_intersect(arc2.box)) {
- return ip;
- }
-
- // Special case: overlapping arcs
- // May return up to 4 intersection points
- if (arc1.pc.equalTo(arc2.pc) && Flatten.Utils.EQ(arc1.r, arc2.r)) {
- let pt;
-
- pt = arc1.start;
- if (pt.on(arc2))
- ip.push(pt);
-
- pt = arc1.end;
- if (pt.on(arc2))
- ip.push(pt);
-
- pt = arc2.start;
- if (pt.on(arc1)) ip.push(pt);
-
- pt = arc2.end;
- if (pt.on(arc1)) ip.push(pt);
-
- return ip;
- }
-
- // Common case
- let circle1 = new Flatten.Circle(arc1.pc, arc1.r);
- let circle2 = new Flatten.Circle(arc2.pc, arc2.r);
- let ip_tmp = circle1.intersect(circle2);
- for (let pt of ip_tmp) {
- if (pt.on(arc1) && pt.on(arc2)) {
- ip.push(pt);
- }
- }
- return ip;
- }
-
- function intersectArc2Circle(arc, circle) {
- let ip = [];
-
- if (arc.box.not_intersect(circle.box)) {
- return ip;
- }
-
- // Case when arc center incident to circle center
- // Return arc's end points as 2 intersection points
- if (circle.pc.equalTo(arc.pc) && Flatten.Utils.EQ(circle.r, arc.r)) {
- ip.push(arc.start);
- ip.push(arc.end);
- return ip;
- }
-
- // Common case
- let circle1 = circle;
- let circle2 = new Flatten.Circle(arc.pc, arc.r);
- let ip_tmp = intersectCircle2Circle(circle1, circle2);
- for (let pt of ip_tmp) {
- if (pt.on(arc)) {
- ip.push(pt);
- }
- }
- return ip;
- }
-
- function intersectArc2Box(arc, box) {
- let ips = [];
- for (let seg of box.toSegments()) {
- let ips_tmp = intersectSegment2Arc(seg, arc);
- for (let ip of ips_tmp) {
- ips.push(ip);
- }
- }
- return ips;
- }
-
- function intersectEdge2Segment(edge, segment) {
- return edge.isSegment ? intersectSegment2Segment(edge.shape, segment) : intersectSegment2Arc(segment, edge.shape);
- }
-
- function intersectEdge2Arc(edge, arc) {
- return edge.isSegment ? intersectSegment2Arc(edge.shape, arc) : intersectArc2Arc(edge.shape, arc);
- }
-
- function intersectEdge2Line(edge, line) {
- return edge.isSegment ? intersectSegment2Line(edge.shape, line) : intersectLine2Arc(line, edge.shape);
- }
-
- function intersectEdge2Ray(edge, ray) {
- return edge.isSegment ? intersectRay2Segment(ray, edge.shape) : intersectRay2Arc(ray, edge.shape);
- }
-
- function intersectEdge2Circle(edge, circle) {
- return edge.isSegment ? intersectSegment2Circle(edge.shape, circle) : intersectArc2Circle(edge.shape, circle);
- }
-
- function intersectSegment2Polygon(segment, polygon) {
- let ip = [];
-
- for (let edge of polygon.edges) {
- for (let pt of intersectEdge2Segment(edge, segment)) {
- ip.push(pt);
- }
- }
-
- return ip;
- }
-
- function intersectArc2Polygon(arc, polygon) {
- let ip = [];
-
- for (let edge of polygon.edges) {
- for (let pt of intersectEdge2Arc(edge, arc)) {
- ip.push(pt);
- }
- }
-
- return ip;
- }
-
- function intersectLine2Polygon(line, polygon) {
- let ip = [];
-
- if (polygon.isEmpty()) {
- return ip;
- }
-
- for (let edge of polygon.edges) {
- for (let pt of intersectEdge2Line(edge, line)) {
- if (!ptInIntPoints(pt, ip)) {
- ip.push(pt);
- }
+
+ function intersectCircle2Box(circle, box) {
+ let ips = [];
+ for (let seg of box.toSegments()) {
+ let ips_tmp = intersectSegment2Circle(seg, circle);
+ for (let ip of ips_tmp) {
+ ips.push(ip);
}
}
-
- return line.sortPoints(ip);
+ return ips;
}
- function intersectCircle2Polygon(circle, polygon) {
+ function intersectArc2Arc(arc1, arc2) {
let ip = [];
- if (polygon.isEmpty()) {
+ if (arc1.box.not_intersect(arc2.box)) {
return ip;
}
- for (let edge of polygon.edges) {
- for (let pt of intersectEdge2Circle(edge, circle)) {
+ // Special case: overlapping arcs
+ // May return up to 4 intersection points
+ if (arc1.pc.equalTo(arc2.pc) && Flatten.Utils.EQ(arc1.r, arc2.r)) {
+ let pt;
+
+ pt = arc1.start;
+ if (pt.on(arc2))
ip.push(pt);
- }
- }
- return ip;
- }
+ pt = arc1.end;
+ if (pt.on(arc2))
+ ip.push(pt);
- function intersectEdge2Edge(edge1, edge2) {
- if (edge1.isSegment) {
- return intersectEdge2Segment(edge2, edge1.shape)
- }
- else if (edge1.isArc) {
- return intersectEdge2Arc(edge2, edge1.shape)
- }
- else if (edge1.isLine) {
- return intersectEdge2Line(edge2, edge1.shape)
- }
- else if (edge1.isRay) {
- return intersectEdge2Ray(edge2, edge1.shape)
- }
- return []
- }
+ pt = arc2.start;
+ if (pt.on(arc1)) ip.push(pt);
- function intersectEdge2Polygon(edge, polygon) {
- let ip = [];
+ pt = arc2.end;
+ if (pt.on(arc1)) ip.push(pt);
- if (polygon.isEmpty() || edge.shape.box.not_intersect(polygon.box)) {
return ip;
}
- let resp_edges = polygon.edges.search(edge.shape.box);
-
- for (let resp_edge of resp_edges) {
- ip = [...ip, ...intersectEdge2Edge(edge, resp_edge)];
+ // Common case
+ let circle1 = new Flatten.Circle(arc1.pc, arc1.r);
+ let circle2 = new Flatten.Circle(arc2.pc, arc2.r);
+ let ip_tmp = circle1.intersect(circle2);
+ for (let pt of ip_tmp) {
+ if (pt.on(arc1) && pt.on(arc2)) {
+ ip.push(pt);
+ }
}
-
return ip;
}
- function intersectPolygon2Polygon(polygon1, polygon2) {
+ function intersectArc2Circle(arc, circle) {
let ip = [];
- if (polygon1.isEmpty() || polygon2.isEmpty()) {
+ if (arc.box.not_intersect(circle.box)) {
return ip;
}
- if (polygon1.box.not_intersect(polygon2.box)) {
+ // Case when arc center incident to circle center
+ // Return arc's end points as 2 intersection points
+ if (circle.pc.equalTo(arc.pc) && Flatten.Utils.EQ(circle.r, arc.r)) {
+ ip.push(arc.start);
+ ip.push(arc.end);
return ip;
}
- for (let edge1 of polygon1.edges) {
- ip = [...ip, ...intersectEdge2Polygon(edge1, polygon2)];
+ // Common case
+ let circle1 = circle;
+ let circle2 = new Flatten.Circle(arc.pc, arc.r);
+ let ip_tmp = intersectCircle2Circle(circle1, circle2);
+ for (let pt of ip_tmp) {
+ if (pt.on(arc)) {
+ ip.push(pt);
+ }
}
-
return ip;
}
- function intersectShape2Polygon(shape, polygon) {
- if (shape instanceof Flatten.Line) {
- return intersectLine2Polygon(shape, polygon);
- }
- else if (shape instanceof Flatten.Segment) {
- return intersectSegment2Polygon(shape, polygon);
- }
- else if (shape instanceof Flatten.Arc) {
- return intersectArc2Polygon(shape, polygon);
- }
- else {
- return [];
+ function intersectArc2Box(arc, box) {
+ let ips = [];
+ for (let seg of box.toSegments()) {
+ let ips_tmp = intersectSegment2Arc(seg, arc);
+ for (let ip of ips_tmp) {
+ ips.push(ip);
+ }
}
+ return ips;
}
- function ptInIntPoints(new_pt, ip) {
- return ip.some( pt => pt.equalTo(new_pt) )
- }
-
- function createLineFromRay(ray) {
- return new Flatten.Line(ray.start, ray.norm)
- }
- function intersectRay2Segment(ray, segment) {
- return intersectSegment2Line(segment, createLineFromRay(ray))
- .filter(pt => ray.contains(pt));
- }
-
- function intersectRay2Arc(ray, arc) {
- return intersectLine2Arc(createLineFromRay(ray), arc)
- .filter(pt => ray.contains(pt))
- }
-
- function intersectRay2Circle(ray, circle) {
- return intersectLine2Circle(createLineFromRay(ray), circle)
- .filter(pt => ray.contains(pt))
- }
-
- function intersectRay2Box(ray, box) {
- return intersectLine2Box(createLineFromRay(ray), box)
- .filter(pt => ray.contains(pt))
- }
-
- function intersectRay2Line(ray, line) {
- return intersectLine2Line(createLineFromRay(ray), line)
- .filter(pt => ray.contains(pt))
- }
-
- function intersectRay2Ray(ray1, ray2) {
- return intersectLine2Line(createLineFromRay(ray1), createLineFromRay(ray2))
- .filter(pt => ray1.contains(pt))
- .filter(pt => ray2.contains(pt))
+ function intersectEdge2Segment(edge, segment) {
+ return edge.isSegment ? intersectSegment2Segment(edge.shape, segment) : intersectSegment2Arc(segment, edge.shape);
}
- function intersectRay2Polygon(ray, polygon) {
- return intersectLine2Polygon(createLineFromRay(ray), polygon)
- .filter(pt => ray.contains(pt))
+ function intersectEdge2Arc(edge, arc) {
+ return edge.isSegment ? intersectSegment2Arc(edge.shape, arc) : intersectArc2Arc(edge.shape, arc);
}
- const defaultAttributes = {
- stroke: "black"
- };
-
- class SVGAttributes {
- constructor(args = defaultAttributes) {
- for(const property in args) {
- this[property] = args[property];
- }
- this.stroke = args.stroke ?? defaultAttributes.stroke;
- }
-
- toAttributesString() {
- return Object.keys(this)
- .reduce( (acc, key) =>
- acc + (this[key] !== undefined ? this.toAttrString(key, this[key]) : "")
- , ``)
- }
-
- toAttrString(key, value) {
- const SVGKey = key === "className" ? "class" : this.convertCamelToKebabCase(key);
- return value === null ? `${SVGKey} ` : `${SVGKey}="${value.toString()}" `
- }
-
- convertCamelToKebabCase(str) {
- return str
- .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
- .join('-')
- .toLowerCase();
- }
+ function intersectEdge2Line(edge, line) {
+ return edge.isSegment ? intersectSegment2Line(edge.shape, line) : intersectLine2Arc(line, edge.shape);
}
- function convertToString(attrs) {
- return new SVGAttributes(attrs).toAttributesString()
+ function intersectEdge2Ray(edge, ray) {
+ return edge.isSegment ? intersectRay2Segment(ray, edge.shape) : intersectRay2Arc(ray, edge.shape);
}
- /**
- * Class Multiline represent connected path of [edges]{@link Flatten.Edge}, where each edge may be
- * [segment]{@link Flatten.Segment}, [arc]{@link Flatten.Arc}, [line]{@link Flatten.Line} or [ray]{@link Flatten.Ray}
- */
- class Multiline extends LinkedList {
- constructor(...args) {
- super();
-
- if (args.length === 0) {
- return;
- }
-
- if (args.length === 1) {
- if (args[0] instanceof Array) {
- let shapes = args[0];
- if (shapes.length === 0)
- return;
-
- // TODO: more strict validation:
- // there may be only one line
- // only first and last may be rays
- shapes.every((shape) => {
- return shape instanceof Flatten.Segment ||
- shape instanceof Flatten.Arc ||
- shape instanceof Flatten.Ray ||
- shape instanceof Flatten.Line
- });
-
- for (let shape of shapes) {
- let edge = new Flatten.Edge(shape);
- this.append(edge);
- }
-
- this.setArcLength();
- }
- }
- }
-
- /**
- * (Getter) Return array of edges
- * @returns {Edge[]}
- */
- get edges() {
- return [...this];
- }
-
- /**
- * (Getter) Return bounding box of the multiline
- * @returns {Box}
- */
- get box() {
- return this.edges.reduce( (acc,edge) => acc.merge(edge.box), new Flatten.Box() );
- }
-
- /**
- * (Getter) Returns array of vertices
- * @returns {Point[]}
- */
- get vertices() {
- let v = this.edges.map(edge => edge.start);
- v.push(this.last.end);
- return v;
- }
-
- /**
- * Return new cloned instance of Multiline
- * @returns {Multiline}
- */
- clone() {
- return new Multiline(this.toShapes());
- }
+ function intersectEdge2Circle(edge, circle) {
+ return edge.isSegment ? intersectSegment2Circle(edge.shape, circle) : intersectArc2Circle(edge.shape, circle);
+ }
- /**
- * Set arc_length property for each of the edges in the face.
- * Arc_length of the edge it the arc length from the first edge of the face
- */
- setArcLength() {
- for (let edge of this) {
- this.setOneEdgeArcLength(edge);
+ function intersectSegment2Polygon(segment, polygon) {
+ let ip = [];
+
+ for (let edge of polygon.edges) {
+ for (let pt of intersectEdge2Segment(edge, segment)) {
+ ip.push(pt);
}
}
- setOneEdgeArcLength(edge) {
- if (edge === this.first) {
- edge.arc_length = 0.0;
- } else {
- edge.arc_length = edge.prev.arc_length + edge.prev.length;
+ return ip;
+ }
+
+ function intersectArc2Polygon(arc, polygon) {
+ let ip = [];
+
+ for (let edge of polygon.edges) {
+ for (let pt of intersectEdge2Arc(edge, arc)) {
+ ip.push(pt);
}
}
- /**
- * Split edge and add new vertex, return new edge inserted
- * @param {Point} pt - point on edge that will be added as new vertex
- * @param {Edge} edge - edge to split
- * @returns {Edge}
- */
- addVertex(pt, edge) {
- let shapes = edge.shape.split(pt);
- // if (shapes.length < 2) return;
+ return ip;
+ }
- if (shapes[0] === null) // point incident to edge start vertex, return previous edge
- return edge.prev;
+ function intersectLine2Polygon(line, polygon) {
+ let ip = [];
- if (shapes[1] === null) // point incident to edge end vertex, return edge itself
- return edge;
+ if (polygon.isEmpty()) {
+ return ip;
+ }
- let newEdge = new Flatten.Edge(shapes[0]);
- let edgeBefore = edge.prev;
+ for (let edge of polygon.edges) {
+ for (let pt of intersectEdge2Line(edge, line)) {
+ if (!ptInIntPoints(pt, ip)) {
+ ip.push(pt);
+ }
+ }
+ }
- /* Insert first split edge into linked list after edgeBefore */
- this.insert(newEdge, edgeBefore); // edge.face ?
+ return line.sortPoints(ip);
+ }
- // Update edge shape with second split edge keeping links
- edge.shape = shapes[1];
+ function intersectCircle2Polygon(circle, polygon) {
+ let ip = [];
- return newEdge;
+ if (polygon.isEmpty()) {
+ return ip;
}
- getChain(edgeFrom, edgeTo) {
- let edges = [];
- for (let edge = edgeFrom; edge !== edgeTo.next; edge = edge.next) {
- edges.push(edge);
+ for (let edge of polygon.edges) {
+ for (let pt of intersectEdge2Circle(edge, circle)) {
+ ip.push(pt);
}
- return edges
}
- /**
- * Split edges of multiline with intersection points and return mutated multiline
- * @param {Point[]} ip - array of points to be added as new vertices
- * @returns {Multiline}
- */
- split(ip) {
- for (let pt of ip) {
- let edge = this.findEdgeByPoint(pt);
- this.addVertex(pt, edge);
- }
- return this;
- }
+ return ip;
+ }
- /**
- * Returns edge which contains given point
- * @param {Point} pt
- * @returns {Edge}
- */
- findEdgeByPoint(pt) {
- let edgeFound;
- for (let edge of this) {
- if (edge.shape.contains(pt)) {
- edgeFound = edge;
- break;
- }
- }
- return edgeFound;
+ function intersectEdge2Edge(edge1, edge2) {
+ if (edge1.isSegment) {
+ return intersectEdge2Segment(edge2, edge1.shape)
+ }
+ else if (edge1.isArc) {
+ return intersectEdge2Arc(edge2, edge1.shape)
}
+ else if (edge1.isLine) {
+ return intersectEdge2Line(edge2, edge1.shape)
+ }
+ else if (edge1.isRay) {
+ return intersectEdge2Ray(edge2, edge1.shape)
+ }
+ return []
+ }
- /**
- * Returns new multiline translated by vector vec
- * @param {Vector} vec
- * @returns {Multiline}
- */
- translate(vec) {
- return new Multiline(this.edges.map( edge => edge.shape.translate(vec)));
+ function intersectEdge2Polygon(edge, polygon) {
+ let ip = [];
+
+ if (polygon.isEmpty() || edge.shape.box.not_intersect(polygon.box)) {
+ return ip;
}
- /**
- * Return new multiline rotated by given angle around given point
- * If point omitted, rotate around origin (0,0)
- * Positive value of angle defines rotation counterclockwise, negative - clockwise
- * @param {number} angle - rotation angle in radians
- * @param {Point} center - rotation center, default is (0,0)
- * @returns {Multiline} - new rotated polygon
- */
- rotate(angle = 0, center = new Flatten.Point()) {
- return new Multiline(this.edges.map( edge => edge.shape.rotate(angle, center) ));
+ let resp_edges = polygon.edges.search(edge.shape.box);
+
+ for (let resp_edge of resp_edges) {
+ ip = [...ip, ...intersectEdge2Edge(edge, resp_edge)];
}
- /**
- * Return new multiline transformed using affine transformation matrix
- * Method does not support unbounded shapes
- * @param {Matrix} matrix - affine transformation matrix
- * @returns {Multiline} - new multiline
- */
- transform(matrix = new Flatten.Matrix()) {
- return new Multiline(this.edges.map( edge => edge.shape.transform(matrix)));
+ return ip;
+ }
+
+ function intersectPolygon2Polygon(polygon1, polygon2) {
+ let ip = [];
+
+ if (polygon1.isEmpty() || polygon2.isEmpty()) {
+ return ip;
}
- /**
- * Transform multiline into array of shapes
- * @returns {Shape[]}
- */
- toShapes() {
- return this.edges.map(edge => edge.shape.clone())
+ if (polygon1.box.not_intersect(polygon2.box)) {
+ return ip;
}
- /**
- * This method returns an object that defines how data will be
- * serialized when called JSON.stringify() method
- * @returns {Object}
- */
- toJSON() {
- return this.edges.map(edge => edge.toJSON());
+ for (let edge1 of polygon1.edges) {
+ ip = [...ip, ...intersectEdge2Polygon(edge1, polygon2)];
}
- /**
- * Return string to draw multiline in svg
- * @param attrs - an object with attributes for svg path element
- * TODO: support semi-infinite Ray and infinite Line
- * @returns {string}
- */
- svg(attrs = {}) {
- let svgStr = `\n\n`;
- return svgStr;
+ return ip;
+ }
+
+ function intersectShape2Polygon(shape, polygon) {
+ if (shape instanceof Flatten.Line) {
+ return intersectLine2Polygon(shape, polygon);
+ }
+ else if (shape instanceof Flatten.Segment) {
+ return intersectSegment2Polygon(shape, polygon);
+ }
+ else if (shape instanceof Flatten.Arc) {
+ return intersectArc2Polygon(shape, polygon);
+ }
+ else {
+ return [];
}
}
- Flatten.Multiline = Multiline;
+ function ptInIntPoints(new_pt, ip) {
+ return ip.some( pt => pt.equalTo(new_pt) )
+ }
- /**
- * Shortcut function to create multiline
- * @param args
- */
- const multiline = (...args) => new Flatten.Multiline(...args);
- Flatten.multiline = multiline;
+ function createLineFromRay(ray) {
+ return new Flatten.Line(ray.start, ray.norm)
+ }
+ function intersectRay2Segment(ray, segment) {
+ return intersectSegment2Line(segment, createLineFromRay(ray))
+ .filter(pt => ray.contains(pt));
+ }
+
+ function intersectRay2Arc(ray, arc) {
+ return intersectLine2Arc(createLineFromRay(ray), arc)
+ .filter(pt => ray.contains(pt))
+ }
+
+ function intersectRay2Circle(ray, circle) {
+ return intersectLine2Circle(createLineFromRay(ray), circle)
+ .filter(pt => ray.contains(pt))
+ }
+
+ function intersectRay2Box(ray, box) {
+ return intersectLine2Box(createLineFromRay(ray), box)
+ .filter(pt => ray.contains(pt))
+ }
+
+ function intersectRay2Line(ray, line) {
+ return intersectLine2Line(createLineFromRay(ray), line)
+ .filter(pt => ray.contains(pt))
+ }
+
+ function intersectRay2Ray(ray1, ray2) {
+ return intersectLine2Line(createLineFromRay(ray1), createLineFromRay(ray2))
+ .filter(pt => ray1.contains(pt))
+ .filter(pt => ray2.contains(pt))
+ }
+
+ function intersectRay2Polygon(ray, polygon) {
+ return intersectLine2Polygon(createLineFromRay(ray), polygon)
+ .filter(pt => ray.contains(pt))
+ }
/**
* @module RayShoot
diff --git a/package.json b/package.json
index 1c5df0b..424715c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@flatten-js/core",
- "version": "1.5.2",
+ "version": "1.5.3",
"description": "Javascript library for 2d geometry",
"main": "dist/main.cjs",
"umd:main": "dist/main.umd.js",
diff --git a/src/data_structures/smart_intersections.js b/src/data_structures/smart_intersections.js
index ace7ccc..da43df6 100644
--- a/src/data_structures/smart_intersections.js
+++ b/src/data_structures/smart_intersections.js
@@ -4,6 +4,7 @@
*/
import * as Utils from "../utils/utils";
import * as Constants from '../utils/constants';
+import {Multiline} from "../classes/multiline";
export function addToIntPoints(edge, pt, int_points)
{
@@ -55,11 +56,7 @@ export function addToIntPoints(edge, pt, int_points)
export function sortIntersections(intersections)
{
- // if (intersections.int_points1.length === 0) return;
-
// augment intersections with new sorted arrays
- // intersections.int_points1_sorted = intersections.int_points1.slice().sort(compareFn);
- // intersections.int_points2_sorted = intersections.int_points2.slice().sort(compareFn);
intersections.int_points1_sorted = getSortedArray(intersections.int_points1);
intersections.int_points2_sorted = getSortedArray(intersections.int_points2);
}
@@ -103,18 +100,6 @@ function compareFn(ip1, ip2)
return 0;
}
-// export function getSortedArrayOnLine(line, int_points) {
-// return int_points.slice().sort( (int_point1, int_point2) => {
-// if (line.coord(int_point1.pt) < line.coord(int_point2.pt)) {
-// return -1;
-// }
-// if (line.coord(int_point1.pt) > line.coord(int_point2.pt)) {
-// return 1;
-// }
-// return 0;
-// })
-// }
-
export function filterDuplicatedIntersections(intersections)
{
if (intersections.int_points1.length < 2) return;
@@ -346,14 +331,10 @@ export function splitByIntersections(polygon, int_points)
int_point.is_vertex |= Constants.END_VERTEX;
}
- if (int_point.is_vertex & Constants.START_VERTEX) { // nothing to split
+ if (int_point.is_vertex & Constants.START_VERTEX) { // nothing to split
+ int_point.edge_before = edge.prev;
if (edge.prev) {
- int_point.edge_before = edge.prev; // polygon
- int_point.is_vertex = Constants.END_VERTEX;
- }
- else { // multiline start vertex
- int_point.edge_after = int_point.edge_before
- int_point.edge_before = edge.prev
+ int_point.is_vertex = Constants.END_VERTEX; // polygon
}
continue;
}
@@ -369,6 +350,11 @@ export function splitByIntersections(polygon, int_points)
if (int_point.edge_before) {
int_point.edge_after = int_point.edge_before.next;
}
+ else {
+ if (polygon instanceof Multiline && int_point.is_vertex & Constants.START_VERTEX) {
+ int_point.edge_after = polygon.first
+ }
+ }
}
}
diff --git a/test/classes/polygon.js b/test/classes/polygon.js
index bdf63dc..9e41e3a 100644
--- a/test/classes/polygon.js
+++ b/test/classes/polygon.js
@@ -933,6 +933,54 @@ describe('#Flatten.Polygon', function() {
expect(newPoly.faces.size).to.equal(2);
expect(newPoly.edges.size).to.equal(10)
})
+ it('Polygon.cut error #175', () => {
+ // Create polygon from json
+ let json = [
+ [
+ {
+ ps: {x: 641.64, y: 118.32, name: "point"},
+ pe: {x: 641.64, y: 151.74, name: "point"},
+ name: "segment"
+ },
+ {
+ ps: {x: 641.64, y: 151.74, name: "point"},
+ pe: {x: 504.66, y: 151.74, name: "point"},
+ name: "segment"
+ },
+ {
+ ps: {x: 504.66, y: 151.74, name: "point"},
+ pe: {x: 504.66, y: 118.32, name: "point"},
+ name: "segment"
+ },
+ {
+ ps: {x: 504.66, y: 118.32, name: "point"},
+ pe: {x: 641.64, y: 118.32, name: "point"},
+ name: "segment"
+ }
+ ]
+ ];
+
+ let polygon = new Polygon(json);
+
+ // Create Multiline
+ let mlj = [
+ {
+ ps: {x: 576.48, y: 118.32, name: "point"},
+ pe: {x: 576.48, y: 274.14, name: "point"},
+ name: "segment"
+ },
+ {
+ ps: {x: 576.48, y: 274.14, name: "point"},
+ pe: {x: 641.64, y: 274.14, name: "point"},
+ name: "segment"
+ }
+ ];
+ let ml = multiline(mlj.map((s) => segment(s)));
+ const newPoly = polygon.cut(ml)
+ const a = newPoly.toArray()
+ expect(newPoly.faces.size).to.equal(2);
+ expect(newPoly.edges.size).to.equal(8)
+ })
describe('#Intersections', function () {
it('Can perform intersection between polygons', function () {
const poly1 = new Polygon(