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.
- package/dist/FilePicker.svelte +37 -20
- package/dist/Icon.svelte +2 -2
- package/dist/MillerIndexInput.svelte +60 -0
- package/dist/MillerIndexInput.svelte.d.ts +7 -0
- package/dist/app.css +38 -2
- package/dist/brillouin/BrillouinZone.svelte +20 -62
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneExportPane.svelte +12 -20
- package/dist/brillouin/BrillouinZoneScene.svelte +2 -2
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram.svelte +192 -0
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +677 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2688 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
- package/dist/chempot-diagram/color.d.ts +10 -0
- package/dist/chempot-diagram/color.js +33 -0
- package/dist/chempot-diagram/compute.d.ts +38 -0
- package/dist/chempot-diagram/compute.js +650 -0
- package/dist/chempot-diagram/index.d.ts +5 -0
- package/dist/chempot-diagram/index.js +5 -0
- package/dist/chempot-diagram/pointer.d.ts +16 -0
- package/dist/chempot-diagram/pointer.js +40 -0
- package/dist/chempot-diagram/temperature.d.ts +15 -0
- package/dist/chempot-diagram/temperature.js +37 -0
- package/dist/chempot-diagram/types.d.ts +83 -0
- package/dist/chempot-diagram/types.js +27 -0
- package/dist/colors/index.d.ts +3 -1
- package/dist/colors/index.js +4 -0
- package/dist/composition/BarChart.svelte +13 -22
- package/dist/composition/BubbleChart.svelte +5 -3
- package/dist/composition/FormulaFilter.svelte +770 -90
- package/dist/composition/FormulaFilter.svelte.d.ts +37 -1
- package/dist/composition/PieChart.svelte +43 -18
- package/dist/composition/PieChart.svelte.d.ts +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/convex-hull/ConvexHull.svelte +14 -1
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +14 -45
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +396 -134
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +93 -42
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullControls.svelte +94 -31
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +4 -2
- package/dist/convex-hull/ConvexHullStats.svelte +697 -128
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
- package/dist/convex-hull/ConvexHullTooltip.svelte +1 -0
- package/dist/convex-hull/GasPressureControls.svelte +72 -38
- package/dist/convex-hull/GasPressureControls.svelte.d.ts +2 -1
- package/dist/convex-hull/TemperatureSlider.svelte +46 -19
- package/dist/convex-hull/TemperatureSlider.svelte.d.ts +2 -1
- package/dist/convex-hull/demo-temperature.d.ts +6 -0
- package/dist/convex-hull/demo-temperature.js +36 -0
- package/dist/convex-hull/gas-thermodynamics.js +16 -5
- package/dist/convex-hull/helpers.d.ts +7 -1
- package/dist/convex-hull/helpers.js +45 -15
- package/dist/convex-hull/index.d.ts +15 -1
- package/dist/convex-hull/index.js +1 -0
- package/dist/convex-hull/thermodynamics.d.ts +8 -21
- package/dist/convex-hull/thermodynamics.js +106 -17
- package/dist/convex-hull/types.d.ts +7 -0
- package/dist/convex-hull/types.js +11 -0
- package/dist/coordination/CoordinationBarPlot.svelte +29 -46
- package/dist/element/BohrAtom.svelte +1 -1
- package/dist/element/data.js +2 -14
- package/dist/element/data.json.gz +0 -0
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +1 -0
- package/dist/element/types.d.ts +1 -0
- package/dist/fermi-surface/FermiSurface.svelte +21 -65
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/compute.js +1 -21
- package/dist/fermi-surface/marching-cubes.d.ts +2 -13
- package/dist/fermi-surface/marching-cubes.js +2 -519
- package/dist/fermi-surface/parse.js +17 -23
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +1273 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +171 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +31 -0
- package/dist/heatmap-matrix/index.d.ts +53 -0
- package/dist/heatmap-matrix/index.js +100 -0
- package/dist/heatmap-matrix/shared.d.ts +2 -0
- package/dist/heatmap-matrix/shared.js +4 -0
- package/dist/icons.d.ts +119 -0
- package/dist/icons.js +119 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/io/export.js +15 -3
- package/dist/io/file-drop.d.ts +7 -0
- package/dist/io/file-drop.js +43 -0
- package/dist/io/index.d.ts +2 -2
- package/dist/io/index.js +2 -112
- package/dist/io/types.d.ts +1 -0
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +118 -0
- package/dist/isosurface/Isosurface.svelte +231 -0
- package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
- package/dist/isosurface/IsosurfaceControls.svelte +273 -0
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +9 -0
- package/dist/isosurface/index.d.ts +5 -0
- package/dist/isosurface/index.js +6 -0
- package/dist/isosurface/parse.d.ts +6 -0
- package/dist/isosurface/parse.js +548 -0
- package/dist/isosurface/slice.d.ts +11 -0
- package/dist/isosurface/slice.js +145 -0
- package/dist/isosurface/types.d.ts +55 -0
- package/dist/isosurface/types.js +178 -0
- package/dist/labels.d.ts +2 -1
- package/dist/labels.js +1 -0
- package/dist/layout/InfoTag.svelte +62 -62
- package/dist/layout/SubpageGrid.svelte +74 -0
- package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/layout/json-tree/JsonNode.svelte +226 -53
- package/dist/layout/json-tree/JsonTree.svelte +425 -51
- package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
- package/dist/layout/json-tree/JsonValue.svelte +218 -97
- package/dist/layout/json-tree/types.d.ts +27 -2
- package/dist/layout/json-tree/utils.d.ts +14 -1
- package/dist/layout/json-tree/utils.js +254 -0
- package/dist/marching-cubes.d.ts +14 -0
- package/dist/marching-cubes.js +519 -0
- package/dist/math.d.ts +8 -0
- package/dist/math.js +374 -7
- package/dist/overlays/ContextMenu.svelte +3 -2
- package/dist/overlays/DraggablePane.svelte +163 -58
- package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +232 -77
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
- package/dist/phase-diagram/PhaseDiagramControls.svelte +32 -11
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +103 -0
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +102 -95
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +7 -0
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +100 -26
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
- package/dist/phase-diagram/index.d.ts +2 -0
- package/dist/phase-diagram/index.js +2 -0
- package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
- package/dist/phase-diagram/svg-to-diagram.js +865 -0
- package/dist/phase-diagram/types.d.ts +10 -0
- package/dist/phase-diagram/utils.d.ts +7 -4
- package/dist/phase-diagram/utils.js +149 -59
- package/dist/plot/AxisLabel.svelte +26 -0
- package/dist/plot/AxisLabel.svelte.d.ts +16 -0
- package/dist/plot/BarPlot.svelte +473 -228
- package/dist/plot/BarPlot.svelte.d.ts +3 -3
- package/dist/plot/BarPlotControls.svelte +3 -2
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ColorBar.svelte +54 -54
- package/dist/plot/ColorBar.svelte.d.ts +1 -1
- package/dist/plot/ElementScatter.svelte +4 -3
- package/dist/plot/FillArea.svelte +4 -1
- package/dist/plot/Histogram.svelte +320 -230
- package/dist/plot/Histogram.svelte.d.ts +2 -2
- package/dist/plot/HistogramControls.svelte +29 -10
- package/dist/plot/HistogramControls.svelte.d.ts +6 -2
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +2 -2
- package/dist/plot/PlotControls.svelte +109 -27
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/PlotLegend.svelte +1 -1
- package/dist/plot/PortalSelect.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
- package/dist/plot/ReferencePlane.svelte +1 -3
- package/dist/plot/ScatterPlot.svelte +343 -209
- package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
- package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3DControls.svelte +203 -250
- package/dist/plot/ScatterPlot3DScene.svelte +4 -7
- package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlotControls.svelte +95 -55
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ZeroLines.svelte +44 -0
- package/dist/plot/ZeroLines.svelte.d.ts +32 -0
- package/dist/plot/ZoomRect.svelte +21 -0
- package/dist/plot/ZoomRect.svelte.d.ts +8 -0
- package/dist/plot/axis-utils.d.ts +1 -1
- package/dist/plot/data-cleaning.js +1 -5
- package/dist/plot/index.d.ts +6 -2
- package/dist/plot/index.js +6 -2
- package/dist/plot/interactions.d.ts +8 -10
- package/dist/plot/interactions.js +10 -19
- package/dist/plot/layout.d.ts +7 -1
- package/dist/plot/layout.js +12 -4
- package/dist/plot/reference-line.d.ts +4 -21
- package/dist/plot/reference-line.js +7 -81
- package/dist/plot/types.d.ts +42 -17
- package/dist/plot/types.js +10 -0
- package/dist/plot/utils/label-placement.js +14 -11
- package/dist/plot/utils.d.ts +1 -0
- package/dist/plot/utils.js +14 -0
- package/dist/rdf/RdfPlot.svelte +55 -66
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/index.d.ts +1 -1
- package/dist/rdf/index.js +1 -1
- package/dist/settings.d.ts +5 -0
- package/dist/settings.js +37 -3
- package/dist/spectral/Bands.svelte +515 -143
- package/dist/spectral/Bands.svelte.d.ts +22 -2
- package/dist/spectral/helpers.d.ts +23 -1
- package/dist/spectral/helpers.js +65 -9
- package/dist/spectral/types.d.ts +2 -0
- package/dist/structure/AtomLegend.svelte +31 -10
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/CellSelect.svelte +92 -22
- package/dist/structure/Lattice.svelte +2 -0
- package/dist/structure/Structure.svelte +716 -173
- package/dist/structure/Structure.svelte.d.ts +7 -2
- package/dist/structure/StructureControls.svelte +26 -14
- package/dist/structure/StructureControls.svelte.d.ts +5 -1
- package/dist/structure/StructureInfoPane.svelte +7 -1
- package/dist/structure/StructureScene.svelte +386 -95
- package/dist/structure/StructureScene.svelte.d.ts +15 -4
- package/dist/structure/atom-properties.d.ts +6 -2
- package/dist/structure/atom-properties.js +38 -25
- package/dist/structure/export.js +10 -7
- package/dist/structure/ferrox-wasm-types.d.ts +3 -2
- package/dist/structure/ferrox-wasm-types.js +0 -3
- package/dist/structure/ferrox-wasm.d.ts +3 -2
- package/dist/structure/ferrox-wasm.js +1 -2
- package/dist/structure/index.d.ts +7 -0
- package/dist/structure/index.js +22 -0
- package/dist/structure/parse.js +19 -16
- package/dist/structure/partial-occupancy.d.ts +25 -0
- package/dist/structure/partial-occupancy.js +102 -0
- package/dist/structure/validation.js +6 -3
- package/dist/symmetry/SymmetryStats.svelte +18 -4
- package/dist/symmetry/WyckoffTable.svelte +18 -10
- package/dist/symmetry/index.d.ts +7 -4
- package/dist/symmetry/index.js +83 -18
- package/dist/table/HeatmapTable.svelte +468 -69
- package/dist/table/HeatmapTable.svelte.d.ts +13 -1
- package/dist/table/ToggleMenu.svelte +291 -44
- package/dist/table/ToggleMenu.svelte.d.ts +4 -1
- package/dist/table/index.d.ts +3 -0
- package/dist/tooltip/index.d.ts +1 -1
- package/dist/tooltip/index.js +1 -0
- package/dist/trajectory/Trajectory.svelte +147 -145
- package/dist/trajectory/TrajectoryExportPane.svelte +13 -9
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
- package/dist/trajectory/constants.d.ts +6 -0
- package/dist/trajectory/constants.js +7 -0
- package/dist/trajectory/extract.js +3 -5
- package/dist/trajectory/format-detect.d.ts +9 -0
- package/dist/trajectory/format-detect.js +76 -0
- package/dist/trajectory/frame-reader.d.ts +17 -0
- package/dist/trajectory/frame-reader.js +339 -0
- package/dist/trajectory/helpers.d.ts +15 -0
- package/dist/trajectory/helpers.js +187 -0
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +11 -4
- package/dist/trajectory/parse/ase.d.ts +2 -0
- package/dist/trajectory/parse/ase.js +76 -0
- package/dist/trajectory/parse/hdf5.d.ts +2 -0
- package/dist/trajectory/parse/hdf5.js +121 -0
- package/dist/trajectory/parse/index.d.ts +12 -0
- package/dist/trajectory/parse/index.js +304 -0
- package/dist/trajectory/parse/lammps.d.ts +5 -0
- package/dist/trajectory/parse/lammps.js +169 -0
- package/dist/trajectory/parse/vasp.d.ts +2 -0
- package/dist/trajectory/parse/vasp.js +65 -0
- package/dist/trajectory/parse/xyz.d.ts +2 -0
- package/dist/trajectory/parse/xyz.js +109 -0
- package/dist/trajectory/types.d.ts +11 -0
- package/dist/trajectory/types.js +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +4 -0
- package/dist/xrd/XrdPlot.svelte +6 -4
- package/dist/xrd/calc-xrd.js +0 -1
- package/package.json +33 -23
- package/readme.md +4 -4
- package/dist/trajectory/parse.d.ts +0 -42
- package/dist/trajectory/parse.js +0 -1267
- /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 (!
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
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
|
-
|
|
153
|
+
}
|
|
154
|
+
return left_vec.reduce((sum, val, idx) => sum + val * right_vec[idx], 0);
|
|
140
155
|
}
|
|
141
156
|
// Matrix-vector multiplication
|
|
142
|
-
if (
|
|
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 (
|
|
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
|
|
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?.();
|