classes/multiline.js

  1. "use strict";
  2. import Flatten from '../flatten';
  3. import LinkedList from '../data_structures/linked_list';
  4. import {convertToString} from "../utils/attributes";
  5. /**
  6. * Class Multiline represent connected path of [edges]{@link Flatten.Edge}, where each edge may be
  7. * [segment]{@link Flatten.Segment}, [arc]{@link Flatten.Arc}, [line]{@link Flatten.Line} or [ray]{@link Flatten.Ray}
  8. */
  9. export class Multiline extends LinkedList {
  10. constructor(...args) {
  11. super();
  12. if (args.length === 0) {
  13. return;
  14. }
  15. if (args.length === 1) {
  16. if (args[0] instanceof Array) {
  17. let shapes = args[0];
  18. if (shapes.length === 0)
  19. return;
  20. // TODO: more strict validation:
  21. // there may be only one line
  22. // only first and last may be rays
  23. let validShapes = shapes.every((shape) => {
  24. return shape instanceof Flatten.Segment ||
  25. shape instanceof Flatten.Arc ||
  26. shape instanceof Flatten.Ray ||
  27. shape instanceof Flatten.Line
  28. });
  29. for (let shape of shapes) {
  30. let edge = new Flatten.Edge(shape);
  31. this.append(edge);
  32. }
  33. this.setArcLength()
  34. }
  35. }
  36. }
  37. /**
  38. * (Getter) Return array of edges
  39. * @returns {Edge[]}
  40. */
  41. get edges() {
  42. return [...this];
  43. }
  44. /**
  45. * (Getter) Return bounding box of the multiline
  46. * @returns {Box}
  47. */
  48. get box() {
  49. return this.edges.reduce( (acc,edge) => acc.merge(edge.box), new Flatten.Box() );
  50. }
  51. /**
  52. * (Getter) Returns array of vertices
  53. * @returns {Point[]}
  54. */
  55. get vertices() {
  56. let v = this.edges.map(edge => edge.start);
  57. v.push(this.last.end);
  58. return v;
  59. }
  60. /**
  61. * Return new cloned instance of Multiline
  62. * @returns {Multiline}
  63. */
  64. clone() {
  65. return new Multiline(this.toShapes());
  66. }
  67. /**
  68. * Set arc_length property for each of the edges in the face.
  69. * Arc_length of the edge it the arc length from the first edge of the face
  70. */
  71. setArcLength() {
  72. for (let edge of this) {
  73. this.setOneEdgeArcLength(edge);
  74. }
  75. }
  76. setOneEdgeArcLength(edge) {
  77. if (edge === this.first) {
  78. edge.arc_length = 0.0;
  79. } else {
  80. edge.arc_length = edge.prev.arc_length + edge.prev.length;
  81. }
  82. }
  83. /**
  84. * Split edge and add new vertex, return new edge inserted
  85. * @param {Point} pt - point on edge that will be added as new vertex
  86. * @param {Edge} edge - edge to split
  87. * @returns {Edge}
  88. */
  89. addVertex(pt, edge) {
  90. let shapes = edge.shape.split(pt);
  91. // if (shapes.length < 2) return;
  92. if (shapes[0] === null) // point incident to edge start vertex, return previous edge
  93. return edge.prev;
  94. if (shapes[1] === null) // point incident to edge end vertex, return edge itself
  95. return edge;
  96. let newEdge = new Flatten.Edge(shapes[0]);
  97. let edgeBefore = edge.prev;
  98. /* Insert first split edge into linked list after edgeBefore */
  99. this.insert(newEdge, edgeBefore); // edge.face ?
  100. // Update edge shape with second split edge keeping links
  101. edge.shape = shapes[1];
  102. return newEdge;
  103. }
  104. getChain(edgeFrom, edgeTo) {
  105. let edges = []
  106. for (let edge = edgeFrom; edge !== edgeTo.next; edge = edge.next) {
  107. edges.push(edge)
  108. }
  109. return edges
  110. }
  111. /**
  112. * Split edges of multiline with intersection points and return mutated multiline
  113. * @param {Point[]} ip - array of points to be added as new vertices
  114. * @returns {Multiline}
  115. */
  116. split(ip) {
  117. for (let pt of ip) {
  118. let edge = this.findEdgeByPoint(pt);
  119. this.addVertex(pt, edge);
  120. }
  121. return this;
  122. }
  123. /**
  124. * Returns edge which contains given point
  125. * @param {Point} pt
  126. * @returns {Edge}
  127. */
  128. findEdgeByPoint(pt) {
  129. let edgeFound;
  130. for (let edge of this) {
  131. if (edge.shape.contains(pt)) {
  132. edgeFound = edge;
  133. break;
  134. }
  135. }
  136. return edgeFound;
  137. }
  138. /**
  139. * Returns new multiline translated by vector vec
  140. * @param {Vector} vec
  141. * @returns {Multiline}
  142. */
  143. translate(vec) {
  144. return new Multiline(this.edges.map( edge => edge.shape.translate(vec)));
  145. }
  146. /**
  147. * Return new multiline rotated by given angle around given point
  148. * If point omitted, rotate around origin (0,0)
  149. * Positive value of angle defines rotation counterclockwise, negative - clockwise
  150. * @param {number} angle - rotation angle in radians
  151. * @param {Point} center - rotation center, default is (0,0)
  152. * @returns {Multiline} - new rotated polygon
  153. */
  154. rotate(angle = 0, center = new Flatten.Point()) {
  155. return new Multiline(this.edges.map( edge => edge.shape.rotate(angle, center) ));
  156. }
  157. /**
  158. * Return new multiline transformed using affine transformation matrix
  159. * Method does not support unbounded shapes
  160. * @param {Matrix} matrix - affine transformation matrix
  161. * @returns {Multiline} - new multiline
  162. */
  163. transform(matrix = new Flatten.Matrix()) {
  164. return new Multiline(this.edges.map( edge => edge.shape.transform(matrix)));
  165. }
  166. /**
  167. * Transform multiline into array of shapes
  168. * @returns {Shape[]}
  169. */
  170. toShapes() {
  171. return this.edges.map(edge => edge.shape.clone())
  172. }
  173. /**
  174. * This method returns an object that defines how data will be
  175. * serialized when called JSON.stringify() method
  176. * @returns {Object}
  177. */
  178. toJSON() {
  179. return this.edges.map(edge => edge.toJSON());
  180. }
  181. /**
  182. * Return string to be inserted into 'points' attribute of <polyline> element
  183. * @returns {string}
  184. */
  185. svgPoints() {
  186. return this.vertices.map(p => `${p.x},${p.y}`).join(' ')
  187. }
  188. /**
  189. * Return string to be assigned to 'd' attribute of <path> element
  190. * @returns {*}
  191. */
  192. dpath() {
  193. let dPathStr = `M${this.first.start.x},${this.first.start.y}`;
  194. for (let edge of this) {
  195. dPathStr += edge.svg();
  196. }
  197. return dPathStr
  198. }
  199. /**
  200. * Return string to draw multiline in svg
  201. * @param attrs - an object with attributes for svg path element
  202. * TODO: support semi-infinite Ray and infinite Line
  203. * @returns {string}
  204. */
  205. svg(attrs = {}) {
  206. let svgStr = `\n<path ${convertToString({fill: "none", ...attrs})} d="`;
  207. svgStr += `\nM${this.first.start.x},${this.first.start.y}`;
  208. for (let edge of this) {
  209. svgStr += edge.svg();
  210. }
  211. svgStr += `" >\n</path>`;
  212. return svgStr;
  213. }
  214. }
  215. Flatten.Multiline = Multiline;
  216. /**
  217. * Shortcut function to create multiline
  218. * @param args
  219. */
  220. export const multiline = (...args) => new Flatten.Multiline(...args);
  221. Flatten.multiline = multiline;