matterviz 0.4.2 → 0.4.3

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 (244) hide show
  1. package/dist/FilePicker.svelte +40 -50
  2. package/dist/api/optimade.js +10 -7
  3. package/dist/app.css +15 -0
  4. package/dist/brillouin/BrillouinZone.svelte +18 -9
  5. package/dist/brillouin/BrillouinZoneScene.svelte +20 -29
  6. package/dist/brillouin/compute.js +10 -16
  7. package/dist/brillouin/geometry.js +2 -2
  8. package/dist/chempot-diagram/ChemPotDiagram.svelte +18 -15
  9. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +17 -113
  10. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +104 -264
  11. package/dist/chempot-diagram/async-compute.svelte.js +12 -13
  12. package/dist/chempot-diagram/color.d.ts +21 -1
  13. package/dist/chempot-diagram/color.js +70 -1
  14. package/dist/chempot-diagram/compute.d.ts +7 -0
  15. package/dist/chempot-diagram/compute.js +85 -26
  16. package/dist/chempot-diagram/export.js +1 -6
  17. package/dist/chempot-diagram/temperature.js +9 -10
  18. package/dist/composition/BarChart.svelte +7 -9
  19. package/dist/composition/Composition.svelte +3 -8
  20. package/dist/composition/Formula.svelte +2 -2
  21. package/dist/composition/FormulaFilter.svelte +27 -93
  22. package/dist/composition/format.js +6 -8
  23. package/dist/composition/parse.js +50 -105
  24. package/dist/constants.d.ts +0 -1
  25. package/dist/constants.js +6 -17
  26. package/dist/convex-hull/ConvexHull2D.svelte +0 -2
  27. package/dist/convex-hull/ConvexHull3D.svelte +19 -47
  28. package/dist/convex-hull/ConvexHull4D.svelte +65 -95
  29. package/dist/convex-hull/StructurePopup.svelte +1 -1
  30. package/dist/convex-hull/gas-thermodynamics.js +21 -53
  31. package/dist/convex-hull/helpers.js +1 -7
  32. package/dist/convex-hull/thermodynamics.js +19 -27
  33. package/dist/coordination/CoordinationBarPlot.svelte +12 -21
  34. package/dist/element/BohrAtom.svelte +0 -1
  35. package/dist/element/ElementTile.svelte +26 -44
  36. package/dist/element/data.d.ts +2 -0
  37. package/dist/element/data.js +5 -1
  38. package/dist/element/index.d.ts +1 -1
  39. package/dist/element/index.js +1 -1
  40. package/dist/fermi-surface/FermiSurface.svelte +22 -23
  41. package/dist/fermi-surface/FermiSurface.svelte.d.ts +2 -6
  42. package/dist/fermi-surface/FermiSurfaceScene.svelte +32 -77
  43. package/dist/fermi-surface/compute.js +22 -55
  44. package/dist/fermi-surface/parse.js +38 -70
  45. package/dist/file-viewer/JsonBrowser.svelte +1043 -0
  46. package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
  47. package/dist/file-viewer/PlotPanel.svelte +350 -0
  48. package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
  49. package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
  50. package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
  51. package/dist/file-viewer/detect.d.ts +11 -0
  52. package/dist/file-viewer/detect.js +381 -0
  53. package/dist/file-viewer/eligibility.d.ts +4 -0
  54. package/dist/file-viewer/eligibility.js +53 -0
  55. package/dist/file-viewer/host-protocol.d.ts +50 -0
  56. package/dist/file-viewer/host-transfer.d.ts +25 -0
  57. package/dist/file-viewer/host-transfer.js +42 -0
  58. package/dist/file-viewer/index.d.ts +5 -0
  59. package/dist/file-viewer/index.js +5 -0
  60. package/dist/file-viewer/main.d.ts +44 -0
  61. package/dist/file-viewer/main.js +535 -0
  62. package/dist/file-viewer/parse.d.ts +9 -0
  63. package/dist/file-viewer/parse.js +170 -0
  64. package/dist/file-viewer/plot-utils.d.ts +24 -0
  65. package/dist/file-viewer/plot-utils.js +191 -0
  66. package/dist/file-viewer/types.d.ts +4 -0
  67. package/dist/file-viewer/types.js +5 -0
  68. package/dist/heatmap-matrix/HeatmapMatrix.svelte +57 -84
  69. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
  70. package/dist/index.d.ts +2 -2
  71. package/dist/index.js +1 -1
  72. package/dist/io/ExportPane.svelte +1 -1
  73. package/dist/io/decompress.d.ts +2 -0
  74. package/dist/io/decompress.js +3 -2
  75. package/dist/io/export.d.ts +7 -4
  76. package/dist/io/export.js +70 -18
  77. package/dist/io/file-drop.js +60 -35
  78. package/dist/io/url-drop.d.ts +2 -0
  79. package/dist/io/url-drop.js +28 -16
  80. package/dist/isosurface/Isosurface.svelte +687 -196
  81. package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
  82. package/dist/isosurface/IsosurfaceControls.svelte +564 -85
  83. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
  84. package/dist/isosurface/VolumeSlice.svelte +224 -0
  85. package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
  86. package/dist/isosurface/coloring.d.ts +20 -0
  87. package/dist/isosurface/coloring.js +118 -0
  88. package/dist/isosurface/geometry-worker-types.d.ts +38 -0
  89. package/dist/isosurface/geometry.worker.d.ts +1 -0
  90. package/dist/isosurface/geometry.worker.js +59 -0
  91. package/dist/isosurface/grid.d.ts +10 -0
  92. package/dist/isosurface/grid.js +31 -0
  93. package/dist/isosurface/index.d.ts +4 -0
  94. package/dist/isosurface/index.js +4 -0
  95. package/dist/isosurface/profile.d.ts +12 -0
  96. package/dist/isosurface/profile.js +19 -0
  97. package/dist/isosurface/sampling.d.ts +34 -0
  98. package/dist/isosurface/sampling.js +408 -0
  99. package/dist/isosurface/slice-rendering.d.ts +13 -0
  100. package/dist/isosurface/slice-rendering.js +79 -0
  101. package/dist/isosurface/slice.d.ts +22 -2
  102. package/dist/isosurface/slice.js +200 -116
  103. package/dist/isosurface/types.d.ts +24 -1
  104. package/dist/isosurface/types.js +120 -8
  105. package/dist/json-path.d.ts +4 -0
  106. package/dist/json-path.js +89 -0
  107. package/dist/labels.js +1 -1
  108. package/dist/layout/InfoCard.svelte +1 -3
  109. package/dist/layout/SettingsSection.svelte +52 -61
  110. package/dist/layout/SettingsSection.svelte.d.ts +6 -7
  111. package/dist/layout/ViewerChrome.svelte +7 -1
  112. package/dist/layout/json-tree/JsonNode.svelte +14 -48
  113. package/dist/layout/json-tree/JsonTree.svelte +48 -95
  114. package/dist/layout/json-tree/JsonValue.svelte +3 -18
  115. package/dist/layout/json-tree/utils.d.ts +0 -3
  116. package/dist/layout/json-tree/utils.js +50 -179
  117. package/dist/marching-cubes.d.ts +8 -1
  118. package/dist/marching-cubes.js +115 -48
  119. package/dist/math.d.ts +3 -2
  120. package/dist/math.js +29 -38
  121. package/dist/overlays/ContextMenu.svelte +15 -32
  122. package/dist/overlays/DraggablePane.svelte +39 -58
  123. package/dist/overlays/DraggablePane.svelte.d.ts +1 -1
  124. package/dist/overlays/GlassChip.svelte +3 -1
  125. package/dist/periodic-table/PeriodicTableControls.svelte +26 -52
  126. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +18 -34
  127. package/dist/phase-diagram/build-diagram.js +1 -6
  128. package/dist/phase-diagram/svg-to-diagram.js +9 -14
  129. package/dist/plot/bar/BarPlot.svelte +38 -42
  130. package/dist/plot/bar/SpacegroupBarPlot.svelte +18 -42
  131. package/dist/plot/box/BoxPlot.svelte +27 -23
  132. package/dist/plot/core/auto-place.d.ts +1 -1
  133. package/dist/plot/core/auto-place.js +1 -1
  134. package/dist/plot/core/data-transform.d.ts +1 -2
  135. package/dist/plot/core/data-transform.js +0 -10
  136. package/dist/plot/core/layout.d.ts +12 -5
  137. package/dist/plot/core/layout.js +37 -27
  138. package/dist/plot/core/pan-zoom.svelte.d.ts +2 -4
  139. package/dist/plot/core/pan-zoom.svelte.js +34 -17
  140. package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
  141. package/dist/plot/core/placed-tween.svelte.js +68 -21
  142. package/dist/plot/core/svg.js +0 -1
  143. package/dist/plot/core/utils/hierarchy-chart.d.ts +1 -0
  144. package/dist/plot/core/utils/hierarchy-chart.js +12 -0
  145. package/dist/plot/core/utils/hierarchy-labels.js +3 -2
  146. package/dist/plot/histogram/Histogram.svelte +21 -26
  147. package/dist/plot/sankey/sankey.js +13 -9
  148. package/dist/plot/scatter/BinnedScatterPlot.svelte +59 -43
  149. package/dist/plot/scatter/ScatterPlot.svelte +67 -52
  150. package/dist/plot/scatter/adaptive-density.d.ts +1 -1
  151. package/dist/plot/scatter/adaptive-density.js +36 -20
  152. package/dist/plot/sunburst/Sunburst.svelte +20 -20
  153. package/dist/plot/treemap/Treemap.svelte +175 -60
  154. package/dist/plot/treemap/Treemap.svelte.d.ts +6 -0
  155. package/dist/plot/treemap/index.d.ts +1 -0
  156. package/dist/plot/treemap/labels.d.ts +38 -0
  157. package/dist/plot/treemap/labels.js +80 -0
  158. package/dist/plot/treemap/treemap.d.ts +4 -2
  159. package/dist/plot/treemap/treemap.js +5 -2
  160. package/dist/rdf/RdfPlot.svelte +17 -35
  161. package/dist/rdf/calc-rdf.js +59 -44
  162. package/dist/rdf/index.d.ts +0 -1
  163. package/dist/sanitize.js +84 -26
  164. package/dist/scene/SceneCamera.svelte +1 -1
  165. package/dist/scene/SceneCamera.svelte.d.ts +1 -1
  166. package/dist/scene/index.d.ts +1 -2
  167. package/dist/scene/index.js +1 -1
  168. package/dist/scene/{props.js → props.svelte.js} +15 -1
  169. package/dist/settings.js +159 -482
  170. package/dist/spectral/Bands.svelte +151 -158
  171. package/dist/spectral/BrillouinBandsDos.svelte +4 -19
  172. package/dist/spectral/Dos.svelte +26 -24
  173. package/dist/spectral/helpers.js +40 -105
  174. package/dist/structure/AtomLegend.svelte +8 -13
  175. package/dist/structure/AtomLegend.svelte.d.ts +2 -3
  176. package/dist/structure/CellSelect.svelte +35 -10
  177. package/dist/structure/Structure.svelte +177 -99
  178. package/dist/structure/Structure.svelte.d.ts +1 -1
  179. package/dist/structure/StructureCarousel.svelte +10 -3
  180. package/dist/structure/StructureControls.svelte +8 -26
  181. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  182. package/dist/structure/StructureInfoPane.svelte +65 -76
  183. package/dist/structure/StructureScene.svelte +75 -109
  184. package/dist/structure/StructureScene.svelte.d.ts +3 -1
  185. package/dist/structure/StructureViewport.svelte +120 -55
  186. package/dist/structure/StructureViewport.svelte.d.ts +3 -1
  187. package/dist/structure/atom-properties.d.ts +1 -1
  188. package/dist/structure/atom-properties.js +4 -3
  189. package/dist/structure/bond-order-perception.js +3 -8
  190. package/dist/structure/bonding.d.ts +0 -1
  191. package/dist/structure/bonding.js +16 -23
  192. package/dist/structure/export.js +50 -74
  193. package/dist/structure/format-detect.js +5 -7
  194. package/dist/structure/index.js +6 -12
  195. package/dist/structure/parse.js +38 -100
  196. package/dist/structure/partial-occupancy.js +7 -4
  197. package/dist/structure/pbc.js +8 -10
  198. package/dist/structure/polyhedra.js +5 -4
  199. package/dist/structure/prop-groups.d.ts +85 -0
  200. package/dist/structure/prop-groups.js +18 -0
  201. package/dist/symmetry/SymmetryStats.svelte +0 -4
  202. package/dist/symmetry/WyckoffTable.svelte +5 -4
  203. package/dist/symmetry/cell-transform.js +5 -14
  204. package/dist/symmetry/index.js +6 -9
  205. package/dist/table/HeatmapTable.svelte +61 -103
  206. package/dist/table/ToggleMenu.svelte +166 -44
  207. package/dist/table/ToggleMenu.svelte.d.ts +1 -1
  208. package/dist/theme/embedded.d.ts +18 -0
  209. package/dist/theme/embedded.js +206 -0
  210. package/dist/theme/index.d.ts +4 -0
  211. package/dist/theme/index.js +1 -2
  212. package/dist/theme/themes.mjs +31 -60
  213. package/dist/tooltip/TooltipContent.svelte +1 -1
  214. package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
  215. package/dist/tooltip/index.d.ts +8 -1
  216. package/dist/trajectory/Trajectory.svelte +140 -120
  217. package/dist/trajectory/TrajectoryError.svelte +1 -66
  218. package/dist/trajectory/TrajectoryExportPane.svelte +0 -1
  219. package/dist/trajectory/TrajectoryInfoPane.svelte +55 -80
  220. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
  221. package/dist/trajectory/extract.js +19 -29
  222. package/dist/trajectory/format-detect.d.ts +2 -0
  223. package/dist/trajectory/format-detect.js +7 -5
  224. package/dist/trajectory/frame-reader.js +3 -4
  225. package/dist/trajectory/helpers.js +16 -49
  226. package/dist/trajectory/index.js +2 -8
  227. package/dist/trajectory/parse/ase.js +1 -1
  228. package/dist/trajectory/parse/index.d.ts +4 -3
  229. package/dist/trajectory/parse/index.js +12 -17
  230. package/dist/trajectory/parse/vaspout-h5.d.ts +0 -1
  231. package/dist/trajectory/parse/vaspout-h5.js +1 -4
  232. package/dist/trajectory/parse/xyz.js +48 -2
  233. package/dist/trajectory/plotting.js +14 -37
  234. package/dist/xrd/XrdPlot.svelte +43 -68
  235. package/dist/xrd/calc-xrd.js +22 -58
  236. package/dist/xrd/parse.js +13 -22
  237. package/package.json +40 -8
  238. package/readme.md +6 -5
  239. package/dist/scene/types.d.ts +0 -26
  240. package/dist/tooltip/types.d.ts +0 -8
  241. package/dist/trajectory/constants.d.ts +0 -6
  242. package/dist/trajectory/constants.js +0 -11
  243. /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
  244. /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
@@ -1,5 +1,5 @@
1
- // Pre-compiled regex for valid JS identifiers (used in path formatting)
2
- const VALID_IDENTIFIER_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
1
+ // JSON Tree utility functions
2
+ import { build_path, format_path, parse_path } from '../../json-path';
3
3
  // Circular-safe JSON.stringify helper (hoisted for reuse)
4
4
  function safe_stringify(val) {
5
5
  const seen = new WeakSet();
@@ -26,34 +26,22 @@ export function get_value_type(value) {
26
26
  if (value === undefined)
27
27
  return `undefined`;
28
28
  const type = typeof value;
29
- if (type === `string`)
30
- return `string`;
31
- if (type === `number`)
32
- return `number`;
33
- if (type === `boolean`)
34
- return `boolean`;
35
- if (type === `symbol`)
36
- return `symbol`;
37
- if (type === `bigint`)
38
- return `bigint`;
39
- if (type === `function`)
40
- return `function`;
41
- if (type === `object`) {
42
- if (Array.isArray(value))
43
- return `array`;
44
- if (value instanceof Date)
45
- return `date`;
46
- if (value instanceof RegExp)
47
- return `regexp`;
48
- if (value instanceof Map)
49
- return `map`;
50
- if (value instanceof Set)
51
- return `set`;
52
- if (value instanceof Error)
53
- return `error`;
54
- return `object`;
55
- }
56
- return `object`; // fallback
29
+ // string/number/boolean/symbol/bigint/function map directly to JsonValueType
30
+ if (type !== `object`)
31
+ return type;
32
+ if (Array.isArray(value))
33
+ return `array`;
34
+ if (value instanceof Date)
35
+ return `date`;
36
+ if (value instanceof RegExp)
37
+ return `regexp`;
38
+ if (value instanceof Map)
39
+ return `map`;
40
+ if (value instanceof Set)
41
+ return `set`;
42
+ if (value instanceof Error)
43
+ return `error`;
44
+ return `object`;
57
45
  }
58
46
  // Check if a value type is expandable (has children)
59
47
  export const is_expandable_type = (value_type) => value_type === `object` ||
@@ -82,40 +70,6 @@ export function get_child_count(value) {
82
70
  return value.size;
83
71
  return 0;
84
72
  }
85
- // Format a path segment for display
86
- // Handles both string keys and numeric indices
87
- // is_first: true for the first segment (no leading dot for valid identifiers)
88
- function format_path_segment(segment, is_first = false) {
89
- if (typeof segment === `number`) {
90
- return `[${segment}]`;
91
- }
92
- // Check if the key is a valid identifier (can use dot notation)
93
- if (VALID_IDENTIFIER_RE.test(segment)) {
94
- return is_first ? segment : `.${segment}`;
95
- }
96
- // Use bracket notation for keys with special characters
97
- return `["${segment.replaceAll('"', `\\"`)}"]`;
98
- }
99
- // Format a full path from segments
100
- // e.g., ["users", 0, "name"] -> "users[0].name"
101
- // e.g., [0, "name"] -> "[0].name" (root numeric index)
102
- // e.g., ["key.with.dot"] -> '["key.with.dot"]' (root special key)
103
- export function format_path(segments) {
104
- if (segments.length === 0)
105
- return ``;
106
- let result = format_path_segment(segments[0], true);
107
- for (let idx = 1; idx < segments.length; idx++) {
108
- result += format_path_segment(segments[idx]);
109
- }
110
- return result;
111
- }
112
- // Build a path string from parent path and key
113
- export function build_path(parent_path, key) {
114
- if (!parent_path) {
115
- return format_path_segment(key, true);
116
- }
117
- return parent_path + format_path_segment(key);
118
- }
119
73
  // Format a primitive/special value to string (shared by serialize and preview)
120
74
  function format_special_value(value, type) {
121
75
  if (type === `undefined`)
@@ -190,18 +144,13 @@ export function matches_search(path, key, value, query) {
190
144
  if (!query)
191
145
  return false;
192
146
  const lower_query = query.toLowerCase();
193
- // Check path
194
147
  if (path.toLowerCase().includes(lower_query))
195
148
  return true;
196
- // Check key
197
149
  if (key !== null && String(key).toLowerCase().includes(lower_query))
198
150
  return true;
199
- // Check value (for primitives)
200
- const type = get_value_type(value);
201
- if (is_primitive_type(type)) {
202
- return String(value).toLowerCase().includes(lower_query);
203
- }
204
- return false;
151
+ // Check value (only primitives are searchable as strings)
152
+ return (is_primitive_type(get_value_type(value)) &&
153
+ String(value).toLowerCase().includes(lower_query));
205
154
  }
206
155
  // Iterate over children of an expandable value, calling visitor for each
207
156
  function for_each_child(value, type, visitor) {
@@ -291,89 +240,31 @@ export function get_ancestor_paths(path) {
291
240
  }
292
241
  return ancestors;
293
242
  }
294
- // Parse a path string into segments
295
- // e.g., "users[0].name" -> ["users", 0, "name"]
296
- export function parse_path(path) {
297
- if (!path)
298
- return [];
299
- const segments = [];
300
- let current = ``;
301
- let in_bracket = false;
302
- // Bracket tokens are numeric indices or quoted keys (quotes stripped, \" unescaped)
303
- const push_bracket_token = (token) => {
304
- const num = Number(token);
305
- if (Number.isNaN(num)) {
306
- segments.push(token.replaceAll(/^"|"$/g, ``).replaceAll(String.raw `\"`, `"`));
307
- }
308
- else
309
- segments.push(num);
310
- };
311
- for (const char of path) {
312
- if (char === `.` && !in_bracket) {
313
- if (current)
314
- segments.push(current);
315
- current = ``;
316
- }
317
- else if (char === `[`) {
318
- if (current)
319
- segments.push(current);
320
- current = ``;
321
- in_bracket = true;
322
- }
323
- else if (char === `]`) {
324
- if (current)
325
- push_bracket_token(current);
326
- current = ``;
327
- in_bracket = false;
328
- }
329
- else
330
- current += char;
331
- }
332
- // Handle trailing content (e.g., unclosed bracket like "a[0")
333
- if (current) {
334
- if (in_bracket)
335
- push_bracket_token(current);
336
- else
337
- segments.push(current);
338
- }
339
- return segments;
340
- }
341
243
  // Check if two values are deeply equal (for change detection)
342
244
  export function values_equal(val_a, val_b) {
343
245
  if (val_a === val_b)
344
246
  return true;
345
- // Handle NaN (NaN !== NaN in JS, but we want NaN === NaN for change detection)
346
- if (typeof val_a === `number` &&
347
- typeof val_b === `number` &&
348
- Number.isNaN(val_a) &&
349
- Number.isNaN(val_b))
350
- return true;
351
- if (val_a === null || val_b === null)
352
- return false;
353
- if (typeof val_a !== typeof val_b)
247
+ // NaN !== NaN in JS, but we want NaN === NaN for change detection
248
+ if (typeof val_a === `number` && typeof val_b === `number`) {
249
+ return Number.isNaN(val_a) && Number.isNaN(val_b);
250
+ }
251
+ if (val_a === null || val_b === null || typeof val_a !== typeof val_b)
354
252
  return false;
355
253
  const type = get_value_type(val_a);
356
- // For primitives, strict equality is sufficient
357
- if (is_primitive_type(type) || type === `symbol`) {
358
- return val_a === val_b;
359
- }
360
- // For dates, compare timestamps
361
- if (type === `date`) {
254
+ if (type !== get_value_type(val_b))
255
+ return false;
256
+ if (is_primitive_type(type) || type === `symbol`)
257
+ return false; // strict equality failed above
258
+ if (type === `date`)
362
259
  return val_a.getTime() === val_b.getTime();
363
- }
364
- // For regex, compare string representation
365
- if (type === `regexp`) {
260
+ if (type === `regexp`)
366
261
  return val_a.toString() === val_b.toString();
367
- }
368
- // For objects and arrays, do shallow comparison (for performance)
369
- // Deep changes will be detected at the child level
370
- if (type === `array`) {
262
+ // Objects and arrays use shallow size comparison for performance —
263
+ // deep changes are detected at the child level
264
+ if (type === `array`)
371
265
  return val_a.length === val_b.length;
372
- }
373
266
  if (type === `object`) {
374
- const keys_a = Object.keys(val_a);
375
- const keys_b = Object.keys(val_b);
376
- return keys_a.length === keys_b.length;
267
+ return Object.keys(val_a).length === Object.keys(val_b).length;
377
268
  }
378
269
  return false;
379
270
  }
@@ -513,42 +404,27 @@ export function format_byte_size(bytes) {
513
404
  export function compute_diff(old_val, new_val, current_path = ``, result = new Map(), seen = new WeakSet()) {
514
405
  const old_type = get_value_type(old_val);
515
406
  const new_type = get_value_type(new_val);
407
+ const mark_changed = () => result.set(current_path, {
408
+ status: `changed`,
409
+ path: current_path,
410
+ old_value: old_val,
411
+ new_value: new_val,
412
+ });
516
413
  // Different types = changed
517
414
  if (old_type !== new_type) {
518
- result.set(current_path, {
519
- status: `changed`,
520
- path: current_path,
521
- old_value: old_val,
522
- new_value: new_val,
523
- });
415
+ mark_changed();
524
416
  return result;
525
417
  }
526
- // Both primitive: compare values (with NaN === NaN special case)
418
+ // Both primitive: compare values (values_equal treats NaN === NaN)
527
419
  if (is_primitive_type(old_type)) {
528
- const both_nan = typeof old_val === `number` &&
529
- typeof new_val === `number` &&
530
- Number.isNaN(old_val) &&
531
- Number.isNaN(new_val);
532
- if (!both_nan && old_val !== new_val) {
533
- result.set(current_path, {
534
- status: `changed`,
535
- path: current_path,
536
- old_value: old_val,
537
- new_value: new_val,
538
- });
539
- }
420
+ if (!values_equal(old_val, new_val))
421
+ mark_changed();
540
422
  return result;
541
423
  }
542
424
  // Non-expandable special types (date, regexp, etc): compare string forms
543
425
  if (!is_expandable_type(old_type)) {
544
- if (String(old_val) !== String(new_val)) {
545
- result.set(current_path, {
546
- status: `changed`,
547
- path: current_path,
548
- old_value: old_val,
549
- new_value: new_val,
550
- });
551
- }
426
+ if (String(old_val) !== String(new_val))
427
+ mark_changed();
552
428
  return result;
553
429
  }
554
430
  // Prevent circular references
@@ -616,13 +492,8 @@ export function compute_diff(old_val, new_val, current_path = ``, result = new M
616
492
  }
617
493
  // Maps: wrap entries as { key, value } to match JsonNode.get_children() rendering
618
494
  if (old_type === `map`) {
619
- diff_indexed(Array.from(old_val.entries()).map(([key, val]) => ({
620
- key,
621
- value: val,
622
- })), Array.from(new_val.entries()).map(([key, val]) => ({
623
- key,
624
- value: val,
625
- })));
495
+ const wrap_entries = (map) => Array.from(map, ([key, value]) => ({ key, value }));
496
+ diff_indexed(wrap_entries(old_val), wrap_entries(new_val));
626
497
  return result;
627
498
  }
628
499
  if (old_type === `set`) {
@@ -1,14 +1,21 @@
1
- import type { Matrix3x3, Vec3 } from './math';
1
+ import { type Matrix3x3, type Vec3 } from './math';
2
2
  export interface MarchingCubesResult {
3
3
  vertices: Vec3[];
4
4
  faces: number[][];
5
5
  normals: Vec3[];
6
6
  }
7
+ export interface MarchingCubesBuffers {
8
+ positions: Float32Array;
9
+ indices: Uint32Array;
10
+ normals: Float32Array;
11
+ }
7
12
  export interface MarchingCubesOptions {
8
13
  periodic?: boolean;
9
14
  interpolate?: boolean;
10
15
  centered?: boolean;
11
16
  normals?: boolean;
17
+ position_offset?: Vec3;
12
18
  }
19
+ export declare function marching_cubes_buffers(grid: number[][][], iso_value: number, k_lattice: Matrix3x3, options?: MarchingCubesOptions): MarchingCubesBuffers;
13
20
  export declare function marching_cubes(grid: number[][][], iso_value: number, k_lattice: Matrix3x3, options?: MarchingCubesOptions): MarchingCubesResult;
14
21
  export declare function compute_vertex_normals(vertices: Vec3[], faces: number[][]): Vec3[];
@@ -1,3 +1,6 @@
1
+ // Marching Cubes algorithm for isosurface extraction
2
+ // Based on the classic algorithm by Lorensen & Cline (1987)
3
+ import { mat3x3_vec3_multiply, matrix_inverse_3x3 } from './math';
1
4
  const wrap_grid_idx = (val, dim) => ((val % dim) + dim) % dim;
2
5
  const clamp_grid_idx = (val, max) => Math.max(0, Math.min(val, max));
3
6
  // Edge table: for each cube configuration (256 cases), which edges are intersected
@@ -311,18 +314,18 @@ function compute_gradient(grid, ix, iy, iz, nx, ny, nz, periodic) {
311
314
  const [iz_m, iz_p] = periodic
312
315
  ? [wrap_grid_idx(iz - 1, nz), wrap_grid_idx(iz + 1, nz)]
313
316
  : [Math.max(0, iz - 1), Math.min(nz - 1, iz + 1)];
314
- const dx = (grid[ix_p][iy_w][iz_w] - grid[ix_m][iy_w][iz_w]) * 0.5;
315
- const dy = (grid[ix_w][iy_p][iz_w] - grid[ix_w][iy_m][iz_w]) * 0.5;
316
- const dz = (grid[ix_w][iy_w][iz_p] - grid[ix_w][iy_w][iz_m]) * 0.5;
317
- const len_sq = dx * dx + dy * dy + dz * dz;
318
- if (len_sq < 1e-20)
319
- return [0, 0, 1];
320
- const inv_len = 1 / Math.sqrt(len_sq);
321
- return [-dx * inv_len, -dy * inv_len, -dz * inv_len];
317
+ const x_lo = grid[ix_m][iy_w][iz_w];
318
+ const x_hi = grid[ix_p][iy_w][iz_w];
319
+ const y_lo = grid[ix_w][iy_m][iz_w];
320
+ const y_hi = grid[ix_w][iy_p][iz_w];
321
+ const z_row = grid[ix_w][iy_w];
322
+ const scale = (lo, hi) => 1 / Math.max(1, periodic ? 2 : hi - lo);
323
+ const gz = -(z_row[iz_p] - z_row[iz_m]) * scale(iz_m, iz_p);
324
+ return [-(x_hi - x_lo) * scale(ix_m, ix_p), -(y_hi - y_lo) * scale(iy_m, iy_p), gz];
322
325
  }
323
326
  // Main marching cubes algorithm (optimized version)
324
- export function marching_cubes(grid, iso_value, k_lattice, options = {}) {
325
- const { periodic = true, interpolate = true, centered = true, normals: compute_norms = true, } = options;
327
+ function marching_cubes_raw(grid, iso_value, k_lattice, options = {}) {
328
+ const { periodic = true, interpolate = true, centered = true, normals: compute_norms = true, position_offset, } = options;
326
329
  // When centered=true, shift fractional coordinates by -0.5 so the grid is
327
330
  // centered at the origin (Γ point). This is needed for proper BZ visualization.
328
331
  const center_offset = centered ? 0.5 : 0;
@@ -330,24 +333,30 @@ export function marching_cubes(grid, iso_value, k_lattice, options = {}) {
330
333
  const ny = grid[0]?.length || 0;
331
334
  const nz = grid[0]?.[0]?.length || 0;
332
335
  if (nx < 2 || ny < 2 || nz < 2) {
333
- return { vertices: [], faces: [], normals: [] };
336
+ return { positions: [], indices: [], normals: [] };
334
337
  }
335
- const vertices = [];
336
- const faces = [];
338
+ const positions = [];
339
+ const indices = [];
337
340
  const normals = [];
338
- // Cache keys use UNWRAPPED grid coords (reach n in periodic mode, hence radix n+1):
339
- // wrapping would merge vertices on opposite cell faces into cell-spanning triangles.
340
- const key_nz = nz + 1;
341
- const key_ny_nz = (ny + 1) * key_nz;
342
- const max_flat = (nx + 1) * key_ny_nz; // for computing cache keys
343
- // Use numeric cache key - safe for grids up to ~300³ (2^53 / 2 / max_flat)
344
- // For much larger grids (>30M cells), consider switching to Map<string, number>
345
- // with keys like `${flat1},${flat2}` or Map<bigint, number> to avoid
346
- // Number.MAX_SAFE_INTEGER limits. The current approach is faster for typical grids.
347
- if (max_flat > 30_000_000) {
348
- console.warn(`Grid size ${nx}×${ny}×${nz} may cause cache key overflow`);
349
- }
350
- const vertex_cache = new Map();
341
+ // Iterate over all cubes in the grid
342
+ const max_x = periodic ? nx : nx - 1;
343
+ const max_y = periodic ? ny : ny - 1;
344
+ const max_z = periodic ? nz : nz - 1;
345
+ // Rolling typed edge caches retain only the current x slab. Coordinates stay
346
+ // UNWRAPPED (reach n in periodic mode), so opposite cell faces remain distinct
347
+ // and never create cell-spanning triangles.
348
+ const edge_stride = max_z + 1;
349
+ const edge_plane_size = (max_y + 1) * edge_stride;
350
+ const x_edge_cache = new Int32Array(edge_plane_size);
351
+ let y_edge_current = new Int32Array(edge_plane_size);
352
+ let y_edge_next = new Int32Array(edge_plane_size);
353
+ let z_edge_current = new Int32Array(edge_plane_size);
354
+ let z_edge_next = new Int32Array(edge_plane_size);
355
+ x_edge_cache.fill(-1);
356
+ y_edge_current.fill(-1);
357
+ y_edge_next.fill(-1);
358
+ z_edge_current.fill(-1);
359
+ z_edge_next.fill(-1);
351
360
  // Precompute k_lattice values for faster coordinate transform
352
361
  const [kx0, kx1, kx2] = k_lattice[0];
353
362
  const [ky0, ky1, ky2] = k_lattice[1];
@@ -358,6 +367,16 @@ export function marching_cubes(grid, iso_value, k_lattice, options = {}) {
358
367
  const inv_nx = 1 / (periodic ? nx : nx - 1);
359
368
  const inv_ny = 1 / (periodic ? ny : ny - 1);
360
369
  const inv_nz = 1 / (periodic ? nz : nz - 1);
370
+ // Singular lattices cannot map covectors; fall back to index-space unit gradients.
371
+ let normal_transform = null;
372
+ if (compute_norms) {
373
+ try {
374
+ normal_transform = matrix_inverse_3x3(k_lattice);
375
+ }
376
+ catch {
377
+ /* keep null */
378
+ }
379
+ }
361
380
  // Get or create vertex on an edge (fully optimized with flat array lookups)
362
381
  const get_vertex_on_edge = (ix, iy, iz, edge_idx, cube_values) => {
363
382
  // Use flat arrays instead of destructuring
@@ -369,13 +388,24 @@ export function marching_cubes(grid, iso_value, k_lattice, options = {}) {
369
388
  const ox2 = CUBE_VERTS_X[v2_idx];
370
389
  const oy2 = CUBE_VERTS_Y[v2_idx];
371
390
  const oz2 = CUBE_VERTS_Z[v2_idx];
372
- // Sorted numeric key from UNWRAPPED coords (value/gradient lookups wrap internally).
373
- // Safe for grids up to ~300³ before exceeding Number.MAX_SAFE_INTEGER
374
- const flat1 = (ix + ox1) * key_ny_nz + (iy + oy1) * key_nz + (iz + oz1);
375
- const flat2 = (ix + ox2) * key_ny_nz + (iy + oy2) * key_nz + (iz + oz2);
376
- const cache_key = flat1 < flat2 ? flat1 * max_flat + flat2 : flat2 * max_flat + flat1;
377
- const cached = vertex_cache.get(cache_key);
378
- if (cached !== undefined)
391
+ // Key the edge's lower endpoint. For x-edges, x is the rolling slab and
392
+ // y/z are equal; y- and z-edges normalize their sole varying key coordinate.
393
+ let cache;
394
+ let cache_idx;
395
+ if (ox1 !== ox2) {
396
+ cache = x_edge_cache;
397
+ cache_idx = (iy + oy1) * edge_stride + iz + oz1;
398
+ }
399
+ else if (oy1 !== oy2) {
400
+ cache = ox1 === 0 ? y_edge_current : y_edge_next;
401
+ cache_idx = (iy + Math.min(oy1, oy2)) * edge_stride + iz + oz1;
402
+ }
403
+ else {
404
+ cache = ox1 === 0 ? z_edge_current : z_edge_next;
405
+ cache_idx = (iy + oy1) * edge_stride + iz + Math.min(oz1, oz2);
406
+ }
407
+ const cached = cache[cache_idx];
408
+ if (cached >= 0)
379
409
  return cached;
380
410
  // Compute vertex position
381
411
  const v1 = cube_values[v1_idx];
@@ -407,26 +437,37 @@ export function marching_cubes(grid, iso_value, k_lattice, options = {}) {
407
437
  fz = (iz + (oz1 + oz2) * 0.5) * inv_nz - center_offset;
408
438
  }
409
439
  // Transform to Cartesian (inlined)
410
- const vert_idx = vertices.length;
411
- vertices.push([
412
- fx * kx0 + fy * ky0 + fz * kz0,
413
- fx * kx1 + fy * ky1 + fz * kz1,
414
- fx * kx2 + fy * ky2 + fz * kz2,
415
- ]);
416
- // Compute normal from grid gradient (skip if caller will compute from geometry)
440
+ const vert_idx = positions.length / 3;
441
+ const cart_x = fx * kx0 + fy * ky0 + fz * kz0;
442
+ const cart_y = fx * kx1 + fy * ky1 + fz * kz1;
443
+ const cart_z = fx * kx2 + fy * ky2 + fz * kz2;
444
+ if (position_offset) {
445
+ positions.push(cart_x + position_offset[0], cart_y + position_offset[1], cart_z + position_offset[2]);
446
+ }
447
+ else
448
+ positions.push(cart_x, cart_y, cart_z);
417
449
  if (compute_norms) {
418
- normals.push(compute_gradient(grid, ix + ox1, iy + oy1, iz + oz1, nx, ny, nz, periodic));
450
+ const [gx, gy, gz] = compute_gradient(grid, ix + ox1, iy + oy1, iz + oz1, nx, ny, nz, periodic);
451
+ // Scale by grid spacing without a lattice inverse (singular / anisotropic grids)
452
+ const index_grad = [gx / inv_nx, gy / inv_ny, gz / inv_nz];
453
+ const [cx, cy, cz] = normal_transform
454
+ ? mat3x3_vec3_multiply(normal_transform, index_grad)
455
+ : index_grad;
456
+ const length = Math.hypot(cx, cy, cz);
457
+ if (length > 1e-10)
458
+ normals.push(cx / length, cy / length, cz / length);
459
+ else
460
+ normals.push(0, 0, 1);
419
461
  }
420
- vertex_cache.set(cache_key, vert_idx);
462
+ cache[cache_idx] = vert_idx;
421
463
  return vert_idx;
422
464
  };
423
- // Iterate over all cubes in the grid
424
- const max_x = periodic ? nx : nx - 1;
425
- const max_y = periodic ? ny : ny - 1;
426
- const max_z = periodic ? nz : nz - 1;
427
465
  // Preallocate cube_values array (reuse across iterations)
428
466
  const cube_values = Array(8);
429
467
  for (let ix = 0; ix < max_x; ix++) {
468
+ x_edge_cache.fill(-1);
469
+ y_edge_next.fill(-1);
470
+ z_edge_next.fill(-1);
430
471
  const ix_row = grid[ix];
431
472
  const ix1_row = grid[(ix + 1) % nx];
432
473
  for (let iy = 0; iy < max_y; iy++) {
@@ -476,13 +517,39 @@ export function marching_cubes(grid, iso_value, k_lattice, options = {}) {
476
517
  const v2 = get_vertex_on_edge(ix, iy, iz, tri_list[tri_idx + 2], cube_values);
477
518
  // Skip degenerate triangles
478
519
  if (v0 !== v1 && v1 !== v2 && v0 !== v2) {
479
- faces.push([v0, v1, v2]);
520
+ indices.push(v0, v1, v2);
480
521
  }
481
522
  }
482
523
  }
483
524
  }
525
+ ;
526
+ [y_edge_current, y_edge_next] = [y_edge_next, y_edge_current];
527
+ [z_edge_current, z_edge_next] = [z_edge_next, z_edge_current];
484
528
  }
485
- return { vertices, faces, normals };
529
+ return { positions, indices, normals };
530
+ }
531
+ // Buffer-oriented result for renderers that otherwise immediately flatten the
532
+ // compatibility arrays. This avoids one Vec3 and one triangle-array allocation
533
+ // per emitted vertex/face while preserving the public marching_cubes() API.
534
+ export function marching_cubes_buffers(grid, iso_value, k_lattice, options = {}) {
535
+ const raw = marching_cubes_raw(grid, iso_value, k_lattice, options);
536
+ return {
537
+ positions: Float32Array.from(raw.positions),
538
+ indices: Uint32Array.from(raw.indices),
539
+ normals: Float32Array.from(raw.normals),
540
+ };
541
+ }
542
+ const packed_to_vec3 = (values) => Array.from({ length: values.length / 3 }, (_, idx) => {
543
+ const offset = idx * 3;
544
+ return [values[offset], values[offset + 1], values[offset + 2]];
545
+ });
546
+ export function marching_cubes(grid, iso_value, k_lattice, options = {}) {
547
+ const raw = marching_cubes_raw(grid, iso_value, k_lattice, options);
548
+ return {
549
+ vertices: packed_to_vec3(raw.positions),
550
+ faces: packed_to_vec3(raw.indices),
551
+ normals: packed_to_vec3(raw.normals),
552
+ };
486
553
  }
487
554
  // Compute per-vertex normals from faces using area-weighted averaging
488
555
  // Uses fan triangulation for N-gon faces (quads, etc.)
package/dist/math.d.ts CHANGED
@@ -63,6 +63,7 @@ export declare function vec9_to_mat3x3(flat_array: number[]): Matrix3x3;
63
63
  export declare function tensor_to_flat_array(tensor: number[][]): number[];
64
64
  export declare const transpose_3x3_matrix: (matrix: Matrix3x3) => Matrix3x3;
65
65
  export declare function scale_lattice_matrix(orig_matrix: Matrix3x3, scaling_factors: Vec3): Matrix3x3;
66
+ export declare const create_cart_to_frac_matrix: (lattice: Matrix3x3) => Matrix3x3;
66
67
  export declare const create_frac_to_cart: (lattice: Matrix3x3) => (frac: Vec3) => Vec3;
67
68
  export declare const create_cart_to_frac: (lattice: Matrix3x3) => (cart: Vec3) => Vec3;
68
69
  export type LatticeConverters = {
@@ -106,6 +107,6 @@ export declare function polygon_centroid(vertices: Vec2[]): Vec2;
106
107
  export declare function solve_linear_system(A: number[][], // NxN coefficient matrix
107
108
  b: number[]): number[] | null;
108
109
  export declare const cross_2d: (origin: Vec2, point_a: Vec2, point_b: Vec2) => number;
109
- export declare const monotone_chain: (sorted: Vec2[]) => Vec2[];
110
- export declare function convex_hull_2d(points: Vec2[]): Vec2[];
110
+ export declare const monotone_chain: (sorted: Vec2[], tolerance?: number) => Vec2[];
111
+ export declare function convex_hull_2d(points: Vec2[], tolerance?: number): Vec2[];
111
112
  export {};