matterviz 0.3.5 → 0.3.6
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/.vscode/launch.json +13 -0
- package/.vscodeignore +7 -0
- package/dist/assets/STLExporter-BpTH3YHE.js +8 -0
- package/dist/assets/browser-DdDecX_W.js +1 -0
- package/dist/assets/export-qgn-H9y6.js +2 -0
- package/dist/assets/main-DiKYzti2.css +1 -0
- package/dist/assets/moyo_wasm_bg-0ocwg7xY.wasm +0 -0
- package/dist/extension.js +31293 -0
- package/dist/src/lib/FilePicker.svelte +360 -0
- package/dist/src/lib/MillerIndexInput.svelte +66 -0
- package/dist/src/lib/api/mp.ts +26 -0
- package/dist/src/lib/api/optimade.ts +204 -0
- package/dist/src/lib/brillouin/BrillouinZone.svelte +549 -0
- package/dist/src/lib/brillouin/BrillouinZoneControls.svelte +144 -0
- package/dist/src/lib/brillouin/BrillouinZoneExportPane.svelte +146 -0
- package/dist/src/lib/brillouin/BrillouinZoneInfoPane.svelte +146 -0
- package/dist/src/lib/brillouin/BrillouinZoneScene.svelte +476 -0
- package/dist/src/lib/brillouin/BrillouinZoneTooltip.svelte +92 -0
- package/dist/src/lib/brillouin/compute.ts +529 -0
- package/dist/src/lib/brillouin/index.ts +8 -0
- package/dist/src/lib/brillouin/types.ts +51 -0
- package/dist/src/lib/chempot-diagram/ChemPotDiagram.svelte +327 -0
- package/dist/src/lib/chempot-diagram/ChemPotDiagram2D.svelte +846 -0
- package/dist/src/lib/chempot-diagram/ChemPotDiagram3D.svelte +3193 -0
- package/dist/src/lib/chempot-diagram/async-compute.svelte.ts +94 -0
- package/dist/src/lib/chempot-diagram/chempot-worker.ts +11 -0
- package/dist/src/lib/chempot-diagram/color.ts +42 -0
- package/dist/src/lib/chempot-diagram/compute.ts +1014 -0
- package/dist/src/lib/chempot-diagram/index.ts +6 -0
- package/dist/src/lib/chempot-diagram/pointer.ts +56 -0
- package/dist/src/lib/chempot-diagram/temperature.ts +77 -0
- package/dist/src/lib/chempot-diagram/types.ts +130 -0
- package/dist/src/lib/colors/index.ts +249 -0
- package/dist/src/lib/composition/BarChart.svelte +297 -0
- package/dist/src/lib/composition/BubbleChart.svelte +218 -0
- package/dist/src/lib/composition/Composition.svelte +165 -0
- package/dist/src/lib/composition/Formula.svelte +268 -0
- package/dist/src/lib/composition/FormulaFilter.svelte +1257 -0
- package/dist/src/lib/composition/PieChart.svelte +323 -0
- package/dist/src/lib/composition/format.ts +155 -0
- package/dist/src/lib/composition/index.ts +37 -0
- package/dist/src/lib/composition/parse.ts +605 -0
- package/dist/src/lib/constants.ts +134 -0
- package/dist/src/lib/controls.ts +42 -0
- package/dist/src/lib/convex-hull/ConvexHull.svelte +157 -0
- package/dist/src/lib/convex-hull/ConvexHull2D.svelte +825 -0
- package/dist/src/lib/convex-hull/ConvexHull3D.svelte +1801 -0
- package/dist/src/lib/convex-hull/ConvexHull4D.svelte +1398 -0
- package/dist/src/lib/convex-hull/ConvexHullControls.svelte +535 -0
- package/dist/src/lib/convex-hull/ConvexHullInfoPane.svelte +125 -0
- package/dist/src/lib/convex-hull/ConvexHullStats.svelte +929 -0
- package/dist/src/lib/convex-hull/ConvexHullTooltip.svelte +131 -0
- package/dist/src/lib/convex-hull/GasPressureControls.svelte +247 -0
- package/dist/src/lib/convex-hull/StructurePopup.svelte +151 -0
- package/dist/src/lib/convex-hull/TemperatureSlider.svelte +140 -0
- package/dist/src/lib/convex-hull/barycentric-coords.ts +246 -0
- package/dist/src/lib/convex-hull/demo-temperature.ts +63 -0
- package/dist/src/lib/convex-hull/gas-thermodynamics.ts +405 -0
- package/dist/src/lib/convex-hull/helpers.ts +932 -0
- package/dist/src/lib/convex-hull/index.ts +202 -0
- package/dist/src/lib/convex-hull/thermodynamics.ts +2192 -0
- package/dist/src/lib/convex-hull/types.ts +267 -0
- package/dist/src/lib/coordination/CoordinationBarPlot.svelte +311 -0
- package/dist/src/lib/coordination/calc-coordination.ts +93 -0
- package/dist/src/lib/coordination/index.ts +9 -0
- package/dist/src/lib/effects.svelte.ts +48 -0
- package/dist/src/lib/element/BohrAtom.svelte +147 -0
- package/dist/src/lib/element/ElementHeading.svelte +26 -0
- package/dist/src/lib/element/ElementPhoto.svelte +57 -0
- package/dist/src/lib/element/ElementStats.svelte +80 -0
- package/dist/src/lib/element/ElementTile.svelte +484 -0
- package/dist/src/lib/element/data.json.gz.d.ts +4 -0
- package/dist/src/lib/element/data.ts +14 -0
- package/dist/src/lib/element/index.ts +8 -0
- package/dist/src/lib/element/types.ts +62 -0
- package/dist/src/lib/feedback/ClickFeedback.svelte +58 -0
- package/dist/src/lib/feedback/DragOverlay.svelte +42 -0
- package/dist/src/lib/feedback/index.ts +4 -0
- package/dist/src/lib/fermi-surface/FermiSlice.svelte +189 -0
- package/dist/src/lib/fermi-surface/FermiSurface.svelte +600 -0
- package/dist/src/lib/fermi-surface/FermiSurfaceControls.svelte +448 -0
- package/dist/src/lib/fermi-surface/FermiSurfaceScene.svelte +794 -0
- package/dist/src/lib/fermi-surface/FermiSurfaceTooltip.svelte +111 -0
- package/dist/src/lib/fermi-surface/compute.ts +728 -0
- package/dist/src/lib/fermi-surface/constants.ts +32 -0
- package/dist/src/lib/fermi-surface/export.ts +64 -0
- package/dist/src/lib/fermi-surface/index.ts +14 -0
- package/dist/src/lib/fermi-surface/marching-cubes.ts +3 -0
- package/dist/src/lib/fermi-surface/parse.ts +574 -0
- package/dist/src/lib/fermi-surface/symmetry.ts +56 -0
- package/dist/src/lib/fermi-surface/types.ts +159 -0
- package/dist/src/lib/heatmap-matrix/HeatmapMatrix.svelte +1545 -0
- package/dist/src/lib/heatmap-matrix/HeatmapMatrixControls.svelte +225 -0
- package/dist/src/lib/heatmap-matrix/index.ts +167 -0
- package/dist/src/lib/heatmap-matrix/shared.ts +7 -0
- package/dist/src/lib/icons.ts +650 -0
- package/dist/src/lib/index.ts +61 -0
- package/dist/src/lib/io/decompress.ts +92 -0
- package/dist/src/lib/io/export.ts +385 -0
- package/dist/src/lib/io/fetch.ts +46 -0
- package/dist/src/lib/io/file-drop.ts +51 -0
- package/dist/src/lib/io/index.ts +7 -0
- package/dist/src/lib/io/is-binary.ts +24 -0
- package/dist/src/lib/io/types.ts +8 -0
- package/dist/src/lib/io/url-drop.ts +141 -0
- package/dist/src/lib/isosurface/Isosurface.svelte +285 -0
- package/dist/src/lib/isosurface/IsosurfaceControls.svelte +277 -0
- package/dist/src/lib/isosurface/index.ts +7 -0
- package/dist/src/lib/isosurface/parse.ts +656 -0
- package/dist/src/lib/isosurface/slice.ts +175 -0
- package/dist/src/lib/isosurface/types.ts +309 -0
- package/dist/src/lib/labels.ts +320 -0
- package/dist/src/lib/layout/FullscreenToggle.svelte +50 -0
- package/dist/src/lib/layout/InfoCard.svelte +120 -0
- package/dist/src/lib/layout/InfoTag.svelte +185 -0
- package/dist/src/lib/layout/PropertyFilter.svelte +246 -0
- package/dist/src/lib/layout/SettingsSection.svelte +148 -0
- package/dist/src/lib/layout/SubpageGrid.svelte +82 -0
- package/dist/src/lib/layout/fullscreen.ts +65 -0
- package/dist/src/lib/layout/index.ts +11 -0
- package/dist/src/lib/layout/json-tree/JsonNode.svelte +548 -0
- package/dist/src/lib/layout/json-tree/JsonTree.svelte +1230 -0
- package/dist/src/lib/layout/json-tree/JsonValue.svelte +334 -0
- package/dist/src/lib/layout/json-tree/index.ts +3 -0
- package/dist/src/lib/layout/json-tree/types.ts +126 -0
- package/dist/src/lib/layout/json-tree/utils.ts +682 -0
- package/dist/src/lib/marching-cubes.ts +614 -0
- package/dist/src/lib/math.ts +1081 -0
- package/dist/src/lib/overlays/ContextMenu.svelte +162 -0
- package/dist/src/lib/overlays/CopyButton.svelte +45 -0
- package/dist/src/lib/overlays/DragControlTab.svelte +98 -0
- package/dist/src/lib/overlays/DraggablePane.svelte +487 -0
- package/dist/src/lib/overlays/InfoPaneCards.svelte +149 -0
- package/dist/src/lib/overlays/index.ts +3 -0
- package/dist/src/lib/periodic-table/PeriodicTable.svelte +469 -0
- package/dist/src/lib/periodic-table/PeriodicTableControls.svelte +557 -0
- package/dist/src/lib/periodic-table/PropertySelect.svelte +37 -0
- package/dist/src/lib/periodic-table/index.ts +12 -0
- package/dist/src/lib/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +1086 -0
- package/dist/src/lib/phase-diagram/PhaseDiagramControls.svelte +444 -0
- package/dist/src/lib/phase-diagram/PhaseDiagramEditorPane.svelte +126 -0
- package/dist/src/lib/phase-diagram/PhaseDiagramExportPane.svelte +184 -0
- package/dist/src/lib/phase-diagram/PhaseDiagramTooltip.svelte +391 -0
- package/dist/src/lib/phase-diagram/TdbInfoPanel.svelte +203 -0
- package/dist/src/lib/phase-diagram/build-diagram.ts +186 -0
- package/dist/src/lib/phase-diagram/colors.ts +58 -0
- package/dist/src/lib/phase-diagram/diagram-input.ts +40 -0
- package/dist/src/lib/phase-diagram/index.ts +13 -0
- package/dist/src/lib/phase-diagram/parse.ts +348 -0
- package/dist/src/lib/phase-diagram/svg-to-diagram.ts +1023 -0
- package/dist/src/lib/phase-diagram/types.ts +144 -0
- package/dist/src/lib/phase-diagram/utils.ts +775 -0
- package/dist/src/lib/plot/AxisLabel.svelte +51 -0
- package/dist/src/lib/plot/BarPlot.svelte +2113 -0
- package/dist/src/lib/plot/BarPlotControls.svelte +66 -0
- package/dist/src/lib/plot/BinnedScatterPlot.svelte +1114 -0
- package/dist/src/lib/plot/ColorBar.svelte +721 -0
- package/dist/src/lib/plot/ColorScaleSelect.svelte +54 -0
- package/dist/src/lib/plot/ElementScatter.svelte +63 -0
- package/dist/src/lib/plot/FillArea.svelte +223 -0
- package/dist/src/lib/plot/Histogram.svelte +1558 -0
- package/dist/src/lib/plot/HistogramControls.svelte +212 -0
- package/dist/src/lib/plot/InteractiveAxisLabel.svelte +96 -0
- package/dist/src/lib/plot/Line.svelte +84 -0
- package/dist/src/lib/plot/PlotAxis.svelte +169 -0
- package/dist/src/lib/plot/PlotControls.svelte +537 -0
- package/dist/src/lib/plot/PlotLegend.svelte +569 -0
- package/dist/src/lib/plot/PlotTooltip.svelte +67 -0
- package/dist/src/lib/plot/PortalSelect.svelte +253 -0
- package/dist/src/lib/plot/ReferenceLine3D.svelte +156 -0
- package/dist/src/lib/plot/ReferencePlane.svelte +175 -0
- package/dist/src/lib/plot/ScatterPlot.svelte +2778 -0
- package/dist/src/lib/plot/ScatterPlot3D.svelte +529 -0
- package/dist/src/lib/plot/ScatterPlot3DControls.svelte +437 -0
- package/dist/src/lib/plot/ScatterPlot3DScene.svelte +912 -0
- package/dist/src/lib/plot/ScatterPlotControls.svelte +306 -0
- package/dist/src/lib/plot/ScatterPoint.svelte +182 -0
- package/dist/src/lib/plot/SpacegroupBarPlot.svelte +293 -0
- package/dist/src/lib/plot/Surface3D.svelte +197 -0
- package/dist/src/lib/plot/ZeroLines.svelte +97 -0
- package/dist/src/lib/plot/ZoomRect.svelte +23 -0
- package/dist/src/lib/plot/adaptive-density.ts +316 -0
- package/dist/src/lib/plot/auto-place.ts +184 -0
- package/dist/src/lib/plot/axis-utils.ts +122 -0
- package/dist/src/lib/plot/binned-scatter-types.ts +83 -0
- package/dist/src/lib/plot/data-cleaning.ts +1069 -0
- package/dist/src/lib/plot/data-transform.ts +69 -0
- package/dist/src/lib/plot/defaults.ts +9 -0
- package/dist/src/lib/plot/fill-utils.ts +494 -0
- package/dist/src/lib/plot/hover-lock.svelte.ts +60 -0
- package/dist/src/lib/plot/index.ts +53 -0
- package/dist/src/lib/plot/interactions.ts +119 -0
- package/dist/src/lib/plot/layout.ts +425 -0
- package/dist/src/lib/plot/reference-line.ts +426 -0
- package/dist/src/lib/plot/scales.ts +654 -0
- package/dist/src/lib/plot/svg.ts +23 -0
- package/dist/src/lib/plot/types.ts +1144 -0
- package/dist/src/lib/plot/utils/label-placement.ts +541 -0
- package/dist/src/lib/plot/utils/series-visibility.ts +140 -0
- package/dist/src/lib/plot/utils.ts +11 -0
- package/dist/src/lib/rdf/RdfPlot.svelte +247 -0
- package/dist/src/lib/rdf/calc-rdf.ts +167 -0
- package/dist/src/lib/rdf/index.ts +27 -0
- package/dist/src/lib/sanitize.ts +126 -0
- package/dist/src/lib/settings.ts +1479 -0
- package/dist/src/lib/spectral/Bands.svelte +1040 -0
- package/dist/src/lib/spectral/BandsAndDos.svelte +134 -0
- package/dist/src/lib/spectral/BrillouinBandsDos.svelte +252 -0
- package/dist/src/lib/spectral/Dos.svelte +697 -0
- package/dist/src/lib/spectral/helpers.ts +1381 -0
- package/dist/src/lib/spectral/index.ts +8 -0
- package/dist/src/lib/spectral/types.ts +112 -0
- package/dist/src/lib/state.svelte.ts +64 -0
- package/dist/src/lib/structure/Arrow.svelte +72 -0
- package/dist/src/lib/structure/AtomLegend.svelte +815 -0
- package/dist/src/lib/structure/Bond.svelte +140 -0
- package/dist/src/lib/structure/CanvasTooltip.svelte +33 -0
- package/dist/src/lib/structure/CellSelect.svelte +349 -0
- package/dist/src/lib/structure/Cylinder.svelte +45 -0
- package/dist/src/lib/structure/Lattice.svelte +196 -0
- package/dist/src/lib/structure/Structure.svelte +2248 -0
- package/dist/src/lib/structure/StructureControls.svelte +1273 -0
- package/dist/src/lib/structure/StructureExportPane.svelte +252 -0
- package/dist/src/lib/structure/StructureInfoPane.svelte +737 -0
- package/dist/src/lib/structure/StructureScene.svelte +2255 -0
- package/dist/src/lib/structure/atom-properties.ts +316 -0
- package/dist/src/lib/structure/bond-order-perception.ts +447 -0
- package/dist/src/lib/structure/bonding.ts +944 -0
- package/dist/src/lib/structure/export.ts +861 -0
- package/dist/src/lib/structure/index.ts +291 -0
- package/dist/src/lib/structure/label-placement.ts +130 -0
- package/dist/src/lib/structure/measure.ts +45 -0
- package/dist/src/lib/structure/parse.ts +1705 -0
- package/dist/src/lib/structure/partial-occupancy.ts +183 -0
- package/dist/src/lib/structure/pbc.ts +164 -0
- package/dist/src/lib/structure/supercell.ts +226 -0
- package/dist/src/lib/structure/validation.ts +11 -0
- package/dist/src/lib/symmetry/SymmetryStats.svelte +226 -0
- package/dist/src/lib/symmetry/WyckoffTable.svelte +120 -0
- package/dist/src/lib/symmetry/cell-transform.ts +118 -0
- package/dist/src/lib/symmetry/index.ts +348 -0
- package/dist/src/lib/symmetry/spacegroups.ts +404 -0
- package/dist/src/lib/table/HeatmapTable.svelte +1833 -0
- package/dist/src/lib/table/ToggleMenu.svelte +385 -0
- package/dist/src/lib/table/index.ts +139 -0
- package/dist/src/lib/theme/ThemeControl.svelte +53 -0
- package/dist/src/lib/theme/index.ts +107 -0
- package/dist/src/lib/theme/themes.mjs +297 -0
- package/dist/src/lib/time.ts +71 -0
- package/dist/src/lib/tooltip/TooltipContent.svelte +58 -0
- package/dist/src/lib/tooltip/index.ts +2 -0
- package/dist/src/lib/tooltip/types.ts +13 -0
- package/dist/src/lib/trajectory/Trajectory.svelte +1545 -0
- package/dist/src/lib/trajectory/TrajectoryError.svelte +128 -0
- package/dist/src/lib/trajectory/TrajectoryExportPane.svelte +357 -0
- package/dist/src/lib/trajectory/TrajectoryInfoPane.svelte +313 -0
- package/dist/src/lib/trajectory/constants.ts +7 -0
- package/dist/src/lib/trajectory/extract.ts +196 -0
- package/dist/src/lib/trajectory/format-detect.ts +96 -0
- package/dist/src/lib/trajectory/frame-reader.ts +456 -0
- package/dist/src/lib/trajectory/helpers.ts +217 -0
- package/dist/src/lib/trajectory/index.ts +218 -0
- package/dist/src/lib/trajectory/parse/ase.ts +109 -0
- package/dist/src/lib/trajectory/parse/hdf5.ts +173 -0
- package/dist/src/lib/trajectory/parse/index.ts +411 -0
- package/dist/src/lib/trajectory/parse/lammps.ts +215 -0
- package/dist/src/lib/trajectory/parse/vasp.ts +102 -0
- package/dist/src/lib/trajectory/parse/xyz.ts +143 -0
- package/dist/src/lib/trajectory/plotting.ts +599 -0
- package/dist/src/lib/trajectory/types.ts +13 -0
- package/dist/src/lib/utils.ts +56 -0
- package/dist/src/lib/xrd/XrdPlot.svelte +615 -0
- package/dist/src/lib/xrd/broadening.ts +130 -0
- package/dist/src/lib/xrd/calc-xrd.ts +397 -0
- package/dist/src/lib/xrd/index.ts +38 -0
- package/dist/src/lib/xrd/parse.ts +858 -0
- package/dist/webview.js +29421 -0
- package/icon.png +0 -0
- package/license +1 -1
- package/matterviz-0.3.2.vsix +0 -0
- package/matterviz-0.3.4.vsix +0 -0
- package/matterviz-0.3.5.vsix +0 -0
- package/package.json +1460 -215
- package/readme.md +171 -98
- package/scripts/sync-config.ts +101 -0
- package/src/declarations.d.ts +2 -0
- package/src/extension.ts +972 -0
- package/src/node-io.ts +65 -0
- package/src/types.ts +17 -0
- package/src/webview/JsonBrowser.svelte +1079 -0
- package/src/webview/PlotPanel.svelte +346 -0
- package/src/webview/detect.ts +444 -0
- package/src/webview/main.ts +764 -0
- package/src/webview/plot-utils.ts +250 -0
- package/test-fixtures/all-viz-types.json.gz +0 -0
- package/test-fixtures/plot-demo-data.json.gz +0 -0
- package/tests/detect.test.ts +604 -0
- package/tests/extension.test.ts +2041 -0
- package/tests/node-io.test.ts +39 -0
- package/tests/plot-utils.test.ts +302 -0
- package/tests/vite-plugin-json-gz.test.ts +114 -0
- package/tests/vscode-mock.ts +18 -0
- package/tests/webview.test.ts +231 -0
- package/tsconfig.json +20 -0
- package/vite-plugin-json-gz.ts +29 -0
- package/vite.config.ts +34 -0
- package/vite.extension.config.ts +34 -0
- package/dist/EmptyState.svelte.d.ts +0 -9
- package/dist/FilePicker.svelte +0 -360
- package/dist/FilePicker.svelte.d.ts +0 -17
- package/dist/Icon.svelte.d.ts +0 -13
- package/dist/MillerIndexInput.svelte +0 -66
- package/dist/MillerIndexInput.svelte.d.ts +0 -7
- package/dist/api/mp.d.ts +0 -6
- package/dist/api/mp.js +0 -22
- package/dist/api/optimade.d.ts +0 -45
- package/dist/api/optimade.js +0 -135
- package/dist/brillouin/BrillouinZone.svelte +0 -546
- package/dist/brillouin/BrillouinZone.svelte.d.ts +0 -83
- package/dist/brillouin/BrillouinZoneControls.svelte +0 -144
- package/dist/brillouin/BrillouinZoneControls.svelte.d.ts +0 -17
- package/dist/brillouin/BrillouinZoneExportPane.svelte +0 -148
- package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +0 -15
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +0 -146
- package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +0 -13
- package/dist/brillouin/BrillouinZoneScene.svelte +0 -476
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +0 -48
- package/dist/brillouin/BrillouinZoneTooltip.svelte +0 -92
- package/dist/brillouin/BrillouinZoneTooltip.svelte.d.ts +0 -8
- package/dist/brillouin/compute.d.ts +0 -17
- package/dist/brillouin/compute.js +0 -426
- package/dist/brillouin/index.d.ts +0 -8
- package/dist/brillouin/index.js +0 -8
- package/dist/brillouin/types.d.ts +0 -48
- package/dist/brillouin/types.js +0 -1
- package/dist/chempot-diagram/ChemPotDiagram.svelte +0 -327
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +0 -13
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +0 -847
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +0 -16
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +0 -3194
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +0 -16
- package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +0 -7
- package/dist/chempot-diagram/async-compute.svelte.d.ts +0 -3
- package/dist/chempot-diagram/async-compute.svelte.js +0 -78
- package/dist/chempot-diagram/chempot-worker.d.ts +0 -1
- package/dist/chempot-diagram/chempot-worker.js +0 -11
- package/dist/chempot-diagram/color.d.ts +0 -10
- package/dist/chempot-diagram/color.js +0 -32
- package/dist/chempot-diagram/compute.d.ts +0 -48
- package/dist/chempot-diagram/compute.js +0 -812
- package/dist/chempot-diagram/index.d.ts +0 -6
- package/dist/chempot-diagram/index.js +0 -6
- package/dist/chempot-diagram/pointer.d.ts +0 -16
- package/dist/chempot-diagram/pointer.js +0 -40
- package/dist/chempot-diagram/temperature.d.ts +0 -15
- package/dist/chempot-diagram/temperature.js +0 -36
- package/dist/chempot-diagram/types.d.ts +0 -86
- package/dist/chempot-diagram/types.js +0 -28
- package/dist/colors/index.d.ts +0 -47
- package/dist/colors/index.js +0 -203
- package/dist/composition/BarChart.svelte +0 -297
- package/dist/composition/BarChart.svelte.d.ts +0 -39
- package/dist/composition/BubbleChart.svelte +0 -218
- package/dist/composition/BubbleChart.svelte.d.ts +0 -28
- package/dist/composition/Composition.svelte +0 -164
- package/dist/composition/Composition.svelte.d.ts +0 -15
- package/dist/composition/Formula.svelte +0 -265
- package/dist/composition/Formula.svelte.d.ts +0 -19
- package/dist/composition/FormulaFilter.svelte +0 -1259
- package/dist/composition/FormulaFilter.svelte.d.ts +0 -51
- package/dist/composition/PieChart.svelte +0 -323
- package/dist/composition/PieChart.svelte.d.ts +0 -37
- package/dist/composition/format.d.ts +0 -15
- package/dist/composition/format.js +0 -109
- package/dist/composition/index.d.ts +0 -20
- package/dist/composition/index.js +0 -14
- package/dist/composition/parse.d.ts +0 -55
- package/dist/composition/parse.js +0 -459
- package/dist/constants.d.ts +0 -29
- package/dist/constants.js +0 -99
- package/dist/controls.d.ts +0 -14
- package/dist/controls.js +0 -30
- package/dist/convex-hull/ConvexHull.svelte +0 -157
- package/dist/convex-hull/ConvexHull.svelte.d.ts +0 -13
- package/dist/convex-hull/ConvexHull2D.svelte +0 -814
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +0 -11
- package/dist/convex-hull/ConvexHull3D.svelte +0 -1790
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +0 -8
- package/dist/convex-hull/ConvexHull4D.svelte +0 -1386
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +0 -8
- package/dist/convex-hull/ConvexHullControls.svelte +0 -546
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +0 -48
- package/dist/convex-hull/ConvexHullInfoPane.svelte +0 -122
- package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +0 -18
- package/dist/convex-hull/ConvexHullStats.svelte +0 -922
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +0 -15
- package/dist/convex-hull/ConvexHullTooltip.svelte +0 -131
- package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +0 -33
- package/dist/convex-hull/GasPressureControls.svelte +0 -247
- package/dist/convex-hull/GasPressureControls.svelte.d.ts +0 -11
- package/dist/convex-hull/StructurePopup.svelte +0 -116
- package/dist/convex-hull/StructurePopup.svelte.d.ts +0 -18
- package/dist/convex-hull/TemperatureSlider.svelte +0 -137
- package/dist/convex-hull/TemperatureSlider.svelte.d.ts +0 -8
- package/dist/convex-hull/barycentric-coords.d.ts +0 -18
- package/dist/convex-hull/barycentric-coords.js +0 -182
- package/dist/convex-hull/demo-temperature.d.ts +0 -6
- package/dist/convex-hull/demo-temperature.js +0 -40
- package/dist/convex-hull/gas-thermodynamics.d.ts +0 -16
- package/dist/convex-hull/gas-thermodynamics.js +0 -316
- package/dist/convex-hull/helpers.d.ts +0 -103
- package/dist/convex-hull/helpers.js +0 -689
- package/dist/convex-hull/index.d.ts +0 -118
- package/dist/convex-hull/index.js +0 -57
- package/dist/convex-hull/thermodynamics.d.ts +0 -66
- package/dist/convex-hull/thermodynamics.js +0 -1752
- package/dist/convex-hull/types.d.ts +0 -162
- package/dist/convex-hull/types.js +0 -36
- package/dist/coordination/CoordinationBarPlot.svelte +0 -311
- package/dist/coordination/CoordinationBarPlot.svelte.d.ts +0 -30
- package/dist/coordination/calc-coordination.d.ts +0 -15
- package/dist/coordination/calc-coordination.js +0 -63
- package/dist/coordination/index.d.ts +0 -8
- package/dist/coordination/index.js +0 -7
- package/dist/element/BohrAtom.svelte +0 -147
- package/dist/element/BohrAtom.svelte.d.ts +0 -20
- package/dist/element/ElementHeading.svelte +0 -26
- package/dist/element/ElementHeading.svelte.d.ts +0 -8
- package/dist/element/ElementPhoto.svelte +0 -57
- package/dist/element/ElementPhoto.svelte.d.ts +0 -9
- package/dist/element/ElementStats.svelte +0 -80
- package/dist/element/ElementStats.svelte.d.ts +0 -8
- package/dist/element/ElementTile.svelte +0 -484
- package/dist/element/ElementTile.svelte.d.ts +0 -29
- package/dist/element/Nucleus.svelte.d.ts +0 -17
- package/dist/element/data.d.ts +0 -2
- package/dist/element/data.js +0 -2
- package/dist/element/data.json.gz.d.ts +0 -2
- package/dist/element/index.d.ts +0 -8
- package/dist/element/index.js +0 -8
- package/dist/element/types.d.ts +0 -57
- package/dist/element/types.js +0 -1
- package/dist/feedback/ClickFeedback.svelte +0 -58
- package/dist/feedback/ClickFeedback.svelte.d.ts +0 -12
- package/dist/feedback/DragOverlay.svelte +0 -42
- package/dist/feedback/DragOverlay.svelte.d.ts +0 -7
- package/dist/feedback/Spinner.svelte.d.ts +0 -7
- package/dist/feedback/StatusMessage.svelte.d.ts +0 -9
- package/dist/feedback/index.d.ts +0 -4
- package/dist/feedback/index.js +0 -4
- package/dist/fermi-surface/FermiSlice.svelte +0 -189
- package/dist/fermi-surface/FermiSlice.svelte.d.ts +0 -24
- package/dist/fermi-surface/FermiSurface.svelte +0 -600
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +0 -83
- package/dist/fermi-surface/FermiSurfaceControls.svelte +0 -452
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +0 -35
- package/dist/fermi-surface/FermiSurfaceScene.svelte +0 -792
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +0 -50
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +0 -111
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte.d.ts +0 -8
- package/dist/fermi-surface/compute.d.ts +0 -5
- package/dist/fermi-surface/compute.js +0 -538
- package/dist/fermi-surface/constants.d.ts +0 -9
- package/dist/fermi-surface/constants.js +0 -27
- package/dist/fermi-surface/export.d.ts +0 -5
- package/dist/fermi-surface/export.js +0 -63
- package/dist/fermi-surface/index.d.ts +0 -12
- package/dist/fermi-surface/index.js +0 -13
- package/dist/fermi-surface/marching-cubes.d.ts +0 -2
- package/dist/fermi-surface/marching-cubes.js +0 -2
- package/dist/fermi-surface/parse.d.ts +0 -2
- package/dist/fermi-surface/parse.js +0 -495
- package/dist/fermi-surface/symmetry.d.ts +0 -3
- package/dist/fermi-surface/symmetry.js +0 -46
- package/dist/fermi-surface/types.d.ts +0 -113
- package/dist/fermi-surface/types.js +0 -4
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +0 -1527
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +0 -110
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +0 -225
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +0 -30
- package/dist/heatmap-matrix/index.d.ts +0 -53
- package/dist/heatmap-matrix/index.js +0 -100
- package/dist/heatmap-matrix/shared.d.ts +0 -2
- package/dist/heatmap-matrix/shared.js +0 -4
- package/dist/icons.d.ts +0 -569
- package/dist/icons.js +0 -648
- package/dist/index.d.ts +0 -39
- package/dist/index.js +0 -39
- package/dist/io/decompress.d.ts +0 -10
- package/dist/io/decompress.js +0 -74
- package/dist/io/export.d.ts +0 -16
- package/dist/io/export.js +0 -316
- package/dist/io/fetch.d.ts +0 -5
- package/dist/io/fetch.js +0 -39
- package/dist/io/file-drop.d.ts +0 -7
- package/dist/io/file-drop.js +0 -43
- package/dist/io/index.d.ts +0 -7
- package/dist/io/index.js +0 -7
- package/dist/io/is-binary.d.ts +0 -1
- package/dist/io/is-binary.js +0 -20
- package/dist/io/types.d.ts +0 -8
- package/dist/io/types.js +0 -1
- package/dist/io/url-drop.d.ts +0 -2
- package/dist/io/url-drop.js +0 -117
- package/dist/isosurface/Isosurface.svelte +0 -285
- package/dist/isosurface/Isosurface.svelte.d.ts +0 -8
- package/dist/isosurface/IsosurfaceControls.svelte +0 -291
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +0 -9
- package/dist/isosurface/index.d.ts +0 -5
- package/dist/isosurface/index.js +0 -6
- package/dist/isosurface/parse.d.ts +0 -6
- package/dist/isosurface/parse.js +0 -553
- package/dist/isosurface/slice.d.ts +0 -11
- package/dist/isosurface/slice.js +0 -140
- package/dist/isosurface/types.d.ts +0 -56
- package/dist/isosurface/types.js +0 -227
- package/dist/labels.d.ts +0 -53
- package/dist/labels.js +0 -274
- package/dist/layout/FullscreenToggle.svelte +0 -50
- package/dist/layout/FullscreenToggle.svelte.d.ts +0 -7
- package/dist/layout/InfoCard.svelte +0 -120
- package/dist/layout/InfoCard.svelte.d.ts +0 -21
- package/dist/layout/InfoTag.svelte +0 -183
- package/dist/layout/InfoTag.svelte.d.ts +0 -19
- package/dist/layout/PropertyFilter.svelte +0 -244
- package/dist/layout/PropertyFilter.svelte.d.ts +0 -24
- package/dist/layout/SettingsSection.svelte +0 -148
- package/dist/layout/SettingsSection.svelte.d.ts +0 -17
- package/dist/layout/SubpageGrid.svelte +0 -82
- package/dist/layout/SubpageGrid.svelte.d.ts +0 -14
- package/dist/layout/fullscreen.d.ts +0 -9
- package/dist/layout/fullscreen.js +0 -53
- package/dist/layout/index.d.ts +0 -10
- package/dist/layout/index.js +0 -8
- package/dist/layout/json-tree/JsonNode.svelte +0 -548
- package/dist/layout/json-tree/JsonNode.svelte.d.ts +0 -11
- package/dist/layout/json-tree/JsonTree.svelte +0 -1222
- package/dist/layout/json-tree/JsonTree.svelte.d.ts +0 -6
- package/dist/layout/json-tree/JsonValue.svelte +0 -334
- package/dist/layout/json-tree/JsonValue.svelte.d.ts +0 -9
- package/dist/layout/json-tree/index.d.ts +0 -3
- package/dist/layout/json-tree/index.js +0 -3
- package/dist/layout/json-tree/types.d.ts +0 -73
- package/dist/layout/json-tree/types.js +0 -3
- package/dist/layout/json-tree/utils.d.ts +0 -29
- package/dist/layout/json-tree/utils.js +0 -649
- package/dist/marching-cubes.d.ts +0 -14
- package/dist/marching-cubes.js +0 -542
- package/dist/math.d.ts +0 -91
- package/dist/math.js +0 -896
- package/dist/overlays/ContextMenu.svelte +0 -162
- package/dist/overlays/ContextMenu.svelte.d.ts +0 -25
- package/dist/overlays/CopyButton.svelte +0 -45
- package/dist/overlays/CopyButton.svelte.d.ts +0 -8
- package/dist/overlays/DraggablePane.svelte +0 -564
- package/dist/overlays/DraggablePane.svelte.d.ts +0 -36
- package/dist/overlays/InfoPaneCards.svelte +0 -149
- package/dist/overlays/InfoPaneCards.svelte.d.ts +0 -22
- package/dist/overlays/index.d.ts +0 -2
- package/dist/overlays/index.js +0 -2
- package/dist/periodic-table/PeriodicTable.svelte +0 -469
- package/dist/periodic-table/PeriodicTable.svelte.d.ts +0 -55
- package/dist/periodic-table/PeriodicTableControls.svelte +0 -557
- package/dist/periodic-table/PeriodicTableControls.svelte.d.ts +0 -24
- package/dist/periodic-table/PropertySelect.svelte +0 -37
- package/dist/periodic-table/PropertySelect.svelte.d.ts +0 -13
- package/dist/periodic-table/TableInset.svelte.d.ts +0 -9
- package/dist/periodic-table/index.d.ts +0 -10
- package/dist/periodic-table/index.js +0 -4
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +0 -1084
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +0 -44
- package/dist/phase-diagram/PhaseDiagramControls.svelte +0 -449
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +0 -30
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +0 -126
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +0 -15
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +0 -192
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +0 -19
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +0 -392
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +0 -16
- package/dist/phase-diagram/TdbInfoPanel.svelte +0 -203
- package/dist/phase-diagram/TdbInfoPanel.svelte.d.ts +0 -12
- package/dist/phase-diagram/build-diagram.d.ts +0 -11
- package/dist/phase-diagram/build-diagram.js +0 -167
- package/dist/phase-diagram/colors.d.ts +0 -35
- package/dist/phase-diagram/colors.js +0 -51
- package/dist/phase-diagram/diagram-input.d.ts +0 -33
- package/dist/phase-diagram/diagram-input.js +0 -3
- package/dist/phase-diagram/index.d.ts +0 -13
- package/dist/phase-diagram/index.js +0 -13
- package/dist/phase-diagram/parse.d.ts +0 -55
- package/dist/phase-diagram/parse.js +0 -276
- package/dist/phase-diagram/svg-to-diagram.d.ts +0 -2
- package/dist/phase-diagram/svg-to-diagram.js +0 -867
- package/dist/phase-diagram/types.d.ts +0 -99
- package/dist/phase-diagram/types.js +0 -1
- package/dist/phase-diagram/utils.d.ts +0 -118
- package/dist/phase-diagram/utils.js +0 -606
- package/dist/plot/AxisLabel.svelte +0 -51
- package/dist/plot/AxisLabel.svelte.d.ts +0 -16
- package/dist/plot/BarPlot.svelte +0 -2265
- package/dist/plot/BarPlot.svelte.d.ts +0 -83
- package/dist/plot/BarPlotControls.svelte +0 -66
- package/dist/plot/BarPlotControls.svelte.d.ts +0 -18
- package/dist/plot/ColorBar.svelte +0 -719
- package/dist/plot/ColorBar.svelte.d.ts +0 -31
- package/dist/plot/ColorScaleSelect.svelte +0 -54
- package/dist/plot/ColorScaleSelect.svelte.d.ts +0 -15
- package/dist/plot/ElementScatter.svelte +0 -63
- package/dist/plot/ElementScatter.svelte.d.ts +0 -14
- package/dist/plot/FillArea.svelte +0 -225
- package/dist/plot/FillArea.svelte.d.ts +0 -21
- package/dist/plot/Histogram.svelte +0 -1672
- package/dist/plot/Histogram.svelte.d.ts +0 -50
- package/dist/plot/HistogramControls.svelte +0 -212
- package/dist/plot/HistogramControls.svelte.d.ts +0 -22
- package/dist/plot/InteractiveAxisLabel.svelte +0 -94
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +0 -14
- package/dist/plot/Line.svelte +0 -84
- package/dist/plot/Line.svelte.d.ts +0 -15
- package/dist/plot/PlotControls.svelte +0 -537
- package/dist/plot/PlotControls.svelte.d.ts +0 -4
- package/dist/plot/PlotLegend.svelte +0 -569
- package/dist/plot/PlotLegend.svelte.d.ts +0 -29
- package/dist/plot/PlotTooltip.svelte +0 -67
- package/dist/plot/PlotTooltip.svelte.d.ts +0 -17
- package/dist/plot/PortalSelect.svelte +0 -253
- package/dist/plot/PortalSelect.svelte.d.ts +0 -16
- package/dist/plot/ReferenceLine.svelte.d.ts +0 -20
- package/dist/plot/ReferenceLine3D.svelte +0 -154
- package/dist/plot/ReferenceLine3D.svelte.d.ts +0 -14
- package/dist/plot/ReferencePlane.svelte +0 -178
- package/dist/plot/ReferencePlane.svelte.d.ts +0 -14
- package/dist/plot/ScatterPlot.svelte +0 -2845
- package/dist/plot/ScatterPlot.svelte.d.ts +0 -93
- package/dist/plot/ScatterPlot3D.svelte +0 -502
- package/dist/plot/ScatterPlot3D.svelte.d.ts +0 -94
- package/dist/plot/ScatterPlot3DControls.svelte +0 -437
- package/dist/plot/ScatterPlot3DControls.svelte.d.ts +0 -20
- package/dist/plot/ScatterPlot3DScene.svelte +0 -912
- package/dist/plot/ScatterPlot3DScene.svelte.d.ts +0 -74
- package/dist/plot/ScatterPlotControls.svelte +0 -307
- package/dist/plot/ScatterPlotControls.svelte.d.ts +0 -17
- package/dist/plot/ScatterPoint.svelte +0 -191
- package/dist/plot/ScatterPoint.svelte.d.ts +0 -21
- package/dist/plot/SpacegroupBarPlot.svelte +0 -293
- package/dist/plot/SpacegroupBarPlot.svelte.d.ts +0 -9
- package/dist/plot/Surface3D.svelte +0 -200
- package/dist/plot/Surface3D.svelte.d.ts +0 -13
- package/dist/plot/ZeroLines.svelte +0 -96
- package/dist/plot/ZeroLines.svelte.d.ts +0 -32
- package/dist/plot/ZoomRect.svelte +0 -23
- package/dist/plot/ZoomRect.svelte.d.ts +0 -8
- package/dist/plot/axis-utils.d.ts +0 -19
- package/dist/plot/axis-utils.js +0 -80
- package/dist/plot/data-cleaning.d.ts +0 -37
- package/dist/plot/data-cleaning.js +0 -855
- package/dist/plot/data-transform.d.ts +0 -16
- package/dist/plot/data-transform.js +0 -45
- package/dist/plot/defaults.d.ts +0 -19
- package/dist/plot/defaults.js +0 -9
- package/dist/plot/fill-utils.d.ts +0 -51
- package/dist/plot/fill-utils.js +0 -337
- package/dist/plot/hover-lock.svelte.d.ts +0 -14
- package/dist/plot/hover-lock.svelte.js +0 -46
- package/dist/plot/index.d.ts +0 -37
- package/dist/plot/index.js +0 -37
- package/dist/plot/interactions.d.ts +0 -12
- package/dist/plot/interactions.js +0 -100
- package/dist/plot/layout.d.ts +0 -60
- package/dist/plot/layout.js +0 -230
- package/dist/plot/reference-line.d.ts +0 -60
- package/dist/plot/reference-line.js +0 -316
- package/dist/plot/scales.d.ts +0 -48
- package/dist/plot/scales.js +0 -484
- package/dist/plot/svg.d.ts +0 -1
- package/dist/plot/svg.js +0 -11
- package/dist/plot/types.d.ts +0 -859
- package/dist/plot/types.js +0 -103
- package/dist/plot/utils/label-placement.d.ts +0 -47
- package/dist/plot/utils/label-placement.js +0 -256
- package/dist/plot/utils/series-visibility.d.ts +0 -9
- package/dist/plot/utils/series-visibility.js +0 -67
- package/dist/plot/utils.d.ts +0 -1
- package/dist/plot/utils.js +0 -14
- package/dist/rdf/RdfPlot.svelte +0 -247
- package/dist/rdf/RdfPlot.svelte.d.ts +0 -27
- package/dist/rdf/calc-rdf.d.ts +0 -4
- package/dist/rdf/calc-rdf.js +0 -111
- package/dist/rdf/index.d.ts +0 -23
- package/dist/rdf/index.js +0 -2
- package/dist/sanitize.d.ts +0 -4
- package/dist/sanitize.js +0 -114
- package/dist/settings.d.ts +0 -255
- package/dist/settings.js +0 -1132
- package/dist/spectral/Bands.svelte +0 -1040
- package/dist/spectral/Bands.svelte.d.ts +0 -40
- package/dist/spectral/BandsAndDos.svelte +0 -128
- package/dist/spectral/BandsAndDos.svelte.d.ts +0 -18
- package/dist/spectral/BrillouinBandsDos.svelte +0 -248
- package/dist/spectral/BrillouinBandsDos.svelte.d.ts +0 -20
- package/dist/spectral/Dos.svelte +0 -697
- package/dist/spectral/Dos.svelte.d.ts +0 -29
- package/dist/spectral/helpers.d.ts +0 -117
- package/dist/spectral/helpers.js +0 -1023
- package/dist/spectral/index.d.ts +0 -6
- package/dist/spectral/index.js +0 -7
- package/dist/spectral/types.d.ts +0 -84
- package/dist/spectral/types.js +0 -2
- package/dist/state.svelte.d.ts +0 -25
- package/dist/state.svelte.js +0 -45
- package/dist/structure/Arrow.svelte +0 -72
- package/dist/structure/Arrow.svelte.d.ts +0 -15
- package/dist/structure/AtomLegend.svelte +0 -798
- package/dist/structure/AtomLegend.svelte.d.ts +0 -34
- package/dist/structure/Bond.svelte +0 -140
- package/dist/structure/Bond.svelte.d.ts +0 -9
- package/dist/structure/CanvasTooltip.svelte +0 -33
- package/dist/structure/CanvasTooltip.svelte.d.ts +0 -12
- package/dist/structure/CellSelect.svelte +0 -351
- package/dist/structure/CellSelect.svelte.d.ts +0 -13
- package/dist/structure/Cylinder.svelte +0 -45
- package/dist/structure/Cylinder.svelte.d.ts +0 -10
- package/dist/structure/Lattice.svelte +0 -196
- package/dist/structure/Lattice.svelte.d.ts +0 -17
- package/dist/structure/Structure.svelte +0 -1999
- package/dist/structure/Structure.svelte.d.ts +0 -87
- package/dist/structure/StructureControls.svelte +0 -1298
- package/dist/structure/StructureControls.svelte.d.ts +0 -31
- package/dist/structure/StructureExportPane.svelte +0 -251
- package/dist/structure/StructureExportPane.svelte.d.ts +0 -17
- package/dist/structure/StructureInfoPane.svelte +0 -735
- package/dist/structure/StructureInfoPane.svelte.d.ts +0 -19
- package/dist/structure/StructureScene.svelte +0 -1905
- package/dist/structure/StructureScene.svelte.d.ts +0 -108
- package/dist/structure/atom-properties.d.ts +0 -37
- package/dist/structure/atom-properties.js +0 -200
- package/dist/structure/bond-order-perception.d.ts +0 -13
- package/dist/structure/bond-order-perception.js +0 -367
- package/dist/structure/bonding.d.ts +0 -42
- package/dist/structure/bonding.js +0 -525
- package/dist/structure/export.d.ts +0 -20
- package/dist/structure/export.js +0 -727
- package/dist/structure/index.d.ts +0 -125
- package/dist/structure/index.js +0 -171
- package/dist/structure/label-placement.d.ts +0 -14
- package/dist/structure/label-placement.js +0 -72
- package/dist/structure/measure.d.ts +0 -6
- package/dist/structure/measure.js +0 -29
- package/dist/structure/parse.d.ts +0 -66
- package/dist/structure/parse.js +0 -1363
- package/dist/structure/partial-occupancy.d.ts +0 -25
- package/dist/structure/partial-occupancy.js +0 -99
- package/dist/structure/pbc.d.ts +0 -9
- package/dist/structure/pbc.js +0 -123
- package/dist/structure/supercell.d.ts +0 -8
- package/dist/structure/supercell.js +0 -170
- package/dist/structure/validation.d.ts +0 -2
- package/dist/structure/validation.js +0 -10
- package/dist/symmetry/SymmetryStats.svelte +0 -226
- package/dist/symmetry/SymmetryStats.svelte.d.ts +0 -21
- package/dist/symmetry/WyckoffTable.svelte +0 -113
- package/dist/symmetry/WyckoffTable.svelte.d.ts +0 -11
- package/dist/symmetry/cell-transform.d.ts +0 -12
- package/dist/symmetry/cell-transform.js +0 -91
- package/dist/symmetry/index.d.ts +0 -43
- package/dist/symmetry/index.js +0 -229
- package/dist/symmetry/spacegroups.d.ts +0 -9
- package/dist/symmetry/spacegroups.js +0 -394
- package/dist/table/HeatmapTable.svelte +0 -1854
- package/dist/table/HeatmapTable.svelte.d.ts +0 -49
- package/dist/table/ToggleMenu.svelte +0 -376
- package/dist/table/ToggleMenu.svelte.d.ts +0 -11
- package/dist/table/index.d.ts +0 -74
- package/dist/table/index.js +0 -38
- package/dist/theme/ThemeControl.svelte +0 -53
- package/dist/theme/ThemeControl.svelte.d.ts +0 -9
- package/dist/theme/index.d.ts +0 -29
- package/dist/theme/index.js +0 -79
- package/dist/theme/themes.mjs +0 -285
- package/dist/time.d.ts +0 -4
- package/dist/time.js +0 -70
- package/dist/tooltip/TooltipContent.svelte +0 -58
- package/dist/tooltip/TooltipContent.svelte.d.ts +0 -31
- package/dist/tooltip/index.d.ts +0 -2
- package/dist/tooltip/index.js +0 -2
- package/dist/tooltip/types.d.ts +0 -8
- package/dist/tooltip/types.js +0 -1
- package/dist/trajectory/Trajectory.svelte +0 -1517
- package/dist/trajectory/Trajectory.svelte.d.ts +0 -77
- package/dist/trajectory/TrajectoryError.svelte +0 -128
- package/dist/trajectory/TrajectoryError.svelte.d.ts +0 -13
- package/dist/trajectory/TrajectoryExportPane.svelte +0 -357
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +0 -17
- package/dist/trajectory/TrajectoryInfoPane.svelte +0 -313
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +0 -17
- package/dist/trajectory/constants.d.ts +0 -6
- package/dist/trajectory/constants.js +0 -7
- package/dist/trajectory/extract.d.ts +0 -5
- package/dist/trajectory/extract.js +0 -162
- package/dist/trajectory/format-detect.d.ts +0 -9
- package/dist/trajectory/format-detect.js +0 -76
- package/dist/trajectory/frame-reader.d.ts +0 -17
- package/dist/trajectory/frame-reader.js +0 -332
- package/dist/trajectory/helpers.d.ts +0 -14
- package/dist/trajectory/helpers.js +0 -172
- package/dist/trajectory/index.d.ts +0 -63
- package/dist/trajectory/index.js +0 -126
- package/dist/trajectory/parse/ase.d.ts +0 -2
- package/dist/trajectory/parse/ase.js +0 -73
- package/dist/trajectory/parse/hdf5.d.ts +0 -2
- package/dist/trajectory/parse/hdf5.js +0 -127
- package/dist/trajectory/parse/index.d.ts +0 -12
- package/dist/trajectory/parse/index.js +0 -299
- package/dist/trajectory/parse/lammps.d.ts +0 -5
- package/dist/trajectory/parse/lammps.js +0 -179
- package/dist/trajectory/parse/vasp.d.ts +0 -2
- package/dist/trajectory/parse/vasp.js +0 -68
- package/dist/trajectory/parse/xyz.d.ts +0 -2
- package/dist/trajectory/parse/xyz.js +0 -110
- package/dist/trajectory/plotting.d.ts +0 -28
- package/dist/trajectory/plotting.js +0 -423
- package/dist/trajectory/types.d.ts +0 -11
- package/dist/trajectory/types.js +0 -1
- package/dist/utils.d.ts +0 -5
- package/dist/utils.js +0 -36
- package/dist/xrd/XrdPlot.svelte +0 -615
- package/dist/xrd/XrdPlot.svelte.d.ts +0 -28
- package/dist/xrd/broadening.d.ts +0 -20
- package/dist/xrd/broadening.js +0 -97
- package/dist/xrd/calc-xrd.d.ts +0 -37
- package/dist/xrd/calc-xrd.js +0 -337
- package/dist/xrd/index.d.ts +0 -37
- package/dist/xrd/index.js +0 -4
- package/dist/xrd/parse.d.ts +0 -13
- package/dist/xrd/parse.js +0 -749
- /package/dist/{EmptyState.svelte → src/lib/EmptyState.svelte} +0 -0
- /package/dist/{Icon.svelte → src/lib/Icon.svelte} +0 -0
- /package/dist/{app.css → src/lib/app.css} +0 -0
- /package/dist/{chempot-diagram → src/lib/chempot-diagram}/ChemPotScene3D.svelte +0 -0
- /package/dist/{colors → src/lib/colors}/alloy-colors.json +0 -0
- /package/dist/{colors → src/lib/colors}/dark-mode-colors.json +0 -0
- /package/dist/{colors → src/lib/colors}/jmol-colors.json +0 -0
- /package/dist/{colors → src/lib/colors}/muted-colors.json +0 -0
- /package/dist/{colors → src/lib/colors}/pastel-colors.json +0 -0
- /package/dist/{colors → src/lib/colors}/vesta-colors.json +0 -0
- /package/dist/{element → src/lib/element}/Nucleus.svelte +0 -0
- /package/dist/{element → src/lib/element}/data.json +0 -0
- /package/dist/{element → src/lib/element}/data.json.gz +0 -0
- /package/dist/{element → src/lib/element}/data.schema.json +0 -0
- /package/dist/{element-image-urls.json → src/lib/element-image-urls.json} +0 -0
- /package/dist/{feedback → src/lib/feedback}/Spinner.svelte +0 -0
- /package/dist/{feedback → src/lib/feedback}/StatusMessage.svelte +0 -0
- /package/dist/{periodic-table → src/lib/periodic-table}/TableInset.svelte +0 -0
- /package/dist/{plot → src/lib/plot}/ReferenceLine.svelte +0 -0
- /package/dist/{xrd → src/lib/xrd}/atomic_scattering_params.json +0 -0
|
@@ -1,1752 +0,0 @@
|
|
|
1
|
-
import { count_atoms_in_composition, extract_formula_elements, sort_by_electronegativity, } from '../composition';
|
|
2
|
-
import * as math from '../math';
|
|
3
|
-
import { barycentric_to_ternary_xyz, barycentric_to_tetrahedral, composition_to_barycentric_3d, composition_to_barycentric_4d, composition_to_barycentric_nd, } from './barycentric-coords';
|
|
4
|
-
import { get_arity, HULL_STABILITY_TOL, is_on_hull, is_unary_entry } from './helpers';
|
|
5
|
-
// Track warned keys to avoid log spam on large datasets with repeated invalid keys
|
|
6
|
-
const warned_keys = new Set();
|
|
7
|
-
// Normalize convex hull composition keys by stripping oxidation states (e.g. "V4+" -> "V")
|
|
8
|
-
// and merging amounts for keys that map to the same element. Filters non-positive amounts.
|
|
9
|
-
// Only extracts FIRST valid element from each key (e.g. "Fe2O3" -> "Fe", not both Fe and O).
|
|
10
|
-
export function normalize_hull_composition_keys(composition) {
|
|
11
|
-
const normalized = {};
|
|
12
|
-
for (const [key, amount] of Object.entries(composition)) {
|
|
13
|
-
if (typeof amount !== `number` || !Number.isFinite(amount) || amount <= 0)
|
|
14
|
-
continue;
|
|
15
|
-
// Extract first valid element symbol from key (handles oxidation states like "V4+", "Fe2+")
|
|
16
|
-
const elem = extract_formula_elements(key, { unique: false })[0];
|
|
17
|
-
if (!elem) {
|
|
18
|
-
// Dedupe warnings to avoid log spam on large datasets
|
|
19
|
-
if (!warned_keys.has(key)) {
|
|
20
|
-
warned_keys.add(key);
|
|
21
|
-
console.warn(`Skipping unrecognized composition key: "${key}"`);
|
|
22
|
-
}
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
normalized[elem] = (normalized[elem] || 0) + amount;
|
|
26
|
-
}
|
|
27
|
-
return normalized;
|
|
28
|
-
}
|
|
29
|
-
export function process_hull_entries(entries) {
|
|
30
|
-
// Normalize composition keys to strip oxidation states (e.g. "Fe3+" -> "Fe")
|
|
31
|
-
// Filter out entries whose composition normalizes to {} (all keys invalid or non-positive)
|
|
32
|
-
const normalized_entries = entries
|
|
33
|
-
.map((entry) => ({
|
|
34
|
-
...entry,
|
|
35
|
-
composition: normalize_hull_composition_keys(entry.composition),
|
|
36
|
-
}))
|
|
37
|
-
.filter((entry) => Object.keys(entry.composition).length > 0);
|
|
38
|
-
// Single-pass partition instead of two filter passes
|
|
39
|
-
const stable_entries = [];
|
|
40
|
-
const unstable_entries = [];
|
|
41
|
-
for (const entry of normalized_entries) {
|
|
42
|
-
const stable = typeof entry.is_stable === `boolean`
|
|
43
|
-
? entry.is_stable
|
|
44
|
-
: (entry.e_above_hull ?? Infinity) <= HULL_STABILITY_TOL;
|
|
45
|
-
(stable ? stable_entries : unstable_entries).push(entry);
|
|
46
|
-
}
|
|
47
|
-
// Extract unique element symbols from normalized compositions
|
|
48
|
-
const elements = Array.from(new Set(normalized_entries.flatMap((entry) => Object.keys(entry.composition)))).sort();
|
|
49
|
-
const el_refs = Object.fromEntries(stable_entries
|
|
50
|
-
.filter(is_unary_entry)
|
|
51
|
-
.map((entry) => [Object.keys(entry.composition)[0], entry]));
|
|
52
|
-
return {
|
|
53
|
-
entries: normalized_entries,
|
|
54
|
-
stable_entries,
|
|
55
|
-
unstable_entries,
|
|
56
|
-
elements,
|
|
57
|
-
el_refs,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
// Get energy per atom with correction applied, or fallback to raw energy_per_atom/energy.
|
|
61
|
-
// Note: correction is expected to be a total-entry value (eV), not per-atom.
|
|
62
|
-
// This matches the Materials Project convention where corrections are applied to total energies.
|
|
63
|
-
function get_energy_per_atom(entry) {
|
|
64
|
-
// Use Math.max instead of || to prevent pathological negative totals from flipping sign
|
|
65
|
-
const atoms = Math.max(count_atoms_in_composition(entry.composition), 1e-12);
|
|
66
|
-
if (typeof entry.correction === `number`) {
|
|
67
|
-
const total = typeof entry.energy_per_atom === `number`
|
|
68
|
-
? entry.energy_per_atom * atoms
|
|
69
|
-
: (entry.energy ?? 0);
|
|
70
|
-
return (total + entry.correction) / atoms;
|
|
71
|
-
}
|
|
72
|
-
return entry.energy_per_atom ?? (entry.energy ?? 0) / atoms;
|
|
73
|
-
}
|
|
74
|
-
export function compute_e_form_per_atom(entry, el_refs) {
|
|
75
|
-
const atoms = count_atoms_in_composition(entry.composition);
|
|
76
|
-
if (atoms <= 0)
|
|
77
|
-
return null;
|
|
78
|
-
let ref_sum = 0;
|
|
79
|
-
for (const [el, amt] of Object.entries(entry.composition)) {
|
|
80
|
-
const ref = el_refs[el];
|
|
81
|
-
if (!ref)
|
|
82
|
-
return null;
|
|
83
|
-
ref_sum += (amt / atoms) * get_energy_per_atom(ref);
|
|
84
|
-
}
|
|
85
|
-
return get_energy_per_atom(entry) - ref_sum;
|
|
86
|
-
}
|
|
87
|
-
export function find_lowest_energy_unary_refs(entries) {
|
|
88
|
-
const refs = {};
|
|
89
|
-
for (const entry of entries) {
|
|
90
|
-
if (!is_unary_entry(entry))
|
|
91
|
-
continue;
|
|
92
|
-
const el = Object.keys(entry.composition).find((key) => (entry.composition[key] ?? 0) > 0);
|
|
93
|
-
if (!el)
|
|
94
|
-
continue;
|
|
95
|
-
const current = refs[el];
|
|
96
|
-
if (!current || get_energy_per_atom(entry) < get_energy_per_atom(current)) {
|
|
97
|
-
refs[el] = entry;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return refs;
|
|
101
|
-
}
|
|
102
|
-
export function calculate_e_above_hull(input, reference_entries) {
|
|
103
|
-
const is_single = !Array.isArray(input);
|
|
104
|
-
const entries_of_interest = is_single ? [input] : input;
|
|
105
|
-
if (entries_of_interest.length === 0)
|
|
106
|
-
return {}; // Empty input → empty result (not an error)
|
|
107
|
-
if (reference_entries.length === 0) {
|
|
108
|
-
throw new Error(`Reference entries cannot be empty`);
|
|
109
|
-
}
|
|
110
|
-
// 1. Identify chemical system
|
|
111
|
-
const elements = Array.from(new Set(reference_entries.flatMap((entry) => Object.keys(entry.composition)))).sort();
|
|
112
|
-
// 2. Validate subset
|
|
113
|
-
const element_set = new Set(elements);
|
|
114
|
-
for (const entry of entries_of_interest) {
|
|
115
|
-
for (const el of Object.keys(entry.composition)) {
|
|
116
|
-
if (!element_set.has(el)) {
|
|
117
|
-
throw new Error(`Entry contains element ${el} not present in reference system: ${elements.join(`-`)}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
// 3. Compute formation energies
|
|
122
|
-
const refs = find_lowest_energy_unary_refs(reference_entries);
|
|
123
|
-
const compute_e_form = (entry) => typeof entry.e_form_per_atom === `number`
|
|
124
|
-
? entry.e_form_per_atom
|
|
125
|
-
: compute_e_form_per_atom(entry, refs);
|
|
126
|
-
const interest_data = entries_of_interest.map((entry) => ({
|
|
127
|
-
entry,
|
|
128
|
-
e_form: compute_e_form(entry),
|
|
129
|
-
}));
|
|
130
|
-
// 4. Branch by arity
|
|
131
|
-
const arity = elements.length;
|
|
132
|
-
const results = {};
|
|
133
|
-
if (arity === 1) {
|
|
134
|
-
// Unary system
|
|
135
|
-
for (const { entry, e_form } of interest_data) {
|
|
136
|
-
const id = entry.entry_id ?? JSON.stringify(entry.composition);
|
|
137
|
-
// For unary, e_above_hull is simply e_form (since stable state is 0)
|
|
138
|
-
// Unless we have multiple polymorphs, in which case the hull is at min(e_form) which should be 0
|
|
139
|
-
// But compute_e_form_per_atom already subtracts the stable unary reference energy.
|
|
140
|
-
// So e_form IS e_above_hull for unary systems if correction logic holds.
|
|
141
|
-
results[id] = e_form ?? NaN;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
else if (arity === 2) {
|
|
145
|
-
// Binary system
|
|
146
|
-
const [_el1, el2] = elements;
|
|
147
|
-
// Build hull points from references
|
|
148
|
-
const hull_input_map = new Map(); // x -> min_e_form
|
|
149
|
-
for (const ref of reference_entries) {
|
|
150
|
-
const e_form = compute_e_form(ref);
|
|
151
|
-
if (typeof e_form !== `number`)
|
|
152
|
-
continue;
|
|
153
|
-
const total = count_atoms_in_composition(ref.composition);
|
|
154
|
-
if (total <= 0)
|
|
155
|
-
continue;
|
|
156
|
-
const x = (ref.composition[el2] || 0) / total;
|
|
157
|
-
const current = hull_input_map.get(x);
|
|
158
|
-
if (current === undefined || e_form < current) {
|
|
159
|
-
hull_input_map.set(x, e_form);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
// Ensure endpoints (pure elements default to e_form = 0)
|
|
163
|
-
if (!hull_input_map.has(0))
|
|
164
|
-
hull_input_map.set(0, 0);
|
|
165
|
-
if (!hull_input_map.has(1))
|
|
166
|
-
hull_input_map.set(1, 0);
|
|
167
|
-
const hull_points = Array.from(hull_input_map, ([x, y]) => ({ x, y }));
|
|
168
|
-
const lower_hull = compute_lower_hull_2d(hull_points);
|
|
169
|
-
for (const { entry, e_form } of interest_data) {
|
|
170
|
-
const id = entry.entry_id ?? JSON.stringify(entry.composition);
|
|
171
|
-
if (typeof e_form !== `number`) {
|
|
172
|
-
results[id] = NaN;
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
const total = count_atoms_in_composition(entry.composition);
|
|
176
|
-
// Guard for degenerate compositions (mirror the refs loop check)
|
|
177
|
-
if (total <= 0) {
|
|
178
|
-
results[id] = NaN;
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
const x = (entry.composition[el2] || 0) / total;
|
|
182
|
-
const y_hull = interpolate_hull_2d(lower_hull, x);
|
|
183
|
-
results[id] = y_hull === null ? NaN : Math.max(0, e_form - y_hull);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
else if (arity === 3) {
|
|
187
|
-
// Ternary system
|
|
188
|
-
const ref_points = [];
|
|
189
|
-
for (const ref of reference_entries) {
|
|
190
|
-
const e_form = compute_e_form(ref);
|
|
191
|
-
if (typeof e_form !== `number`)
|
|
192
|
-
continue;
|
|
193
|
-
try {
|
|
194
|
-
const bary = composition_to_barycentric_3d(ref.composition, elements);
|
|
195
|
-
const point = barycentric_to_ternary_xyz(bary, e_form);
|
|
196
|
-
ref_points.push(point);
|
|
197
|
-
}
|
|
198
|
-
catch {
|
|
199
|
-
// Ignore invalid compositions
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
// Ensure corner points (pure elements default to e_form = 0)
|
|
203
|
-
for (const el of elements) {
|
|
204
|
-
const corner = barycentric_to_ternary_xyz(composition_to_barycentric_3d({ [el]: 1 }, elements), 0);
|
|
205
|
-
if (!ref_points.some((point) => Math.hypot(point.x - corner.x, point.y - corner.y, point.z - corner.z) < 1e-9)) {
|
|
206
|
-
ref_points.push(corner);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
const hull_triangles = compute_lower_hull_triangles(ref_points);
|
|
210
|
-
const hull_models = build_lower_hull_model(hull_triangles);
|
|
211
|
-
for (const { entry, e_form } of interest_data) {
|
|
212
|
-
const id = entry.entry_id ?? JSON.stringify(entry.composition);
|
|
213
|
-
if (typeof e_form !== `number`) {
|
|
214
|
-
results[id] = NaN;
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
try {
|
|
218
|
-
const bary = composition_to_barycentric_3d(entry.composition, elements);
|
|
219
|
-
const point = barycentric_to_ternary_xyz(bary, e_form);
|
|
220
|
-
const z_hull = e_hull_at_xy(hull_models, point.x, point.y);
|
|
221
|
-
results[id] = z_hull === null ? NaN : Math.max(0, point.z - z_hull);
|
|
222
|
-
}
|
|
223
|
-
catch {
|
|
224
|
-
results[id] = NaN;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
else if (arity === 4) {
|
|
229
|
-
// Quaternary system
|
|
230
|
-
const ref_points = [];
|
|
231
|
-
for (const ref of reference_entries) {
|
|
232
|
-
const e_form = compute_e_form(ref);
|
|
233
|
-
if (typeof e_form !== `number`)
|
|
234
|
-
continue;
|
|
235
|
-
try {
|
|
236
|
-
const bary = composition_to_barycentric_4d(ref.composition, elements);
|
|
237
|
-
const tet = barycentric_to_tetrahedral(bary);
|
|
238
|
-
ref_points.push({ ...tet, w: e_form });
|
|
239
|
-
}
|
|
240
|
-
catch {
|
|
241
|
-
// Ignore invalid
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
// Ensure corner points (pure elements default to e_form = 0)
|
|
245
|
-
for (const el of elements) {
|
|
246
|
-
const tet = barycentric_to_tetrahedral(composition_to_barycentric_4d({ [el]: 1 }, elements));
|
|
247
|
-
const corner = { ...tet, w: 0 };
|
|
248
|
-
const dist = (point) => Math.hypot(point.x - corner.x, point.y - corner.y, point.z - corner.z, point.w);
|
|
249
|
-
if (!ref_points.some((point) => dist(point) < 1e-9))
|
|
250
|
-
ref_points.push(corner);
|
|
251
|
-
}
|
|
252
|
-
const hull_tetrahedra = compute_lower_hull_4d(ref_points);
|
|
253
|
-
const interest_points = [];
|
|
254
|
-
const interest_indices = [];
|
|
255
|
-
interest_data.forEach(({ entry, e_form }, idx) => {
|
|
256
|
-
if (typeof e_form === `number`) {
|
|
257
|
-
try {
|
|
258
|
-
const bary = composition_to_barycentric_4d(entry.composition, elements);
|
|
259
|
-
const tet = barycentric_to_tetrahedral(bary);
|
|
260
|
-
interest_points.push({ ...tet, w: e_form });
|
|
261
|
-
interest_indices.push(idx);
|
|
262
|
-
}
|
|
263
|
-
catch {
|
|
264
|
-
// Skip
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
const distances = compute_e_above_hull_4d(interest_points, hull_tetrahedra);
|
|
269
|
-
// Build reverse lookup for O(1) access
|
|
270
|
-
const idx_to_point_idx = new Map();
|
|
271
|
-
interest_indices.forEach((original_idx, point_idx) => {
|
|
272
|
-
idx_to_point_idx.set(original_idx, point_idx);
|
|
273
|
-
});
|
|
274
|
-
// Map back
|
|
275
|
-
for (let idx = 0; idx < interest_data.length; idx++) {
|
|
276
|
-
const { entry } = interest_data[idx];
|
|
277
|
-
const id = entry.entry_id ?? JSON.stringify(entry.composition);
|
|
278
|
-
const point_idx = idx_to_point_idx.get(idx) ?? -1;
|
|
279
|
-
if (point_idx !== -1) {
|
|
280
|
-
results[id] = Math.max(0, distances[point_idx]);
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
results[id] = NaN;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
// Arity 5+ uses generalized N-dimensional convex hull
|
|
289
|
-
// Helper to convert entry to hull point, returns null on expected errors
|
|
290
|
-
const to_hull_point = (entry, e_form) => {
|
|
291
|
-
try {
|
|
292
|
-
return [...composition_to_barycentric_nd(entry.composition, elements), e_form];
|
|
293
|
-
}
|
|
294
|
-
catch (err) {
|
|
295
|
-
// Skip expected errors (missing elements), warn on unexpected
|
|
296
|
-
if (err instanceof Error && !err.message.includes(`no elements from the system`)) {
|
|
297
|
-
console.warn(`Skipping entry: ${err.message}`);
|
|
298
|
-
}
|
|
299
|
-
return null;
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
// Build reference points
|
|
303
|
-
const ref_points = [];
|
|
304
|
-
for (const ref of reference_entries) {
|
|
305
|
-
const e_form = compute_e_form(ref);
|
|
306
|
-
if (typeof e_form !== `number`)
|
|
307
|
-
continue;
|
|
308
|
-
const point = to_hull_point(ref, e_form);
|
|
309
|
-
if (point)
|
|
310
|
-
ref_points.push(point);
|
|
311
|
-
}
|
|
312
|
-
// Ensure corner points (pure elements default to e_form = 0)
|
|
313
|
-
for (let el_idx = 0; el_idx < arity; el_idx++) {
|
|
314
|
-
const corner = Array(arity + 1).fill(0);
|
|
315
|
-
corner[el_idx] = 1; // ith barycentric coord = 1
|
|
316
|
-
if (!ref_points.some((pt) => norm_nd(subtract_nd(pt, corner)) < EPS)) {
|
|
317
|
-
ref_points.push(corner);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
const hull_facets = compute_lower_hull_nd(compute_quickhull_nd(ref_points));
|
|
321
|
-
// Warn if hull is degenerate (all points coplanar or insufficient spread)
|
|
322
|
-
if (hull_facets.length === 0 && ref_points.length >= arity + 1) {
|
|
323
|
-
console.warn(`N-dimensional hull for ${arity}-element system is degenerate. ` +
|
|
324
|
-
`Falling back to tie-hyperplane at energy 0. ` +
|
|
325
|
-
`Consider using pymatgen for complex high-dimensional phase diagrams.`);
|
|
326
|
-
}
|
|
327
|
-
// Build query points with mapping back to original indices
|
|
328
|
-
const interest_points = [];
|
|
329
|
-
const idx_to_point_idx = new Map();
|
|
330
|
-
for (let idx = 0; idx < interest_data.length; idx++) {
|
|
331
|
-
const { entry, e_form } = interest_data[idx];
|
|
332
|
-
if (typeof e_form !== `number`)
|
|
333
|
-
continue;
|
|
334
|
-
const point = to_hull_point(entry, e_form);
|
|
335
|
-
if (point) {
|
|
336
|
-
idx_to_point_idx.set(idx, interest_points.length);
|
|
337
|
-
interest_points.push(point);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
// Compute hull distances (empty array if degenerate hull)
|
|
341
|
-
const distances = hull_facets.length > 0
|
|
342
|
-
? compute_e_above_hull_nd(interest_points, hull_facets, ref_points)
|
|
343
|
-
: [];
|
|
344
|
-
// Map results back to entries
|
|
345
|
-
for (let idx = 0; idx < interest_data.length; idx++) {
|
|
346
|
-
const { entry, e_form } = interest_data[idx];
|
|
347
|
-
const id = entry.entry_id ?? JSON.stringify(entry.composition);
|
|
348
|
-
const point_idx = idx_to_point_idx.get(idx);
|
|
349
|
-
if (point_idx === undefined) {
|
|
350
|
-
results[id] = NaN;
|
|
351
|
-
}
|
|
352
|
-
else if (hull_facets.length === 0 && typeof e_form === `number`) {
|
|
353
|
-
// Degenerate case: hull is tie-hyperplane at energy 0
|
|
354
|
-
results[id] = Math.max(0, e_form);
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
results[id] = Math.max(0, distances[point_idx]);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
if (is_single) {
|
|
362
|
-
const id = entries_of_interest[0].entry_id ?? JSON.stringify(entries_of_interest[0].composition);
|
|
363
|
-
return results[id];
|
|
364
|
-
}
|
|
365
|
-
return results;
|
|
366
|
-
}
|
|
367
|
-
export function get_convex_hull_stats(processed_entries, elements, max_arity = 4) {
|
|
368
|
-
if (processed_entries.length === 0)
|
|
369
|
-
return null;
|
|
370
|
-
max_arity = Math.max(1, max_arity);
|
|
371
|
-
let unary = 0;
|
|
372
|
-
let binary = 0;
|
|
373
|
-
let ternary = 0;
|
|
374
|
-
let quaternary = 0;
|
|
375
|
-
let quinary_plus = 0;
|
|
376
|
-
for (const entry of processed_entries) {
|
|
377
|
-
const arity = get_arity(entry);
|
|
378
|
-
if (arity === 1)
|
|
379
|
-
unary++;
|
|
380
|
-
else if (arity === 2)
|
|
381
|
-
binary++;
|
|
382
|
-
else if (arity === 3)
|
|
383
|
-
ternary++;
|
|
384
|
-
else if (arity === 4)
|
|
385
|
-
quaternary++;
|
|
386
|
-
else if (arity >= 5)
|
|
387
|
-
quinary_plus++;
|
|
388
|
-
}
|
|
389
|
-
// Zero out counts beyond system dimensionality for cleaner display
|
|
390
|
-
// quinary_plus is intentionally not zeroed — it's a catch-all bucket that
|
|
391
|
-
// is naturally 0 for systems with fewer than 5 elements
|
|
392
|
-
if (max_arity < 4)
|
|
393
|
-
quaternary = 0;
|
|
394
|
-
if (max_arity < 3)
|
|
395
|
-
ternary = 0;
|
|
396
|
-
if (max_arity < 2)
|
|
397
|
-
binary = 0;
|
|
398
|
-
const stable_count = processed_entries.filter((entry) => is_on_hull(entry)).length;
|
|
399
|
-
const unstable_count = processed_entries.length - stable_count;
|
|
400
|
-
const energies = processed_entries
|
|
401
|
-
.map((entry) => entry.e_form_per_atom ?? entry.energy_per_atom ?? get_energy_per_atom(entry))
|
|
402
|
-
.filter(Number.isFinite);
|
|
403
|
-
// Use reduce instead of Math.min/max(...arr) to avoid stack overflow on large datasets
|
|
404
|
-
const energy_range = energies.length > 0
|
|
405
|
-
? {
|
|
406
|
-
min: energies.reduce((min, val) => (val < min ? val : min), Infinity),
|
|
407
|
-
max: energies.reduce((max, val) => (val > max ? val : max), -Infinity),
|
|
408
|
-
avg: energies.reduce((sum, val) => sum + val, 0) / energies.length,
|
|
409
|
-
}
|
|
410
|
-
: { min: 0, max: 0, avg: 0 };
|
|
411
|
-
const hull_distances = processed_entries
|
|
412
|
-
.map((entry) => entry.e_above_hull)
|
|
413
|
-
.filter((val) => typeof val === `number` && val >= 0);
|
|
414
|
-
const hull_distance = hull_distances.length > 0
|
|
415
|
-
? {
|
|
416
|
-
max: hull_distances.reduce((max, val) => (val > max ? val : max), -Infinity),
|
|
417
|
-
avg: hull_distances.reduce((sum, val) => sum + val, 0) / hull_distances.length,
|
|
418
|
-
}
|
|
419
|
-
: { max: 0, avg: 0 };
|
|
420
|
-
return {
|
|
421
|
-
total: processed_entries.length,
|
|
422
|
-
unary,
|
|
423
|
-
binary,
|
|
424
|
-
ternary,
|
|
425
|
-
quaternary,
|
|
426
|
-
quinary_plus,
|
|
427
|
-
stable: stable_count,
|
|
428
|
-
unstable: unstable_count,
|
|
429
|
-
energy_range,
|
|
430
|
-
hull_distance,
|
|
431
|
-
elements: elements.length,
|
|
432
|
-
chemical_system: sort_by_electronegativity([...elements]).join(`-`),
|
|
433
|
-
max_arity,
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
// Convert a PhaseData entry to a ConvexHullEntry with default visual fields.
|
|
437
|
-
// x/y/z default to 0 since high-dim systems aren't visually plotted.
|
|
438
|
-
function to_hull_entry(entry) {
|
|
439
|
-
return {
|
|
440
|
-
...entry,
|
|
441
|
-
visible: true,
|
|
442
|
-
is_element: get_arity(entry) === 1,
|
|
443
|
-
x: 0,
|
|
444
|
-
y: 0,
|
|
445
|
-
z: 0,
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
// Process raw hull entries for high-dimensional systems (5+ elements) where the
|
|
449
|
-
// ConvexHull visual component can't render. Computes formation energies, hull distances,
|
|
450
|
-
// stable/unstable classification, and phase stats. Returns null on failure.
|
|
451
|
-
// Optionally accepts `elements` to scope the chemical system; if omitted, elements
|
|
452
|
-
// are derived from the entries' compositions.
|
|
453
|
-
export function process_hull_for_stats(entries, elements) {
|
|
454
|
-
if (!entries.length)
|
|
455
|
-
return null;
|
|
456
|
-
const processed = process_hull_entries(entries);
|
|
457
|
-
if (!processed.entries.length)
|
|
458
|
-
return null;
|
|
459
|
-
const hull_elements = elements ?? processed.elements;
|
|
460
|
-
// Compute formation energies
|
|
461
|
-
const el_refs = find_lowest_energy_unary_refs(processed.entries);
|
|
462
|
-
for (const entry of processed.entries) {
|
|
463
|
-
if (entry.e_form_per_atom === undefined) {
|
|
464
|
-
const e_form = compute_e_form_per_atom(entry, el_refs);
|
|
465
|
-
if (e_form !== null)
|
|
466
|
-
entry.e_form_per_atom = e_form;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
// Compute hull distances. Note: entries without entry_id are keyed by
|
|
470
|
-
// JSON.stringify(composition), so polymorphs at the same composition
|
|
471
|
-
// collide — the last-processed entry's distance wins for all of them.
|
|
472
|
-
try {
|
|
473
|
-
const hull_distances = calculate_e_above_hull(processed.entries, processed.entries);
|
|
474
|
-
for (const entry of processed.entries) {
|
|
475
|
-
const dist = hull_distances[entry.entry_id ?? JSON.stringify(entry.composition)];
|
|
476
|
-
if (typeof dist === `number` && Number.isFinite(dist)) {
|
|
477
|
-
entry.e_above_hull = dist;
|
|
478
|
-
entry.is_stable = dist < HULL_STABILITY_TOL;
|
|
479
|
-
}
|
|
480
|
-
else {
|
|
481
|
-
// Clear stale hull metadata so previous values don't persist
|
|
482
|
-
entry.e_above_hull = undefined;
|
|
483
|
-
entry.is_stable = undefined;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
catch (err) {
|
|
488
|
-
console.warn(`Failed to compute high-dim hull:`, err);
|
|
489
|
-
return null;
|
|
490
|
-
}
|
|
491
|
-
const hull_entries = processed.entries.map(to_hull_entry);
|
|
492
|
-
return {
|
|
493
|
-
stable_entries: hull_entries.filter((entry) => is_on_hull(entry)),
|
|
494
|
-
unstable_entries: hull_entries.filter((entry) => !is_on_hull(entry)),
|
|
495
|
-
phase_stats: get_convex_hull_stats(processed.entries, hull_elements, hull_elements.length),
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
// --- 2D Convex Hull (Binary Phase Diagrams) ---
|
|
499
|
-
export function compute_lower_hull_2d(points) {
|
|
500
|
-
// Andrew's monotone chain for lower hull
|
|
501
|
-
// Sort by x then y
|
|
502
|
-
const sorted = [...points].sort((p1, p2) => p1.x - p2.x || p1.y - p2.y);
|
|
503
|
-
const lower = [];
|
|
504
|
-
const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
|
|
505
|
-
for (const point of sorted) {
|
|
506
|
-
while (lower.length >= 2 &&
|
|
507
|
-
cross(lower[lower.length - 2], lower[lower.length - 1], point) <= 0)
|
|
508
|
-
lower.pop();
|
|
509
|
-
lower.push(point);
|
|
510
|
-
}
|
|
511
|
-
return lower;
|
|
512
|
-
}
|
|
513
|
-
export function interpolate_hull_2d(hull, x) {
|
|
514
|
-
if (hull.length < 2)
|
|
515
|
-
return null;
|
|
516
|
-
// Handle out of bounds by clamping to endpoints
|
|
517
|
-
if (x <= hull[0].x)
|
|
518
|
-
return hull[0].y;
|
|
519
|
-
if (x >= hull[hull.length - 1].x)
|
|
520
|
-
return hull[hull.length - 1].y;
|
|
521
|
-
for (let idx = 0; idx < hull.length - 1; idx++) {
|
|
522
|
-
const p1 = hull[idx];
|
|
523
|
-
const p2 = hull[idx + 1];
|
|
524
|
-
if (x >= p1.x && x <= p2.x) {
|
|
525
|
-
const t = (x - p1.x) / Math.max(1e-12, p2.x - p1.x);
|
|
526
|
-
return p1.y * (1 - t) + p2.y * t;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
// --- Convex hull geometry ---
|
|
532
|
-
const EPS = 1e-9;
|
|
533
|
-
const subtract = (pt1, pt2) => ({
|
|
534
|
-
x: pt1.x - pt2.x,
|
|
535
|
-
y: pt1.y - pt2.y,
|
|
536
|
-
z: pt1.z - pt2.z,
|
|
537
|
-
});
|
|
538
|
-
const cross = (vec1, vec2) => ({
|
|
539
|
-
x: vec1.y * vec2.z - vec1.z * vec2.y,
|
|
540
|
-
y: vec1.z * vec2.x - vec1.x * vec2.z,
|
|
541
|
-
z: vec1.x * vec2.y - vec1.y * vec2.x,
|
|
542
|
-
});
|
|
543
|
-
const norm = (point) => Math.sqrt(point.x * point.x + point.y * point.y + point.z * point.z);
|
|
544
|
-
function normalize(point) {
|
|
545
|
-
const length = norm(point);
|
|
546
|
-
if (length < EPS)
|
|
547
|
-
return { x: 0, y: 0, z: 0 };
|
|
548
|
-
return { x: point.x / length, y: point.y / length, z: point.z / length };
|
|
549
|
-
}
|
|
550
|
-
function compute_plane(p1, p2, p3) {
|
|
551
|
-
const edge_12 = subtract(p2, p1);
|
|
552
|
-
const edge_13 = subtract(p3, p1);
|
|
553
|
-
const normal = normalize(cross(edge_12, edge_13));
|
|
554
|
-
const offset = -(normal.x * p1.x + normal.y * p1.y + normal.z * p1.z);
|
|
555
|
-
return { normal, offset };
|
|
556
|
-
}
|
|
557
|
-
const point_plane_signed_distance = (plane, point) => plane.normal.x * point.x + plane.normal.y * point.y + plane.normal.z * point.z + plane.offset;
|
|
558
|
-
const compute_centroid = (p1, p2, p3) => ({
|
|
559
|
-
x: (p1.x + p2.x + p3.x) / 3,
|
|
560
|
-
y: (p1.y + p2.y + p3.y) / 3,
|
|
561
|
-
z: (p1.z + p2.z + p3.z) / 3,
|
|
562
|
-
});
|
|
563
|
-
function distance_point_to_line(line_start, line_end, point) {
|
|
564
|
-
const line_vec = subtract(line_end, line_start);
|
|
565
|
-
const to_point = subtract(point, line_start);
|
|
566
|
-
const cross_prod = cross(line_vec, to_point);
|
|
567
|
-
const line_len = norm(line_vec);
|
|
568
|
-
if (line_len < EPS)
|
|
569
|
-
return 0;
|
|
570
|
-
return norm(cross_prod) / line_len;
|
|
571
|
-
}
|
|
572
|
-
function choose_initial_tetrahedron(points) {
|
|
573
|
-
if (points.length < 4)
|
|
574
|
-
return null;
|
|
575
|
-
let idx_min_x = 0;
|
|
576
|
-
let idx_max_x = 0;
|
|
577
|
-
for (let idx = 1; idx < points.length; idx++) {
|
|
578
|
-
if (points[idx].x < points[idx_min_x].x)
|
|
579
|
-
idx_min_x = idx;
|
|
580
|
-
if (points[idx].x > points[idx_max_x].x)
|
|
581
|
-
idx_max_x = idx;
|
|
582
|
-
}
|
|
583
|
-
if (idx_min_x === idx_max_x)
|
|
584
|
-
return null;
|
|
585
|
-
let idx_far_line = -1;
|
|
586
|
-
let best_dist_line = -1;
|
|
587
|
-
for (let idx = 0; idx < points.length; idx++) {
|
|
588
|
-
if (idx === idx_min_x || idx === idx_max_x)
|
|
589
|
-
continue;
|
|
590
|
-
const dist = distance_point_to_line(points[idx_min_x], points[idx_max_x], points[idx]);
|
|
591
|
-
if (dist > best_dist_line) {
|
|
592
|
-
best_dist_line = dist;
|
|
593
|
-
idx_far_line = idx;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
if (idx_far_line === -1 || best_dist_line < EPS)
|
|
597
|
-
return null;
|
|
598
|
-
const plane0 = compute_plane(points[idx_min_x], points[idx_max_x], points[idx_far_line]);
|
|
599
|
-
let idx_far_plane = -1;
|
|
600
|
-
let best_dist_plane = -1;
|
|
601
|
-
for (let idx = 0; idx < points.length; idx++) {
|
|
602
|
-
if (idx === idx_min_x || idx === idx_max_x || idx === idx_far_line)
|
|
603
|
-
continue;
|
|
604
|
-
const dist = Math.abs(point_plane_signed_distance(plane0, points[idx]));
|
|
605
|
-
if (dist > best_dist_plane) {
|
|
606
|
-
best_dist_plane = dist;
|
|
607
|
-
idx_far_plane = idx;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
if (idx_far_plane === -1 || best_dist_plane < EPS)
|
|
611
|
-
return null;
|
|
612
|
-
return [idx_min_x, idx_max_x, idx_far_line, idx_far_plane];
|
|
613
|
-
}
|
|
614
|
-
function make_face(points, a, b, c, interior_point) {
|
|
615
|
-
let plane = compute_plane(points[a], points[b], points[c]);
|
|
616
|
-
let centroid = compute_centroid(points[a], points[b], points[c]);
|
|
617
|
-
const dist_interior = point_plane_signed_distance(plane, interior_point);
|
|
618
|
-
if (dist_interior > 0) {
|
|
619
|
-
plane = compute_plane(points[a], points[c], points[b]);
|
|
620
|
-
centroid = compute_centroid(points[a], points[c], points[b]);
|
|
621
|
-
return { vertices: [a, c, b], plane, centroid, outside_points: new Set() };
|
|
622
|
-
}
|
|
623
|
-
return { vertices: [a, b, c], plane, centroid, outside_points: new Set() };
|
|
624
|
-
}
|
|
625
|
-
function assign_outside_points(face, points, candidate_indices) {
|
|
626
|
-
face.outside_points.clear();
|
|
627
|
-
for (const idx of candidate_indices) {
|
|
628
|
-
const distance = point_plane_signed_distance(face.plane, points[idx]);
|
|
629
|
-
if (distance > EPS)
|
|
630
|
-
face.outside_points.add(idx);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
function collect_candidate_points(faces) {
|
|
634
|
-
const set = new Set();
|
|
635
|
-
for (const face of faces)
|
|
636
|
-
for (const idx of face.outside_points)
|
|
637
|
-
set.add(idx);
|
|
638
|
-
return Array.from(set);
|
|
639
|
-
}
|
|
640
|
-
function farthest_point_for_face(points, face) {
|
|
641
|
-
let best_idx = -1;
|
|
642
|
-
let best_distance = -1;
|
|
643
|
-
for (const idx of face.outside_points) {
|
|
644
|
-
const distance = point_plane_signed_distance(face.plane, points[idx]);
|
|
645
|
-
if (distance > best_distance) {
|
|
646
|
-
best_distance = distance;
|
|
647
|
-
best_idx = idx;
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
if (best_idx === -1)
|
|
651
|
-
return null;
|
|
652
|
-
return { idx: best_idx, distance: best_distance };
|
|
653
|
-
}
|
|
654
|
-
function build_horizon(faces, visible_face_indices) {
|
|
655
|
-
const edge_count = new Map();
|
|
656
|
-
for (const face_idx of visible_face_indices) {
|
|
657
|
-
const face = faces[face_idx];
|
|
658
|
-
const [a, b, c] = face.vertices;
|
|
659
|
-
const edges = [
|
|
660
|
-
[a, b],
|
|
661
|
-
[b, c],
|
|
662
|
-
[c, a],
|
|
663
|
-
];
|
|
664
|
-
for (const [u, v] of edges) {
|
|
665
|
-
const key = u < v ? `${u}|${v}` : `${v}|${u}`;
|
|
666
|
-
if (!edge_count.has(key))
|
|
667
|
-
edge_count.set(key, [u, v]);
|
|
668
|
-
else
|
|
669
|
-
edge_count.set(key, [Number.NaN, Number.NaN]);
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
const horizon = [];
|
|
673
|
-
for (const uv of edge_count.values()) {
|
|
674
|
-
if (Number.isNaN(uv[0]))
|
|
675
|
-
continue;
|
|
676
|
-
horizon.push(uv);
|
|
677
|
-
}
|
|
678
|
-
return horizon;
|
|
679
|
-
}
|
|
680
|
-
export function compute_quickhull_triangles(points) {
|
|
681
|
-
if (points.length < 4)
|
|
682
|
-
return []; // hull needs at least 4 non-coplanar points, bail if not provided
|
|
683
|
-
const initial = choose_initial_tetrahedron(points);
|
|
684
|
-
if (!initial)
|
|
685
|
-
return [];
|
|
686
|
-
const [i0, i1, i2, i3] = initial;
|
|
687
|
-
const interior_point = {
|
|
688
|
-
x: (points[i0].x + points[i1].x + points[i2].x + points[i3].x) / 4,
|
|
689
|
-
y: (points[i0].y + points[i1].y + points[i2].y + points[i3].y) / 4,
|
|
690
|
-
z: (points[i0].z + points[i1].z + points[i2].z + points[i3].z) / 4,
|
|
691
|
-
};
|
|
692
|
-
const faces = [
|
|
693
|
-
make_face(points, i0, i1, i2, interior_point),
|
|
694
|
-
make_face(points, i0, i2, i3, interior_point),
|
|
695
|
-
make_face(points, i0, i3, i1, interior_point),
|
|
696
|
-
make_face(points, i1, i3, i2, interior_point),
|
|
697
|
-
];
|
|
698
|
-
const all_indices = [];
|
|
699
|
-
for (let idx = 0; idx < points.length; idx++) {
|
|
700
|
-
if (idx === i0 || idx === i1 || idx === i2 || idx === i3)
|
|
701
|
-
continue;
|
|
702
|
-
all_indices.push(idx);
|
|
703
|
-
}
|
|
704
|
-
for (const face of faces)
|
|
705
|
-
assign_outside_points(face, points, all_indices);
|
|
706
|
-
while (true) {
|
|
707
|
-
let chosen_face_idx = -1;
|
|
708
|
-
let chosen_point_idx = -1;
|
|
709
|
-
let max_distance = -1;
|
|
710
|
-
for (let face_idx = 0; face_idx < faces.length; face_idx++) {
|
|
711
|
-
const face = faces[face_idx];
|
|
712
|
-
if (face.outside_points.size === 0)
|
|
713
|
-
continue;
|
|
714
|
-
const far = farthest_point_for_face(points, face);
|
|
715
|
-
if (far && far.distance > max_distance) {
|
|
716
|
-
max_distance = far.distance;
|
|
717
|
-
chosen_face_idx = face_idx;
|
|
718
|
-
chosen_point_idx = far.idx;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
if (chosen_face_idx === -1)
|
|
722
|
-
break;
|
|
723
|
-
const eye_idx = chosen_point_idx;
|
|
724
|
-
const visible_face_indices = new Set();
|
|
725
|
-
for (let face_idx = 0; face_idx < faces.length; face_idx++) {
|
|
726
|
-
const face = faces[face_idx];
|
|
727
|
-
const dist = point_plane_signed_distance(face.plane, points[eye_idx]);
|
|
728
|
-
if (dist > EPS)
|
|
729
|
-
visible_face_indices.add(face_idx);
|
|
730
|
-
}
|
|
731
|
-
const horizon_edges = build_horizon(faces, visible_face_indices);
|
|
732
|
-
const visible_faces = Array.from(visible_face_indices).sort((a, b) => b - a);
|
|
733
|
-
const candidate_points = collect_candidate_points(visible_faces.map((idx) => faces[idx]));
|
|
734
|
-
for (const idx of visible_faces)
|
|
735
|
-
faces.splice(idx, 1);
|
|
736
|
-
const new_faces = [];
|
|
737
|
-
for (const [u, v] of horizon_edges) {
|
|
738
|
-
const new_face = make_face(points, u, v, eye_idx, interior_point);
|
|
739
|
-
new_faces.push(new_face);
|
|
740
|
-
}
|
|
741
|
-
for (const face of new_faces)
|
|
742
|
-
face.outside_points.clear();
|
|
743
|
-
for (const idx of candidate_points) {
|
|
744
|
-
if (idx === eye_idx)
|
|
745
|
-
continue;
|
|
746
|
-
let best_face = null;
|
|
747
|
-
let best_distance = EPS;
|
|
748
|
-
for (const face of new_faces) {
|
|
749
|
-
const dist = point_plane_signed_distance(face.plane, points[idx]);
|
|
750
|
-
if (dist > best_distance) {
|
|
751
|
-
best_distance = dist;
|
|
752
|
-
best_face = face;
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
if (best_face)
|
|
756
|
-
best_face.outside_points.add(idx);
|
|
757
|
-
}
|
|
758
|
-
faces.push(...new_faces);
|
|
759
|
-
}
|
|
760
|
-
return faces.map((face) => {
|
|
761
|
-
const [a, b, c] = face.vertices;
|
|
762
|
-
const normal = face.plane.normal;
|
|
763
|
-
const centroid = face.centroid;
|
|
764
|
-
return {
|
|
765
|
-
vertices: [points[a], points[b], points[c]],
|
|
766
|
-
normal,
|
|
767
|
-
centroid,
|
|
768
|
-
};
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
export function compute_lower_hull_triangles(points) {
|
|
772
|
-
const all_faces = compute_quickhull_triangles(points);
|
|
773
|
-
return all_faces.filter((face) => face.normal.z < 0 - EPS);
|
|
774
|
-
}
|
|
775
|
-
export const build_lower_hull_model = (faces) => faces.map((tri) => {
|
|
776
|
-
const [p1, p2, p3] = tri.vertices;
|
|
777
|
-
const plane = (() => {
|
|
778
|
-
const x1 = p1.x, y1 = p1.y, z1 = p1.z;
|
|
779
|
-
const x2 = p2.x, y2 = p2.y, z2 = p2.z;
|
|
780
|
-
const x3 = p3.x, y3 = p3.y, z3 = p3.z;
|
|
781
|
-
const det = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2);
|
|
782
|
-
if (Math.abs(det) < 1e-12)
|
|
783
|
-
return { a: 0, b: 0, c: (z1 + z2 + z3) / 3 };
|
|
784
|
-
const a = (z1 * (y2 - y3) + z2 * (y3 - y1) + z3 * (y1 - y2)) / det;
|
|
785
|
-
const b = (z1 * (x3 - x2) + z2 * (x1 - x3) + z3 * (x2 - x1)) / det;
|
|
786
|
-
const c = (z1 * (x2 * y3 - x3 * y2) + z2 * (x3 * y1 - x1 * y3) + z3 * (x1 * y2 - x2 * y1)) / det;
|
|
787
|
-
return { a, b, c };
|
|
788
|
-
})();
|
|
789
|
-
const [min_x, _mx, max_x] = [p1.x, p2.x, p3.x].sort((a, b) => a - b);
|
|
790
|
-
const [min_y, _my, max_y] = [p1.y, p2.y, p3.y].sort((a, b) => a - b);
|
|
791
|
-
const { x: x1, y: y1 } = p1;
|
|
792
|
-
const { x: x2, y: y2 } = p2;
|
|
793
|
-
const { x: x3, y: y3 } = p3;
|
|
794
|
-
const denom = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3);
|
|
795
|
-
return {
|
|
796
|
-
a: plane.a,
|
|
797
|
-
b: plane.b,
|
|
798
|
-
c: plane.c,
|
|
799
|
-
x1,
|
|
800
|
-
y1,
|
|
801
|
-
x2,
|
|
802
|
-
y2,
|
|
803
|
-
x3,
|
|
804
|
-
y3,
|
|
805
|
-
min_x,
|
|
806
|
-
max_x,
|
|
807
|
-
min_y,
|
|
808
|
-
max_y,
|
|
809
|
-
denom,
|
|
810
|
-
};
|
|
811
|
-
});
|
|
812
|
-
function point_in_triangle_xy(model, x, y) {
|
|
813
|
-
const { x1, y1, x2, y2, x3, y3, denom } = model;
|
|
814
|
-
if (Math.abs(denom) < 1e-14)
|
|
815
|
-
return false;
|
|
816
|
-
const l1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / denom;
|
|
817
|
-
const l2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / denom;
|
|
818
|
-
const l3 = 1 - l1 - l2;
|
|
819
|
-
const eps = -1e-9;
|
|
820
|
-
return l1 >= eps && l2 >= eps && l3 >= eps;
|
|
821
|
-
}
|
|
822
|
-
export function e_hull_at_xy(models, x, y) {
|
|
823
|
-
let z = null;
|
|
824
|
-
for (const model of models) {
|
|
825
|
-
if (x < model.min_x - 1e-9 ||
|
|
826
|
-
x > model.max_x + 1e-9 ||
|
|
827
|
-
y < model.min_y - 1e-9 ||
|
|
828
|
-
y > model.max_y + 1e-9)
|
|
829
|
-
continue;
|
|
830
|
-
if (!point_in_triangle_xy(model, x, y))
|
|
831
|
-
continue;
|
|
832
|
-
const z_face = model.a * x + model.b * y + model.c;
|
|
833
|
-
z = z === null ? z_face : Math.min(z, z_face);
|
|
834
|
-
}
|
|
835
|
-
return z;
|
|
836
|
-
}
|
|
837
|
-
export const compute_e_above_hull_for_points = (points, models) => points.map((point) => {
|
|
838
|
-
const z_hull = e_hull_at_xy(models, point.x, point.y);
|
|
839
|
-
if (z_hull === null)
|
|
840
|
-
return 0;
|
|
841
|
-
return point.z - z_hull;
|
|
842
|
-
});
|
|
843
|
-
const subtract_4d = (pt1, pt2) => ({
|
|
844
|
-
x: pt1.x - pt2.x,
|
|
845
|
-
y: pt1.y - pt2.y,
|
|
846
|
-
z: pt1.z - pt2.z,
|
|
847
|
-
w: pt1.w - pt2.w,
|
|
848
|
-
});
|
|
849
|
-
const dot_4d = (vec_a, vec_b) => vec_a.x * vec_b.x + vec_a.y * vec_b.y + vec_a.z * vec_b.z + vec_a.w * vec_b.w;
|
|
850
|
-
const norm_4d = (point) => Math.sqrt(point.x * point.x + point.y * point.y + point.z * point.z + point.w * point.w);
|
|
851
|
-
function normalize_4d(point) {
|
|
852
|
-
const length = norm_4d(point);
|
|
853
|
-
if (length < EPS)
|
|
854
|
-
return { x: 0, y: 0, z: 0, w: 0 };
|
|
855
|
-
return {
|
|
856
|
-
x: point.x / length,
|
|
857
|
-
y: point.y / length,
|
|
858
|
-
z: point.z / length,
|
|
859
|
-
w: point.w / length,
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
// Compute normal to a 3D hyperplane in 4D space defined by 4 points
|
|
863
|
-
//
|
|
864
|
-
// Mathematical Background:
|
|
865
|
-
// A 3D hyperplane (tetrahedral facet) in 4D is defined by 4 points. The normal vector
|
|
866
|
-
// must be orthogonal to all three edge vectors spanning the hyperplane. This is the
|
|
867
|
-
// 4D analog of computing a cross product of two vectors in 3D.
|
|
868
|
-
//
|
|
869
|
-
// Approach:
|
|
870
|
-
// 1. Form three edge vectors v1, v2, v3 from point p1
|
|
871
|
-
// 2. Find vector n such that: n · v1 = 0, n · v2 = 0, n · v3 = 0
|
|
872
|
-
// 3. This is equivalent to finding the null space of the 3×4 matrix [v1; v2; v3]
|
|
873
|
-
//
|
|
874
|
-
// Implementation:
|
|
875
|
-
// The normal components (nx, ny, nz, nw) are computed using Laplace expansion
|
|
876
|
-
// (cofactor method) along each column of the matrix. Each component is the determinant
|
|
877
|
-
// of the 3×3 submatrix obtained by removing that column, with alternating signs.
|
|
878
|
-
//
|
|
879
|
-
// References:
|
|
880
|
-
// - Barber et al. (1996) "The Quickhull Algorithm for Convex Hulls"
|
|
881
|
-
// - https://en.wikipedia.org/wiki/Cross_product#Multilinear_algebra
|
|
882
|
-
// - https://mathworld.wolfram.com/Nullspace.html
|
|
883
|
-
function compute_plane_4d(p1, p2, p3, p4) {
|
|
884
|
-
// Three edge vectors from p1
|
|
885
|
-
const v1 = subtract_4d(p2, p1);
|
|
886
|
-
const v2 = subtract_4d(p3, p1);
|
|
887
|
-
const v3 = subtract_4d(p4, p1);
|
|
888
|
-
// Build matrix [v1; v2; v3] and compute normal via cofactor expansion
|
|
889
|
-
const matrix = [
|
|
890
|
-
[v1.x, v1.y, v1.z, v1.w],
|
|
891
|
-
[v2.x, v2.y, v2.z, v2.w],
|
|
892
|
-
[v3.x, v3.y, v3.z, v3.w],
|
|
893
|
-
];
|
|
894
|
-
// Helper: extract 3×3 submatrix by removing column col_skip, then compute determinant
|
|
895
|
-
const det_submatrix = (col_skip) => {
|
|
896
|
-
const cols = [0, 1, 2, 3].filter((col) => col !== col_skip);
|
|
897
|
-
const submatrix = [
|
|
898
|
-
[matrix[0][cols[0]], matrix[0][cols[1]], matrix[0][cols[2]]],
|
|
899
|
-
[matrix[1][cols[0]], matrix[1][cols[1]], matrix[1][cols[2]]],
|
|
900
|
-
[matrix[2][cols[0]], matrix[2][cols[1]], matrix[2][cols[2]]],
|
|
901
|
-
];
|
|
902
|
-
return math.det_3x3(submatrix);
|
|
903
|
-
};
|
|
904
|
-
// Compute normal components using Laplace expansion along each column
|
|
905
|
-
// Alternating signs: +, -, +, -
|
|
906
|
-
const signs = [1, -1, 1, -1];
|
|
907
|
-
const normal_components = [0, 1, 2, 3].map((col_idx) => signs[col_idx] * det_submatrix(col_idx));
|
|
908
|
-
const [x, y, z, w] = normal_components;
|
|
909
|
-
const normal = normalize_4d({ x, y, z, w });
|
|
910
|
-
// Guard against degenerate (nearly co-planar) points
|
|
911
|
-
const normal_magnitude = Math.abs(normal.x) + Math.abs(normal.y) + Math.abs(normal.z) + Math.abs(normal.w);
|
|
912
|
-
if (normal_magnitude < EPS) {
|
|
913
|
-
return { normal: { x: 0, y: 0, z: 0, w: 0 }, offset: 0 };
|
|
914
|
-
}
|
|
915
|
-
const offset = -dot_4d(normal, p1);
|
|
916
|
-
return { normal, offset };
|
|
917
|
-
}
|
|
918
|
-
const point_plane_signed_distance_4d = (plane, point) => dot_4d(plane.normal, point) + plane.offset;
|
|
919
|
-
const compute_centroid_4d = (p1, p2, p3, p4) => ({
|
|
920
|
-
x: (p1.x + p2.x + p3.x + p4.x) / 4,
|
|
921
|
-
y: (p1.y + p2.y + p3.y + p4.y) / 4,
|
|
922
|
-
z: (p1.z + p2.z + p3.z + p4.z) / 4,
|
|
923
|
-
w: (p1.w + p2.w + p3.w + p4.w) / 4,
|
|
924
|
-
});
|
|
925
|
-
function distance_point_to_hyperplane_4d(p1, p2, p3, point) {
|
|
926
|
-
// Distance from point to the 2D hyperplane spanned by p1, p2, p3
|
|
927
|
-
const v1 = subtract_4d(p2, p1);
|
|
928
|
-
const v2 = subtract_4d(p3, p1);
|
|
929
|
-
const vp = subtract_4d(point, p1);
|
|
930
|
-
// Project vp onto the plane spanned by v1 and v2
|
|
931
|
-
// Use Gram-Schmidt to find orthogonal component
|
|
932
|
-
const v1_norm_sq = dot_4d(v1, v1);
|
|
933
|
-
const v2_norm_sq = dot_4d(v2, v2);
|
|
934
|
-
const v1_dot_v2 = dot_4d(v1, v2);
|
|
935
|
-
if (v1_norm_sq < EPS || v2_norm_sq < EPS)
|
|
936
|
-
return 0;
|
|
937
|
-
const vp_dot_v1 = dot_4d(vp, v1);
|
|
938
|
-
const vp_dot_v2 = dot_4d(vp, v2);
|
|
939
|
-
// Solve linear system for projection coefficients
|
|
940
|
-
const det = v1_norm_sq * v2_norm_sq - v1_dot_v2 * v1_dot_v2;
|
|
941
|
-
if (Math.abs(det) < EPS)
|
|
942
|
-
return 0;
|
|
943
|
-
const alpha = (v2_norm_sq * vp_dot_v1 - v1_dot_v2 * vp_dot_v2) / det;
|
|
944
|
-
const beta = (v1_norm_sq * vp_dot_v2 - v1_dot_v2 * vp_dot_v1) / det;
|
|
945
|
-
// Compute projection
|
|
946
|
-
const proj_x = p1.x + alpha * v1.x + beta * v2.x;
|
|
947
|
-
const proj_y = p1.y + alpha * v1.y + beta * v2.y;
|
|
948
|
-
const proj_z = p1.z + alpha * v1.z + beta * v2.z;
|
|
949
|
-
const proj_w = p1.w + alpha * v1.w + beta * v2.w;
|
|
950
|
-
// Distance is the length of (point - projection)
|
|
951
|
-
const dx = point.x - proj_x;
|
|
952
|
-
const dy = point.y - proj_y;
|
|
953
|
-
const dz = point.z - proj_z;
|
|
954
|
-
const dw = point.w - proj_w;
|
|
955
|
-
return Math.sqrt(dx * dx + dy * dy + dz * dz + dw * dw);
|
|
956
|
-
}
|
|
957
|
-
// Distance from point to line in 4D
|
|
958
|
-
function distance_point_to_line_4d(a, b, p) {
|
|
959
|
-
const ab = subtract_4d(b, a);
|
|
960
|
-
const ap = subtract_4d(p, a);
|
|
961
|
-
const ab_len_sq = dot_4d(ab, ab);
|
|
962
|
-
if (ab_len_sq < EPS)
|
|
963
|
-
return norm_4d(ap);
|
|
964
|
-
// Project ap onto ab
|
|
965
|
-
const t = dot_4d(ap, ab) / ab_len_sq;
|
|
966
|
-
const projection = {
|
|
967
|
-
x: a.x + t * ab.x,
|
|
968
|
-
y: a.y + t * ab.y,
|
|
969
|
-
z: a.z + t * ab.z,
|
|
970
|
-
w: a.w + t * ab.w,
|
|
971
|
-
};
|
|
972
|
-
return norm_4d(subtract_4d(p, projection));
|
|
973
|
-
}
|
|
974
|
-
// Maximum sample size for initial simplex selection in 4D hulls (avoids O(n²) for large datasets)
|
|
975
|
-
const INITIAL_SIMPLEX_SAMPLE_SIZE = 100;
|
|
976
|
-
function choose_initial_4_simplex(points) {
|
|
977
|
-
if (points.length < 5)
|
|
978
|
-
return null;
|
|
979
|
-
// Find two points farthest apart across all dimensions for better numerical stability
|
|
980
|
-
// Sample a small subset if dataset is large to avoid O(n²) scaling
|
|
981
|
-
const sample_size = Math.min(points.length, INITIAL_SIMPLEX_SAMPLE_SIZE);
|
|
982
|
-
const sample_indices = points.length <= sample_size
|
|
983
|
-
? points.map((_, idx) => idx)
|
|
984
|
-
: Array.from({ length: sample_size }, (_, idx) => Math.floor((idx * points.length) / sample_size));
|
|
985
|
-
let idx_far_a = 0;
|
|
986
|
-
let idx_far_b = 0;
|
|
987
|
-
let max_dist_sq = -1;
|
|
988
|
-
for (const idx_a of sample_indices) {
|
|
989
|
-
for (const idx_b of sample_indices) {
|
|
990
|
-
if (idx_a >= idx_b)
|
|
991
|
-
continue;
|
|
992
|
-
const pa = points[idx_a];
|
|
993
|
-
const pb = points[idx_b];
|
|
994
|
-
const dist_sq = (pa.x - pb.x) ** 2 + (pa.y - pb.y) ** 2 + (pa.z - pb.z) ** 2 + (pa.w - pb.w) ** 2;
|
|
995
|
-
if (dist_sq > max_dist_sq) {
|
|
996
|
-
max_dist_sq = dist_sq;
|
|
997
|
-
idx_far_a = idx_a;
|
|
998
|
-
idx_far_b = idx_b;
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
if (idx_far_a === idx_far_b || max_dist_sq < EPS)
|
|
1003
|
-
return null;
|
|
1004
|
-
// Find point farthest from line through idx_far_a and idx_far_b
|
|
1005
|
-
let idx_far_line = -1;
|
|
1006
|
-
let best_dist_line = -1;
|
|
1007
|
-
for (let idx = 0; idx < points.length; idx++) {
|
|
1008
|
-
if (idx === idx_far_a || idx === idx_far_b)
|
|
1009
|
-
continue;
|
|
1010
|
-
const dist = distance_point_to_line_4d(points[idx_far_a], points[idx_far_b], points[idx]);
|
|
1011
|
-
if (dist > best_dist_line) {
|
|
1012
|
-
best_dist_line = dist;
|
|
1013
|
-
idx_far_line = idx;
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
if (idx_far_line === -1 || best_dist_line < EPS)
|
|
1017
|
-
return null;
|
|
1018
|
-
// Find point farthest from 2D plane through first three points
|
|
1019
|
-
let idx_far_plane = -1;
|
|
1020
|
-
let best_dist_plane = -1;
|
|
1021
|
-
for (let idx = 0; idx < points.length; idx++) {
|
|
1022
|
-
if (idx === idx_far_a || idx === idx_far_b || idx === idx_far_line)
|
|
1023
|
-
continue;
|
|
1024
|
-
const dist = distance_point_to_hyperplane_4d(points[idx_far_a], points[idx_far_b], points[idx_far_line], points[idx]);
|
|
1025
|
-
if (dist > best_dist_plane) {
|
|
1026
|
-
best_dist_plane = dist;
|
|
1027
|
-
idx_far_plane = idx;
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
if (idx_far_plane === -1 || best_dist_plane < EPS)
|
|
1031
|
-
return null;
|
|
1032
|
-
// Find point farthest from 3D hyperplane through first four points
|
|
1033
|
-
const plane0 = compute_plane_4d(points[idx_far_a], points[idx_far_b], points[idx_far_line], points[idx_far_plane]);
|
|
1034
|
-
let idx_far_hyperplane = -1;
|
|
1035
|
-
let best_dist_hyperplane = -1;
|
|
1036
|
-
for (let idx = 0; idx < points.length; idx++) {
|
|
1037
|
-
if (idx === idx_far_a ||
|
|
1038
|
-
idx === idx_far_b ||
|
|
1039
|
-
idx === idx_far_line ||
|
|
1040
|
-
idx === idx_far_plane)
|
|
1041
|
-
continue;
|
|
1042
|
-
const dist = Math.abs(point_plane_signed_distance_4d(plane0, points[idx]));
|
|
1043
|
-
if (dist > best_dist_hyperplane) {
|
|
1044
|
-
best_dist_hyperplane = dist;
|
|
1045
|
-
idx_far_hyperplane = idx;
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
if (idx_far_hyperplane === -1 || best_dist_hyperplane < EPS)
|
|
1049
|
-
return null;
|
|
1050
|
-
return [idx_far_a, idx_far_b, idx_far_line, idx_far_plane, idx_far_hyperplane];
|
|
1051
|
-
}
|
|
1052
|
-
function make_face_4d(points, a, b, c, d, interior_point) {
|
|
1053
|
-
let plane = compute_plane_4d(points[a], points[b], points[c], points[d]);
|
|
1054
|
-
let centroid = compute_centroid_4d(points[a], points[b], points[c], points[d]);
|
|
1055
|
-
const dist_interior = point_plane_signed_distance_4d(plane, interior_point);
|
|
1056
|
-
// Ensure normal points outward (away from interior)
|
|
1057
|
-
if (dist_interior > 0) {
|
|
1058
|
-
// Swap two vertices to flip normal
|
|
1059
|
-
plane = compute_plane_4d(points[a], points[c], points[b], points[d]);
|
|
1060
|
-
centroid = compute_centroid_4d(points[a], points[c], points[b], points[d]);
|
|
1061
|
-
return { vertices: [a, c, b, d], plane, centroid, outside_points: new Set() };
|
|
1062
|
-
}
|
|
1063
|
-
return { vertices: [a, b, c, d], plane, centroid, outside_points: new Set() };
|
|
1064
|
-
}
|
|
1065
|
-
function assign_outside_points_4d(face, points, candidate_indices) {
|
|
1066
|
-
face.outside_points.clear();
|
|
1067
|
-
for (const idx of candidate_indices) {
|
|
1068
|
-
const distance = point_plane_signed_distance_4d(face.plane, points[idx]);
|
|
1069
|
-
if (distance > EPS)
|
|
1070
|
-
face.outside_points.add(idx);
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
function collect_candidate_points_4d(faces) {
|
|
1074
|
-
const set = new Set();
|
|
1075
|
-
for (const face of faces) {
|
|
1076
|
-
for (const idx of face.outside_points)
|
|
1077
|
-
set.add(idx);
|
|
1078
|
-
}
|
|
1079
|
-
return Array.from(set);
|
|
1080
|
-
}
|
|
1081
|
-
function farthest_point_for_face_4d(points, face) {
|
|
1082
|
-
let best_idx = -1;
|
|
1083
|
-
let best_distance = -1;
|
|
1084
|
-
for (const idx of face.outside_points) {
|
|
1085
|
-
const distance = point_plane_signed_distance_4d(face.plane, points[idx]);
|
|
1086
|
-
if (distance > best_distance) {
|
|
1087
|
-
best_distance = distance;
|
|
1088
|
-
best_idx = idx;
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
if (best_idx === -1)
|
|
1092
|
-
return null;
|
|
1093
|
-
return { idx: best_idx, distance: best_distance };
|
|
1094
|
-
}
|
|
1095
|
-
function build_horizon_4d(faces, visible_face_indices) {
|
|
1096
|
-
// In 4D, horizon "ridges" are triangles (3 vertices)
|
|
1097
|
-
const ridge_count = new Map();
|
|
1098
|
-
for (const face_idx of visible_face_indices) {
|
|
1099
|
-
const face = faces[face_idx];
|
|
1100
|
-
const [a, b, c, d] = face.vertices;
|
|
1101
|
-
// Each tetrahedron face has 4 triangular ridges
|
|
1102
|
-
const ridges = [
|
|
1103
|
-
[a, b, c],
|
|
1104
|
-
[a, b, d],
|
|
1105
|
-
[a, c, d],
|
|
1106
|
-
[b, c, d],
|
|
1107
|
-
];
|
|
1108
|
-
for (const ridge of ridges) {
|
|
1109
|
-
const sorted = ridge.slice().sort((x, y) => x - y);
|
|
1110
|
-
const key = sorted.join(`|`);
|
|
1111
|
-
if (!ridge_count.has(key)) {
|
|
1112
|
-
ridge_count.set(key, ridge);
|
|
1113
|
-
}
|
|
1114
|
-
else {
|
|
1115
|
-
// Mark as seen twice (internal ridge)
|
|
1116
|
-
ridge_count.set(key, [Number.NaN, Number.NaN, Number.NaN]);
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
const horizon = [];
|
|
1121
|
-
for (const ridge of ridge_count.values()) {
|
|
1122
|
-
if (!Number.isNaN(ridge[0])) {
|
|
1123
|
-
horizon.push(ridge);
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
return horizon;
|
|
1127
|
-
}
|
|
1128
|
-
export function compute_quickhull_4d(points) {
|
|
1129
|
-
if (points.length < 5)
|
|
1130
|
-
return []; // Need at least 5 non-coplanar points for 4D hull
|
|
1131
|
-
const initial = choose_initial_4_simplex(points);
|
|
1132
|
-
if (!initial)
|
|
1133
|
-
return [];
|
|
1134
|
-
const [i0, i1, i2, i3, i4] = initial;
|
|
1135
|
-
// Interior point for orientation
|
|
1136
|
-
const interior_point = {
|
|
1137
|
-
x: (points[i0].x + points[i1].x + points[i2].x + points[i3].x + points[i4].x) / 5,
|
|
1138
|
-
y: (points[i0].y + points[i1].y + points[i2].y + points[i3].y + points[i4].y) / 5,
|
|
1139
|
-
z: (points[i0].z + points[i1].z + points[i2].z + points[i3].z + points[i4].z) / 5,
|
|
1140
|
-
w: (points[i0].w + points[i1].w + points[i2].w + points[i3].w + points[i4].w) / 5,
|
|
1141
|
-
};
|
|
1142
|
-
// Initial 4-simplex has 5 tetrahedral faces
|
|
1143
|
-
const faces = [
|
|
1144
|
-
make_face_4d(points, i0, i1, i2, i3, interior_point),
|
|
1145
|
-
make_face_4d(points, i0, i1, i2, i4, interior_point),
|
|
1146
|
-
make_face_4d(points, i0, i1, i3, i4, interior_point),
|
|
1147
|
-
make_face_4d(points, i0, i2, i3, i4, interior_point),
|
|
1148
|
-
make_face_4d(points, i1, i2, i3, i4, interior_point),
|
|
1149
|
-
];
|
|
1150
|
-
const all_indices = [];
|
|
1151
|
-
for (let idx = 0; idx < points.length; idx++) {
|
|
1152
|
-
if (idx === i0 || idx === i1 || idx === i2 || idx === i3 || idx === i4)
|
|
1153
|
-
continue;
|
|
1154
|
-
all_indices.push(idx);
|
|
1155
|
-
}
|
|
1156
|
-
for (const face of faces) {
|
|
1157
|
-
assign_outside_points_4d(face, points, all_indices);
|
|
1158
|
-
}
|
|
1159
|
-
// Main Quick Hull iteration
|
|
1160
|
-
while (true) {
|
|
1161
|
-
// Step 1: Find face with farthest outside point (the "eye" point)
|
|
1162
|
-
let chosen_face_idx = -1;
|
|
1163
|
-
let chosen_point_idx = -1;
|
|
1164
|
-
let max_distance = -1;
|
|
1165
|
-
for (let face_idx = 0; face_idx < faces.length; face_idx++) {
|
|
1166
|
-
const face = faces[face_idx];
|
|
1167
|
-
if (face.outside_points.size === 0)
|
|
1168
|
-
continue;
|
|
1169
|
-
const far = farthest_point_for_face_4d(points, face);
|
|
1170
|
-
if (far && far.distance > max_distance) {
|
|
1171
|
-
max_distance = far.distance;
|
|
1172
|
-
chosen_face_idx = face_idx;
|
|
1173
|
-
chosen_point_idx = far.idx;
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
if (chosen_face_idx === -1)
|
|
1177
|
-
break; // All points processed
|
|
1178
|
-
const eye_idx = chosen_point_idx;
|
|
1179
|
-
// Step 2: Find all faces visible from the eye point
|
|
1180
|
-
const visible_face_indices = new Set();
|
|
1181
|
-
for (let face_idx = 0; face_idx < faces.length; face_idx++) {
|
|
1182
|
-
const face = faces[face_idx];
|
|
1183
|
-
const dist = point_plane_signed_distance_4d(face.plane, points[eye_idx]);
|
|
1184
|
-
if (dist > EPS)
|
|
1185
|
-
visible_face_indices.add(face_idx);
|
|
1186
|
-
}
|
|
1187
|
-
// Step 3: Build horizon ridges (boundary between visible and non-visible faces)
|
|
1188
|
-
const horizon_ridges = build_horizon_4d(faces, visible_face_indices);
|
|
1189
|
-
const visible_faces = Array.from(visible_face_indices).sort((a, b) => b - a);
|
|
1190
|
-
const candidate_points = collect_candidate_points_4d(visible_faces.map((idx) => faces[idx]));
|
|
1191
|
-
// Step 4: Remove visible faces (they'll be replaced by new ones through eye point)
|
|
1192
|
-
for (const idx of visible_faces) {
|
|
1193
|
-
faces.splice(idx, 1);
|
|
1194
|
-
}
|
|
1195
|
-
// Step 5: Create new faces connecting horizon ridges to eye point
|
|
1196
|
-
const new_faces = [];
|
|
1197
|
-
for (const [u, v, w] of horizon_ridges) {
|
|
1198
|
-
const new_face = make_face_4d(points, u, v, w, eye_idx, interior_point);
|
|
1199
|
-
new_faces.push(new_face);
|
|
1200
|
-
}
|
|
1201
|
-
// Step 6: Reassign outside points from removed faces to new faces
|
|
1202
|
-
for (const face of new_faces)
|
|
1203
|
-
face.outside_points.clear();
|
|
1204
|
-
for (const idx of candidate_points) {
|
|
1205
|
-
if (idx === eye_idx)
|
|
1206
|
-
continue;
|
|
1207
|
-
let best_face = null;
|
|
1208
|
-
let best_distance = EPS;
|
|
1209
|
-
for (const face of new_faces) {
|
|
1210
|
-
const dist = point_plane_signed_distance_4d(face.plane, points[idx]);
|
|
1211
|
-
if (dist > best_distance) {
|
|
1212
|
-
best_distance = dist;
|
|
1213
|
-
best_face = face;
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
if (best_face)
|
|
1217
|
-
best_face.outside_points.add(idx);
|
|
1218
|
-
}
|
|
1219
|
-
faces.push(...new_faces);
|
|
1220
|
-
}
|
|
1221
|
-
return faces.map((face) => {
|
|
1222
|
-
const [a, b, c, d] = face.vertices;
|
|
1223
|
-
return {
|
|
1224
|
-
vertices: [points[a], points[b], points[c], points[d]],
|
|
1225
|
-
normal: face.plane.normal,
|
|
1226
|
-
centroid: face.centroid,
|
|
1227
|
-
};
|
|
1228
|
-
});
|
|
1229
|
-
}
|
|
1230
|
-
export function compute_lower_hull_4d(points) {
|
|
1231
|
-
const all_faces = compute_quickhull_4d(points);
|
|
1232
|
-
// Filter for "lower" faces: those with normal pointing down in w direction
|
|
1233
|
-
return all_faces.filter((face) => face.normal.w < 0 - EPS);
|
|
1234
|
-
}
|
|
1235
|
-
// Check if 3D point (x,y,z) is inside 3D tetrahedron using barycentric coordinates
|
|
1236
|
-
function point_in_tetrahedron_3d(p0, p1, p2, p3, point) {
|
|
1237
|
-
// Solve for barycentric coordinates: point = l0*p0 + l1*p1 + l2*p2 + l3*p3
|
|
1238
|
-
// with l0 + l1 + l2 + l3 = 1
|
|
1239
|
-
// Build the linear system
|
|
1240
|
-
const matrix = [
|
|
1241
|
-
[p0.x, p1.x, p2.x, p3.x],
|
|
1242
|
-
[p0.y, p1.y, p2.y, p3.y],
|
|
1243
|
-
[p0.z, p1.z, p2.z, p3.z],
|
|
1244
|
-
[1, 1, 1, 1],
|
|
1245
|
-
];
|
|
1246
|
-
const rhs = [point.x, point.y, point.z, 1];
|
|
1247
|
-
// Solve using Cramer's rule with 4x4 determinants
|
|
1248
|
-
const det_main = math.det_4x4(matrix);
|
|
1249
|
-
if (Math.abs(det_main) < EPS) {
|
|
1250
|
-
return { inside: false, bary: [0, 0, 0, 0] };
|
|
1251
|
-
}
|
|
1252
|
-
// Compute barycentric coordinates using Cramer's rule
|
|
1253
|
-
const bary = [0, 0, 0, 0];
|
|
1254
|
-
for (let idx = 0; idx < 4; idx++) {
|
|
1255
|
-
const m_i = matrix.map((row) => [...row]);
|
|
1256
|
-
for (let row = 0; row < 4; row++) {
|
|
1257
|
-
m_i[row][idx] = rhs[row];
|
|
1258
|
-
}
|
|
1259
|
-
bary[idx] = math.det_4x4(m_i) / det_main;
|
|
1260
|
-
}
|
|
1261
|
-
// Check if inside: all barycentric coords must be >= 0 and sum to 1
|
|
1262
|
-
const eps_bary = -1e-9;
|
|
1263
|
-
const inside = bary.every((coord) => coord >= eps_bary) &&
|
|
1264
|
-
Math.abs(bary.reduce((sum, coord) => sum + coord, 0) - 1) < 1e-6;
|
|
1265
|
-
return { inside, bary };
|
|
1266
|
-
}
|
|
1267
|
-
const build_tetrahedron_models = (hull_tetrahedra) => hull_tetrahedra.map((tet) => {
|
|
1268
|
-
const [p0, p1, p2, p3] = tet.vertices;
|
|
1269
|
-
const vertices_3d = [
|
|
1270
|
-
{ x: p0.x, y: p0.y, z: p0.z },
|
|
1271
|
-
{ x: p1.x, y: p1.y, z: p1.z },
|
|
1272
|
-
{ x: p2.x, y: p2.y, z: p2.z },
|
|
1273
|
-
{ x: p3.x, y: p3.y, z: p3.z },
|
|
1274
|
-
];
|
|
1275
|
-
const xs = [p0.x, p1.x, p2.x, p3.x];
|
|
1276
|
-
const ys = [p0.y, p1.y, p2.y, p3.y];
|
|
1277
|
-
const zs = [p0.z, p1.z, p2.z, p3.z];
|
|
1278
|
-
return {
|
|
1279
|
-
vertices: tet.vertices,
|
|
1280
|
-
vertices_3d,
|
|
1281
|
-
min_x: Math.min(...xs),
|
|
1282
|
-
max_x: Math.max(...xs),
|
|
1283
|
-
min_y: Math.min(...ys),
|
|
1284
|
-
max_y: Math.max(...ys),
|
|
1285
|
-
min_z: Math.min(...zs),
|
|
1286
|
-
max_z: Math.max(...zs),
|
|
1287
|
-
};
|
|
1288
|
-
});
|
|
1289
|
-
// Compute distance from point to lower hull in 4D
|
|
1290
|
-
export const compute_e_above_hull_4d = (points, hull_tetrahedra) => {
|
|
1291
|
-
// Precompute bounding boxes for fast prefiltering
|
|
1292
|
-
const models = build_tetrahedron_models(hull_tetrahedra);
|
|
1293
|
-
return points.map(({ x, y, z, w }) => {
|
|
1294
|
-
let hull_w = null;
|
|
1295
|
-
for (const model of models) {
|
|
1296
|
-
// Fast bounding box prefilter
|
|
1297
|
-
if (x < model.min_x - EPS ||
|
|
1298
|
-
x > model.max_x + EPS ||
|
|
1299
|
-
y < model.min_y - EPS ||
|
|
1300
|
-
y > model.max_y + EPS ||
|
|
1301
|
-
z < model.min_z - EPS ||
|
|
1302
|
-
z > model.max_z + EPS)
|
|
1303
|
-
continue;
|
|
1304
|
-
// Check if point's (x,y,z) is inside the 3D projection of the tetrahedron
|
|
1305
|
-
const { inside, bary } = point_in_tetrahedron_3d(model.vertices_3d[0], model.vertices_3d[1], model.vertices_3d[2], model.vertices_3d[3], { x, y, z });
|
|
1306
|
-
if (inside) {
|
|
1307
|
-
// Compute w on the hull at this (x,y,z) using barycentric interpolation
|
|
1308
|
-
const [p0, p1, p2, p3] = model.vertices;
|
|
1309
|
-
const w_on_hull = bary[0] * p0.w + bary[1] * p1.w + bary[2] * p2.w + bary[3] * p3.w;
|
|
1310
|
-
hull_w = hull_w === null ? w_on_hull : Math.min(hull_w, w_on_hull);
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
// If no tetrahedron contains this point's spatial projection, it's outside the valid
|
|
1314
|
-
// composition domain. Return NaN to indicate invalid input.
|
|
1315
|
-
if (hull_w === null)
|
|
1316
|
-
return NaN;
|
|
1317
|
-
return w - hull_w;
|
|
1318
|
-
});
|
|
1319
|
-
};
|
|
1320
|
-
// --- N-Dimensional Convex Hull (for 5+ element systems) ---
|
|
1321
|
-
// N-dimensional vector operations with dimension validation
|
|
1322
|
-
const subtract_nd = (vec_a, vec_b) => {
|
|
1323
|
-
if (vec_a.length !== vec_b.length) {
|
|
1324
|
-
throw new Error(`Vector dimension mismatch: ${vec_a.length} vs ${vec_b.length}`);
|
|
1325
|
-
}
|
|
1326
|
-
return vec_a.map((val, idx) => val - vec_b[idx]);
|
|
1327
|
-
};
|
|
1328
|
-
const dot_nd = (vec_a, vec_b) => {
|
|
1329
|
-
if (vec_a.length !== vec_b.length) {
|
|
1330
|
-
throw new Error(`Vector dimension mismatch: ${vec_a.length} vs ${vec_b.length}`);
|
|
1331
|
-
}
|
|
1332
|
-
return vec_a.reduce((sum, val, idx) => sum + val * vec_b[idx], 0);
|
|
1333
|
-
};
|
|
1334
|
-
const norm_nd = (vec) => Math.sqrt(dot_nd(vec, vec));
|
|
1335
|
-
const normalize_nd = (vec) => {
|
|
1336
|
-
const len = norm_nd(vec);
|
|
1337
|
-
if (len < EPS)
|
|
1338
|
-
return vec.map(() => 0);
|
|
1339
|
-
return vec.map((val) => val / len);
|
|
1340
|
-
};
|
|
1341
|
-
// Compute normal to hyperplane through N points in N-dimensional space
|
|
1342
|
-
// Uses null space computation via cofactor expansion
|
|
1343
|
-
function compute_hyperplane_nd(points) {
|
|
1344
|
-
const n = points.length;
|
|
1345
|
-
if (n < 2)
|
|
1346
|
-
return { normal: [], offset: 0 };
|
|
1347
|
-
// Build (N-1) edge vectors from points[0]
|
|
1348
|
-
const edges = points.slice(1).map((pt) => subtract_nd(pt, points[0]));
|
|
1349
|
-
// Compute normal via cofactors of the (N-1)×N edge matrix
|
|
1350
|
-
// Each component is ±det of (N-1)×(N-1) submatrix with that column removed
|
|
1351
|
-
const dim = points[0].length;
|
|
1352
|
-
const normal_components = [];
|
|
1353
|
-
for (let col = 0; col < dim; col++) {
|
|
1354
|
-
// Build (N-1)×(N-1) submatrix by removing column col
|
|
1355
|
-
const submatrix = edges.map((row) => [...row.slice(0, col), ...row.slice(col + 1)]);
|
|
1356
|
-
const sign = col % 2 === 0 ? 1 : -1;
|
|
1357
|
-
normal_components.push(sign * math.det_nxn(submatrix));
|
|
1358
|
-
}
|
|
1359
|
-
const normal = normalize_nd(normal_components);
|
|
1360
|
-
// Degenerate case: co-hyperplanar points produce zero normal
|
|
1361
|
-
if (norm_nd(normal) < EPS)
|
|
1362
|
-
return { normal, offset: 0 };
|
|
1363
|
-
const offset = -dot_nd(normal, points[0]);
|
|
1364
|
-
return { normal, offset };
|
|
1365
|
-
}
|
|
1366
|
-
const point_hyperplane_signed_distance_nd = (plane, point) => dot_nd(plane.normal, point) + plane.offset;
|
|
1367
|
-
const compute_centroid_nd = (points) => {
|
|
1368
|
-
if (points.length === 0)
|
|
1369
|
-
return [];
|
|
1370
|
-
const dim = points[0].length;
|
|
1371
|
-
return Array.from({ length: dim }, (_, idx) => points.reduce((sum, pt) => sum + pt[idx], 0) / points.length);
|
|
1372
|
-
};
|
|
1373
|
-
// Find N+1 points that span N dimensions (initial simplex for quickhull)
|
|
1374
|
-
function choose_initial_simplex_nd(points) {
|
|
1375
|
-
const n = points[0]?.length;
|
|
1376
|
-
if (!n || points.length < n + 1)
|
|
1377
|
-
return null;
|
|
1378
|
-
const chosen = [];
|
|
1379
|
-
// Greedily pick points that maximize distance from current affine hull
|
|
1380
|
-
// Start with two points that are farthest apart
|
|
1381
|
-
let [best_i, best_j, best_dist] = [0, 1, -1];
|
|
1382
|
-
const sample_size = Math.min(points.length, 100);
|
|
1383
|
-
const sample_indices = points.length <= sample_size
|
|
1384
|
-
? points.map((_, idx) => idx)
|
|
1385
|
-
: Array.from({ length: sample_size }, (_, idx) => Math.floor((idx * points.length) / sample_size));
|
|
1386
|
-
for (const idx_a of sample_indices) {
|
|
1387
|
-
for (const idx_b of sample_indices) {
|
|
1388
|
-
if (idx_a >= idx_b)
|
|
1389
|
-
continue;
|
|
1390
|
-
const dist = norm_nd(subtract_nd(points[idx_a], points[idx_b]));
|
|
1391
|
-
if (dist > best_dist) {
|
|
1392
|
-
best_dist = dist;
|
|
1393
|
-
best_i = idx_a;
|
|
1394
|
-
best_j = idx_b;
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
if (best_dist < EPS)
|
|
1399
|
-
return null;
|
|
1400
|
-
chosen.push(best_i, best_j);
|
|
1401
|
-
const chosen_set = new Set(chosen);
|
|
1402
|
-
// Add remaining points to span higher dimensions
|
|
1403
|
-
while (chosen.length < n + 1) {
|
|
1404
|
-
let [best_idx, best_distance] = [-1, -1];
|
|
1405
|
-
// Hoist chosen_points computation outside inner loop for O(n) instead of O(n²)
|
|
1406
|
-
const chosen_points = chosen.map((idx_c) => points[idx_c]);
|
|
1407
|
-
for (let idx = 0; idx < points.length; idx++) {
|
|
1408
|
-
if (chosen_set.has(idx))
|
|
1409
|
-
continue;
|
|
1410
|
-
// Compute distance from point to affine hull of chosen points
|
|
1411
|
-
const dist = distance_to_affine_hull_nd(points[idx], chosen_points);
|
|
1412
|
-
if (dist > best_distance) {
|
|
1413
|
-
best_distance = dist;
|
|
1414
|
-
best_idx = idx;
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
if (best_idx === -1 || best_distance < EPS)
|
|
1418
|
-
return null;
|
|
1419
|
-
chosen.push(best_idx);
|
|
1420
|
-
chosen_set.add(best_idx);
|
|
1421
|
-
}
|
|
1422
|
-
return chosen;
|
|
1423
|
-
}
|
|
1424
|
-
// Distance from point to affine hull spanned by given points
|
|
1425
|
-
function distance_to_affine_hull_nd(point, hull_points) {
|
|
1426
|
-
if (hull_points.length === 1) {
|
|
1427
|
-
return norm_nd(subtract_nd(point, hull_points[0]));
|
|
1428
|
-
}
|
|
1429
|
-
if (hull_points.length === 2) {
|
|
1430
|
-
// Distance to line
|
|
1431
|
-
const [pt_a, pt_b] = hull_points;
|
|
1432
|
-
const ab = subtract_nd(pt_b, pt_a);
|
|
1433
|
-
const ap = subtract_nd(point, pt_a);
|
|
1434
|
-
const ab_len_sq = dot_nd(ab, ab);
|
|
1435
|
-
if (ab_len_sq < EPS)
|
|
1436
|
-
return norm_nd(ap);
|
|
1437
|
-
const t = dot_nd(ap, ab) / ab_len_sq;
|
|
1438
|
-
const proj = pt_a.map((val, idx) => val + t * ab[idx]);
|
|
1439
|
-
return norm_nd(subtract_nd(point, proj));
|
|
1440
|
-
}
|
|
1441
|
-
// For 3+ points, use orthogonal projection
|
|
1442
|
-
// Build edge vectors from hull_points[0]
|
|
1443
|
-
const origin = hull_points[0];
|
|
1444
|
-
const edges = hull_points.slice(1).map((pt) => subtract_nd(pt, origin));
|
|
1445
|
-
const vp = subtract_nd(point, origin);
|
|
1446
|
-
// Solve least squares using Gram matrix G[i][j] = dot(edge_i, edge_j)
|
|
1447
|
-
const gram = edges.map((edge_i) => edges.map((edge_j) => dot_nd(edge_i, edge_j)));
|
|
1448
|
-
const rhs = edges.map((edge) => dot_nd(edge, vp));
|
|
1449
|
-
// Solve Gram * coeffs = rhs using simple Gaussian elimination
|
|
1450
|
-
const coeffs = solve_linear_system(gram, rhs);
|
|
1451
|
-
if (!coeffs) {
|
|
1452
|
-
// Fallback: Gram-Schmidt when Gram matrix is singular (linearly dependent edges)
|
|
1453
|
-
// Build orthogonal basis and accumulate projection in single pass
|
|
1454
|
-
const ortho_basis = [];
|
|
1455
|
-
let projection = vp.map(() => 0);
|
|
1456
|
-
for (const edge of edges) {
|
|
1457
|
-
// Orthogonalize edge against existing basis
|
|
1458
|
-
let ortho = [...edge];
|
|
1459
|
-
for (const basis of ortho_basis) {
|
|
1460
|
-
const norm_sq = dot_nd(basis, basis);
|
|
1461
|
-
if (norm_sq > EPS) {
|
|
1462
|
-
const coeff = dot_nd(ortho, basis) / norm_sq;
|
|
1463
|
-
ortho = ortho.map((val, idx) => val - coeff * basis[idx]);
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
// Add to basis and update projection if linearly independent
|
|
1467
|
-
const ortho_norm_sq = dot_nd(ortho, ortho);
|
|
1468
|
-
if (ortho_norm_sq > EPS) {
|
|
1469
|
-
ortho_basis.push(ortho);
|
|
1470
|
-
const proj_coeff = dot_nd(vp, ortho) / ortho_norm_sq;
|
|
1471
|
-
projection = projection.map((val, idx) => val + proj_coeff * ortho[idx]);
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
return norm_nd(subtract_nd(vp, projection));
|
|
1475
|
-
}
|
|
1476
|
-
// Compute projection: origin + sum(coeffs[i] * edges[i])
|
|
1477
|
-
const proj = origin.map((val, dim) => val + coeffs.reduce((sum, coeff, idx) => sum + coeff * edges[idx][dim], 0));
|
|
1478
|
-
return norm_nd(subtract_nd(point, proj));
|
|
1479
|
-
}
|
|
1480
|
-
// Solve linear system Ax = b using Gaussian elimination with partial pivoting
|
|
1481
|
-
function solve_linear_system(matrix_a, vec_b) {
|
|
1482
|
-
const n = matrix_a.length;
|
|
1483
|
-
if (n === 0)
|
|
1484
|
-
return [];
|
|
1485
|
-
if (vec_b.length !== n)
|
|
1486
|
-
return null; // Dimension mismatch
|
|
1487
|
-
// Augmented matrix
|
|
1488
|
-
const aug = matrix_a.map((row, idx) => [...row, vec_b[idx]]);
|
|
1489
|
-
for (let col = 0; col < n; col++) {
|
|
1490
|
-
// Find pivot
|
|
1491
|
-
let max_row = col;
|
|
1492
|
-
for (let row = col + 1; row < n; row++) {
|
|
1493
|
-
if (Math.abs(aug[row][col]) > Math.abs(aug[max_row][col])) {
|
|
1494
|
-
max_row = row;
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
if (Math.abs(aug[max_row][col]) < EPS)
|
|
1498
|
-
return null; // Singular
|
|
1499
|
-
// Swap rows if needed
|
|
1500
|
-
if (max_row !== col) {
|
|
1501
|
-
const temp = aug[col];
|
|
1502
|
-
aug[col] = aug[max_row];
|
|
1503
|
-
aug[max_row] = temp;
|
|
1504
|
-
}
|
|
1505
|
-
// Eliminate
|
|
1506
|
-
for (let row = col + 1; row < n; row++) {
|
|
1507
|
-
const factor = aug[row][col] / aug[col][col];
|
|
1508
|
-
for (let elim_col = col; elim_col <= n; elim_col++) {
|
|
1509
|
-
aug[row][elim_col] -= factor * aug[col][elim_col];
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
// Back substitution
|
|
1514
|
-
const result = Array(n).fill(0);
|
|
1515
|
-
for (let row = n - 1; row >= 0; row--) {
|
|
1516
|
-
let sum = aug[row][n];
|
|
1517
|
-
for (let col = row + 1; col < n; col++) {
|
|
1518
|
-
sum -= aug[row][col] * result[col];
|
|
1519
|
-
}
|
|
1520
|
-
result[row] = sum / aug[row][row];
|
|
1521
|
-
}
|
|
1522
|
-
return result;
|
|
1523
|
-
}
|
|
1524
|
-
// Create a simplex face with correct normal orientation (outward from interior)
|
|
1525
|
-
function make_face_nd(points, vertex_indices, interior_point) {
|
|
1526
|
-
const face_points = vertex_indices.map((idx) => points[idx]);
|
|
1527
|
-
const plane = compute_hyperplane_nd(face_points);
|
|
1528
|
-
const centroid = compute_centroid_nd(face_points);
|
|
1529
|
-
// Flip normal if it points toward interior instead of away
|
|
1530
|
-
const dist_interior = point_hyperplane_signed_distance_nd(plane, interior_point);
|
|
1531
|
-
if (dist_interior > 0) {
|
|
1532
|
-
plane.normal = plane.normal.map((val) => -val);
|
|
1533
|
-
plane.offset = -plane.offset;
|
|
1534
|
-
}
|
|
1535
|
-
return { vertex_indices, plane, centroid, outside_points: new Set() };
|
|
1536
|
-
}
|
|
1537
|
-
function assign_outside_points_nd(face, points, candidate_indices) {
|
|
1538
|
-
face.outside_points.clear();
|
|
1539
|
-
for (const idx of candidate_indices) {
|
|
1540
|
-
const dist = point_hyperplane_signed_distance_nd(face.plane, points[idx]);
|
|
1541
|
-
if (dist > EPS)
|
|
1542
|
-
face.outside_points.add(idx);
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
function farthest_outside_point_nd(points, face) {
|
|
1546
|
-
let best = null;
|
|
1547
|
-
for (const idx of face.outside_points) {
|
|
1548
|
-
const distance = point_hyperplane_signed_distance_nd(face.plane, points[idx]);
|
|
1549
|
-
if (!best || distance > best.distance)
|
|
1550
|
-
best = { idx, distance };
|
|
1551
|
-
}
|
|
1552
|
-
return best;
|
|
1553
|
-
}
|
|
1554
|
-
// Build horizon ridges (boundary between visible and non-visible faces)
|
|
1555
|
-
// In N dimensions, ridges are (N-2)-simplices with (N-1) vertices
|
|
1556
|
-
function build_horizon_nd(faces, visible_indices) {
|
|
1557
|
-
const ridge_count = new Map();
|
|
1558
|
-
for (const face_idx of visible_indices) {
|
|
1559
|
-
const face = faces[face_idx];
|
|
1560
|
-
const verts = face.vertex_indices;
|
|
1561
|
-
const n = verts.length;
|
|
1562
|
-
// Each face has n ridges, each ridge omits one vertex
|
|
1563
|
-
for (let skip = 0; skip < n; skip++) {
|
|
1564
|
-
const ridge = verts.filter((_, idx) => idx !== skip);
|
|
1565
|
-
const sorted = [...ridge].sort((a, b) => a - b);
|
|
1566
|
-
const key = sorted.join(`|`);
|
|
1567
|
-
if (!ridge_count.has(key)) {
|
|
1568
|
-
ridge_count.set(key, ridge);
|
|
1569
|
-
}
|
|
1570
|
-
else {
|
|
1571
|
-
// Mark as internal (seen twice)
|
|
1572
|
-
ridge_count.set(key, []);
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
// Return only boundary ridges (seen exactly once, not marked empty)
|
|
1577
|
-
return [...ridge_count.values()].filter((ridge) => ridge.length > 0);
|
|
1578
|
-
}
|
|
1579
|
-
// N-dimensional quickhull algorithm
|
|
1580
|
-
export function compute_quickhull_nd(points) {
|
|
1581
|
-
if (points.length === 0)
|
|
1582
|
-
return [];
|
|
1583
|
-
const n = points[0].length;
|
|
1584
|
-
if (points.length < n + 1)
|
|
1585
|
-
return [];
|
|
1586
|
-
// Find initial n-simplex
|
|
1587
|
-
const initial = choose_initial_simplex_nd(points);
|
|
1588
|
-
if (!initial)
|
|
1589
|
-
return [];
|
|
1590
|
-
// Interior point for normal orientation
|
|
1591
|
-
const interior = compute_centroid_nd(initial.map((idx) => points[idx]));
|
|
1592
|
-
// Create initial n+1 facets (each omits one vertex from the simplex)
|
|
1593
|
-
const faces = [];
|
|
1594
|
-
for (let skip = 0; skip <= n; skip++) {
|
|
1595
|
-
const verts = initial.filter((_, idx) => idx !== skip);
|
|
1596
|
-
faces.push(make_face_nd(points, verts, interior));
|
|
1597
|
-
}
|
|
1598
|
-
// Assign outside points to initial faces
|
|
1599
|
-
const remaining = points.map((_, idx) => idx).filter((idx) => !initial.includes(idx));
|
|
1600
|
-
for (const face of faces) {
|
|
1601
|
-
assign_outside_points_nd(face, points, remaining);
|
|
1602
|
-
}
|
|
1603
|
-
// Main quickhull loop
|
|
1604
|
-
while (true) {
|
|
1605
|
-
// Find face with farthest outside point
|
|
1606
|
-
let [chosen_face_idx, chosen_point_idx, max_distance] = [-1, -1, -1];
|
|
1607
|
-
for (let face_idx = 0; face_idx < faces.length; face_idx++) {
|
|
1608
|
-
const face = faces[face_idx];
|
|
1609
|
-
if (face.outside_points.size === 0)
|
|
1610
|
-
continue;
|
|
1611
|
-
const far = farthest_outside_point_nd(points, face);
|
|
1612
|
-
if (far && far.distance > max_distance) {
|
|
1613
|
-
max_distance = far.distance;
|
|
1614
|
-
chosen_face_idx = face_idx;
|
|
1615
|
-
chosen_point_idx = far.idx;
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
if (chosen_face_idx === -1)
|
|
1619
|
-
break; // All points processed
|
|
1620
|
-
const eye_idx = chosen_point_idx;
|
|
1621
|
-
// Find all faces visible from eye point
|
|
1622
|
-
const visible_indices = new Set();
|
|
1623
|
-
for (let face_idx = 0; face_idx < faces.length; face_idx++) {
|
|
1624
|
-
const dist = point_hyperplane_signed_distance_nd(faces[face_idx].plane, points[eye_idx]);
|
|
1625
|
-
if (dist > EPS)
|
|
1626
|
-
visible_indices.add(face_idx);
|
|
1627
|
-
}
|
|
1628
|
-
// Build horizon ridges
|
|
1629
|
-
const horizon = build_horizon_nd(faces, visible_indices);
|
|
1630
|
-
// Collect candidate points from visible faces
|
|
1631
|
-
const candidates = new Set();
|
|
1632
|
-
for (const face_idx of visible_indices) {
|
|
1633
|
-
for (const pt_idx of faces[face_idx].outside_points) {
|
|
1634
|
-
candidates.add(pt_idx);
|
|
1635
|
-
}
|
|
1636
|
-
}
|
|
1637
|
-
// Remove visible faces (in reverse order to maintain indices)
|
|
1638
|
-
const sorted_visible = Array.from(visible_indices).sort((a, b) => b - a);
|
|
1639
|
-
for (const idx of sorted_visible) {
|
|
1640
|
-
faces.splice(idx, 1);
|
|
1641
|
-
}
|
|
1642
|
-
// Create new faces from horizon ridges to eye point
|
|
1643
|
-
const new_faces = [];
|
|
1644
|
-
for (const ridge of horizon) {
|
|
1645
|
-
const new_verts = [...ridge, eye_idx];
|
|
1646
|
-
new_faces.push(make_face_nd(points, new_verts, interior));
|
|
1647
|
-
}
|
|
1648
|
-
// Reassign candidate points to new faces
|
|
1649
|
-
for (const face of new_faces)
|
|
1650
|
-
face.outside_points.clear();
|
|
1651
|
-
for (const pt_idx of candidates) {
|
|
1652
|
-
if (pt_idx === eye_idx)
|
|
1653
|
-
continue;
|
|
1654
|
-
let best_face = null;
|
|
1655
|
-
let best_dist = EPS;
|
|
1656
|
-
for (const face of new_faces) {
|
|
1657
|
-
const dist = point_hyperplane_signed_distance_nd(face.plane, points[pt_idx]);
|
|
1658
|
-
if (dist > best_dist) {
|
|
1659
|
-
best_dist = dist;
|
|
1660
|
-
best_face = face;
|
|
1661
|
-
}
|
|
1662
|
-
}
|
|
1663
|
-
if (best_face)
|
|
1664
|
-
best_face.outside_points.add(pt_idx);
|
|
1665
|
-
}
|
|
1666
|
-
faces.push(...new_faces);
|
|
1667
|
-
}
|
|
1668
|
-
return faces;
|
|
1669
|
-
}
|
|
1670
|
-
// Filter for lower hull facets (normal pointing "down" in energy dimension)
|
|
1671
|
-
export function compute_lower_hull_nd(faces) {
|
|
1672
|
-
// Last dimension is energy; negative normal means "downward"
|
|
1673
|
-
return faces.filter((face) => (face.plane.normal.at(-1) ?? 0) < -EPS);
|
|
1674
|
-
}
|
|
1675
|
-
function build_simplex_models_nd(faces, points) {
|
|
1676
|
-
return faces.map((face) => {
|
|
1677
|
-
const vertices = face.vertex_indices.map((idx) => points[idx]);
|
|
1678
|
-
const n = vertices[0].length;
|
|
1679
|
-
// Spatial coords are all except last (energy)
|
|
1680
|
-
const vertices_spatial = vertices.map((pt) => pt.slice(0, n - 1));
|
|
1681
|
-
// Compute bounding box in spatial dimensions
|
|
1682
|
-
const spatial_dim = n - 1;
|
|
1683
|
-
const bbox_min = Array.from({ length: spatial_dim }, (_, idx) => Math.min(...vertices_spatial.map((pt) => pt[idx])));
|
|
1684
|
-
const bbox_max = Array.from({ length: spatial_dim }, (_, idx) => Math.max(...vertices_spatial.map((pt) => pt[idx])));
|
|
1685
|
-
return { vertices, vertices_spatial, bbox_min, bbox_max };
|
|
1686
|
-
});
|
|
1687
|
-
}
|
|
1688
|
-
// Check if point is inside simplex and return barycentric coordinates
|
|
1689
|
-
// Uses linear system solution: point = sum(bary[i] * vertex[i]) with sum(bary) = 1
|
|
1690
|
-
function point_in_simplex_nd(point, simplex_vertices) {
|
|
1691
|
-
const n = simplex_vertices.length; // Number of vertices = spatial_dim + 1
|
|
1692
|
-
if (n === 0)
|
|
1693
|
-
return null;
|
|
1694
|
-
const dim = point.length;
|
|
1695
|
-
if (dim !== n - 1)
|
|
1696
|
-
return null; // Spatial dim should be one less than vertex count
|
|
1697
|
-
// Build linear system: [v1-v0, v2-v0, ..., vn-v0] * [b1, b2, ..., bn] = point - v0
|
|
1698
|
-
// Then b0 = 1 - sum(b1..bn)
|
|
1699
|
-
const v0 = simplex_vertices[0];
|
|
1700
|
-
const edges = simplex_vertices.slice(1).map((vert) => subtract_nd(vert, v0));
|
|
1701
|
-
const rhs = subtract_nd(point, v0);
|
|
1702
|
-
// For square system (dim == n-1), solve directly
|
|
1703
|
-
// Build matrix where each column is an edge vector
|
|
1704
|
-
const matrix = [];
|
|
1705
|
-
for (let row = 0; row < dim; row++) {
|
|
1706
|
-
matrix.push(edges.map((edge) => edge[row]));
|
|
1707
|
-
}
|
|
1708
|
-
const coeffs = solve_linear_system(matrix, rhs);
|
|
1709
|
-
if (!coeffs)
|
|
1710
|
-
return null;
|
|
1711
|
-
// Compute b0 = 1 - sum(coeffs), ensuring sum(bary) = 1 by construction
|
|
1712
|
-
const sum_coeffs = coeffs.reduce((sum, val) => sum + val, 0);
|
|
1713
|
-
const bary = [1 - sum_coeffs, ...coeffs];
|
|
1714
|
-
// Point is inside simplex if all barycentric coords are non-negative
|
|
1715
|
-
return bary.every((val) => val >= -EPS) ? bary : null;
|
|
1716
|
-
}
|
|
1717
|
-
// Compute energy above hull for N-dimensional points
|
|
1718
|
-
export function compute_e_above_hull_nd(query_points, hull_facets, all_points) {
|
|
1719
|
-
const models = build_simplex_models_nd(hull_facets, all_points);
|
|
1720
|
-
return query_points.map((query) => {
|
|
1721
|
-
const n = query.length;
|
|
1722
|
-
const spatial = query.slice(0, n - 1); // All but last coord
|
|
1723
|
-
const energy = query[n - 1];
|
|
1724
|
-
let hull_energy = null;
|
|
1725
|
-
for (const model of models) {
|
|
1726
|
-
// Fast bounding box rejection
|
|
1727
|
-
let outside_bbox = false;
|
|
1728
|
-
for (let idx = 0; idx < spatial.length; idx++) {
|
|
1729
|
-
if (spatial[idx] < model.bbox_min[idx] - EPS ||
|
|
1730
|
-
spatial[idx] > model.bbox_max[idx] + EPS) {
|
|
1731
|
-
outside_bbox = true;
|
|
1732
|
-
break;
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
if (outside_bbox)
|
|
1736
|
-
continue;
|
|
1737
|
-
// Check if spatial coords are inside simplex projection
|
|
1738
|
-
const bary = point_in_simplex_nd(spatial, model.vertices_spatial);
|
|
1739
|
-
if (!bary)
|
|
1740
|
-
continue;
|
|
1741
|
-
// Interpolate energy using barycentric coords
|
|
1742
|
-
const e_hull = bary.reduce((sum, coeff, idx) => sum + coeff * model.vertices[idx][n - 1], 0);
|
|
1743
|
-
hull_energy = hull_energy === null ? e_hull : Math.min(hull_energy, e_hull);
|
|
1744
|
-
}
|
|
1745
|
-
// If no facet contains this point's spatial projection, it's outside the valid
|
|
1746
|
-
// composition domain. Return NaN to indicate invalid input rather than 0 (which
|
|
1747
|
-
// would falsely imply the point is stable/on-hull).
|
|
1748
|
-
if (hull_energy === null)
|
|
1749
|
-
return NaN;
|
|
1750
|
-
return energy - hull_energy;
|
|
1751
|
-
});
|
|
1752
|
-
}
|