flo-mat 4.1.4 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (379) hide show
  1. package/__tests__/closest-boundary-point/get-endpoint-info.spec.ts +140 -0
  2. package/__tests__/closest-boundary-point/root-interval-to-distance-squared-interval.spec.ts +87 -0
  3. package/__tests__/cp-node/circle-identity.spec.ts +56 -0
  4. package/__tests__/cp-node/fs/get-all-on-loop-by-loop.spec.ts +55 -0
  5. package/__tests__/cp-node/fs/get-angle-between-mat-curves.spec.ts +39 -3
  6. package/__tests__/cp-node/fs/get-boundary-bezier-parts-to-next.spec.ts +87 -3
  7. package/__tests__/cp-node/fs/get-boundary-beziers-between.spec.ts +68 -3
  8. package/__tests__/cp-node/fs/get-boundary-beziers-to-next.spec.ts +41 -3
  9. package/__tests__/cp-node/fs/get-boundary-distance-to-next.spec.ts +20 -3
  10. package/__tests__/cp-node/fs/get-branch-beziers.spec.ts +42 -3
  11. package/__tests__/cp-node/fs/get-branch.spec.ts +60 -3
  12. package/__tests__/cp-node/fs/get-cp-node-ordering.spec.ts +19 -3
  13. package/__tests__/cp-node/fs/get-distance-to-next.spec.ts +37 -4
  14. package/__tests__/cp-node/fs/get-edge-direction.spec.ts +50 -3
  15. package/__tests__/cp-node/fs/get-first-exit.spec.ts +78 -3
  16. package/__tests__/cp-node/fs/get-hole-closers.spec.ts +32 -3
  17. package/__tests__/cp-node/fs/get-implied-boundary-bezier-between.spec.ts +48 -3
  18. package/__tests__/cp-node/fs/get-mat-curve-between.spec.ts +77 -3
  19. package/__tests__/cp-node/fs/get-mat-curve-to-next.spec.ts +17 -4
  20. package/__tests__/cp-node/fs/get-mat-curves-between.spec.ts +38 -3
  21. package/__tests__/cp-node/fs/get-salience.spec.ts +68 -3
  22. package/__tests__/cp-node/fs/get-vertex-forward-children.spec.ts +40 -3
  23. package/__tests__/cp-node/fs/insert-cp-node.spec.ts +87 -3
  24. package/__tests__/cp-node/fs/is-special.spec.ts +1 -1
  25. package/__tests__/cp-node/fs/remove-cp-node.spec.ts +42 -3
  26. package/__tests__/find-2-prong/calc-seperation-tolerance.spec.ts +46 -0
  27. package/__tests__/find-2-prong/get-medial/dd-get-medial-point-coeffs-same-curve.spec.ts +7 -5
  28. package/__tests__/find-2-prong/get-medial/dd-get-medial-point-coeffs.spec.ts +4 -2
  29. package/__tests__/find-2-prong/get-medial/get-closest-points.spec.ts +1 -1
  30. package/__tests__/floyd-warshall/get-graph.spec.ts +100 -0
  31. package/__tests__/get-mats-with-hole.ts +28 -0
  32. package/__tests__/get-mats1.spec.ts +61 -0
  33. package/__tests__/get-mats1.ts +75 -67
  34. package/__tests__/helpers/cp-node-by-id.ts +27 -0
  35. package/__tests__/helpers/expect-point-close.ts +42 -0
  36. package/__tests__/mat/hausdorff-distance-one-sided-exceeds.spec.ts +129 -0
  37. package/__tests__/mat/simplify-mat.spec.ts +85 -0
  38. package/__tests__/reconstruct-from-mat/reconstruct-from-mat.spec.ts +39 -0
  39. package/__tests__/specific-cases/test-svgs/achispado.svg +14 -0
  40. package/__tests__/utils/get-beziers-from-tangents.spec.ts +142 -0
  41. package/browser/index.js +17882 -13800
  42. package/browser/index.min.js +1 -1
  43. package/node/add-n-prong.ts/join-spokes.js +3 -2
  44. package/node/add-n-prong.ts/join-spokes.js.map +1 -1
  45. package/node/closest-boundary-point/cull-curve-pieces.d.ts +21 -0
  46. package/node/closest-boundary-point/cull-curve-pieces.js +40 -0
  47. package/node/closest-boundary-point/cull-curve-pieces.js.map +1 -0
  48. package/node/closest-boundary-point/get-close-boundary-points-certified.js +5 -2
  49. package/node/closest-boundary-point/get-close-boundary-points-certified.js.map +1 -1
  50. package/node/closest-boundary-point/get-endpoint-info.d.ts +39 -0
  51. package/node/closest-boundary-point/get-endpoint-info.js +78 -0
  52. package/node/closest-boundary-point/get-endpoint-info.js.map +1 -0
  53. package/node/closest-boundary-point/get-potential-closest-points-on-curve-certified.js +5 -19
  54. package/node/closest-boundary-point/get-potential-closest-points-on-curve-certified.js.map +1 -1
  55. package/node/closest-boundary-point/root-interval-to-distance-squared-interval.d.ts +17 -5
  56. package/node/closest-boundary-point/root-interval-to-distance-squared-interval.js +37 -50
  57. package/node/closest-boundary-point/root-interval-to-distance-squared-interval.js.map +1 -1
  58. package/node/corner/get-corner.d.ts +18 -1
  59. package/node/corner/get-corner.js +8 -1
  60. package/node/corner/get-corner.js.map +1 -1
  61. package/node/corner/get-interface-ccw.js.map +1 -1
  62. package/node/cp-node/cp-node.d.ts +4 -3
  63. package/node/cp-node/fs/enhance-cp-node.d.ts +9 -9
  64. package/node/cp-node/fs/get-all-on-loop-by-loop.js +0 -4
  65. package/node/cp-node/fs/get-all-on-loop-by-loop.js.map +1 -1
  66. package/node/cp-node/fs/get-angle-between-mat-curves.d.ts +1 -1
  67. package/node/cp-node/fs/get-angle-between-mat-curves.js +7 -4
  68. package/node/cp-node/fs/get-angle-between-mat-curves.js.map +1 -1
  69. package/node/cp-node/fs/get-boundary-beziers-between.d.ts +6 -4
  70. package/node/cp-node/fs/get-boundary-beziers-between.js +6 -4
  71. package/node/cp-node/fs/get-boundary-beziers-between.js.map +1 -1
  72. package/node/cp-node/fs/get-branch-beziers.d.ts +2 -1
  73. package/node/cp-node/fs/get-branch-beziers.js +3 -2
  74. package/node/cp-node/fs/get-branch-beziers.js.map +1 -1
  75. package/node/cp-node/fs/get-branch.js +1 -2
  76. package/node/cp-node/fs/get-branch.js.map +1 -1
  77. package/node/cp-node/fs/get-distance-to-next.js +4 -2
  78. package/node/cp-node/fs/get-distance-to-next.js.map +1 -1
  79. package/node/cp-node/fs/get-edge-direction.js +3 -2
  80. package/node/cp-node/fs/get-edge-direction.js.map +1 -1
  81. package/node/cp-node/fs/get-first-exit.d.ts +7 -3
  82. package/node/cp-node/fs/get-first-exit.js +13 -11
  83. package/node/cp-node/fs/get-first-exit.js.map +1 -1
  84. package/node/cp-node/fs/get-implied-boundary-bezier-between.d.ts +10 -2
  85. package/node/cp-node/fs/get-implied-boundary-bezier-between.js +11 -54
  86. package/node/cp-node/fs/get-implied-boundary-bezier-between.js.map +1 -1
  87. package/node/cp-node/fs/get-mat-curve-between.d.ts +5 -3
  88. package/node/cp-node/fs/get-mat-curve-between.js +6 -49
  89. package/node/cp-node/fs/get-mat-curve-between.js.map +1 -1
  90. package/node/cp-node/fs/get-mat-curve-to-next.d.ts +1 -1
  91. package/node/cp-node/fs/get-mat-curves-between.d.ts +8 -0
  92. package/node/cp-node/fs/get-mat-curves-between.js +9 -2
  93. package/node/cp-node/fs/get-mat-curves-between.js.map +1 -1
  94. package/node/cp-node/fs/get-real-prong-count.d.ts +2 -1
  95. package/node/cp-node/fs/get-real-prong-count.js +11 -5
  96. package/node/cp-node/fs/get-real-prong-count.js.map +1 -1
  97. package/node/cp-node/fs/get-salience.d.ts +10 -0
  98. package/node/cp-node/fs/get-salience.js +17 -1
  99. package/node/cp-node/fs/get-salience.js.map +1 -1
  100. package/node/cp-node/fs/is-fully-terminating.d.ts +4 -2
  101. package/node/cp-node/fs/is-fully-terminating.js +6 -17
  102. package/node/cp-node/fs/is-fully-terminating.js.map +1 -1
  103. package/node/cp-node/fs/is-on-same-circle.d.ts +3 -0
  104. package/node/cp-node/fs/is-on-same-circle.js +4 -4
  105. package/node/cp-node/fs/is-on-same-circle.js.map +1 -1
  106. package/node/cp-node/fs/is-on-same-loop.d.ts +1 -1
  107. package/node/cp-node/fs/is-special.d.ts +7 -0
  108. package/node/cp-node/fs/is-special.js +9 -2
  109. package/node/cp-node/fs/is-special.js.map +1 -1
  110. package/node/cp-node/fs/is-vertex.d.ts +1 -1
  111. package/node/cp-node/fs/traverse-edges.d.ts +7 -3
  112. package/node/cp-node/fs/traverse-edges.js +7 -3
  113. package/node/cp-node/fs/traverse-edges.js.map +1 -1
  114. package/node/cp-node/stringification/from-stringifyable.d.ts +2 -2
  115. package/node/cp-node/stringification/from-stringifyable.js +17 -1
  116. package/node/cp-node/stringification/from-stringifyable.js.map +1 -1
  117. package/node/cp-node/stringification/to-stringifyable.d.ts +3 -3
  118. package/node/cp-node/stringification/to-stringifyable.js +3 -3
  119. package/node/debug/debug.d.ts +26 -3
  120. package/node/debug/debug.js +16 -5
  121. package/node/debug/debug.js.map +1 -1
  122. package/node/find-2-prong/add-2-prong.js +7 -9
  123. package/node/find-2-prong/add-2-prong.js.map +1 -1
  124. package/node/find-2-prong/calc-seperation-tolerance.d.ts +28 -5
  125. package/node/find-2-prong/calc-seperation-tolerance.js +32 -89
  126. package/node/find-2-prong/calc-seperation-tolerance.js.map +1 -1
  127. package/node/find-2-prong/find-2-prong.js +8 -10
  128. package/node/find-2-prong/find-2-prong.js.map +1 -1
  129. package/node/find-2-prong/find-and-add-2-prongs.js +2 -2
  130. package/node/find-2-prong/find-and-add-2-prongs.js.map +1 -1
  131. package/node/find-2-prong/get-medial/get-antipodal-points.d.ts +1 -2
  132. package/node/find-2-prong/get-medial/get-antipodal-points.js +2 -3
  133. package/node/find-2-prong/get-medial/get-antipodal-points.js.map +1 -1
  134. package/node/find-2-prong/get-medial/get-closest-points.d.ts +1 -8
  135. package/node/find-2-prong/get-medial/get-closest-points.js +4 -40
  136. package/node/find-2-prong/get-medial/get-closest-points.js.map +1 -1
  137. package/node/find-2-prong/reduce-radius.d.ts +1 -1
  138. package/node/find-2-prong/reduce-radius.js +6 -3
  139. package/node/find-2-prong/reduce-radius.js.map +1 -1
  140. package/node/find-3-prong/calc-better-x.js +1 -1
  141. package/node/find-3-prong/calc-better-x.js.map +1 -1
  142. package/node/find-3-prong/find-3-prong-for-delta3s.d.ts +3 -2
  143. package/node/find-3-prong/find-3-prong-for-delta3s.js +36 -42
  144. package/node/find-3-prong/find-3-prong-for-delta3s.js.map +1 -1
  145. package/node/find-3-prong/find-3-prong.js +3 -1
  146. package/node/find-3-prong/find-3-prong.js.map +1 -1
  147. package/node/find-3-prong/find-and-add-3-prongs.js +16 -0
  148. package/node/find-3-prong/find-and-add-3-prongs.js.map +1 -1
  149. package/node/find-mat/create-initial-cp-tree.js +2 -2
  150. package/node/find-mat/create-initial-cp-tree.js.map +1 -1
  151. package/node/find-mat/find-mat.js +3 -4
  152. package/node/find-mat/find-mat.js.map +1 -1
  153. package/node/find-mat/find-mats.d.ts +6 -12
  154. package/node/find-mat/find-mats.js +10 -27
  155. package/node/find-mat/find-mats.js.map +1 -1
  156. package/node/find-mat/get-dull-corners-on-loop.js +2 -2
  157. package/node/find-mat/get-dull-corners-on-loop.js.map +1 -1
  158. package/node/find-mat/get-for-1-prongs-on-loop.js +36 -8
  159. package/node/find-mat/get-for-1-prongs-on-loop.js.map +1 -1
  160. package/node/find-mat/get-meta.d.ts +14 -4
  161. package/node/find-mat/get-meta.js +30 -24
  162. package/node/find-mat/get-meta.js.map +1 -1
  163. package/node/find-mat/get-sharp-corners.js +2 -2
  164. package/node/find-mat/get-sharp-corners.js.map +1 -1
  165. package/node/find-mat/mat-options.d.ts +2 -2
  166. package/node/floyd-warshall/get-graph.d.ts +12 -1
  167. package/node/floyd-warshall/get-graph.js +41 -38
  168. package/node/floyd-warshall/get-graph.js.map +1 -1
  169. package/node/geometry/get-bounding-box-.d.ts +16 -2
  170. package/node/geometry/get-bounding-box-.js +5 -2
  171. package/node/geometry/get-bounding-box-.js.map +1 -1
  172. package/node/geometry/get-closest-square-distance-to-rect.d.ts +3 -0
  173. package/node/geometry/get-closest-square-distance-to-rect.js +7 -5
  174. package/node/geometry/get-closest-square-distance-to-rect.js.map +1 -1
  175. package/node/geometry/get-closest-squared-distance-to-rotated-rect.d.ts +4 -0
  176. package/node/geometry/get-closest-squared-distance-to-rotated-rect.js +12 -5
  177. package/node/geometry/get-closest-squared-distance-to-rotated-rect.js.map +1 -1
  178. package/node/get-medial-points/double-double/dd-get-medial-point-coeffs-bez3.js +1 -2
  179. package/node/get-medial-points/double-double/dd-get-medial-point-coeffs-bez3.js.map +1 -1
  180. package/node/index.d.ts +3 -2
  181. package/node/index.js +2 -1
  182. package/node/index.js.map +1 -1
  183. package/node/mat/clone-mat.d.ts +9 -0
  184. package/node/mat/clone-mat.js +17 -0
  185. package/node/mat/clone-mat.js.map +1 -0
  186. package/node/mat/clone-meta.d.ts +12 -0
  187. package/node/mat/clone-meta.js +24 -0
  188. package/node/mat/clone-meta.js.map +1 -0
  189. package/node/mat/get-boundary-piece-beziers.d.ts +5 -0
  190. package/node/mat/get-boundary-piece-beziers.js +7 -93
  191. package/node/mat/get-boundary-piece-beziers.js.map +1 -1
  192. package/node/mat/get-closeby-cp-if-exist.js +2 -1
  193. package/node/mat/get-closeby-cp-if-exist.js.map +1 -1
  194. package/node/mat/get-largest-3-prong.d.ts +6 -0
  195. package/node/mat/get-largest-3-prong.js +8 -5
  196. package/node/mat/get-largest-3-prong.js.map +1 -1
  197. package/node/mat/get-largest-vertex.d.ts +15 -1
  198. package/node/mat/get-largest-vertex.js +20 -7
  199. package/node/mat/get-largest-vertex.js.map +1 -1
  200. package/node/mat/hausdorff-distance-one-sided-exceeds.d.ts +79 -0
  201. package/node/mat/hausdorff-distance-one-sided-exceeds.js +251 -0
  202. package/node/mat/hausdorff-distance-one-sided-exceeds.js.map +1 -0
  203. package/node/mat/mat-meta.d.ts +0 -6
  204. package/node/mat/simplify-mat.d.ts +25 -8
  205. package/node/mat/simplify-mat.js +182 -53
  206. package/node/mat/simplify-mat.js.map +1 -1
  207. package/node/mat/trim-mat.d.ts +4 -2
  208. package/node/mat/trim-mat.js +3 -1
  209. package/node/mat/trim-mat.js.map +1 -1
  210. package/node/point-on-shape/get-boundary-tangent-line.d.ts +19 -0
  211. package/node/point-on-shape/get-boundary-tangent-line.js +33 -0
  212. package/node/point-on-shape/get-boundary-tangent-line.js.map +1 -0
  213. package/node/point-on-shape/get-osculating-circle.d.ts +6 -4
  214. package/node/point-on-shape/get-osculating-circle.js +6 -4
  215. package/node/point-on-shape/get-osculating-circle.js.map +1 -1
  216. package/node/point-on-shape/get-pos-corner.js +2 -2
  217. package/node/point-on-shape/get-pos-corner.js.map +1 -1
  218. package/node/point-on-shape/point-on-shape.d.ts +5 -0
  219. package/node/reconstruct-from-mat/reconstruct-from-mat.js +20 -80
  220. package/node/reconstruct-from-mat/reconstruct-from-mat.js.map +1 -1
  221. package/node/reconstruct-from-mat/remove-sat-induced-trivial-3prongs.d.ts +4 -0
  222. package/node/reconstruct-from-mat/remove-sat-induced-trivial-3prongs.js +4 -0
  223. package/node/reconstruct-from-mat/remove-sat-induced-trivial-3prongs.js.map +1 -1
  224. package/node/sat/cull.d.ts +7 -6
  225. package/node/sat/cull.js +13 -16
  226. package/node/sat/cull.js.map +1 -1
  227. package/node/sat/get-sat-culls.d.ts +12 -2
  228. package/node/sat/get-sat-culls.js +21 -16
  229. package/node/sat/get-sat-culls.js.map +1 -1
  230. package/node/svg/get-shape-bounds.d.ts +1 -12
  231. package/node/svg/get-shape-bounds.js +2 -3
  232. package/node/svg/get-shape-bounds.js.map +1 -1
  233. package/node/sweep-line/sweep-line.js.map +1 -1
  234. package/node/utils/get-beziers-from-tangents.d.ts +28 -0
  235. package/node/utils/get-beziers-from-tangents.js +92 -0
  236. package/node/utils/get-beziers-from-tangents.js.map +1 -0
  237. package/node/vertex/remove-vertex.d.ts +5 -3
  238. package/node/vertex/remove-vertex.js +5 -3
  239. package/node/vertex/remove-vertex.js.map +1 -1
  240. package/package.json +16 -16
  241. package/qv-infinite-loop.md +120 -0
  242. package/src/add-n-prong.ts/join-spokes.ts +4 -2
  243. package/src/closest-boundary-point/cull-curve-pieces.ts +49 -0
  244. package/src/closest-boundary-point/get-close-boundary-points-certified.ts +5 -2
  245. package/src/closest-boundary-point/get-endpoint-info.ts +96 -0
  246. package/src/closest-boundary-point/get-potential-closest-points-on-curve-certified.ts +5 -23
  247. package/src/closest-boundary-point/root-interval-to-distance-squared-interval.ts +42 -60
  248. package/src/corner/get-corner.ts +10 -1
  249. package/src/corner/get-interface-ccw.ts +2 -2
  250. package/src/cp-node/cp-node.ts +4 -3
  251. package/src/cp-node/fs/get-all-on-loop-by-loop.ts +0 -4
  252. package/src/cp-node/fs/get-angle-between-mat-curves.ts +9 -5
  253. package/src/cp-node/fs/get-boundary-beziers-between.ts +10 -8
  254. package/src/cp-node/fs/get-branch-beziers.ts +3 -2
  255. package/src/cp-node/fs/get-branch.ts +1 -2
  256. package/src/cp-node/fs/get-distance-to-next.ts +4 -2
  257. package/src/cp-node/fs/get-edge-direction.ts +4 -2
  258. package/src/cp-node/fs/get-first-exit.ts +15 -14
  259. package/src/cp-node/fs/get-implied-boundary-bezier-between.ts +18 -70
  260. package/src/cp-node/fs/get-mat-curve-between.ts +10 -67
  261. package/src/cp-node/fs/get-mat-curves-between.ts +9 -2
  262. package/src/cp-node/fs/get-real-prong-count.ts +11 -7
  263. package/src/cp-node/fs/get-salience.ts +15 -2
  264. package/src/cp-node/fs/is-fully-terminating.ts +6 -22
  265. package/src/cp-node/fs/is-on-same-circle.ts +6 -7
  266. package/src/cp-node/fs/is-on-same-loop.ts +2 -2
  267. package/src/cp-node/fs/is-special.ts +11 -6
  268. package/src/cp-node/fs/is-vertex.ts +1 -1
  269. package/src/cp-node/fs/traverse-edges.ts +8 -4
  270. package/src/cp-node/stringification/from-stringifyable.ts +75 -58
  271. package/src/cp-node/stringification/to-stringifyable.ts +3 -3
  272. package/src/debug/debug.ts +44 -8
  273. package/src/find-2-prong/add-2-prong.ts +9 -11
  274. package/src/find-2-prong/calc-seperation-tolerance.ts +34 -115
  275. package/src/find-2-prong/find-2-prong.ts +9 -11
  276. package/src/find-2-prong/find-and-add-2-prongs.ts +2 -2
  277. package/src/find-2-prong/get-medial/get-antipodal-points.ts +1 -3
  278. package/src/find-2-prong/get-medial/get-closest-points.ts +6 -51
  279. package/src/find-2-prong/reduce-radius.ts +6 -4
  280. package/src/find-3-prong/calc-better-x.ts +2 -2
  281. package/src/find-3-prong/find-3-prong-for-delta3s.ts +197 -214
  282. package/src/find-3-prong/find-3-prong.ts +54 -51
  283. package/src/find-3-prong/find-and-add-3-prongs.ts +20 -1
  284. package/src/find-mat/create-initial-cp-tree.ts +2 -2
  285. package/src/find-mat/find-mat.ts +4 -6
  286. package/src/find-mat/find-mats.ts +83 -105
  287. package/src/find-mat/get-dull-corners-on-loop.ts +2 -2
  288. package/src/find-mat/get-for-1-prongs-on-loop.ts +40 -8
  289. package/src/find-mat/get-meta.ts +34 -39
  290. package/src/find-mat/get-sharp-corners.ts +2 -2
  291. package/src/find-mat/mat-options.ts +2 -2
  292. package/src/floyd-warshall/get-graph.ts +46 -47
  293. package/src/geometry/get-bounding-box-.ts +5 -2
  294. package/src/geometry/get-closest-square-distance-to-rect.ts +7 -7
  295. package/src/geometry/get-closest-squared-distance-to-rotated-rect.ts +15 -13
  296. package/src/get-medial-points/double-double/dd-get-medial-point-coeffs-bez3.ts +1 -2
  297. package/src/index.ts +3 -2
  298. package/src/mat/clone-mat.ts +9 -3
  299. package/src/mat/clone-meta.ts +8 -0
  300. package/src/mat/get-boundary-piece-beziers.ts +16 -117
  301. package/src/mat/get-closeby-cp-if-exist.ts +3 -1
  302. package/src/mat/get-largest-3-prong.ts +9 -6
  303. package/src/mat/get-largest-vertex.ts +25 -9
  304. package/src/mat/hausdorff-distance-one-sided-exceeds.ts +314 -0
  305. package/src/mat/mat-meta.ts +0 -9
  306. package/src/mat/simplify-mat.ts +254 -116
  307. package/src/mat/trim-mat.ts +6 -4
  308. package/src/point-on-shape/get-boundary-tangent-line.ts +36 -0
  309. package/src/point-on-shape/get-osculating-circle.ts +7 -5
  310. package/src/point-on-shape/get-pos-corner.ts +2 -2
  311. package/src/point-on-shape/point-on-shape.ts +5 -0
  312. package/src/reconstruct-from-mat/reconstruct-from-mat.ts +22 -111
  313. package/src/reconstruct-from-mat/remove-sat-induced-trivial-3prongs.ts +4 -0
  314. package/src/sat/cull.ts +16 -20
  315. package/src/sat/get-sat-culls.ts +28 -23
  316. package/src/svg/get-shape-bounds.ts +2 -3
  317. package/src/sweep-line/sweep-line.ts +1 -1
  318. package/src/utils/deg-to-rad.ts +4 -2
  319. package/src/utils/get-beziers-from-tangents.ts +110 -0
  320. package/src/utils/map-map.ts +1 -1
  321. package/src/utils/rad-to-deg.ts +4 -1
  322. package/src/vertex/remove-vertex.ts +5 -3
  323. package/webpack.config.cjs +7 -2
  324. package/__tests__/cp-node/fs/get-distance-between.spec.ts +0 -7
  325. package/__tests__/cp-node/fs/get-terminating-on-circle.spec.ts +0 -24
  326. package/__tests__/cp-node/fs/is-single-3-prong.spec.ts +0 -17
  327. package/__tests__/cp-node/fs/iterate-all-on-loop.spec.ts +0 -17
  328. package/__tests__/cp-node/fs/traverse-all-forward.spec.ts +0 -14
  329. package/node/closest-boundary-point/cull-bezier-pieces-1.d.ts +0 -9
  330. package/node/closest-boundary-point/cull-bezier-pieces-1.js +0 -17
  331. package/node/closest-boundary-point/cull-bezier-pieces-1.js.map +0 -1
  332. package/node/closest-boundary-point/cull-by-loose-bounding-box.d.ts +0 -13
  333. package/node/closest-boundary-point/cull-by-loose-bounding-box.js +0 -27
  334. package/node/closest-boundary-point/cull-by-loose-bounding-box.js.map +0 -1
  335. package/node/closest-boundary-point/cull-by-tight-boundary-box.d.ts +0 -11
  336. package/node/closest-boundary-point/cull-by-tight-boundary-box.js +0 -27
  337. package/node/closest-boundary-point/cull-by-tight-boundary-box.js.map +0 -1
  338. package/node/cp-node/fs/find-first.d.ts +0 -10
  339. package/node/cp-node/fs/find-first.js +0 -19
  340. package/node/cp-node/fs/find-first.js.map +0 -1
  341. package/node/debug/functions/check-ordering.d.ts +0 -4
  342. package/node/debug/functions/check-ordering.js +0 -43
  343. package/node/debug/functions/check-ordering.js.map +0 -1
  344. package/node/find-2-prong/cull-bezier-pieces.d.ts +0 -13
  345. package/node/find-2-prong/cull-bezier-pieces.js +0 -43
  346. package/node/find-2-prong/cull-bezier-pieces.js.map +0 -1
  347. package/node/floyd-warshall/get-hole-closer-pairs.d.ts +0 -8
  348. package/node/floyd-warshall/get-hole-closer-pairs.js +0 -27
  349. package/node/floyd-warshall/get-hole-closer-pairs.js.map +0 -1
  350. package/node/utils/dual-set.d.ts +0 -27
  351. package/node/utils/dual-set.js +0 -55
  352. package/node/utils/dual-set.js.map +0 -1
  353. package/node/utils/tri-map.d.ts +0 -27
  354. package/node/utils/tri-map.js +0 -49
  355. package/node/utils/tri-map.js.map +0 -1
  356. package/src/closest-boundary-point/cull-bezier-pieces-1.ts +0 -36
  357. package/src/closest-boundary-point/cull-by-loose-bounding-box.ts +0 -42
  358. package/src/closest-boundary-point/cull-by-tight-boundary-box.ts +0 -43
  359. package/src/closest-boundary-point/get-p-from-box.ts +0 -14
  360. package/src/cp-node/fs/add-distance-from.ts +0 -49
  361. package/src/cp-node/fs/get-distance-between.ts +0 -16
  362. package/src/cp-node/fs/get-terminating-on-circle.ts +0 -24
  363. package/src/cp-node/fs/is-single-3-prong.ts +0 -15
  364. package/src/cp-node/fs/iterate-all-on-loop.ts +0 -15
  365. package/src/cp-node/fs/traverse-all-forward.ts +0 -24
  366. package/src/debug/functions/draw/draw-beziers-as-single-path.ts +0 -40
  367. package/src/debug/functions/draw/draw-disk.ts +0 -34
  368. package/src/debug/functions/draw/xmlns.ts +0 -5
  369. package/src/find-2-prong/cull-bezier-pieces.ts +0 -61
  370. package/src/find-2-prong/find-and-add-2-prong.ts +0 -30
  371. package/src/find-2-prong/get-medial/get-closest-points-d.ts +0 -213
  372. package/src/find-2-prong/x-for-debugging.ts +0 -13
  373. package/src/floyd-warshall/get-hole-closer-pairs.ts +0 -35
  374. package/src/mat/get-two-prong-type.ts +0 -13
  375. package/src/point-on-shape/is-pos-dull-corner.ts +0 -18
  376. package/src/point-on-shape/is-pos-quite-dull-corner.ts +0 -18
  377. package/src/point-on-shape/is-pos-quite-sharp-corner.ts +0 -18
  378. package/src/point-on-shape/pos-to-human-string.ts +0 -18
  379. package/src/sweep-line/are-boxes-intersecting.ts +0 -37
@@ -0,0 +1,140 @@
1
+ import { expect, test } from '@jest/globals';
2
+ import type { Curve } from 'flo-boolean';
3
+ import type { FootAndEndpointInfo } from '../../src/closest-boundary-point/foot-and-endpoint-info.js';
4
+ import { evalDeCasteljauDd } from 'flo-bezier3';
5
+ import { ddDiffDouble, ddMultDd, ddAddDd, ddLt, ddGt, ddToStr } from 'double-double';
6
+ import { getEndpointInfoCertified } from '../../src/closest-boundary-point/get-endpoint-info.js';
7
+ import { squaredDistanceBetweenDd } from '../../src/find-2-prong/squared-distance-between-dd.js';
8
+ import { eps } from '../../src/error-analysis/gamma.js';
9
+
10
+
11
+ /**
12
+ * The *previous* (library) implementation, kept here for comparison only: the
13
+ * point is rounded to double precision *before* the distance is taken, which
14
+ * introduces an absolute error of about `|p|·eps/2` per coordinate; the
15
+ * returned interval only accounts for a *relative* error of `eps` in the
16
+ * squared distance and is thus too narrow whenever the point is closer to `x`
17
+ * than to the origin.
18
+ */
19
+ function getEndpointInfoUncertified(
20
+ curve: Curve,
21
+ x: number[],
22
+ t: number): FootAndEndpointInfo {
23
+
24
+ const { ps } = curve;
25
+
26
+ const p = evalDeCasteljauDd(ps, [0,t]).map(v => v[0] + v[1]);
27
+ const d = squaredDistanceBetweenDd(p, x);
28
+ const dSquaredI = [d*(1 - eps), d*(1 + eps)];
29
+
30
+ const t_ = t === 0 ? 1 : t;
31
+ const curve_ = t === 0 ? curve.prev : curve;
32
+
33
+ return { curve: curve_, t: t_, dSquaredI };
34
+ }
35
+
36
+
37
+ /**
38
+ * The squared distance from `x` to the point at `t` on `ps` in double-double
39
+ * precision (accurate to roughly 30 significant digits): the reference value.
40
+ */
41
+ function squaredDistanceDd(ps: number[][], t: number, x: number[]): number[] {
42
+ const pDd = evalDeCasteljauDd(ps, [0,t]);
43
+ const dx = ddDiffDouble(pDd[0], x[0]);
44
+ const dy = ddDiffDouble(pDd[1], x[1]);
45
+ return ddAddDd(ddMultDd(dx,dx), ddMultDd(dy,dy));
46
+ }
47
+
48
+ const contains = (I: number[], dRef: number[]) => !ddLt(dRef, [0,I[0]]) && !ddGt(dRef, [0,I[1]]);
49
+
50
+
51
+ test('getEndpointInfoCertified vs the old implementation - point far from the origin, close to x', function() {
52
+ // A cubic with a chord of about 1000 units, living around coordinate
53
+ // 1000..2000 (as e.g. glyph outlines do); the point at `t` is about
54
+ // 1300 from the origin but less than 1 unit from `x`.
55
+ const ps = [[1000,0],[1300,0],[1700,0],[2000,0]];
56
+ const t = 0.3;
57
+ const curve = { ps } as Curve;
58
+ const p = evalDeCasteljauDd(ps, [0,t]).map(v => v[0] + v[1]);
59
+ const x = [Math.floor(p[0]), 0]; // on the line, just before the point
60
+ expect(p[0] - x[0]).toBeGreaterThan(0.1);
61
+ expect(p[0] - x[0]).toBeLessThan(1);
62
+
63
+ // The reference squared distance to ~30 digits. The error from rounding
64
+ // the point to double is about |p|*eps/2 ~ 1300 * 1.1e-16 ~ 1.4e-13 in
65
+ // the coordinate, i.e. ~3e-13 in the squared distance - roughly a
66
+ // thousand times the `eps` relative error the old interval claims.
67
+ const dRef = squaredDistanceDd(ps, t, x);
68
+
69
+ const oldI = getEndpointInfoUncertified(curve, x, t).dSquaredI;
70
+ const newI = getEndpointInfoCertified(curve, x, t).dSquaredI;
71
+
72
+ console.log(
73
+ `reference d² = ${ddToStr(dRef)}\n` +
74
+ `old interval = [${oldI[0]}, ${oldI[1]}] width/d = ${(oldI[1]-oldI[0])/oldI[0]}\n` +
75
+ `new interval = [${newI[0]}, ${newI[1]}] width/d = ${(newI[1]-newI[0])/newI[0]}\n` +
76
+ `old interval contains reference: ${contains(oldI, dRef)}\n` +
77
+ `new interval contains reference: ${contains(newI, dRef)}`
78
+ );
79
+
80
+ // the old interval is far too narrow: it misses the reference value
81
+ expect(contains(oldI, dRef)).toBe(false);
82
+ // its claimed relative width is just ~2*eps (a couple of ulps)...
83
+ expect((oldI[1] - oldI[0]) / oldI[0]).toBeLessThan(4*eps);
84
+ // ...while the reference lies ~1000*eps away from it
85
+ const dRefApprox = dRef[1] + dRef[0];
86
+ expect(Math.abs(dRefApprox - oldI[1]) / oldI[1]).toBeGreaterThan(100*eps);
87
+
88
+ // the certified interval contains it and is only ~8*eps wide
89
+ expect(contains(newI, dRef)).toBe(true);
90
+ expect((newI[1] - newI[0]) / newI[0]).toBeLessThan(16*eps);
91
+ });
92
+
93
+
94
+ test('getEndpointInfoCertified - always contains the reference, old version does not', function() {
95
+ // sweep a few parameters and offsets on a curve far from the origin
96
+ const ps = [[5000,3000],[5400,3900],[6100,2200],[6000,3000]];
97
+ const curve = { ps } as Curve;
98
+
99
+ let oldMisses = 0;
100
+ let checks = 0;
101
+ for (const t of [0.1, 0.3, 0.45, 0.7, 0.9]) {
102
+ const p = evalDeCasteljauDd(ps, [0,t]).map(v => v[0] + v[1]);
103
+ for (const off of [[0.5,0.25],[-1,0.75],[2,-3],[0.1,-0.1]]) {
104
+ const x = [p[0] + off[0], p[1] + off[1]];
105
+ const dRef = squaredDistanceDd(ps, t, x);
106
+
107
+ expect(contains(getEndpointInfoCertified(curve, x, t).dSquaredI, dRef)).toBe(true);
108
+ if (!contains(getEndpointInfoUncertified(curve, x, t).dSquaredI, dRef)) { oldMisses++; }
109
+ checks++;
110
+ }
111
+ }
112
+
113
+ // the old interval misses most of the time (it is only right by luck)
114
+ expect(oldMisses).toBeGreaterThan(checks/2);
115
+ });
116
+
117
+
118
+ test('getEndpointInfoCertified - x (numerically) on the point: the lower bound is 0', function() {
119
+ const ps = [[1000,0],[1300,0],[1700,0],[2000,0]];
120
+ const curve = { ps } as Curve;
121
+ const t = 0.3;
122
+ const x = evalDeCasteljauDd(ps, [0,t]).map(v => v[0] + v[1]); // the rounded point
123
+
124
+ const [lo, hi] = getEndpointInfoCertified(curve, x, t).dSquaredI;
125
+ // the true distance is about the rounding error of the point (~1e-13)
126
+ expect(lo).toBeGreaterThanOrEqual(0);
127
+ expect(hi).toBeLessThan(1e-24);
128
+ expect(contains([lo, hi], squaredDistanceDd(ps, t, x))).toBe(true);
129
+ });
130
+
131
+
132
+ test('getEndpointInfoCertified - endpoint at t=0 is attributed to the previous curve', function() {
133
+ const prev = { ps: [[0,0],[10,0]] } as Curve;
134
+ const curve = { ps: [[10,0],[10,10]], prev } as Curve;
135
+ const info = getEndpointInfoCertified(curve, [10,-1], 0);
136
+ expect(info.curve).toBe(prev);
137
+ expect(info.t).toBe(1);
138
+ expect(info.dSquaredI[0]).toBeLessThanOrEqual(1);
139
+ expect(info.dSquaredI[1]).toBeGreaterThanOrEqual(1);
140
+ });
@@ -0,0 +1,87 @@
1
+ import { expect, test } from '@jest/globals';
2
+ import { evalDeCasteljau, getIntervalBox } from 'flo-bezier3';
3
+ import { squaredDistanceBetween } from 'flo-vector2d';
4
+ import { rootIntervalToDistanceSquaredInterval } from '../../src/closest-boundary-point/root-interval-to-distance-squared-interval.js';
5
+
6
+
7
+ test('rootIntervalToDistanceSquaredInterval - exact cases on a plain box', function() {
8
+ const box = [[10, 20], [14, 23]]; // 4 wide, 3 high
9
+
10
+ // p inside the box: the min is 0, the max is the furthest corner
11
+ {
12
+ const [lo, hi] = rootIntervalToDistanceSquaredInterval(box, [11, 21]);
13
+ expect(lo).toBe(0);
14
+ expect(hi).toBeCloseTo(3**2 + 2**2, 12); // corner [14,23]
15
+ }
16
+ // p directly below the box (within its x range): the min is the vertical
17
+ // gap, *not* the distance to a corner (which the old version returned)
18
+ {
19
+ const [lo, hi] = rootIntervalToDistanceSquaredInterval(box, [12, 15]);
20
+ expect(lo).toBeCloseTo(5**2, 12);
21
+ expect(lo).toBeLessThan(2**2 + 5**2); // nearest corner [10,20] or [14,20]
22
+ expect(hi).toBeCloseTo(2**2 + 8**2, 12);
23
+ }
24
+ // p diagonally away from the box: the min and max are both at corners
25
+ {
26
+ const [lo, hi] = rootIntervalToDistanceSquaredInterval(box, [7, 16]);
27
+ expect(lo).toBeCloseTo(3**2 + 4**2, 12); // corner [10,20]
28
+ expect(hi).toBeCloseTo(7**2 + 7**2, 12); // corner [14,23]
29
+ }
30
+ // p on the box's boundary
31
+ {
32
+ const [lo] = rootIntervalToDistanceSquaredInterval(box, [10, 21.5]);
33
+ expect(lo).toBe(0);
34
+ }
35
+ });
36
+
37
+
38
+ test('rootIntervalToDistanceSquaredInterval - encloses the distances to a curve piece', function() {
39
+ // the box engulfs the piece, so the squared distance from `p` to every
40
+ // point of the piece must lie within the returned interval
41
+ const curves = [
42
+ [[0,0],[30,80],[70,-40],[100,0]],
43
+ [[1000,2000],[1200,2500],[1800,1900],[2000,2000]],
44
+ [[-5,3],[4,9],[12,-2]],
45
+ ];
46
+ const tRanges: [number, number][] = [[0.1, 0.11], [0.5, 0.5002], [0.9, 0.95], [0, 1]];
47
+ const offsets = [[0,0],[0.3,-0.2],[5,0],[0,-7],[40,40],[-1000,1]];
48
+
49
+ for (const ps of curves) {
50
+ for (const ts of tRanges) {
51
+ const box = getIntervalBox(ps, ts);
52
+ const mid = evalDeCasteljau(ps, (ts[0]+ts[1])/2);
53
+ for (const off of offsets) {
54
+ const p = [mid[0] + off[0], mid[1] + off[1]];
55
+ const [lo, hi] = rootIntervalToDistanceSquaredInterval(box, p);
56
+ expect(lo).toBeLessThanOrEqual(hi);
57
+
58
+ for (let i=0; i<=50; i++) {
59
+ const t = ts[0] + (ts[1]-ts[0])*i/50;
60
+ const d = squaredDistanceBetween(p, evalDeCasteljau(ps, t));
61
+ expect(d).toBeGreaterThanOrEqual(lo);
62
+ expect(d).toBeLessThanOrEqual(hi);
63
+ }
64
+ }
65
+ }
66
+ }
67
+ });
68
+
69
+
70
+ test('rootIntervalToDistanceSquaredInterval - the min is tight (not a corner distance)', function() {
71
+ // A nearly flat piece of a curve with `p` sitting right above its middle:
72
+ // the true minimum distance is about the vertical gap, while the
73
+ // distance to the nearest box corner is much larger.
74
+ const ps = [[0,0],[33,0.2],[66,0.2],[100,0]];
75
+ const ts: [number, number] = [0.2, 0.8];
76
+ const box = getIntervalBox(ps, ts);
77
+ const p = [50, 10];
78
+
79
+ const [lo] = rootIntervalToDistanceSquaredInterval(box, p);
80
+ const nearestCornerSq = Math.min(
81
+ ...[[box[0][0],box[0][1]],[box[0][0],box[1][1]],[box[1][0],box[0][1]],[box[1][0],box[1][1]]]
82
+ .map(c => squaredDistanceBetween(p, c))
83
+ );
84
+ // the gap is ~10 so lo ~ 100, while the corners are ~30 away horizontally
85
+ expect(lo).toBeLessThan(11**2);
86
+ expect(nearestCornerSq).toBeGreaterThan(30**2);
87
+ });
@@ -0,0 +1,56 @@
1
+ import { expect, test } from '@jest/globals';
2
+ import type { CpNode } from '../../src/cp-node/cp-node.js';
3
+ import type { Circle } from '../../src/geometry/circle.js';
4
+ import { getPathsFromStr } from 'flo-boolean';
5
+ import { findMats } from '../../src/find-mat/find-mats.js';
6
+ import { getAllOnCircle } from '../../src/cp-node/fs/get-all-on-circle.js';
7
+ import { getAllOnLoop_ByLoop } from '../../src/cp-node/fs/get-all-on-loop-by-loop.js';
8
+ import { toStringifyable } from '../../src/cp-node/stringification/to-stringifyable.js';
9
+ import { fromStringifyable } from '../../src/cp-node/stringification/from-stringifyable.js';
10
+
11
+
12
+ /**
13
+ * Checks the `PointOnShape.circle` invariant: one `Circle` object per
14
+ * on-circle ring, shared by all of the ring's `CpNode`s and by no other ring.
15
+ */
16
+ function checkCircleIdentity(cpStart: CpNode): void {
17
+ const cpNodes = getAllOnLoop_ByLoop(cpStart).flat();
18
+
19
+ const ringOfCircle = new Map<Circle, CpNode[]>();
20
+ const seen = new Set<CpNode>();
21
+ for (const cpNode of cpNodes) {
22
+ if (seen.has(cpNode)) { continue; }
23
+
24
+ const ring = getAllOnCircle(cpNode);
25
+ const { circle } = cpNode.pointOnShape;
26
+ for (const cp of ring) {
27
+ seen.add(cp);
28
+ // shared by all of the ring's CpNodes
29
+ expect(cp.pointOnShape.circle).toBe(circle);
30
+ }
31
+
32
+ // ...and by no other ring
33
+ expect(ringOfCircle.has(circle)).toBe(false);
34
+ ringOfCircle.set(circle, ring);
35
+ }
36
+ }
37
+
38
+
39
+ test('one `Circle` object per on-circle ring (incl. hole closers)', function() {
40
+ // a square with a square hole - forces hole-closing 2-prongs
41
+ const loops = getPathsFromStr(
42
+ 'M0 0 L100 0 L100 100 L0 100 Z ' +
43
+ 'M30 30 L30 70 L70 70 L70 30 Z'
44
+ );
45
+ const { cpNode } = findMats(loops, {})[0];
46
+
47
+ // the test is only meaningful if hole closers were actually created
48
+ const cpNodes = getAllOnLoop_ByLoop(cpNode).flat();
49
+ expect(cpNodes.some(cp => cp.holeCloserTwin !== undefined)).toBe(true);
50
+
51
+ checkCircleIdentity(cpNode);
52
+
53
+ // ...and the invariant survives a (JSON) stringification round trip
54
+ const json = JSON.stringify(toStringifyable(cpNode));
55
+ checkCircleIdentity(fromStringifyable(JSON.parse(json)));
56
+ });
@@ -0,0 +1,55 @@
1
+ import { expect, test } from '@jest/globals';
2
+ import { getAllOnLoop_ByLoop } from '../../../src/cp-node/fs/get-all-on-loop-by-loop.js';
3
+ import { getAllOnLoop } from '../../../src/cp-node/fs/get-all-on-loop.js';
4
+ import { getMats1 } from '../../get-mats1.js';
5
+ import { getMatsWithHole } from '../../get-mats-with-hole.js';
6
+
7
+
8
+ test('getAllOnLoop_ByLoop - single loop', function() {
9
+ const { cpNode } = getMats1()[0];
10
+ const all = getAllOnLoop(cpNode);
11
+
12
+ for (const cp of all) {
13
+ const cpNodess = getAllOnLoop_ByLoop(cp);
14
+ expect(cpNodess.length).toBe(1);
15
+ expect(cpNodess[0].length).toBe(all.length);
16
+ expect(cpNodess[0][0]).toBe(cp);
17
+ // same nodes, in the same (boundary) order
18
+ const expected = getAllOnLoop(cp);
19
+ expect(cpNodess[0].every((c, i) => c === expected[i])).toBe(true);
20
+ }
21
+ });
22
+
23
+
24
+ test('getAllOnLoop_ByLoop - two loops joined by hole closers', function() {
25
+ const { cpNode } = getMatsWithHole()[0];
26
+ // `getAllOnLoop` follows `next` across the hole closers and so visits
27
+ // the nodes of *both* loops
28
+ const all = getAllOnLoop(cpNode);
29
+ const loops = new Set(all.map(cp => cp.pointOnShape.curve.loop));
30
+ expect(loops.size).toBe(2);
31
+
32
+ // From *every* start node (in particular from hole closers, which used
33
+ // to drop the other loop) every node must be returned exactly once,
34
+ // grouped by loop.
35
+ for (const cp of all) {
36
+ const cpNodess = getAllOnLoop_ByLoop(cp);
37
+
38
+ expect(cpNodess.length).toBe(2);
39
+ const flat = cpNodess.flat();
40
+ expect(flat.length).toBe(all.length);
41
+ expect(new Set(flat).size).toBe(all.length);
42
+ for (const c of all) { expect(flat.includes(c)).toBe(true); }
43
+
44
+ // the first group starts at the given node
45
+ expect(cpNodess[0][0]).toBe(cp);
46
+
47
+ // each group is on a single loop and the groups are on different ones
48
+ const groupLoops = cpNodess.map(cpNodes => {
49
+ const loop = cpNodes[0].pointOnShape.curve.loop;
50
+ for (const c of cpNodes) { expect(c.pointOnShape.curve.loop).toBe(loop); }
51
+ return loop;
52
+ });
53
+ expect(new Set(groupLoops).size).toBe(2);
54
+ }
55
+ });
@@ -1,7 +1,43 @@
1
1
  import { expect, test } from '@jest/globals';
2
2
  import { getInitialDegAngleBetweenMatCurves } from '../../../src/cp-node/fs/get-angle-between-mat-curves.js';
3
+ import { getAllOnLoop } from '../../../src/cp-node/fs/get-all-on-loop.js';
4
+ import { isTerminating } from '../../../src/cp-node/fs/is-terminating.js';
5
+ import { getMats1 } from '../../get-mats1.js';
6
+ import { cpNodesByIdOn } from '../../helpers/cp-node-by-id.js';
3
7
 
4
- test.skip('getInitialDegAngleBetweenMatCurves [STUB]', () => {
5
- // TODO: implement test
6
- expect(true).toBe(true);
8
+
9
+ test('getInitialDegAngleBetweenMatCurves', function() {
10
+ const { cpNode } = getMats1()[0];
11
+ const byId = cpNodesByIdOn(cpNode);
12
+ const angle = (id1: number, id2: number) =>
13
+ getInitialDegAngleBetweenMatCurves(byId(id1), byId(id2));
14
+
15
+ // The angle is between the *initial* tangents of the MAT edges leaving
16
+ // the two `CpNode`s, folded into [0,90] (it is `|asin(cross)|`).
17
+ const nonTerminating = getAllOnLoop(cpNode).filter(cp => !isTerminating(cp));
18
+ for (const a of nonTerminating) {
19
+ expect(getInitialDegAngleBetweenMatCurves(a, a)).toBeCloseTo(0, 10);
20
+ for (const b of nonTerminating) {
21
+ const ab = getInitialDegAngleBetweenMatCurves(a, b);
22
+ const ba = getInitialDegAngleBetweenMatCurves(b, a);
23
+ expect(ab).toBeCloseTo(ba, 10);
24
+ expect(ab).toBeGreaterThanOrEqual(0);
25
+ expect(ab).toBeLessThanOrEqual(90);
26
+ }
27
+ }
28
+
29
+ // The 3-prong with center [81.5,18.5] (ids 147, 145, 146) has contact
30
+ // points [46,18.5], [81.5,54] and [117,18.5]; its edges leave along the
31
+ // bisectors (-1,1), (1,1) and (0,-1).
32
+ expect(angle(147, 145)).toBeCloseTo(90, 8);
33
+ expect(angle(147, 146)).toBeCloseTo(45, 8); // 135 degrees folded into [0,90]
34
+ expect(angle(145, 146)).toBeCloseTo(45, 8);
35
+
36
+ // The medial axis is smooth through a 2-prong: the edge leaving the sharp
37
+ // corner [24,8] (id 1) and the edge continuing from the 2-prong it reaches
38
+ // (id 10) share a tangent.
39
+ expect(angle(1, 10)).toBeCloseTo(0, 6);
40
+ // ...and the two edges leaving a 2-prong (ids 10 and 11) are exactly
41
+ // opposite, which also folds to 0.
42
+ expect(angle(10, 11)).toBeCloseTo(0, 6);
7
43
  });
@@ -1,7 +1,91 @@
1
1
  import { expect, test } from '@jest/globals';
2
+ import { fromTo, totalLength } from 'flo-bezier3';
2
3
  import { getBoundaryBezierPartsToNext } from '../../../src/cp-node/fs/get-boundary-bezier-parts-to-next.js';
4
+ import { getAllOnLoop } from '../../../src/cp-node/fs/get-all-on-loop.js';
5
+ import { getAllOnLoop_ByLoop } from '../../../src/cp-node/fs/get-all-on-loop-by-loop.js';
6
+ import { getMats1 } from '../../get-mats1.js';
7
+ import { getMatsWithHole } from '../../get-mats-with-hole.js';
8
+ import { cpNodesByIdOn } from '../../helpers/cp-node-by-id.js';
3
9
 
4
- test.skip('getBoundaryBezierPartsToNext [STUB]', () => {
5
- // TODO: implement test
6
- expect(true).toBe(true);
10
+
11
+ test('getBoundaryBezierPartsToNext', function() {
12
+ const { cpNode } = getMats1()[0];
13
+ const byId = cpNodesByIdOn(cpNode);
14
+
15
+ {
16
+ // id 1 is at the end (t=1) of curve 0 and its next (id 10) at the end
17
+ // of curve 1 - so a degenerate piece of curve 0 and all of curve 1.
18
+ const cp = byId(1);
19
+ const parts = getBoundaryBezierPartsToNext(cp);
20
+ expect(parts.length).toBe(2);
21
+ expect(parts[0].ps).toBe(cp.pointOnShape.curve.ps);
22
+ expect(parts[0].ts).toEqual([1, 1]);
23
+ expect(parts[1].ps).toBe(cp.next.pointOnShape.curve.ps);
24
+ expect(parts[1].ts).toEqual([0, 1]);
25
+ }
26
+ {
27
+ // id 147 and its next (id 2) are both on curve 2 - a single piece.
28
+ const cp = byId(147);
29
+ const parts = getBoundaryBezierPartsToNext(cp);
30
+ expect(parts.length).toBe(1);
31
+ expect(parts[0].ps).toBe(cp.pointOnShape.curve.ps);
32
+ expect(parts[0].ts).toEqual([cp.pointOnShape.t, 1]);
33
+ expect(parts[0].ts[0]).toBeCloseTo(0.2282608695652173, 15);
34
+ }
35
+ {
36
+ // ids 150 -> 143 are both strictly inside curve 0
37
+ const cp = byId(150);
38
+ const parts = getBoundaryBezierPartsToNext(cp);
39
+ expect(parts.length).toBe(1);
40
+ expect(parts[0].ts).toEqual([cp.pointOnShape.t, cp.next.pointOnShape.t]);
41
+ expect(parts[0].ts[0]).toBeLessThan(parts[0].ts[1]);
42
+ }
43
+
44
+ // Structural invariants for every node: starts at the (curve,t) of this
45
+ // node, ends at the (curve,t) of the next node, skipped curves are taken
46
+ // whole, and the parameter intervals are ordered.
47
+ let total = 0;
48
+ for (const cp of getAllOnLoop(cpNode)) {
49
+ const parts = getBoundaryBezierPartsToNext(cp);
50
+ expect(parts.length).toBeGreaterThan(0);
51
+
52
+ const first = parts[0];
53
+ const last = parts[parts.length-1];
54
+ expect(first.ps).toBe(cp.pointOnShape.curve.ps);
55
+ expect(first.ts[0]).toBe(cp.pointOnShape.t);
56
+ expect(last.ps).toBe(cp.next.pointOnShape.curve.ps);
57
+ expect(last.ts[1]).toBe(cp.next.pointOnShape.t);
58
+ for (let i=1; i<parts.length; i++) {
59
+ expect(parts[i].ts[0]).toBe(0);
60
+ if (i < parts.length-1) { expect(parts[i].ts[1]).toBe(1); }
61
+ }
62
+ if (parts.length > 1) { expect(first.ts[1]).toBe(1); }
63
+ for (const { ts } of parts) { expect(ts[0]).toBeLessThanOrEqual(ts[1]); }
64
+
65
+ total += parts.reduce((s, bp) => s + totalLength(fromTo(bp.ps, bp.ts[0], bp.ts[1])), 0);
66
+ }
67
+
68
+ // ...and all the pieces together cover the boundary exactly once.
69
+ const perimeter = cpNode.pointOnShape.curve.loop.curves
70
+ .reduce((s, curve) => s + totalLength(curve.ps), 0);
71
+ expect(total).toBeCloseTo(perimeter, 8);
72
+ });
73
+
74
+
75
+ test('getBoundaryBezierPartsToNext - hole closers', function() {
76
+ const { cpNode } = getMatsWithHole()[0];
77
+ const cpNodes = getAllOnLoop_ByLoop(cpNode).flat();
78
+
79
+ // `[]` if and only if the next `CpNode` is on another loop
80
+ let crossings = 0;
81
+ for (const cp of cpNodes) {
82
+ const crossesLoops = cp.pointOnShape.curve.loop !== cp.next.pointOnShape.curve.loop;
83
+ const parts = getBoundaryBezierPartsToNext(cp);
84
+ expect(parts.length === 0).toBe(crossesLoops);
85
+ if (crossesLoops) {
86
+ crossings++;
87
+ expect(cp.isHoleClosing).toBe(true);
88
+ }
89
+ }
90
+ expect(crossings).toBeGreaterThan(0);
7
91
  });
@@ -1,7 +1,72 @@
1
1
  import { expect, test } from '@jest/globals';
2
+ import { totalLength } from 'flo-bezier3';
2
3
  import { getBoundaryBeziersBetween } from '../../../src/cp-node/fs/get-boundary-beziers-between.js';
4
+ import { getBoundaryBeziersToNext } from '../../../src/cp-node/fs/get-boundary-beziers-to-next.js';
5
+ import { getAllOnLoop } from '../../../src/cp-node/fs/get-all-on-loop.js';
6
+ import { getMats1 } from '../../get-mats1.js';
7
+ import { getMatsWithHole } from '../../get-mats-with-hole.js';
8
+ import { cpNodesByIdOn } from '../../helpers/cp-node-by-id.js';
9
+ import { expectBeziersConnected, expectPointClose } from '../../helpers/expect-point-close.js';
3
10
 
4
- test.skip('getBoundaryBeziersBetween [STUB]', () => {
5
- // TODO: implement test
6
- expect(true).toBe(true);
11
+
12
+ const length = (pss: number[][][]) => pss.reduce((s, ps) => s + totalLength(ps), 0);
13
+
14
+
15
+ test('getBoundaryBeziersBetween', function() {
16
+ const { cpNode } = getMats1()[0];
17
+ const byId = cpNodesByIdOn(cpNode);
18
+
19
+ // to the next node: same as `getBoundaryBeziersToNext`
20
+ for (const cp of getAllOnLoop(cpNode)) {
21
+ const { pss, hasHoleCloser } = getBoundaryBeziersBetween(cp, cp.next);
22
+ expect(pss).toEqual(getBoundaryBeziersToNext(cp));
23
+ expect(hasHoleCloser).toBe(false);
24
+ }
25
+
26
+ {
27
+ // from [46,18.5] (id 147) around two corners to [117,54] (id 4):
28
+ // 35.5 up, 71 across - the piece leaving the end node is not included
29
+ const { pss, hasHoleCloser } = getBoundaryBeziersBetween(byId(147), byId(4));
30
+ expect(hasHoleCloser).toBe(false);
31
+ expectPointClose(pss[0][0], [46,18.5]);
32
+ expectPointClose(pss[pss.length-1][1], [117,54]);
33
+ expectBeziersConnected(pss);
34
+ expect(length(pss)).toBeCloseTo(35.5 + 71, 8);
35
+ }
36
+
37
+ // equal start and end: everything or nothing
38
+ const perimeter = cpNode.pointOnShape.curve.loop.curves
39
+ .reduce((s, curve) => s + totalLength(curve.ps), 0);
40
+ {
41
+ const { pss } = getBoundaryBeziersBetween(cpNode, cpNode);
42
+ expect(length(pss)).toBeCloseTo(perimeter, 8);
43
+ expectBeziersConnected(pss);
44
+ expectPointClose(pss[pss.length-1][1], pss[0][0]);
45
+ }
46
+ {
47
+ const { pss } = getBoundaryBeziersBetween(cpNode, cpNode, true);
48
+ expect(length(pss)).toBeCloseTo(perimeter, 8);
49
+ }
50
+ {
51
+ const { pss, hasHoleCloser } = getBoundaryBeziersBetween(cpNode, cpNode, false);
52
+ expect(pss).toEqual([]);
53
+ expect(hasHoleCloser).toBe(false);
54
+ }
55
+ });
56
+
57
+
58
+ test('getBoundaryBeziersBetween - hole closers', function() {
59
+ const { cpNode } = getMatsWithHole()[0];
60
+
61
+ // going all the way round passes the hole closers
62
+ const { hasHoleCloser } = getBoundaryBeziersBetween(cpNode, cpNode);
63
+ expect(hasHoleCloser).toBe(true);
64
+
65
+ // ...but a stretch without any hole closer reports none
66
+ const cps = getAllOnLoop(cpNode);
67
+ const i = cps.findIndex((cp, i) =>
68
+ !cp.isHoleClosing && !cps[(i+1) % cps.length].isHoleClosing);
69
+ expect(i).toBeGreaterThanOrEqual(0);
70
+ const cp = cps[i];
71
+ expect(getBoundaryBeziersBetween(cp, cp.next.next).hasHoleCloser).toBe(false);
7
72
  });
@@ -1,7 +1,45 @@
1
1
  import { expect, test } from '@jest/globals';
2
2
  import { getBoundaryBeziersToNext } from '../../../src/cp-node/fs/get-boundary-beziers-to-next.js';
3
+ import { getAllOnLoop } from '../../../src/cp-node/fs/get-all-on-loop.js';
4
+ import { getMats1 } from '../../get-mats1.js';
5
+ import { cpNodesByIdOn } from '../../helpers/cp-node-by-id.js';
6
+ import { expectBezierClose, expectBeziersConnected, expectPointClose } from '../../helpers/expect-point-close.js';
3
7
 
4
- test.skip('getBoundaryBeziersToNext [STUB]', () => {
5
- // TODO: implement test
6
- expect(true).toBe(true);
8
+
9
+ test('getBoundaryBeziersToNext', function() {
10
+ const { cpNode } = getMats1()[0];
11
+ const byId = cpNodesByIdOn(cpNode);
12
+
13
+ {
14
+ // from the corner [24,8] (end of curve 0) to [46,8] (end of curve 1)
15
+ const pss = getBoundaryBeziersToNext(byId(1));
16
+ expect(pss.length).toBe(2);
17
+ expectBezierClose(pss[0], [[24,8],[24,8]]); // degenerate rest of curve 0
18
+ expectBezierClose(pss[1], [[24,8],[46,8]]); // all of curve 1
19
+ }
20
+ {
21
+ // from [46,18.5] up to the corner [46,54] - part of a single line
22
+ const pss = getBoundaryBeziersToNext(byId(147));
23
+ expect(pss.length).toBe(1);
24
+ expectBezierClose(pss[0], [[46,18.5],[46,54]]);
25
+ }
26
+ {
27
+ // two nodes at the same point (a 1-prong corner) - zero length
28
+ const pss = getBoundaryBeziersToNext(byId(0));
29
+ expect(pss.length).toBe(1);
30
+ expectBezierClose(pss[0], [[24,8],[24,8]]);
31
+ }
32
+
33
+ // For every node the beziers run from the point of this node to that of
34
+ // the next, joined end to start.
35
+ for (const cp of getAllOnLoop(cpNode)) {
36
+ const pss = getBoundaryBeziersToNext(cp);
37
+ expect(pss.length).toBeGreaterThan(0);
38
+ expectPointClose(pss[0][0], cp.pointOnShape.p);
39
+ const last = pss[pss.length-1];
40
+ expectPointClose(last[last.length-1], cp.next.pointOnShape.p);
41
+ expectBeziersConnected(pss);
42
+ // the fixture boundary consists of lines only
43
+ for (const ps of pss) { expect(ps.length).toBe(2); }
44
+ }
7
45
  });
@@ -1,7 +1,24 @@
1
1
  import { expect, test } from '@jest/globals';
2
+ import { totalLength } from 'flo-bezier3';
2
3
  import { getBoundaryDistanceToNext } from '../../../src/cp-node/fs/get-boundary-distance-to-next.js';
4
+ import { getAllOnLoop } from '../../../src/cp-node/fs/get-all-on-loop.js';
5
+ import { getMats1 } from '../../get-mats1.js';
6
+ import { cpNodesByIdOn } from '../../helpers/cp-node-by-id.js';
3
7
 
4
- test.skip('getBoundaryDistanceToNext [STUB]', () => {
5
- // TODO: implement test
6
- expect(true).toBe(true);
8
+
9
+ test('getBoundaryDistanceToNext', function() {
10
+ const { cpNode } = getMats1()[0];
11
+ const byId = cpNodesByIdOn(cpNode);
12
+
13
+ expect(getBoundaryDistanceToNext(byId(1))).toBeCloseTo(22, 10); // [24,8] -> [46,8]
14
+ expect(getBoundaryDistanceToNext(byId(147))).toBeCloseTo(35.5, 10); // [46,18.5] -> [46,54]
15
+ expect(getBoundaryDistanceToNext(byId(144))).toBeCloseTo(10.5, 10); // [46,8] -> [46,18.5] (crosses a corner)
16
+ expect(getBoundaryDistanceToNext(byId(0))).toBe(0); // two nodes at the same corner point
17
+
18
+ // the distances around the loop add up to the perimeter
19
+ const perimeter = cpNode.pointOnShape.curve.loop.curves
20
+ .reduce((s, curve) => s + totalLength(curve.ps), 0);
21
+ const total = getAllOnLoop(cpNode)
22
+ .reduce((s, cp) => s + getBoundaryDistanceToNext(cp), 0);
23
+ expect(total).toBeCloseTo(perimeter, 8);
7
24
  });