matterviz 0.3.0 → 0.3.2

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 (286) hide show
  1. package/dist/FilePicker.svelte +37 -20
  2. package/dist/Icon.svelte +2 -2
  3. package/dist/MillerIndexInput.svelte +60 -0
  4. package/dist/MillerIndexInput.svelte.d.ts +7 -0
  5. package/dist/app.css +38 -2
  6. package/dist/brillouin/BrillouinZone.svelte +20 -62
  7. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  8. package/dist/brillouin/BrillouinZoneExportPane.svelte +12 -20
  9. package/dist/brillouin/BrillouinZoneScene.svelte +2 -2
  10. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  11. package/dist/chempot-diagram/ChemPotDiagram.svelte +192 -0
  12. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
  13. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +677 -0
  14. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
  15. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2688 -0
  16. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
  17. package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -0
  18. package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
  19. package/dist/chempot-diagram/color.d.ts +10 -0
  20. package/dist/chempot-diagram/color.js +33 -0
  21. package/dist/chempot-diagram/compute.d.ts +38 -0
  22. package/dist/chempot-diagram/compute.js +650 -0
  23. package/dist/chempot-diagram/index.d.ts +5 -0
  24. package/dist/chempot-diagram/index.js +5 -0
  25. package/dist/chempot-diagram/pointer.d.ts +16 -0
  26. package/dist/chempot-diagram/pointer.js +40 -0
  27. package/dist/chempot-diagram/temperature.d.ts +15 -0
  28. package/dist/chempot-diagram/temperature.js +37 -0
  29. package/dist/chempot-diagram/types.d.ts +83 -0
  30. package/dist/chempot-diagram/types.js +27 -0
  31. package/dist/colors/index.d.ts +3 -1
  32. package/dist/colors/index.js +4 -0
  33. package/dist/composition/BarChart.svelte +13 -22
  34. package/dist/composition/BubbleChart.svelte +5 -3
  35. package/dist/composition/FormulaFilter.svelte +770 -90
  36. package/dist/composition/FormulaFilter.svelte.d.ts +37 -1
  37. package/dist/composition/PieChart.svelte +43 -18
  38. package/dist/composition/PieChart.svelte.d.ts +1 -1
  39. package/dist/constants.d.ts +1 -0
  40. package/dist/constants.js +2 -0
  41. package/dist/convex-hull/ConvexHull.svelte +14 -1
  42. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
  43. package/dist/convex-hull/ConvexHull2D.svelte +14 -45
  44. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  45. package/dist/convex-hull/ConvexHull3D.svelte +396 -134
  46. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  47. package/dist/convex-hull/ConvexHull4D.svelte +93 -42
  48. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  49. package/dist/convex-hull/ConvexHullControls.svelte +94 -31
  50. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +4 -2
  51. package/dist/convex-hull/ConvexHullStats.svelte +697 -128
  52. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
  53. package/dist/convex-hull/ConvexHullTooltip.svelte +1 -0
  54. package/dist/convex-hull/GasPressureControls.svelte +72 -38
  55. package/dist/convex-hull/GasPressureControls.svelte.d.ts +2 -1
  56. package/dist/convex-hull/TemperatureSlider.svelte +46 -19
  57. package/dist/convex-hull/TemperatureSlider.svelte.d.ts +2 -1
  58. package/dist/convex-hull/demo-temperature.d.ts +6 -0
  59. package/dist/convex-hull/demo-temperature.js +36 -0
  60. package/dist/convex-hull/gas-thermodynamics.js +16 -5
  61. package/dist/convex-hull/helpers.d.ts +7 -1
  62. package/dist/convex-hull/helpers.js +45 -15
  63. package/dist/convex-hull/index.d.ts +15 -1
  64. package/dist/convex-hull/index.js +1 -0
  65. package/dist/convex-hull/thermodynamics.d.ts +8 -21
  66. package/dist/convex-hull/thermodynamics.js +106 -17
  67. package/dist/convex-hull/types.d.ts +7 -0
  68. package/dist/convex-hull/types.js +11 -0
  69. package/dist/coordination/CoordinationBarPlot.svelte +29 -46
  70. package/dist/element/BohrAtom.svelte +1 -1
  71. package/dist/element/data.js +2 -14
  72. package/dist/element/data.json.gz +0 -0
  73. package/dist/element/index.d.ts +1 -1
  74. package/dist/element/index.js +1 -0
  75. package/dist/element/types.d.ts +1 -0
  76. package/dist/fermi-surface/FermiSurface.svelte +21 -65
  77. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  78. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  79. package/dist/fermi-surface/FermiSurfaceScene.svelte +1 -1
  80. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  81. package/dist/fermi-surface/compute.js +1 -21
  82. package/dist/fermi-surface/marching-cubes.d.ts +2 -13
  83. package/dist/fermi-surface/marching-cubes.js +2 -519
  84. package/dist/fermi-surface/parse.js +17 -23
  85. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1273 -0
  86. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
  87. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +171 -0
  88. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +31 -0
  89. package/dist/heatmap-matrix/index.d.ts +53 -0
  90. package/dist/heatmap-matrix/index.js +100 -0
  91. package/dist/heatmap-matrix/shared.d.ts +2 -0
  92. package/dist/heatmap-matrix/shared.js +4 -0
  93. package/dist/icons.d.ts +119 -0
  94. package/dist/icons.js +119 -0
  95. package/dist/index.d.ts +6 -1
  96. package/dist/index.js +6 -1
  97. package/dist/io/export.js +15 -3
  98. package/dist/io/file-drop.d.ts +7 -0
  99. package/dist/io/file-drop.js +43 -0
  100. package/dist/io/index.d.ts +2 -2
  101. package/dist/io/index.js +2 -112
  102. package/dist/io/types.d.ts +1 -0
  103. package/dist/io/url-drop.d.ts +2 -0
  104. package/dist/io/url-drop.js +118 -0
  105. package/dist/isosurface/Isosurface.svelte +231 -0
  106. package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
  107. package/dist/isosurface/IsosurfaceControls.svelte +273 -0
  108. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +9 -0
  109. package/dist/isosurface/index.d.ts +5 -0
  110. package/dist/isosurface/index.js +6 -0
  111. package/dist/isosurface/parse.d.ts +6 -0
  112. package/dist/isosurface/parse.js +548 -0
  113. package/dist/isosurface/slice.d.ts +11 -0
  114. package/dist/isosurface/slice.js +145 -0
  115. package/dist/isosurface/types.d.ts +55 -0
  116. package/dist/isosurface/types.js +178 -0
  117. package/dist/labels.d.ts +2 -1
  118. package/dist/labels.js +1 -0
  119. package/dist/layout/InfoTag.svelte +62 -62
  120. package/dist/layout/SubpageGrid.svelte +74 -0
  121. package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
  122. package/dist/layout/index.d.ts +1 -0
  123. package/dist/layout/index.js +1 -0
  124. package/dist/layout/json-tree/JsonNode.svelte +226 -53
  125. package/dist/layout/json-tree/JsonTree.svelte +425 -51
  126. package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
  127. package/dist/layout/json-tree/JsonValue.svelte +218 -97
  128. package/dist/layout/json-tree/types.d.ts +27 -2
  129. package/dist/layout/json-tree/utils.d.ts +14 -1
  130. package/dist/layout/json-tree/utils.js +254 -0
  131. package/dist/marching-cubes.d.ts +14 -0
  132. package/dist/marching-cubes.js +519 -0
  133. package/dist/math.d.ts +8 -0
  134. package/dist/math.js +374 -7
  135. package/dist/overlays/ContextMenu.svelte +3 -2
  136. package/dist/overlays/DraggablePane.svelte +163 -58
  137. package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
  138. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +232 -77
  139. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
  140. package/dist/phase-diagram/PhaseDiagramControls.svelte +32 -11
  141. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
  142. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +103 -0
  143. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
  144. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +102 -95
  145. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +7 -0
  146. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +100 -26
  147. package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
  148. package/dist/phase-diagram/index.d.ts +2 -0
  149. package/dist/phase-diagram/index.js +2 -0
  150. package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
  151. package/dist/phase-diagram/svg-to-diagram.js +865 -0
  152. package/dist/phase-diagram/types.d.ts +10 -0
  153. package/dist/phase-diagram/utils.d.ts +7 -4
  154. package/dist/phase-diagram/utils.js +149 -59
  155. package/dist/plot/AxisLabel.svelte +26 -0
  156. package/dist/plot/AxisLabel.svelte.d.ts +16 -0
  157. package/dist/plot/BarPlot.svelte +473 -228
  158. package/dist/plot/BarPlot.svelte.d.ts +3 -3
  159. package/dist/plot/BarPlotControls.svelte +3 -2
  160. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  161. package/dist/plot/ColorBar.svelte +54 -54
  162. package/dist/plot/ColorBar.svelte.d.ts +1 -1
  163. package/dist/plot/ElementScatter.svelte +4 -3
  164. package/dist/plot/FillArea.svelte +4 -1
  165. package/dist/plot/Histogram.svelte +320 -230
  166. package/dist/plot/Histogram.svelte.d.ts +2 -2
  167. package/dist/plot/HistogramControls.svelte +29 -10
  168. package/dist/plot/HistogramControls.svelte.d.ts +6 -2
  169. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +2 -2
  170. package/dist/plot/PlotControls.svelte +109 -27
  171. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  172. package/dist/plot/PlotLegend.svelte +1 -1
  173. package/dist/plot/PortalSelect.svelte +2 -1
  174. package/dist/plot/ReferenceLine.svelte +2 -1
  175. package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
  176. package/dist/plot/ReferencePlane.svelte +1 -3
  177. package/dist/plot/ScatterPlot.svelte +343 -209
  178. package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
  179. package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
  180. package/dist/plot/ScatterPlot3DControls.svelte +203 -250
  181. package/dist/plot/ScatterPlot3DScene.svelte +4 -7
  182. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
  183. package/dist/plot/ScatterPlotControls.svelte +95 -55
  184. package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
  185. package/dist/plot/ZeroLines.svelte +44 -0
  186. package/dist/plot/ZeroLines.svelte.d.ts +32 -0
  187. package/dist/plot/ZoomRect.svelte +21 -0
  188. package/dist/plot/ZoomRect.svelte.d.ts +8 -0
  189. package/dist/plot/axis-utils.d.ts +1 -1
  190. package/dist/plot/data-cleaning.js +1 -5
  191. package/dist/plot/index.d.ts +6 -2
  192. package/dist/plot/index.js +6 -2
  193. package/dist/plot/interactions.d.ts +8 -10
  194. package/dist/plot/interactions.js +10 -19
  195. package/dist/plot/layout.d.ts +7 -1
  196. package/dist/plot/layout.js +12 -4
  197. package/dist/plot/reference-line.d.ts +4 -21
  198. package/dist/plot/reference-line.js +7 -81
  199. package/dist/plot/types.d.ts +42 -17
  200. package/dist/plot/types.js +10 -0
  201. package/dist/plot/utils/label-placement.js +14 -11
  202. package/dist/plot/utils.d.ts +1 -0
  203. package/dist/plot/utils.js +14 -0
  204. package/dist/rdf/RdfPlot.svelte +55 -66
  205. package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
  206. package/dist/rdf/index.d.ts +1 -1
  207. package/dist/rdf/index.js +1 -1
  208. package/dist/settings.d.ts +5 -0
  209. package/dist/settings.js +37 -3
  210. package/dist/spectral/Bands.svelte +515 -143
  211. package/dist/spectral/Bands.svelte.d.ts +22 -2
  212. package/dist/spectral/helpers.d.ts +23 -1
  213. package/dist/spectral/helpers.js +65 -9
  214. package/dist/spectral/types.d.ts +2 -0
  215. package/dist/structure/AtomLegend.svelte +31 -10
  216. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  217. package/dist/structure/CellSelect.svelte +92 -22
  218. package/dist/structure/Lattice.svelte +2 -0
  219. package/dist/structure/Structure.svelte +716 -173
  220. package/dist/structure/Structure.svelte.d.ts +7 -2
  221. package/dist/structure/StructureControls.svelte +26 -14
  222. package/dist/structure/StructureControls.svelte.d.ts +5 -1
  223. package/dist/structure/StructureInfoPane.svelte +7 -1
  224. package/dist/structure/StructureScene.svelte +386 -95
  225. package/dist/structure/StructureScene.svelte.d.ts +15 -4
  226. package/dist/structure/atom-properties.d.ts +6 -2
  227. package/dist/structure/atom-properties.js +38 -25
  228. package/dist/structure/export.js +10 -7
  229. package/dist/structure/ferrox-wasm-types.d.ts +3 -2
  230. package/dist/structure/ferrox-wasm-types.js +0 -3
  231. package/dist/structure/ferrox-wasm.d.ts +3 -2
  232. package/dist/structure/ferrox-wasm.js +1 -2
  233. package/dist/structure/index.d.ts +7 -0
  234. package/dist/structure/index.js +22 -0
  235. package/dist/structure/parse.js +19 -16
  236. package/dist/structure/partial-occupancy.d.ts +25 -0
  237. package/dist/structure/partial-occupancy.js +102 -0
  238. package/dist/structure/validation.js +6 -3
  239. package/dist/symmetry/SymmetryStats.svelte +18 -4
  240. package/dist/symmetry/WyckoffTable.svelte +18 -10
  241. package/dist/symmetry/index.d.ts +7 -4
  242. package/dist/symmetry/index.js +83 -18
  243. package/dist/table/HeatmapTable.svelte +468 -69
  244. package/dist/table/HeatmapTable.svelte.d.ts +13 -1
  245. package/dist/table/ToggleMenu.svelte +291 -44
  246. package/dist/table/ToggleMenu.svelte.d.ts +4 -1
  247. package/dist/table/index.d.ts +3 -0
  248. package/dist/tooltip/index.d.ts +1 -1
  249. package/dist/tooltip/index.js +1 -0
  250. package/dist/trajectory/Trajectory.svelte +147 -145
  251. package/dist/trajectory/TrajectoryExportPane.svelte +13 -9
  252. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
  253. package/dist/trajectory/constants.d.ts +6 -0
  254. package/dist/trajectory/constants.js +7 -0
  255. package/dist/trajectory/extract.js +3 -5
  256. package/dist/trajectory/format-detect.d.ts +9 -0
  257. package/dist/trajectory/format-detect.js +76 -0
  258. package/dist/trajectory/frame-reader.d.ts +17 -0
  259. package/dist/trajectory/frame-reader.js +339 -0
  260. package/dist/trajectory/helpers.d.ts +15 -0
  261. package/dist/trajectory/helpers.js +187 -0
  262. package/dist/trajectory/index.d.ts +1 -0
  263. package/dist/trajectory/index.js +11 -4
  264. package/dist/trajectory/parse/ase.d.ts +2 -0
  265. package/dist/trajectory/parse/ase.js +76 -0
  266. package/dist/trajectory/parse/hdf5.d.ts +2 -0
  267. package/dist/trajectory/parse/hdf5.js +121 -0
  268. package/dist/trajectory/parse/index.d.ts +12 -0
  269. package/dist/trajectory/parse/index.js +304 -0
  270. package/dist/trajectory/parse/lammps.d.ts +5 -0
  271. package/dist/trajectory/parse/lammps.js +169 -0
  272. package/dist/trajectory/parse/vasp.d.ts +2 -0
  273. package/dist/trajectory/parse/vasp.js +65 -0
  274. package/dist/trajectory/parse/xyz.d.ts +2 -0
  275. package/dist/trajectory/parse/xyz.js +109 -0
  276. package/dist/trajectory/types.d.ts +11 -0
  277. package/dist/trajectory/types.js +1 -0
  278. package/dist/utils.d.ts +2 -0
  279. package/dist/utils.js +4 -0
  280. package/dist/xrd/XrdPlot.svelte +6 -4
  281. package/dist/xrd/calc-xrd.js +0 -1
  282. package/package.json +33 -23
  283. package/readme.md +4 -4
  284. package/dist/trajectory/parse.d.ts +0 -42
  285. package/dist/trajectory/parse.js +0 -1267
  286. /package/dist/element/{data.json.d.ts → data.json.gz.d.ts} +0 -0
package/dist/math.js CHANGED
@@ -1,3 +1,15 @@
1
+ // Generate all k-element combinations from an array.
2
+ export function combinations(arr, k) {
3
+ if (k === 0)
4
+ return [[]];
5
+ if (arr.length < k)
6
+ return [];
7
+ const [first, ...rest] = arr;
8
+ return [
9
+ ...combinations(rest, k - 1).map((combo) => [first, ...combo]),
10
+ ...combinations(rest, k),
11
+ ];
12
+ }
1
13
  export const LOG_EPS = 1e-9;
2
14
  export const EPS = 1e-10;
3
15
  export const RAD_TO_DEG = 180 / Math.PI;
@@ -130,16 +142,19 @@ function validate_matrix(mat, name) {
130
142
  return cols;
131
143
  }
132
144
  export function dot(vec1, vec2) {
145
+ const vec1_is_matrix = vec1.some((entry) => Array.isArray(entry));
146
+ const vec2_is_matrix = vec2.some((entry) => Array.isArray(entry));
133
147
  // Vector dot product
134
- if (!Array.isArray(vec1[0]) && !Array.isArray(vec2[0])) {
135
- const v1 = vec1;
136
- const v2 = vec2;
137
- if (v1.length !== v2.length)
148
+ if (!vec1_is_matrix && !vec2_is_matrix) {
149
+ const left_vec = vec1;
150
+ const right_vec = vec2;
151
+ if (left_vec.length !== right_vec.length) {
138
152
  throw new Error(`Vectors must be of same length`);
139
- return v1.reduce((sum, val, idx) => sum + val * v2[idx], 0);
153
+ }
154
+ return left_vec.reduce((sum, val, idx) => sum + val * right_vec[idx], 0);
140
155
  }
141
156
  // Matrix-vector multiplication
142
- if (Array.isArray(vec1[0]) && !Array.isArray(vec2[0])) {
157
+ if (vec1_is_matrix && !vec2_is_matrix) {
143
158
  const mat = vec1;
144
159
  const vec = vec2;
145
160
  const cols = validate_matrix(mat, `Matrix`);
@@ -149,7 +164,7 @@ export function dot(vec1, vec2) {
149
164
  return mat.map((row) => row.reduce((sum, val, idx) => sum + val * vec[idx], 0));
150
165
  }
151
166
  // Matrix-matrix multiplication
152
- if (Array.isArray(vec1[0]) && Array.isArray(vec2[0])) {
167
+ if (vec1_is_matrix && vec2_is_matrix) {
153
168
  const mat1 = vec1;
154
169
  const mat2 = vec2;
155
170
  const mat1_cols = validate_matrix(mat1, `First matrix`);
@@ -353,6 +368,10 @@ export const centered_frac = (val) => {
353
368
  wrapped -= 1;
354
369
  return wrapped || 0; // normalize -0 to 0
355
370
  };
371
+ // Element-wise equality check for two optional Vec3s.
372
+ // Returns true if both are the same reference, or both are defined with equal components.
373
+ export const vecs_equal = (vec_a, vec_b) => vec_a === vec_b || (!!vec_a && !!vec_b &&
374
+ vec_a[0] === vec_b[0] && vec_a[1] === vec_b[1] && vec_a[2] === vec_b[2]);
356
375
  // Normalize a Vec3 to unit length, returns zero vector if input is zero
357
376
  export function normalize_vec3(vec, fallback) {
358
377
  const len = Math.hypot(vec[0], vec[1], vec[2]);
@@ -360,6 +379,246 @@ export function normalize_vec3(vec, fallback) {
360
379
  return fallback ?? [0, 0, 0];
361
380
  return [vec[0] / len, vec[1] / len, vec[2] / len];
362
381
  }
382
+ // Compute orthonormal basis vectors in a plane perpendicular to `normal`.
383
+ // Uses Gram-Schmidt orthogonalization + cross product.
384
+ export function compute_in_plane_basis(normal) {
385
+ let ref_vec = [1, 0, 0];
386
+ if (Math.abs(normal[0]) > 0.9)
387
+ ref_vec = [0, 1, 0];
388
+ const dot_nr = dot(normal, ref_vec);
389
+ const u_raw = [
390
+ ref_vec[0] - dot_nr * normal[0],
391
+ ref_vec[1] - dot_nr * normal[1],
392
+ ref_vec[2] - dot_nr * normal[2],
393
+ ];
394
+ const u_vec = normalize_vec3(u_raw, [0, 1, 0]);
395
+ const v_vec = cross_3d(normal, u_vec);
396
+ return [u_vec, v_vec];
397
+ }
398
+ // Check whether N 3D points all lie on the same plane within tolerance.
399
+ // Fewer than 3 points are trivially coplanar.
400
+ // Uses cross product to find a plane normal from non-collinear edges,
401
+ // then checks all remaining points have zero distance to that plane.
402
+ export function are_coplanar(points, tolerance = 1e-6) {
403
+ if (points.length < 3)
404
+ return true;
405
+ const origin = points[0];
406
+ // Find first pair of edges from origin that are not collinear
407
+ let normal = null;
408
+ for (let idx = 1; idx < points.length - 1; idx++) {
409
+ const edge_a = [
410
+ points[idx][0] - origin[0],
411
+ points[idx][1] - origin[1],
412
+ points[idx][2] - origin[2],
413
+ ];
414
+ for (let jdx = idx + 1; jdx < points.length; jdx++) {
415
+ const edge_b = [
416
+ points[jdx][0] - origin[0],
417
+ points[jdx][1] - origin[1],
418
+ points[jdx][2] - origin[2],
419
+ ];
420
+ const cross = cross_3d(edge_a, edge_b);
421
+ const len = Math.hypot(cross[0], cross[1], cross[2]);
422
+ if (len > tolerance) {
423
+ normal = [cross[0] / len, cross[1] / len, cross[2] / len];
424
+ break;
425
+ }
426
+ }
427
+ if (normal)
428
+ break;
429
+ }
430
+ // All edges are collinear -> all points lie on a line -> coplanar
431
+ if (!normal)
432
+ return true;
433
+ const plane_d = dot(normal, origin);
434
+ for (let idx = 1; idx < points.length; idx++) {
435
+ const dist = Math.abs(dot(normal, points[idx]) - plane_d);
436
+ if (dist > tolerance)
437
+ return false;
438
+ }
439
+ return true;
440
+ }
441
+ // Merge coplanar adjacent triangles in a flat non-indexed position array.
442
+ // Takes 9 floats per triangle (3 vertices x 3 coords), groups adjacent coplanar
443
+ // triangles via union-find, then re-triangulates each group with fan triangulation
444
+ // to eliminate internal diagonal edges.
445
+ export function merge_coplanar_triangles(positions, tolerance = 1e-4) {
446
+ const n_triangles = Math.floor(positions.length / 9);
447
+ if (n_triangles === 0)
448
+ return new Float32Array(positions);
449
+ const tri_planes = [];
450
+ for (let tri_idx = 0; tri_idx < n_triangles; tri_idx++) {
451
+ const base = tri_idx * 9;
452
+ const va = [positions[base], positions[base + 1], positions[base + 2]];
453
+ const vb = [positions[base + 3], positions[base + 4], positions[base + 5]];
454
+ const vc = [positions[base + 6], positions[base + 7], positions[base + 8]];
455
+ const edge_ab = [vb[0] - va[0], vb[1] - va[1], vb[2] - va[2]];
456
+ const edge_ac = [vc[0] - va[0], vc[1] - va[1], vc[2] - va[2]];
457
+ const raw_normal = cross_3d(edge_ab, edge_ac);
458
+ const len = Math.hypot(raw_normal[0], raw_normal[1], raw_normal[2]);
459
+ if (len < tolerance) {
460
+ tri_planes.push({
461
+ verts: [va, vb, vc],
462
+ normal: [0, 0, 0],
463
+ plane_d: 0,
464
+ degenerate: true,
465
+ });
466
+ continue;
467
+ }
468
+ // Normalize and canonicalize: first non-zero component must be positive
469
+ let normal = [raw_normal[0] / len, raw_normal[1] / len, raw_normal[2] / len];
470
+ const CANON_EPS = 1e-12;
471
+ const first_nonzero = Math.abs(normal[0]) > CANON_EPS
472
+ ? normal[0]
473
+ : Math.abs(normal[1]) > CANON_EPS
474
+ ? normal[1]
475
+ : normal[2];
476
+ if (first_nonzero < 0)
477
+ normal = [-normal[0], -normal[1], -normal[2]];
478
+ const plane_d = dot(normal, va);
479
+ tri_planes.push({ verts: [va, vb, vc], normal, plane_d, degenerate: false });
480
+ }
481
+ // === Step 2: Build adjacency via edge hash map ===
482
+ // Quantize vertex to integer grid for hashing (only used for equality, not coords)
483
+ const vert_key = (v) => `${Math.round(v[0] / tolerance)},${Math.round(v[1] / tolerance)},${Math.round(v[2] / tolerance)}`;
484
+ const edge_key = (va, vb) => {
485
+ const ka = vert_key(va);
486
+ const kb = vert_key(vb);
487
+ return ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`;
488
+ };
489
+ // Map edge -> list of triangle indices sharing that edge
490
+ const edge_to_tris = new Map();
491
+ for (let tri_idx = 0; tri_idx < n_triangles; tri_idx++) {
492
+ const { verts, degenerate } = tri_planes[tri_idx];
493
+ if (degenerate)
494
+ continue;
495
+ const edges = [
496
+ edge_key(verts[0], verts[1]),
497
+ edge_key(verts[1], verts[2]),
498
+ edge_key(verts[0], verts[2]),
499
+ ];
500
+ for (const ek of edges) {
501
+ const existing = edge_to_tris.get(ek);
502
+ if (existing)
503
+ existing.push(tri_idx);
504
+ else
505
+ edge_to_tris.set(ek, [tri_idx]);
506
+ }
507
+ }
508
+ // === Step 3: Union-Find grouping of coplanar adjacent triangles ===
509
+ const parent = new Int32Array(n_triangles);
510
+ const rank = new Int32Array(n_triangles);
511
+ for (let idx = 0; idx < n_triangles; idx++)
512
+ parent[idx] = idx;
513
+ const find = (x) => {
514
+ while (parent[x] !== x) {
515
+ parent[x] = parent[parent[x]]; // path compression
516
+ x = parent[x];
517
+ }
518
+ return x;
519
+ };
520
+ const union = (a, b) => {
521
+ const ra = find(a), rb = find(b);
522
+ if (ra === rb)
523
+ return;
524
+ if (rank[ra] < rank[rb])
525
+ parent[ra] = rb;
526
+ else if (rank[ra] > rank[rb])
527
+ parent[rb] = ra;
528
+ else {
529
+ parent[rb] = ra;
530
+ rank[ra]++;
531
+ }
532
+ };
533
+ for (const tri_list of edge_to_tris.values()) {
534
+ if (tri_list.length !== 2)
535
+ continue;
536
+ const [idx_a, idx_b] = tri_list;
537
+ const pa = tri_planes[idx_a];
538
+ const pb = tri_planes[idx_b];
539
+ if (pa.degenerate || pb.degenerate)
540
+ continue;
541
+ // Check coplanarity: same canonical normal direction AND same plane distance
542
+ const normal_dot = pa.normal[0] * pb.normal[0] + pa.normal[1] * pb.normal[1] +
543
+ pa.normal[2] * pb.normal[2];
544
+ if (Math.abs(normal_dot) < 1 - tolerance)
545
+ continue;
546
+ if (Math.abs(pa.plane_d - pb.plane_d) > tolerance)
547
+ continue;
548
+ union(idx_a, idx_b);
549
+ }
550
+ // === Step 4: Collect groups ===
551
+ const groups = new Map();
552
+ for (let idx = 0; idx < n_triangles; idx++) {
553
+ const root = find(idx);
554
+ const group = groups.get(root);
555
+ if (group)
556
+ group.push(idx);
557
+ else
558
+ groups.set(root, [idx]);
559
+ }
560
+ // === Step 5: Merge each group and re-triangulate ===
561
+ const output = [];
562
+ // Push a triangle's 3 vertices (9 floats) to the output
563
+ const emit_tri = (va, vb, vc) => {
564
+ output.push(va[0], va[1], va[2], vb[0], vb[1], vb[2], vc[0], vc[1], vc[2]);
565
+ };
566
+ const emit_original = (members) => {
567
+ for (const tri_idx of members) {
568
+ const { verts } = tri_planes[tri_idx];
569
+ emit_tri(verts[0], verts[1], verts[2]);
570
+ }
571
+ };
572
+ for (const members of groups.values()) {
573
+ if (members.length === 1) {
574
+ emit_original(members);
575
+ continue;
576
+ }
577
+ const { normal } = tri_planes[members[0]];
578
+ // Collect all unique vertices from the group
579
+ const seen_keys = new Map();
580
+ for (const tri_idx of members) {
581
+ for (const vert of tri_planes[tri_idx].verts) {
582
+ const key = vert_key(vert);
583
+ if (!seen_keys.has(key))
584
+ seen_keys.set(key, vert);
585
+ }
586
+ }
587
+ const unique_verts = [...seen_keys.values()];
588
+ if (unique_verts.length < 3) {
589
+ emit_original(members);
590
+ continue;
591
+ }
592
+ // Project to 2D using in-plane basis
593
+ const [u_vec, v_vec] = compute_in_plane_basis(normal);
594
+ const pts_2d = unique_verts.map((vert) => [dot(u_vec, vert), dot(v_vec, vert)]);
595
+ const hull = convex_hull_2d(pts_2d);
596
+ if (hull.length < 3) {
597
+ emit_original(members);
598
+ continue;
599
+ }
600
+ // Map 2D hull vertices back to nearest 3D vertex
601
+ const hull_3d = hull.map((pt) => {
602
+ let best_dist = Infinity;
603
+ let best_idx = 0;
604
+ for (let idx = 0; idx < pts_2d.length; idx++) {
605
+ const du = pts_2d[idx][0] - pt[0];
606
+ const dv = pts_2d[idx][1] - pt[1];
607
+ const dist = du * du + dv * dv;
608
+ if (dist < best_dist) {
609
+ best_dist = dist;
610
+ best_idx = idx;
611
+ }
612
+ }
613
+ return unique_verts[best_idx];
614
+ });
615
+ // Fan-triangulate from hull vertex 0
616
+ for (let idx = 1; idx < hull_3d.length - 1; idx++) {
617
+ emit_tri(hull_3d[0], hull_3d[idx], hull_3d[idx + 1]);
618
+ }
619
+ }
620
+ return new Float32Array(output);
621
+ }
363
622
  // Compute axis-aligned bounding box of Vec3 vertices
364
623
  export function compute_bounding_box(vertices) {
365
624
  if (vertices.length === 0) {
@@ -461,3 +720,111 @@ export function polygon_centroid(vertices) {
461
720
  const factor = 1 / (6 * signed_area);
462
721
  return [cx * factor, cy * factor];
463
722
  }
723
+ // Solve linear system Ax = b via LU decomposition with partial pivoting.
724
+ // Returns null if the system is singular (no unique solution).
725
+ // Fast-paths for 2x2 (Cramer's rule) and 3x3 (matrix inverse).
726
+ export function solve_linear_system(A, // NxN coefficient matrix
727
+ b) {
728
+ const n = A.length;
729
+ if (n === 0 || b.length !== n || !A.every((row) => row.length === n))
730
+ return null;
731
+ // 2x2 fast path via Cramer's rule
732
+ if (n === 2) {
733
+ const det = A[0][0] * A[1][1] - A[0][1] * A[1][0];
734
+ if (Math.abs(det) < EPS)
735
+ return null;
736
+ return [
737
+ (b[0] * A[1][1] - b[1] * A[0][1]) / det,
738
+ (A[0][0] * b[1] - A[1][0] * b[0]) / det,
739
+ ];
740
+ }
741
+ // 3x3 fast path via matrix inverse
742
+ if (n === 3) {
743
+ const det = det_3x3(A);
744
+ if (Math.abs(det) < EPS)
745
+ return null;
746
+ const inv = matrix_inverse_3x3(A);
747
+ return mat3x3_vec3_multiply(inv, b);
748
+ }
749
+ // General NxN: LU decomposition with partial pivoting + forward/back substitution
750
+ const lu = A.map((row) => [...row]);
751
+ const perm = Array.from({ length: n }, (_, idx) => idx);
752
+ for (let col = 0; col < n; col++) {
753
+ // Find pivot
754
+ let max_row = col;
755
+ let max_val = Math.abs(lu[col][col]);
756
+ for (let row = col + 1; row < n; row++) {
757
+ const val = Math.abs(lu[row][col]);
758
+ if (val > max_val) {
759
+ max_val = val;
760
+ max_row = row;
761
+ }
762
+ }
763
+ if (max_val < EPS)
764
+ return null; // singular
765
+ // Swap rows
766
+ if (max_row !== col) {
767
+ ;
768
+ [lu[col], lu[max_row]] = [lu[max_row], lu[col]];
769
+ [perm[col], perm[max_row]] = [perm[max_row], perm[col]];
770
+ }
771
+ // Eliminate below pivot
772
+ const pivot = lu[col][col];
773
+ for (let row = col + 1; row < n; row++) {
774
+ const factor = lu[row][col] / pivot;
775
+ lu[row][col] = factor; // store L factor in lower triangle
776
+ for (let k = col + 1; k < n; k++) {
777
+ lu[row][k] -= factor * lu[col][k];
778
+ }
779
+ }
780
+ }
781
+ // Apply permutation to b
782
+ const pb = perm.map((idx) => b[idx]);
783
+ // Forward substitution (Ly = Pb)
784
+ for (let row = 1; row < n; row++) {
785
+ for (let col = 0; col < row; col++) {
786
+ pb[row] -= lu[row][col] * pb[col];
787
+ }
788
+ }
789
+ // Back substitution (Ux = y)
790
+ const x = new Array(n).fill(0);
791
+ for (let row = n - 1; row >= 0; row--) {
792
+ let sum = pb[row];
793
+ for (let col = row + 1; col < n; col++) {
794
+ sum -= lu[row][col] * x[col];
795
+ }
796
+ x[row] = sum / lu[row][row];
797
+ }
798
+ return x;
799
+ }
800
+ // Full 2D convex hull via Andrew's monotone chain algorithm.
801
+ // Returns vertices in counter-clockwise order.
802
+ export function convex_hull_2d(points) {
803
+ if (points.length < 3)
804
+ return [...points];
805
+ const sorted = [...points].sort((a, b) => (a[0] - b[0]) || (a[1] - b[1]));
806
+ const cross = (o, a, b) => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
807
+ // Lower hull
808
+ const lower = [];
809
+ for (const pt of sorted) {
810
+ while (lower.length >= 2 &&
811
+ cross(lower[lower.length - 2], lower[lower.length - 1], pt) <= 0) {
812
+ lower.pop();
813
+ }
814
+ lower.push(pt);
815
+ }
816
+ // Upper hull
817
+ const upper = [];
818
+ for (let idx = sorted.length - 1; idx >= 0; idx--) {
819
+ const pt = sorted[idx];
820
+ while (upper.length >= 2 &&
821
+ cross(upper[upper.length - 2], upper[upper.length - 1], pt) <= 0) {
822
+ upper.pop();
823
+ }
824
+ upper.push(pt);
825
+ }
826
+ // Remove last point of each half (it's the first point of the other)
827
+ lower.pop();
828
+ upper.pop();
829
+ return [...lower, ...upper];
830
+ }
@@ -15,7 +15,7 @@ function get_smart_position() {
15
15
  // Handle click outside to close
16
16
  function handle_click_outside(event) {
17
17
  const target = event.target;
18
- if (visible) {
18
+ if (target instanceof Element && visible) {
19
19
  const menu = target.closest(`.context-menu`);
20
20
  if (!menu)
21
21
  on_close?.();
@@ -25,7 +25,8 @@ function handle_click_outside(event) {
25
25
  function handle_right_click_outside(event) {
26
26
  if (!visible)
27
27
  return;
28
- const menu = event.target.closest(`.context-menu`);
28
+ const target = event.target;
29
+ const menu = target instanceof Element ? target.closest(`.context-menu`) : null;
29
30
  if (!menu) {
30
31
  event.preventDefault();
31
32
  on_close?.();