matterviz 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EmptyState.svelte +10 -4
- package/dist/FilePicker.svelte +48 -60
- package/dist/Icon.svelte +7 -4
- package/dist/MillerIndexInput.svelte +2 -2
- package/dist/api/optimade.js +10 -7
- package/dist/app.css +68 -0
- package/dist/brillouin/BrillouinZone.svelte +90 -91
- package/dist/brillouin/BrillouinZone.svelte.d.ts +5 -7
- package/dist/brillouin/BrillouinZoneControls.svelte +3 -6
- package/dist/brillouin/BrillouinZoneExportPane.svelte +18 -14
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +6 -10
- package/dist/brillouin/BrillouinZoneScene.svelte +46 -68
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/brillouin/compute.js +10 -16
- package/dist/brillouin/geometry.js +2 -2
- package/dist/chempot-diagram/ChemPotDiagram.svelte +48 -50
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +61 -169
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +559 -755
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +1 -1
- package/dist/chempot-diagram/async-compute.svelte.js +13 -14
- package/dist/chempot-diagram/color.d.ts +21 -1
- package/dist/chempot-diagram/color.js +70 -1
- package/dist/chempot-diagram/compute.d.ts +7 -0
- package/dist/chempot-diagram/compute.js +85 -26
- package/dist/chempot-diagram/controls-state.svelte.js +1 -1
- package/dist/chempot-diagram/export.js +1 -6
- package/dist/chempot-diagram/temperature.js +9 -10
- package/dist/colors/index.d.ts +0 -4
- package/dist/colors/index.js +19 -20
- package/dist/composition/BarChart.svelte +30 -26
- package/dist/composition/BubbleChart.svelte +17 -17
- package/dist/composition/Composition.svelte +10 -19
- package/dist/composition/Formula.svelte +20 -27
- package/dist/composition/FormulaFilter.svelte +115 -184
- package/dist/composition/PieChart.svelte +55 -41
- package/dist/composition/format.js +7 -9
- package/dist/composition/index.d.ts +2 -0
- package/dist/composition/index.js +14 -0
- package/dist/composition/parse.d.ts +2 -2
- package/dist/composition/parse.js +61 -115
- package/dist/constants.d.ts +0 -1
- package/dist/constants.js +17 -26
- package/dist/convex-hull/ConvexHull.svelte +12 -15
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +93 -94
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +127 -186
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +131 -180
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullChrome.svelte +36 -40
- package/dist/convex-hull/ConvexHullControls.svelte +46 -48
- package/dist/convex-hull/ConvexHullStats.svelte +193 -195
- package/dist/convex-hull/ConvexHullTooltip.svelte +5 -12
- package/dist/convex-hull/GasPressureControls.svelte +5 -12
- package/dist/convex-hull/StructurePopup.svelte +18 -22
- package/dist/convex-hull/StructurePopup.svelte.d.ts +2 -0
- package/dist/convex-hull/TemperatureSlider.svelte +4 -10
- package/dist/convex-hull/canvas-interactions.svelte.js +2 -1
- package/dist/convex-hull/gas-thermodynamics.js +21 -53
- package/dist/convex-hull/helpers.d.ts +8 -10
- package/dist/convex-hull/helpers.js +37 -43
- package/dist/convex-hull/hull-state.svelte.d.ts +1 -1
- package/dist/convex-hull/thermodynamics.js +37 -44
- package/dist/coordination/CoordinationBarPlot.svelte +49 -53
- package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
- package/dist/coordination/calc-coordination.d.ts +1 -1
- package/dist/coordination/calc-coordination.js +28 -25
- package/dist/element/BohrAtom.svelte +5 -9
- package/dist/element/ElementHeading.svelte +4 -1
- package/dist/element/ElementPhoto.svelte +11 -9
- package/dist/element/ElementStats.svelte +12 -10
- package/dist/element/ElementTile.svelte +44 -67
- package/dist/element/Nucleus.svelte +4 -2
- package/dist/element/data.d.ts +3 -2
- package/dist/element/data.js +5 -1
- package/dist/element/helpers.d.ts +4 -0
- package/dist/element/helpers.js +18 -0
- package/dist/element/index.d.ts +2 -5
- package/dist/element/index.js +4 -19
- package/dist/feedback/DragOverlay.svelte +5 -5
- package/dist/feedback/DragOverlay.svelte.d.ts +2 -2
- package/dist/feedback/StatusMessage.svelte +7 -4
- package/dist/fermi-surface/FermiSlice.svelte +21 -18
- package/dist/fermi-surface/FermiSurface.svelte +35 -44
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +7 -13
- package/dist/fermi-surface/FermiSurfaceControls.svelte +6 -20
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +64 -136
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +9 -6
- package/dist/fermi-surface/compute.js +25 -58
- package/dist/fermi-surface/parse.js +54 -88
- package/dist/file-viewer/JsonBrowser.svelte +1043 -0
- package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
- package/dist/file-viewer/PlotPanel.svelte +350 -0
- package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
- package/dist/file-viewer/detect.d.ts +11 -0
- package/dist/file-viewer/detect.js +381 -0
- package/dist/file-viewer/eligibility.d.ts +4 -0
- package/dist/file-viewer/eligibility.js +53 -0
- package/dist/file-viewer/host-protocol.d.ts +50 -0
- package/dist/file-viewer/host-transfer.d.ts +25 -0
- package/dist/file-viewer/host-transfer.js +42 -0
- package/dist/file-viewer/index.d.ts +5 -0
- package/dist/file-viewer/index.js +5 -0
- package/dist/file-viewer/main.d.ts +44 -0
- package/dist/file-viewer/main.js +535 -0
- package/dist/file-viewer/parse.d.ts +9 -0
- package/dist/file-viewer/parse.js +170 -0
- package/dist/file-viewer/plot-utils.d.ts +24 -0
- package/dist/file-viewer/plot-utils.js +191 -0
- package/dist/file-viewer/types.d.ts +4 -0
- package/dist/file-viewer/types.js +5 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +153 -219
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +2 -2
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +9 -7
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
- package/dist/heatmap-matrix/index.d.ts +5 -0
- package/dist/icons.d.ts +4 -0
- package/dist/icons.js +4 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/io/ExportPane.svelte +11 -7
- package/dist/io/decompress.d.ts +4 -1
- package/dist/io/decompress.js +24 -36
- package/dist/io/export.d.ts +11 -4
- package/dist/io/export.js +75 -19
- package/dist/io/fetch.d.ts +0 -3
- package/dist/io/fetch.js +0 -11
- package/dist/io/file-drop.js +60 -35
- package/dist/io/index.d.ts +0 -1
- package/dist/io/index.js +2 -9
- package/dist/io/is-binary.d.ts +9 -0
- package/dist/io/is-binary.js +45 -1
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +40 -40
- package/dist/isosurface/Isosurface.svelte +700 -215
- package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
- package/dist/isosurface/IsosurfaceControls.svelte +590 -116
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
- package/dist/isosurface/VolumeSlice.svelte +224 -0
- package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
- package/dist/isosurface/coloring.d.ts +20 -0
- package/dist/isosurface/coloring.js +118 -0
- package/dist/isosurface/geometry-worker-types.d.ts +38 -0
- package/dist/isosurface/geometry.worker.d.ts +1 -0
- package/dist/isosurface/geometry.worker.js +59 -0
- package/dist/isosurface/grid.d.ts +10 -0
- package/dist/isosurface/grid.js +31 -0
- package/dist/isosurface/index.d.ts +4 -0
- package/dist/isosurface/index.js +4 -0
- package/dist/isosurface/parse-vaspwave.d.ts +3 -0
- package/dist/isosurface/parse-vaspwave.js +138 -0
- package/dist/isosurface/parse.js +6 -6
- package/dist/isosurface/profile.d.ts +12 -0
- package/dist/isosurface/profile.js +19 -0
- package/dist/isosurface/sampling.d.ts +34 -0
- package/dist/isosurface/sampling.js +408 -0
- package/dist/isosurface/slice-rendering.d.ts +13 -0
- package/dist/isosurface/slice-rendering.js +79 -0
- package/dist/isosurface/slice.d.ts +22 -2
- package/dist/isosurface/slice.js +200 -116
- package/dist/isosurface/types.d.ts +24 -1
- package/dist/isosurface/types.js +120 -8
- package/dist/json-path.d.ts +4 -0
- package/dist/json-path.js +89 -0
- package/dist/labels.d.ts +6 -2
- package/dist/labels.js +44 -14
- package/dist/layout/FullscreenButton.svelte +2 -1
- package/dist/layout/FullscreenToggle.svelte +10 -4
- package/dist/layout/InfoCard.svelte +8 -21
- package/dist/layout/InfoTag.svelte +1 -3
- package/dist/layout/NumberRangeInput.svelte +46 -0
- package/dist/layout/NumberRangeInput.svelte.d.ts +12 -0
- package/dist/layout/PropertyFilter.svelte +2 -6
- package/dist/layout/SettingsSection.svelte +57 -62
- package/dist/layout/SettingsSection.svelte.d.ts +6 -7
- package/dist/layout/SubpageGrid.svelte +3 -1
- package/dist/layout/ViewerChrome.svelte +16 -11
- package/dist/layout/ViewerChrome.svelte.d.ts +2 -1
- package/dist/layout/fullscreen.js +3 -5
- package/dist/layout/fullscreen.svelte.js +1 -2
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/layout/json-tree/JsonNode.svelte +22 -59
- package/dist/layout/json-tree/JsonTree.svelte +101 -172
- package/dist/layout/json-tree/JsonValue.svelte +9 -32
- package/dist/layout/json-tree/utils.d.ts +0 -3
- package/dist/layout/json-tree/utils.js +50 -188
- package/dist/marching-cubes.d.ts +8 -1
- package/dist/marching-cubes.js +115 -48
- package/dist/math.d.ts +5 -2
- package/dist/math.js +48 -38
- package/dist/overlays/ContextMenu.svelte +19 -34
- package/dist/overlays/CopyButton.svelte +11 -5
- package/dist/overlays/DragControlTab.svelte +6 -1
- package/dist/overlays/DraggablePane.svelte +110 -74
- package/dist/overlays/DraggablePane.svelte.d.ts +2 -1
- package/dist/overlays/GlassChip.svelte +32 -0
- package/dist/overlays/GlassChip.svelte.d.ts +8 -0
- package/dist/overlays/InfoPaneCards.svelte +11 -23
- package/dist/overlays/index.d.ts +7 -0
- package/dist/overlays/index.js +26 -0
- package/dist/overlays/portal.d.ts +2 -0
- package/dist/overlays/portal.js +14 -0
- package/dist/periodic-table/PeriodicTable.svelte +113 -145
- package/dist/periodic-table/PeriodicTable.svelte.d.ts +5 -3
- package/dist/periodic-table/PeriodicTableControls.svelte +37 -115
- package/dist/periodic-table/TableInset.svelte +9 -4
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +68 -105
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -8
- package/dist/phase-diagram/PhaseDiagramControls.svelte +70 -94
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +12 -12
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +54 -58
- package/dist/phase-diagram/TdbInfoPanel.svelte +21 -15
- package/dist/phase-diagram/build-diagram.js +4 -9
- package/dist/phase-diagram/diagram-input.d.ts +1 -2
- package/dist/phase-diagram/parse.js +15 -15
- package/dist/phase-diagram/svg-to-diagram.js +28 -27
- package/dist/phase-diagram/utils.js +2 -3
- package/dist/plot/bar/BarPlot.svelte +461 -426
- package/dist/plot/bar/BarPlot.svelte.d.ts +2 -0
- package/dist/plot/bar/BarPlotControls.svelte +1 -3
- package/dist/plot/bar/BarPlotControls.svelte.d.ts +4 -6
- package/dist/plot/bar/SpacegroupBarPlot.svelte +46 -77
- package/dist/plot/bar/data.d.ts +5 -4
- package/dist/plot/bar/data.js +2 -1
- package/dist/plot/box/BoxPlot.svelte +322 -226
- package/dist/plot/box/BoxPlot.svelte.d.ts +2 -0
- package/dist/plot/box/Violin.svelte.d.ts +1 -0
- package/dist/plot/box/box-plot.js +1 -1
- package/dist/plot/core/auto-place.d.ts +1 -1
- package/dist/plot/core/auto-place.js +2 -2
- package/dist/plot/core/axis-utils.d.ts +12 -7
- package/dist/plot/core/axis-utils.js +48 -48
- package/dist/plot/core/components/AxisLabel.svelte +82 -20
- package/dist/plot/core/components/AxisLabel.svelte.d.ts +1 -0
- package/dist/plot/core/components/ColorBar.svelte +101 -106
- package/dist/plot/core/components/ColorScaleSelect.svelte +1 -1
- package/dist/plot/core/components/FillArea.svelte +10 -17
- package/dist/plot/core/components/HierarchyControls.svelte +249 -0
- package/dist/plot/{sunburst/SunburstControls.svelte.d.ts → core/components/HierarchyControls.svelte.d.ts} +12 -8
- package/dist/plot/core/components/InteractiveAxisLabel.svelte +1 -1
- package/dist/plot/core/components/InteractiveAxisLabel.svelte.d.ts +1 -1
- package/dist/plot/core/components/Line.svelte +18 -9
- package/dist/plot/core/components/Line.svelte.d.ts +2 -0
- package/dist/plot/core/components/PlotAxis.svelte +40 -17
- package/dist/plot/core/components/PlotControls.svelte +65 -41
- package/dist/plot/core/components/PlotLegend.svelte +31 -35
- package/dist/plot/core/components/PlotMarginals.svelte +616 -0
- package/dist/plot/core/components/PlotMarginals.svelte.d.ts +15 -0
- package/dist/plot/core/components/PlotTooltip.svelte +1 -1
- package/dist/plot/core/components/PortalSelect.svelte +4 -8
- package/dist/plot/core/components/ReferenceLine.svelte +27 -24
- package/dist/plot/core/components/ReferenceLine3D.svelte +54 -57
- package/dist/plot/core/components/ReferencePlane.svelte +7 -8
- package/dist/plot/core/components/ZeroLines.svelte +4 -8
- package/dist/plot/core/components/ZoomRect.svelte +1 -2
- package/dist/plot/core/components/index.d.ts +1 -0
- package/dist/plot/core/components/index.js +1 -0
- package/dist/plot/core/data-cleaning-signal.d.ts +46 -0
- package/dist/plot/core/data-cleaning-signal.js +467 -0
- package/dist/plot/core/data-cleaning.d.ts +2 -42
- package/dist/plot/core/data-cleaning.js +27 -474
- package/dist/plot/core/data-transform.d.ts +1 -2
- package/dist/plot/core/data-transform.js +9 -15
- package/dist/plot/core/fill-utils.d.ts +3 -1
- package/dist/plot/core/fill-utils.js +28 -6
- package/dist/plot/core/index.d.ts +1 -0
- package/dist/plot/core/index.js +1 -0
- package/dist/plot/core/interactions.js +0 -2
- package/dist/plot/core/layout.d.ts +15 -6
- package/dist/plot/core/layout.js +57 -38
- package/dist/plot/core/marginals.d.ts +170 -0
- package/dist/plot/core/marginals.js +436 -0
- package/dist/plot/core/pan-zoom.svelte.d.ts +2 -4
- package/dist/plot/core/pan-zoom.svelte.js +34 -17
- package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
- package/dist/plot/core/placed-tween.svelte.js +68 -21
- package/dist/plot/core/reference-line.js +1 -1
- package/dist/plot/core/scales.d.ts +23 -0
- package/dist/plot/core/scales.js +39 -13
- package/dist/plot/core/svg.js +0 -1
- package/dist/plot/core/types/fills.d.ts +101 -0
- package/dist/plot/core/types/fills.js +15 -0
- package/dist/plot/core/types/plot-3d.d.ts +87 -0
- package/dist/plot/core/types/plot-3d.js +4 -0
- package/dist/plot/core/types/reference-lines.d.ts +140 -0
- package/dist/plot/core/types/reference-lines.js +9 -0
- package/dist/plot/core/types.d.ts +29 -324
- package/dist/plot/core/types.js +23 -20
- package/dist/plot/core/utils/hierarchy-chart.d.ts +52 -0
- package/dist/plot/core/utils/hierarchy-chart.js +191 -0
- package/dist/plot/core/utils/hierarchy-labels.d.ts +20 -0
- package/dist/plot/core/utils/hierarchy-labels.js +100 -0
- package/dist/plot/core/utils/label-placement.js +2 -2
- package/dist/plot/histogram/Histogram.svelte +314 -340
- package/dist/plot/histogram/Histogram.svelte.d.ts +7 -7
- package/dist/plot/histogram/HistogramControls.svelte +30 -28
- package/dist/plot/histogram/histogram.d.ts +31 -0
- package/dist/plot/histogram/histogram.js +70 -0
- package/dist/plot/histogram/index.d.ts +1 -0
- package/dist/plot/histogram/index.js +1 -0
- package/dist/plot/index.d.ts +1 -0
- package/dist/plot/index.js +1 -0
- package/dist/plot/sankey/Sankey.svelte +78 -69
- package/dist/plot/sankey/Sankey.svelte.d.ts +2 -2
- package/dist/plot/sankey/SankeyControls.svelte +22 -16
- package/dist/plot/sankey/sankey.js +13 -9
- package/dist/plot/scatter/BinnedScatterPlot.svelte +311 -141
- package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +7 -0
- package/dist/plot/scatter/ElementScatter.svelte +4 -1
- package/dist/plot/scatter/ScatterPlot.svelte +517 -410
- package/dist/plot/scatter/ScatterPlot.svelte.d.ts +2 -0
- package/dist/plot/scatter/ScatterPlotControls.svelte +18 -19
- package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/scatter/ScatterPoint.svelte +16 -9
- package/dist/plot/scatter/adaptive-density.d.ts +1 -1
- package/dist/plot/scatter/adaptive-density.js +36 -20
- package/dist/plot/scatter/binned-scatter-types.d.ts +3 -2
- package/dist/plot/scatter/binned-scatter-types.js +6 -1
- package/dist/plot/scatter/scatter-data.d.ts +1 -1
- package/dist/plot/scatter/scatter-data.js +8 -7
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte +25 -34
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +8 -5
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +113 -116
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/scatter-3d/Surface3D.svelte +4 -13
- package/dist/plot/sunburst/Sunburst.svelte +269 -347
- package/dist/plot/sunburst/Sunburst.svelte.d.ts +2 -2
- package/dist/plot/sunburst/index.d.ts +1 -1
- package/dist/plot/sunburst/index.js +2 -1
- package/dist/plot/sunburst/render.d.ts +2 -2
- package/dist/plot/sunburst/render.js +47 -22
- package/dist/plot/sunburst/sunburst.d.ts +5 -1
- package/dist/plot/sunburst/sunburst.js +10 -3
- package/dist/plot/treemap/Treemap.svelte +1022 -0
- package/dist/plot/treemap/Treemap.svelte.d.ts +90 -0
- package/dist/plot/treemap/index.d.ts +4 -0
- package/dist/plot/treemap/index.js +4 -0
- package/dist/plot/treemap/labels.d.ts +38 -0
- package/dist/plot/treemap/labels.js +80 -0
- package/dist/plot/treemap/treemap.d.ts +28 -0
- package/dist/plot/treemap/treemap.js +102 -0
- package/dist/rdf/RdfPlot.svelte +37 -59
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/calc-rdf.js +63 -52
- package/dist/rdf/index.d.ts +0 -1
- package/dist/sanitize.js +85 -27
- package/dist/scene/SceneCamera.svelte +1 -1
- package/dist/scene/SceneCamera.svelte.d.ts +1 -1
- package/dist/scene/bind-renderer.svelte.js +14 -6
- package/dist/scene/index.d.ts +1 -2
- package/dist/scene/index.js +1 -1
- package/dist/scene/{props.js → props.svelte.js} +15 -1
- package/dist/settings.d.ts +19 -9
- package/dist/settings.js +229 -525
- package/dist/spectral/Bands.svelte +207 -221
- package/dist/spectral/BandsAndDos.svelte +11 -9
- package/dist/spectral/BrillouinBandsDos.svelte +19 -42
- package/dist/spectral/Dos.svelte +92 -93
- package/dist/spectral/helpers.d.ts +3 -3
- package/dist/spectral/helpers.js +65 -140
- package/dist/state.svelte.js +5 -18
- package/dist/structure/Arrow.svelte +5 -18
- package/dist/structure/ArrowInstances.svelte +160 -0
- package/dist/structure/ArrowInstances.svelte.d.ts +16 -0
- package/dist/structure/AtomLegend.svelte +120 -85
- package/dist/structure/AtomLegend.svelte.d.ts +2 -3
- package/dist/structure/Bond.svelte +31 -23
- package/dist/structure/CanvasTooltip.svelte +15 -3
- package/dist/structure/CellSelect.svelte +45 -35
- package/dist/structure/Cylinder.svelte +24 -40
- package/dist/structure/InstancedAtoms.svelte +114 -0
- package/dist/structure/InstancedAtoms.svelte.d.ts +15 -0
- package/dist/structure/Lattice.svelte +85 -149
- package/dist/structure/SiteLabels.svelte +122 -0
- package/dist/structure/SiteLabels.svelte.d.ts +18 -0
- package/dist/structure/Structure.svelte +636 -504
- package/dist/structure/Structure.svelte.d.ts +4 -2
- package/dist/structure/StructureCarousel.svelte +637 -0
- package/dist/structure/StructureCarousel.svelte.d.ts +19 -0
- package/dist/structure/StructureControls.svelte +165 -425
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureExportPane.svelte +17 -21
- package/dist/structure/StructureInfoPane.svelte +128 -133
- package/dist/structure/StructureInfoPane.svelte.d.ts +1 -1
- package/dist/structure/StructureScene.svelte +535 -578
- package/dist/structure/StructureScene.svelte.d.ts +6 -2
- package/dist/structure/StructureViewport.svelte +451 -0
- package/dist/structure/StructureViewport.svelte.d.ts +65 -0
- package/dist/structure/atom-label-placement.d.ts +16 -12
- package/dist/structure/atom-label-placement.js +70 -37
- package/dist/structure/atom-properties.d.ts +2 -0
- package/dist/structure/atom-properties.js +105 -38
- package/dist/structure/bond-order-perception.js +3 -8
- package/dist/structure/bonding.d.ts +9 -3
- package/dist/structure/bonding.js +95 -46
- package/dist/structure/edit-history.d.ts +6 -0
- package/dist/structure/edit-history.js +27 -0
- package/dist/structure/export.d.ts +1 -0
- package/dist/structure/export.js +89 -105
- package/dist/structure/format-detect.d.ts +1 -0
- package/dist/structure/format-detect.js +20 -0
- package/dist/structure/geometry.d.ts +9 -0
- package/dist/structure/geometry.js +27 -0
- package/dist/structure/index.d.ts +16 -1
- package/dist/structure/index.js +15 -10
- package/dist/structure/measure.d.ts +1 -1
- package/dist/structure/measure.js +6 -12
- package/dist/structure/parse.d.ts +2 -1
- package/dist/structure/parse.js +272 -313
- package/dist/structure/partial-occupancy.js +10 -5
- package/dist/structure/pbc.js +85 -107
- package/dist/structure/polyhedra.d.ts +0 -1
- package/dist/structure/polyhedra.js +7 -21
- package/dist/structure/prop-groups.d.ts +85 -0
- package/dist/structure/prop-groups.js +18 -0
- package/dist/structure/supercell.d.ts +1 -2
- package/dist/structure/supercell.js +7 -13
- package/dist/symmetry/SymmetryElements.svelte +11 -15
- package/dist/symmetry/SymmetryStats.svelte +55 -71
- package/dist/symmetry/WyckoffTable.svelte +10 -28
- package/dist/symmetry/cell-transform.js +6 -14
- package/dist/symmetry/index.d.ts +1 -0
- package/dist/symmetry/index.js +13 -10
- package/dist/symmetry/spacegroups.js +3 -2
- package/dist/table/HeatmapTable.svelte +1142 -447
- package/dist/table/HeatmapTable.svelte.d.ts +12 -2
- package/dist/table/ToggleMenu.svelte +168 -53
- package/dist/table/ToggleMenu.svelte.d.ts +1 -1
- package/dist/table/index.d.ts +21 -6
- package/dist/table/index.js +34 -21
- package/dist/theme/ThemeControl.svelte +16 -8
- package/dist/theme/embedded.d.ts +18 -0
- package/dist/theme/embedded.js +206 -0
- package/dist/theme/index.d.ts +5 -5
- package/dist/theme/index.js +5 -12
- package/dist/theme/themes.mjs +44 -66
- package/dist/tooltip/KCoords.svelte +4 -1
- package/dist/tooltip/TooltipContent.svelte +3 -6
- package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
- package/dist/tooltip/index.d.ts +8 -1
- package/dist/trajectory/Trajectory.svelte +613 -386
- package/dist/trajectory/Trajectory.svelte.d.ts +1 -1
- package/dist/trajectory/TrajectoryError.svelte +12 -74
- package/dist/trajectory/TrajectoryExportPane.svelte +14 -32
- package/dist/trajectory/TrajectoryInfoPane.svelte +127 -164
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
- package/dist/trajectory/extract.js +61 -49
- package/dist/trajectory/format-detect.d.ts +2 -0
- package/dist/trajectory/format-detect.js +17 -13
- package/dist/trajectory/frame-reader.d.ts +2 -0
- package/dist/trajectory/frame-reader.js +35 -32
- package/dist/trajectory/helpers.d.ts +2 -0
- package/dist/trajectory/helpers.js +34 -56
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +5 -8
- package/dist/trajectory/parse/ase.js +1 -1
- package/dist/trajectory/parse/h5-utils.d.ts +13 -0
- package/dist/trajectory/parse/h5-utils.js +109 -0
- package/dist/trajectory/parse/hdf5.d.ts +1 -1
- package/dist/trajectory/parse/hdf5.js +116 -106
- package/dist/trajectory/parse/index.d.ts +6 -6
- package/dist/trajectory/parse/index.js +25 -33
- package/dist/trajectory/parse/lammps.js +8 -12
- package/dist/trajectory/parse/vasp.js +10 -7
- package/dist/trajectory/parse/vaspout-electronic.d.ts +13 -0
- package/dist/trajectory/parse/vaspout-electronic.js +207 -0
- package/dist/trajectory/parse/vaspout-h5.d.ts +4 -0
- package/dist/trajectory/parse/vaspout-h5.js +281 -0
- package/dist/trajectory/parse/xyz.js +59 -9
- package/dist/trajectory/plotting.d.ts +8 -9
- package/dist/trajectory/plotting.js +113 -64
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +7 -0
- package/dist/xrd/XrdPlot.svelte +79 -118
- package/dist/xrd/broadening.js +2 -1
- package/dist/xrd/calc-xrd.js +24 -62
- package/dist/xrd/parse.d.ts +0 -1
- package/dist/xrd/parse.js +69 -95
- package/package.json +94 -34
- package/readme.md +6 -5
- package/dist/api/mp.d.ts +0 -6
- package/dist/api/mp.js +0 -22
- package/dist/plot/sunburst/SunburstControls.svelte +0 -200
- package/dist/scene/types.d.ts +0 -26
- package/dist/tooltip/types.d.ts +0 -8
- package/dist/trajectory/constants.d.ts +0 -6
- package/dist/trajectory/constants.js +0 -11
- /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
- /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
// Threlte component that renders isosurface meshes from
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
2
|
+
// Threlte component that renders isosurface meshes from one or more volumetric
|
|
3
|
+
// datasets using marching cubes. Each layer picks a geometry-source volume and
|
|
4
|
+
// optionally a different color-source volume whose scalar field is sampled at
|
|
5
|
+
// surface vertices and mapped through a colormap (e.g. density colored by ESP).
|
|
6
|
+
// Geometry is cached per layer so colormap/color-range changes never rerun
|
|
7
|
+
// marching cubes, and sampled scalars are cached so they only remap through the
|
|
8
|
+
// colormap LUT.
|
|
9
|
+
import { marching_cubes_buffers } from '../marching-cubes'
|
|
6
10
|
import type { Matrix3x3, Vec3 } from '../math'
|
|
7
11
|
import { T } from '@threlte/core'
|
|
12
|
+
import { untrack } from 'svelte'
|
|
13
|
+
import { SvelteSet } from 'svelte/reactivity'
|
|
8
14
|
import {
|
|
9
15
|
BackSide,
|
|
10
16
|
BufferAttribute,
|
|
@@ -13,58 +19,173 @@
|
|
|
13
19
|
FrontSide,
|
|
14
20
|
Uint32BufferAttribute,
|
|
15
21
|
} from 'three'
|
|
16
|
-
import type { IsosurfaceLayer, IsosurfaceSettings, VolumetricData } from './types'
|
|
17
22
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
compute_scalar_range,
|
|
24
|
+
DEFAULT_ISO_COLORMAP,
|
|
25
|
+
is_signed_range,
|
|
26
|
+
scalars_to_vertex_colors,
|
|
27
|
+
} from './coloring'
|
|
28
|
+
import type {
|
|
29
|
+
GeometryWorkerRequest,
|
|
30
|
+
GeometryWorkerResponse,
|
|
31
|
+
TransferableVolume,
|
|
32
|
+
} from './geometry-worker-types'
|
|
33
|
+
import { flatten_grid, grid_point_count, inflate_grid } from './grid'
|
|
34
|
+
import { profile_stage, record_profile, type IsosurfaceProfiler } from './profile'
|
|
35
|
+
import type { DisplayRange } from './sampling'
|
|
36
|
+
import {
|
|
37
|
+
prepare_geometry_grid,
|
|
38
|
+
resolve_volume_display_range,
|
|
39
|
+
sample_volume_at_positions,
|
|
40
|
+
} from './sampling'
|
|
41
|
+
import type { IsosurfaceLayer, IsosurfaceSettings, VolumetricData } from './types'
|
|
42
|
+
import { DEFAULT_ISOSURFACE_SETTINGS, MAX_GRID_POINTS } from './types'
|
|
22
43
|
|
|
23
|
-
let {
|
|
24
|
-
|
|
44
|
+
let {
|
|
45
|
+
volumes = [],
|
|
46
|
+
volume = undefined,
|
|
47
|
+
settings = DEFAULT_ISOSURFACE_SETTINGS,
|
|
48
|
+
active_volume_idx = 0,
|
|
49
|
+
tiling = [1, 1, 1],
|
|
50
|
+
profiler = undefined,
|
|
51
|
+
}: {
|
|
52
|
+
volumes?: VolumetricData[]
|
|
53
|
+
// Deprecated single-volume alias (kept for backwards compatibility)
|
|
54
|
+
volume?: VolumetricData
|
|
25
55
|
settings?: IsosurfaceSettings
|
|
56
|
+
// Volume that implicit single-isovalue settings apply to when no explicit
|
|
57
|
+
// layers are set (also the fallback for layers without volume_idx)
|
|
58
|
+
active_volume_idx?: number
|
|
59
|
+
// Supercell tiling applied to geometry volumes (color sampling always uses
|
|
60
|
+
// the original volume with periodic wrapping for full fidelity)
|
|
61
|
+
tiling?: Vec3
|
|
62
|
+
// Internal benchmark hook. Deliberately not re-exported from the package API.
|
|
63
|
+
profiler?: IsosurfaceProfiler
|
|
26
64
|
} = $props()
|
|
27
65
|
|
|
28
|
-
|
|
29
|
-
let
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
66
|
+
let all_volumes = $derived(volumes.length ? volumes : volume ? [volume] : [])
|
|
67
|
+
let reference_origin = $derived<Vec3>(all_volumes[0]?.origin ?? [0, 0, 0])
|
|
68
|
+
let reference_origin_key = $derived(reference_origin.join(`,`))
|
|
69
|
+
|
|
70
|
+
type ResolvedLayer = IsosurfaceLayer & { volume_idx: number }
|
|
71
|
+
|
|
72
|
+
// Resolve layers: explicit layers array if provided (an empty array means zero
|
|
73
|
+
// surfaces), else one implicit layer from single-isovalue settings bound to
|
|
74
|
+
// the active volume. Layers with an explicitly out-of-range volume_idx are
|
|
75
|
+
// skipped rather than clamped — silently rendering a different volume's data
|
|
76
|
+
// would be scientifically wrong.
|
|
77
|
+
let resolved_layers = $derived.by((): ResolvedLayer[] => {
|
|
78
|
+
const n_vols = all_volumes.length
|
|
79
|
+
if (n_vols === 0) return []
|
|
80
|
+
const clamp_idx = (idx: number) => Math.min(Math.max(idx, 0), n_vols - 1)
|
|
81
|
+
if (settings.layers) {
|
|
82
|
+
return settings.layers
|
|
83
|
+
.filter(
|
|
84
|
+
(layer) =>
|
|
85
|
+
layer.volume_idx === undefined ||
|
|
86
|
+
(layer.volume_idx >= 0 && layer.volume_idx < n_vols),
|
|
87
|
+
)
|
|
88
|
+
.map((layer) => ({
|
|
89
|
+
...layer,
|
|
90
|
+
volume_idx: layer.volume_idx ?? clamp_idx(active_volume_idx),
|
|
91
|
+
}))
|
|
92
|
+
}
|
|
93
|
+
return [
|
|
94
|
+
{
|
|
95
|
+
isovalue: settings.isovalue,
|
|
96
|
+
color: settings.positive_color,
|
|
97
|
+
opacity: settings.opacity,
|
|
98
|
+
visible: true,
|
|
99
|
+
show_negative: settings.show_negative,
|
|
100
|
+
negative_color: settings.negative_color,
|
|
101
|
+
volume_idx: clamp_idx(active_volume_idx),
|
|
102
|
+
},
|
|
103
|
+
]
|
|
39
104
|
})
|
|
40
105
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
106
|
+
// Stable identity tokens for volume objects so cache keys detect replacement
|
|
107
|
+
let vol_id_counter = 0
|
|
108
|
+
const vol_ids = new WeakMap<VolumetricData, number>()
|
|
109
|
+
const vol_id = (vol: VolumetricData): number => {
|
|
110
|
+
let id = vol_ids.get(vol)
|
|
111
|
+
if (id === undefined) {
|
|
112
|
+
id = ++vol_id_counter
|
|
113
|
+
vol_ids.set(vol, id)
|
|
114
|
+
}
|
|
115
|
+
return id
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Finite extraction range for this volume. Periodic volumes always use one
|
|
119
|
+
// so integer tiling and fractional VESTA-style bounds share exact endpoint
|
|
120
|
+
// semantics; finite volumes only use an explicitly requested crop (the
|
|
121
|
+
// resolver clamps negative halo and only applies it to periodic volumes).
|
|
122
|
+
const effective_range = (vol: VolumetricData): DisplayRange | null =>
|
|
123
|
+
resolve_volume_display_range(vol, {
|
|
124
|
+
display_range: settings.display_range,
|
|
125
|
+
tiling,
|
|
126
|
+
halo: settings.halo,
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// === Geometry-volume preparation (range extraction or finite-grid downsampling) ===
|
|
130
|
+
// The range fully determines the prepared grid for a given volume: it encodes
|
|
131
|
+
// tiling and halo for periodic volumes and is null/explicit-crop for finite
|
|
132
|
+
// ones (which ignore tiling and halo entirely).
|
|
133
|
+
type PreparedGrid = {
|
|
134
|
+
key: string
|
|
135
|
+
grid: number[][][]
|
|
136
|
+
lattice: Matrix3x3
|
|
137
|
+
// Cartesian shift applied to marching-cubes vertices: range offset plus this
|
|
138
|
+
// volume's origin delta relative to the scene reference (first volume's origin)
|
|
139
|
+
vertex_shift: Vec3
|
|
140
|
+
}
|
|
141
|
+
// Keyed by volume object so removed/replaced volumes release their prepared
|
|
142
|
+
// grids to GC automatically and index shifts never serve stale grids
|
|
143
|
+
const prepared_cache = new WeakMap<VolumetricData, PreparedGrid>()
|
|
144
|
+
|
|
145
|
+
const range_key = (vol: VolumetricData): string =>
|
|
146
|
+
effective_range(vol)?.flat().join(`,`) ?? ``
|
|
147
|
+
|
|
148
|
+
const prepared_key = (vol: VolumetricData): string =>
|
|
149
|
+
`${range_key(vol)}|${reference_origin_key}`
|
|
150
|
+
const vertex_shift = (origin: Vec3): Vec3 =>
|
|
151
|
+
origin.map((value, axis) => value - reference_origin[axis]) as Vec3
|
|
152
|
+
|
|
153
|
+
const current_prepared = (vol: VolumetricData): PreparedGrid | undefined => {
|
|
154
|
+
const cached = prepared_cache.get(vol)
|
|
155
|
+
return cached?.key === prepared_key(vol) ? cached : undefined
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function prepare_geometry_volume(vol: VolumetricData): PreparedGrid {
|
|
159
|
+
const cached = current_prepared(vol)
|
|
160
|
+
if (cached) {
|
|
161
|
+
record_profile(profiler, `prepare_geometry`, 0, { cache_hit: true })
|
|
162
|
+
return cached
|
|
57
163
|
}
|
|
164
|
+
const profile_start = profiler ? performance.now() : 0
|
|
58
165
|
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
166
|
+
const range = effective_range(vol)
|
|
167
|
+
const { grid, lattice, origin } = prepare_geometry_grid(vol, range)
|
|
168
|
+
|
|
169
|
+
const prepared: PreparedGrid = {
|
|
170
|
+
key: prepared_key(vol),
|
|
171
|
+
grid,
|
|
172
|
+
lattice,
|
|
173
|
+
vertex_shift: vertex_shift(origin),
|
|
66
174
|
}
|
|
175
|
+
prepared_cache.set(vol, prepared)
|
|
176
|
+
record_profile(profiler, `prepare_geometry`, performance.now() - profile_start, {
|
|
177
|
+
output_points: grid_point_count(grid),
|
|
178
|
+
extracted_range: range !== null,
|
|
179
|
+
})
|
|
180
|
+
return prepared
|
|
181
|
+
}
|
|
67
182
|
|
|
183
|
+
// Build indexed BufferGeometry from marching cubes output
|
|
184
|
+
function build_geometry(
|
|
185
|
+
positions: Float32Array,
|
|
186
|
+
indices: Uint32Array,
|
|
187
|
+
): BufferGeometry | null {
|
|
188
|
+
if (positions.length === 0 || indices.length === 0) return null
|
|
68
189
|
const geometry = new BufferGeometry()
|
|
69
190
|
geometry.setAttribute(`position`, new BufferAttribute(positions, 3))
|
|
70
191
|
geometry.setIndex(new Uint32BufferAttribute(indices, 1))
|
|
@@ -72,214 +193,578 @@
|
|
|
72
193
|
geometry.computeBoundingSphere()
|
|
73
194
|
return geometry
|
|
74
195
|
}
|
|
196
|
+
const geometry_buffer_bytes = (geometry: BufferGeometry): number =>
|
|
197
|
+
[`position`, `normal`, `color`].reduce(
|
|
198
|
+
(total, name) => total + (geometry.getAttribute(name)?.array.byteLength ?? 0),
|
|
199
|
+
geometry.getIndex()?.array.byteLength ?? 0,
|
|
200
|
+
)
|
|
75
201
|
|
|
76
|
-
|
|
77
|
-
let ds_result = $derived.by(() => {
|
|
78
|
-
if (!volume) return undefined
|
|
79
|
-
return downsample_grid(volume.grid, volume.grid_dims)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
// Run marching cubes at the given isovalue with pre-prepared grid/lattice/shift.
|
|
83
|
-
function extract_surface(
|
|
84
|
-
isovalue: number,
|
|
85
|
-
mc_grid: number[][][],
|
|
86
|
-
mc_lattice: Matrix3x3,
|
|
87
|
-
origin_shift: Vec3 | null,
|
|
88
|
-
): BufferGeometry | null {
|
|
202
|
+
function extract_surface(isovalue: number, prepared: PreparedGrid): BufferGeometry | null {
|
|
89
203
|
if (isovalue === 0) return null
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return build_geometry(result.vertices, result.faces)
|
|
204
|
+
const result = profile_stage(profiler, `marching_cubes`, () =>
|
|
205
|
+
marching_cubes_buffers(prepared.grid, isovalue, prepared.lattice, {
|
|
206
|
+
periodic: false,
|
|
207
|
+
interpolate: true,
|
|
208
|
+
centered: false,
|
|
209
|
+
normals: false,
|
|
210
|
+
position_offset: prepared.vertex_shift,
|
|
211
|
+
}),
|
|
212
|
+
)
|
|
213
|
+
return profile_stage(
|
|
214
|
+
profiler,
|
|
215
|
+
`build_geometry`,
|
|
216
|
+
() => build_geometry(result.positions, result.indices),
|
|
217
|
+
(geometry) => ({ buffer_bytes: geometry ? geometry_buffer_bytes(geometry) : 0 }),
|
|
218
|
+
)
|
|
107
219
|
}
|
|
108
220
|
|
|
109
|
-
// ===
|
|
110
|
-
// Each layer produces up to 2
|
|
111
|
-
|
|
112
|
-
|
|
221
|
+
// === Mesh entry management ===
|
|
222
|
+
// Each visible layer produces up to 2 entries (positive + optional negative lobe).
|
|
223
|
+
type MeshEntry = {
|
|
224
|
+
key: string // unique per (layer, sign) — template/each + colored_keys key
|
|
225
|
+
geo_key: string // geometry signature — cache/reuse key
|
|
226
|
+
layer_idx: number
|
|
227
|
+
sign: 1 | -1
|
|
113
228
|
geometry: BufferGeometry
|
|
114
|
-
color: string
|
|
115
|
-
opacity: number
|
|
116
229
|
render_order: number
|
|
230
|
+
// Cached per-vertex scalars from the color-source volume, so colormap and
|
|
231
|
+
// color-range changes only remap through the LUT without resampling
|
|
232
|
+
scalars: Float32Array | null
|
|
233
|
+
scalars_volume_id: number | null
|
|
117
234
|
}
|
|
118
|
-
|
|
235
|
+
// $state.raw: entries hold Three.js objects that must not be proxied; the array
|
|
236
|
+
// is replaced wholesale on rebuild so the template stays reactive.
|
|
237
|
+
let active_entries = $state.raw<MeshEntry[]>([])
|
|
238
|
+
// keys of entries that currently carry a vertex-color attribute (drives
|
|
239
|
+
// vertexColors on materials reactively without proxying entries)
|
|
240
|
+
const colored_keys = new SvelteSet<string>()
|
|
119
241
|
let raf_id = 0
|
|
120
242
|
let debounce_id = 0
|
|
243
|
+
let rebuild_generation = 0
|
|
244
|
+
let geometry_worker: Worker | undefined
|
|
245
|
+
let reject_worker_job: ((error: Error) => void) | undefined
|
|
246
|
+
|
|
247
|
+
function cancel_geometry_worker(): void {
|
|
248
|
+
reject_worker_job?.(new Error(`Isosurface geometry job superseded`))
|
|
249
|
+
reject_worker_job = undefined
|
|
250
|
+
geometry_worker?.terminate()
|
|
251
|
+
geometry_worker = undefined
|
|
252
|
+
}
|
|
121
253
|
|
|
122
|
-
// Dispose all current geometries
|
|
123
254
|
function dispose_all() {
|
|
124
|
-
for (const entry of
|
|
125
|
-
|
|
255
|
+
for (const entry of active_entries) entry.geometry.dispose()
|
|
256
|
+
active_entries = []
|
|
257
|
+
colored_keys.clear()
|
|
126
258
|
}
|
|
127
259
|
|
|
128
|
-
// Dispose on unmount
|
|
129
|
-
$effect(() => () =>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
origin_shift = [
|
|
161
|
-
ox * la[0] + oy * lb[0] + oz * lc[0],
|
|
162
|
-
ox * la[1] + oy * lb[1] + oz * lc[1],
|
|
163
|
-
ox * la[2] + oy * lb[2] + oz * lc[2],
|
|
164
|
-
]
|
|
165
|
-
}
|
|
260
|
+
// Dispose geometries and cancel pending rebuilds on unmount
|
|
261
|
+
$effect(() => () => {
|
|
262
|
+
rebuild_generation++
|
|
263
|
+
clearTimeout(debounce_id)
|
|
264
|
+
cancelAnimationFrame(raf_id)
|
|
265
|
+
cancel_geometry_worker()
|
|
266
|
+
dispose_all()
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
// range_key covers halo + tiling (encoded in the range for periodic volumes;
|
|
270
|
+
// irrelevant for finite ones), so the geometry identity needs no other inputs
|
|
271
|
+
const geometry_key = (layer: ResolvedLayer, sign: 1 | -1): string => {
|
|
272
|
+
const vol = all_volumes[layer.volume_idx]
|
|
273
|
+
return JSON.stringify([
|
|
274
|
+
layer.volume_idx,
|
|
275
|
+
vol ? vol_id(vol) : 0,
|
|
276
|
+
sign * layer.isovalue,
|
|
277
|
+
vol ? range_key(vol) : ``,
|
|
278
|
+
reference_origin_key,
|
|
279
|
+
])
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
type PendingSurface = {
|
|
283
|
+
key: string
|
|
284
|
+
geo_key: string
|
|
285
|
+
layer_idx: number
|
|
286
|
+
sign: 1 | -1
|
|
287
|
+
isovalue: number
|
|
288
|
+
volume: VolumetricData
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
type EntryPlan = MeshEntry | PendingSurface
|
|
166
292
|
|
|
167
|
-
|
|
168
|
-
|
|
293
|
+
const estimated_prepared_points = (vol: VolumetricData): number => {
|
|
294
|
+
const range = effective_range(vol)
|
|
295
|
+
if (!range) return Math.min(grid_point_count(vol.grid), MAX_GRID_POINTS)
|
|
296
|
+
const estimate = range.reduce((total, [lower, upper], axis) => {
|
|
297
|
+
const intervals = vol.periodic
|
|
298
|
+
? vol.grid_dims[axis]
|
|
299
|
+
: Math.max(vol.grid_dims[axis] - 1, 1)
|
|
300
|
+
return total * Math.max(2, Math.round((upper - lower) * intervals) + 1)
|
|
301
|
+
}, 1)
|
|
302
|
+
return Math.min(estimate, MAX_GRID_POINTS)
|
|
303
|
+
}
|
|
169
304
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
.
|
|
176
|
-
.
|
|
305
|
+
const use_geometry_worker = (pending: PendingSurface[]): boolean =>
|
|
306
|
+
typeof window !== `undefined` &&
|
|
307
|
+
typeof Worker !== `undefined` &&
|
|
308
|
+
pending.some(
|
|
309
|
+
(surface) =>
|
|
310
|
+
!current_prepared(surface.volume) &&
|
|
311
|
+
estimated_prepared_points(surface.volume) >= 200_000,
|
|
177
312
|
)
|
|
178
313
|
|
|
314
|
+
function request_worker_geometries(
|
|
315
|
+
pending: PendingSurface[],
|
|
316
|
+
): Promise<GeometryWorkerResponse> {
|
|
317
|
+
const grouped = new Map<VolumetricData, PendingSurface[]>()
|
|
318
|
+
for (const surface of pending) {
|
|
319
|
+
const surfaces = grouped.get(surface.volume) ?? []
|
|
320
|
+
surfaces.push(surface)
|
|
321
|
+
grouped.set(surface.volume, surfaces)
|
|
322
|
+
}
|
|
323
|
+
const transfer: Transferable[] = []
|
|
324
|
+
const request: GeometryWorkerRequest = {
|
|
325
|
+
volumes: [...grouped].map(([vol, surfaces]) => {
|
|
326
|
+
const serialize_start = profiler ? performance.now() : 0
|
|
327
|
+
const transferred_volume: TransferableVolume = {
|
|
328
|
+
grid_values: flatten_grid(vol.grid).values,
|
|
329
|
+
grid_dims: [...vol.grid_dims],
|
|
330
|
+
lattice: vol.lattice.map((row) => [...row]) as Matrix3x3,
|
|
331
|
+
origin: [...vol.origin],
|
|
332
|
+
periodic: vol.periodic,
|
|
333
|
+
}
|
|
334
|
+
transfer.push(transferred_volume.grid_values.buffer)
|
|
335
|
+
record_profile(profiler, `prepare_geometry`, performance.now() - serialize_start, {
|
|
336
|
+
worker: true,
|
|
337
|
+
serialize: true,
|
|
338
|
+
source_points: transferred_volume.grid_values.length,
|
|
339
|
+
})
|
|
340
|
+
return {
|
|
341
|
+
token: vol_id(vol),
|
|
342
|
+
volume: transferred_volume,
|
|
343
|
+
range: effective_range(vol),
|
|
344
|
+
reference_origin: [...reference_origin],
|
|
345
|
+
surfaces: surfaces.map((surface) => ({
|
|
346
|
+
token: surface.key,
|
|
347
|
+
isovalue: surface.isovalue,
|
|
348
|
+
})),
|
|
349
|
+
}
|
|
350
|
+
}),
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return new Promise((resolve, reject) => {
|
|
354
|
+
const worker = new Worker(new URL(`geometry.worker`, import.meta.url), {
|
|
355
|
+
type: `module`,
|
|
356
|
+
})
|
|
357
|
+
geometry_worker = worker
|
|
358
|
+
reject_worker_job = reject
|
|
359
|
+
const cleanup = (): void => {
|
|
360
|
+
worker.terminate()
|
|
361
|
+
if (geometry_worker === worker) geometry_worker = undefined
|
|
362
|
+
reject_worker_job = undefined
|
|
363
|
+
}
|
|
364
|
+
worker.addEventListener(`message`, ({ data }: MessageEvent<GeometryWorkerResponse>) => {
|
|
365
|
+
cleanup()
|
|
366
|
+
resolve(data)
|
|
367
|
+
})
|
|
368
|
+
worker.addEventListener(`error`, (event) => {
|
|
369
|
+
cleanup()
|
|
370
|
+
reject(new Error(event.message || `Isosurface geometry worker failed`))
|
|
371
|
+
})
|
|
372
|
+
try {
|
|
373
|
+
worker.postMessage(request, transfer)
|
|
374
|
+
} catch (error) {
|
|
375
|
+
cleanup()
|
|
376
|
+
reject(error instanceof Error ? error : new Error(String(error)))
|
|
377
|
+
}
|
|
378
|
+
})
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function rebuild_geometries(
|
|
382
|
+
layers: ResolvedLayer[],
|
|
383
|
+
generation: number,
|
|
384
|
+
): Promise<void> {
|
|
385
|
+
const profile_start = profiler ? performance.now() : 0
|
|
386
|
+
let cache_hits = 0
|
|
387
|
+
// Reuse geometries whose signature is unchanged (take-once so duplicate
|
|
388
|
+
// layers at the same isovalue never share a geometry instance)
|
|
389
|
+
const reusable = new Map(active_entries.map((entry) => [entry.geo_key, entry]))
|
|
390
|
+
const plans: EntryPlan[] = []
|
|
391
|
+
const pending: PendingSurface[] = []
|
|
392
|
+
|
|
179
393
|
for (let layer_idx = 0; layer_idx < layers.length; layer_idx++) {
|
|
180
394
|
const layer = layers[layer_idx]
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
395
|
+
const vol = all_volumes[layer.volume_idx]
|
|
396
|
+
if (!vol || !layer.visible || layer.isovalue <= 0) continue
|
|
397
|
+
|
|
398
|
+
const signs: (1 | -1)[] = layer.show_negative ? [1, -1] : [1]
|
|
399
|
+
for (const sign of signs) {
|
|
400
|
+
const key = `${layer_idx}:${sign}`
|
|
401
|
+
const geo_key = geometry_key(layer, sign)
|
|
402
|
+
const reused = reusable.get(geo_key)
|
|
403
|
+
if (reused) {
|
|
404
|
+
cache_hits++
|
|
405
|
+
reusable.delete(geo_key)
|
|
406
|
+
// Keep only the cached geometry + scalars; key/layer_idx/sign are
|
|
407
|
+
// rebound to the CURRENT loop values (they override the spread)
|
|
408
|
+
plans.push({ ...reused, key, layer_idx, sign })
|
|
409
|
+
continue
|
|
410
|
+
}
|
|
411
|
+
const surface: PendingSurface = {
|
|
412
|
+
key,
|
|
413
|
+
geo_key,
|
|
414
|
+
layer_idx,
|
|
415
|
+
sign,
|
|
416
|
+
isovalue: sign * layer.isovalue,
|
|
417
|
+
volume: vol,
|
|
418
|
+
}
|
|
419
|
+
pending.push(surface)
|
|
420
|
+
plans.push(surface)
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const geometries = new Map<string, BufferGeometry>()
|
|
425
|
+
let build_synchronously = !use_geometry_worker(pending)
|
|
426
|
+
if (!build_synchronously) {
|
|
427
|
+
try {
|
|
428
|
+
const response = await request_worker_geometries(pending)
|
|
429
|
+
if (generation !== rebuild_generation || `error` in response) {
|
|
430
|
+
if (`error` in response && generation === rebuild_generation) {
|
|
431
|
+
throw new Error(response.error)
|
|
432
|
+
}
|
|
433
|
+
return
|
|
434
|
+
}
|
|
435
|
+
const volume_by_token = new Map(
|
|
436
|
+
pending.map(({ volume: source }) => [vol_id(source), source]),
|
|
437
|
+
)
|
|
438
|
+
for (const volume_result of response.volumes) {
|
|
439
|
+
const source_volume = volume_by_token.get(volume_result.token)
|
|
440
|
+
if (source_volume) {
|
|
441
|
+
const grid = profile_stage(
|
|
442
|
+
profiler,
|
|
443
|
+
`prepare_geometry`,
|
|
444
|
+
() => inflate_grid(volume_result.prepared_values, volume_result.grid_dims),
|
|
445
|
+
{
|
|
446
|
+
worker: true,
|
|
447
|
+
inflate: true,
|
|
448
|
+
},
|
|
449
|
+
)
|
|
450
|
+
prepared_cache.set(source_volume, {
|
|
451
|
+
key: prepared_key(source_volume),
|
|
452
|
+
grid,
|
|
453
|
+
lattice: volume_result.lattice,
|
|
454
|
+
vertex_shift: vertex_shift(volume_result.origin),
|
|
455
|
+
})
|
|
456
|
+
}
|
|
457
|
+
record_profile(profiler, `prepare_geometry`, volume_result.prepare_geometry_ms, {
|
|
458
|
+
worker: true,
|
|
459
|
+
})
|
|
460
|
+
for (const surface_result of volume_result.surfaces) {
|
|
461
|
+
record_profile(profiler, `marching_cubes`, surface_result.marching_cubes_ms, {
|
|
462
|
+
worker: true,
|
|
463
|
+
vertices: surface_result.positions.length / 3,
|
|
464
|
+
})
|
|
465
|
+
const geometry = profile_stage(
|
|
466
|
+
profiler,
|
|
467
|
+
`build_geometry`,
|
|
468
|
+
() => build_geometry(surface_result.positions, surface_result.indices),
|
|
469
|
+
(built_geometry) => ({
|
|
470
|
+
worker: true,
|
|
471
|
+
buffer_bytes: built_geometry ? geometry_buffer_bytes(built_geometry) : 0,
|
|
472
|
+
}),
|
|
473
|
+
)
|
|
474
|
+
if (geometry) geometries.set(surface_result.token, geometry)
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
} catch (error) {
|
|
478
|
+
if (generation !== rebuild_generation) return
|
|
479
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
480
|
+
record_profile(profiler, `prepare_geometry`, 0, {
|
|
481
|
+
worker_fallback: true,
|
|
482
|
+
error: message,
|
|
193
483
|
})
|
|
484
|
+
console.warn(`Isosurface geometry worker failed; using synchronous fallback`, error)
|
|
485
|
+
build_synchronously = true
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (build_synchronously) {
|
|
489
|
+
for (const surface of pending) {
|
|
490
|
+
const geometry = extract_surface(
|
|
491
|
+
surface.isovalue,
|
|
492
|
+
prepare_geometry_volume(surface.volume),
|
|
493
|
+
)
|
|
494
|
+
if (geometry) geometries.set(surface.key, geometry)
|
|
194
495
|
}
|
|
496
|
+
}
|
|
195
497
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
498
|
+
if (generation !== rebuild_generation) {
|
|
499
|
+
for (const geometry of geometries.values()) geometry.dispose()
|
|
500
|
+
return
|
|
501
|
+
}
|
|
502
|
+
const entries: MeshEntry[] = []
|
|
503
|
+
for (const plan of plans) {
|
|
504
|
+
if (`geometry` in plan) entries.push(plan)
|
|
505
|
+
else {
|
|
506
|
+
const geometry = geometries.get(plan.key)
|
|
507
|
+
if (geometry) {
|
|
508
|
+
const { key, geo_key, layer_idx, sign } = plan
|
|
199
509
|
entries.push({
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
510
|
+
key,
|
|
511
|
+
geo_key,
|
|
512
|
+
layer_idx,
|
|
513
|
+
sign,
|
|
514
|
+
geometry,
|
|
515
|
+
render_order: 0,
|
|
516
|
+
scalars: null,
|
|
517
|
+
scalars_volume_id: null,
|
|
204
518
|
})
|
|
205
519
|
}
|
|
206
520
|
}
|
|
207
521
|
}
|
|
208
522
|
|
|
209
|
-
|
|
210
|
-
|
|
523
|
+
// Render outer shells first so per-entry back/front passes interleave
|
|
524
|
+
// roughly back-to-front. Isovalues from different volumes aren't directly
|
|
525
|
+
// comparable, so rank by isovalue as a fraction of each volume's abs_max.
|
|
526
|
+
const shell_fraction = (entry: MeshEntry): number => {
|
|
527
|
+
const layer = layers[entry.layer_idx]
|
|
528
|
+
const abs_max = all_volumes[layer.volume_idx]?.data_range.abs_max ?? 1
|
|
529
|
+
return layer.isovalue / Math.max(abs_max, 1e-30)
|
|
530
|
+
}
|
|
531
|
+
entries
|
|
532
|
+
.sort((entry_a, entry_b) => shell_fraction(entry_a) - shell_fraction(entry_b))
|
|
533
|
+
.forEach((entry, rank) => (entry.render_order = rank * 2))
|
|
534
|
+
|
|
535
|
+
// Dispose old geometries that were not reused, then swap in the new list
|
|
536
|
+
const kept = new Set(entries.map((entry) => entry.geometry))
|
|
537
|
+
for (const entry of active_entries) {
|
|
538
|
+
if (!kept.has(entry.geometry)) entry.geometry.dispose()
|
|
539
|
+
}
|
|
540
|
+
// Sync vertex-color flags: reused geometries keep their color attribute;
|
|
541
|
+
// new entries get colored by the color effect after this rebuild
|
|
542
|
+
colored_keys.clear()
|
|
543
|
+
for (const entry of entries) {
|
|
544
|
+
if (entry.geometry.getAttribute(`color`)) colored_keys.add(entry.key)
|
|
545
|
+
}
|
|
546
|
+
active_entries = entries
|
|
547
|
+
let [vertices, triangles, buffer_bytes] = [0, 0, 0]
|
|
548
|
+
for (const entry of entries) {
|
|
549
|
+
vertices += entry.geometry.getAttribute(`position`).count
|
|
550
|
+
triangles += (entry.geometry.getIndex()?.count ?? 0) / 3
|
|
551
|
+
buffer_bytes += geometry_buffer_bytes(entry.geometry)
|
|
552
|
+
}
|
|
553
|
+
record_profile(profiler, `rebuild_total`, performance.now() - profile_start, {
|
|
554
|
+
entries: entries.length,
|
|
555
|
+
cache_hits,
|
|
556
|
+
vertices,
|
|
557
|
+
triangles,
|
|
558
|
+
buffer_bytes,
|
|
559
|
+
})
|
|
211
560
|
}
|
|
212
561
|
|
|
213
|
-
//
|
|
214
|
-
//
|
|
562
|
+
// Geometry-relevant inputs as a string: $derived strings only notify
|
|
563
|
+
// dependents when the value changes, so the rebuild effect below skips
|
|
564
|
+
// color-only updates (colors, opacity, colormap, range are excluded here)
|
|
565
|
+
// without any manual last-signature bookkeeping.
|
|
566
|
+
let geo_sig = $derived(
|
|
567
|
+
resolved_layers
|
|
568
|
+
.map((layer) => {
|
|
569
|
+
const vol = all_volumes[layer.volume_idx]
|
|
570
|
+
if (!vol || !layer.visible || layer.isovalue <= 0) return `off`
|
|
571
|
+
return `${geometry_key(layer, 1)}.${layer.show_negative}`
|
|
572
|
+
})
|
|
573
|
+
.join(`|`),
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
// Rebuild layer geometries when the geometry signature changes (debounced for
|
|
577
|
+
// slider drags). The rAF callback reads resolved_layers fresh, so a rebuild
|
|
578
|
+
// scheduled by an earlier signature still sees the latest layer props.
|
|
215
579
|
$effect(() => {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
580
|
+
void geo_sig
|
|
581
|
+
const generation = ++rebuild_generation
|
|
582
|
+
clearTimeout(debounce_id)
|
|
583
|
+
cancelAnimationFrame(raf_id)
|
|
584
|
+
cancel_geometry_worker()
|
|
585
|
+
if (untrack(() => all_volumes.length) === 0) {
|
|
586
|
+
untrack(() => dispose_all())
|
|
220
587
|
return
|
|
221
588
|
}
|
|
222
589
|
debounce_id = window.setTimeout(() => {
|
|
223
|
-
|
|
590
|
+
// No reactive context inside rAF: reads the freshest layers untracked
|
|
591
|
+
raf_id = requestAnimationFrame(() => {
|
|
592
|
+
void rebuild_geometries(resolved_layers, generation)
|
|
593
|
+
})
|
|
224
594
|
}, 50)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
// === Cross-volume vertex coloring ===
|
|
598
|
+
|
|
599
|
+
// Sample the color volume at this entry's vertices. Vertices are in the scene
|
|
600
|
+
// frame (first volume's origin at the grid corner), so shift back to absolute
|
|
601
|
+
// Cartesian coordinates before sampling. The bulk kernel caches each volume's
|
|
602
|
+
// inverse lattice and avoids per-vertex closures or temporary position arrays.
|
|
603
|
+
function sample_entry_scalars(entry: MeshEntry, color_vol: VolumetricData): Float32Array {
|
|
604
|
+
return profile_stage(profiler, `sample_scalars`, () => {
|
|
605
|
+
const positions = entry.geometry.getAttribute(`position`).array as Float32Array
|
|
606
|
+
return sample_volume_at_positions(color_vol, positions, {
|
|
607
|
+
out_of_bounds: color_vol.periodic ? `clamp` : `fallback`,
|
|
608
|
+
position_offset: reference_origin,
|
|
609
|
+
out: entry.scalars ?? undefined,
|
|
610
|
+
})
|
|
611
|
+
})
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// The layer's scalar-color-source volume, if any
|
|
615
|
+
const color_vol_of = (layer: IsosurfaceLayer | undefined): VolumetricData | undefined =>
|
|
616
|
+
layer?.color_volume_idx != null ? all_volumes[layer.color_volume_idx] : undefined
|
|
617
|
+
|
|
618
|
+
function apply_vertex_colors(entries: MeshEntry[], layers: ResolvedLayer[]) {
|
|
619
|
+
const profile_start = profiler ? performance.now() : 0
|
|
620
|
+
const auto_scalars = new Map<number, Float32Array[]>()
|
|
621
|
+
// First pass: (re)sample scalars for entries with a color source
|
|
622
|
+
for (const entry of entries) {
|
|
623
|
+
const layer = layers[entry.layer_idx]
|
|
624
|
+
const color_vol = color_vol_of(layer)
|
|
625
|
+
if (!layer || !color_vol) {
|
|
626
|
+
if (entry.geometry.getAttribute(`color`)) entry.geometry.deleteAttribute(`color`)
|
|
627
|
+
entry.scalars = null
|
|
628
|
+
entry.scalars_volume_id = null
|
|
629
|
+
colored_keys.delete(entry.key)
|
|
630
|
+
continue
|
|
631
|
+
}
|
|
632
|
+
const color_vol_id = vol_id(color_vol)
|
|
633
|
+
if (!entry.scalars || entry.scalars_volume_id !== color_vol_id) {
|
|
634
|
+
entry.scalars = sample_entry_scalars(entry, color_vol)
|
|
635
|
+
entry.scalars_volume_id = color_vol_id
|
|
636
|
+
} else {
|
|
637
|
+
record_profile(profiler, `sample_scalars`, 0, {
|
|
638
|
+
cache_hit: true,
|
|
639
|
+
vertices: entry.scalars.length,
|
|
640
|
+
})
|
|
641
|
+
}
|
|
642
|
+
if (!layer.color_range) {
|
|
643
|
+
const scalars = auto_scalars.get(entry.layer_idx) ?? []
|
|
644
|
+
scalars.push(entry.scalars)
|
|
645
|
+
auto_scalars.set(entry.layer_idx, scalars)
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Default color range per layer: fit to the scalars actually present on that
|
|
650
|
+
// layer's surfaces (both lobes) — whole-volume ranges are dominated by
|
|
651
|
+
// extremes near nuclei that the surface never touches. Signed source fields
|
|
652
|
+
// keep a symmetric range so diverging colormaps stay centered on zero.
|
|
653
|
+
const auto_ranges = new Map<number, [number, number]>()
|
|
654
|
+
for (const [layer_idx, layer_scalars] of auto_scalars) {
|
|
655
|
+
const layer = layers[layer_idx]
|
|
656
|
+
const color_vol = color_vol_of(layer)
|
|
657
|
+
auto_ranges.set(
|
|
658
|
+
layer_idx,
|
|
659
|
+
profile_stage(profiler, `scalar_range`, () =>
|
|
660
|
+
compute_scalar_range(layer_scalars, {
|
|
661
|
+
symmetric: color_vol ? is_signed_range(color_vol.data_range) : false,
|
|
662
|
+
}),
|
|
663
|
+
),
|
|
664
|
+
)
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Second pass: map scalars through the colormap LUT, reusing the existing
|
|
668
|
+
// color attribute's array where possible to avoid GPU buffer churn
|
|
669
|
+
for (const entry of entries) {
|
|
670
|
+
const layer = layers[entry.layer_idx]
|
|
671
|
+
if (!layer || !entry.scalars) continue
|
|
672
|
+
const color_range = layer.color_range ?? auto_ranges.get(entry.layer_idx) ?? [0, 1]
|
|
673
|
+
const existing = entry.geometry.getAttribute(`color`) as BufferAttribute | undefined
|
|
674
|
+
const colors = profile_stage(profiler, `apply_colormap`, () =>
|
|
675
|
+
scalars_to_vertex_colors(
|
|
676
|
+
entry.scalars as Float32Array,
|
|
677
|
+
{
|
|
678
|
+
colormap: layer.colormap ?? DEFAULT_ISO_COLORMAP,
|
|
679
|
+
color_range,
|
|
680
|
+
fallback_color: entry.sign > 0 ? layer.color : layer.negative_color,
|
|
681
|
+
},
|
|
682
|
+
existing?.array instanceof Float32Array ? existing.array : undefined,
|
|
683
|
+
),
|
|
684
|
+
)
|
|
685
|
+
if (existing && existing.array === colors) existing.needsUpdate = true
|
|
686
|
+
else entry.geometry.setAttribute(`color`, new BufferAttribute(colors, 3))
|
|
687
|
+
colored_keys.add(entry.key)
|
|
228
688
|
}
|
|
689
|
+
record_profile(profiler, `recolor_total`, performance.now() - profile_start, {
|
|
690
|
+
entries: entries.length,
|
|
691
|
+
})
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Color-relevant layer props as a string, including the color-source volume
|
|
695
|
+
// identity (so replacing a color volume in place resamples) but excluding
|
|
696
|
+
// geometry fields like isovalue and opacity, so e.g. isovalue slider drags
|
|
697
|
+
// don't remap colors on every tick.
|
|
698
|
+
let color_sig = $derived(
|
|
699
|
+
resolved_layers
|
|
700
|
+
.map((layer) => {
|
|
701
|
+
const color_vol = color_vol_of(layer)
|
|
702
|
+
return [
|
|
703
|
+
layer.color_volume_idx ?? ``,
|
|
704
|
+
color_vol ? vol_id(color_vol) : ``,
|
|
705
|
+
layer.colormap ?? ``,
|
|
706
|
+
layer.color_range?.join(`,`) ?? ``,
|
|
707
|
+
layer.color,
|
|
708
|
+
layer.negative_color,
|
|
709
|
+
].join(`.`)
|
|
710
|
+
})
|
|
711
|
+
.join(`|`),
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
// Apply/refresh vertex colors when entries are rebuilt or color props change
|
|
715
|
+
$effect(() => {
|
|
716
|
+
void color_sig
|
|
717
|
+
const entries = active_entries
|
|
718
|
+
untrack(() => apply_vertex_colors(entries, resolved_layers))
|
|
229
719
|
})
|
|
230
720
|
</script>
|
|
231
721
|
|
|
232
|
-
<!-- Render each
|
|
233
|
-
{#each
|
|
234
|
-
{
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
<!--
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
side={DoubleSide}
|
|
280
|
-
metalness={0.1}
|
|
281
|
-
roughness={0.6}
|
|
282
|
-
/>
|
|
283
|
-
</T.Mesh>
|
|
722
|
+
<!-- Render each mesh entry; material props react to layer changes without geometry rebuilds -->
|
|
723
|
+
{#each active_entries as entry (entry.key)}
|
|
724
|
+
{@const layer = resolved_layers[entry.layer_idx]}
|
|
725
|
+
{#if layer}
|
|
726
|
+
{@const vertex_colored = colored_keys.has(entry.key)}
|
|
727
|
+
{@const color = vertex_colored
|
|
728
|
+
? `#ffffff`
|
|
729
|
+
: entry.sign > 0
|
|
730
|
+
? layer.color
|
|
731
|
+
: layer.negative_color}
|
|
732
|
+
{@const opacity = layer.opacity}
|
|
733
|
+
{@const transparent = opacity < 1}
|
|
734
|
+
<!-- Recreate materials when vertexColors toggles (needs shader recompile) -->
|
|
735
|
+
{#key vertex_colored}
|
|
736
|
+
{#if settings.wireframe}
|
|
737
|
+
<T.Mesh geometry={entry.geometry} frustumCulled={false}>
|
|
738
|
+
<T.MeshBasicMaterial
|
|
739
|
+
{color}
|
|
740
|
+
vertexColors={vertex_colored}
|
|
741
|
+
wireframe
|
|
742
|
+
{transparent}
|
|
743
|
+
{opacity}
|
|
744
|
+
/>
|
|
745
|
+
</T.Mesh>
|
|
746
|
+
{:else}
|
|
747
|
+
<!-- Transparent surfaces render back then front faces in two passes for
|
|
748
|
+
correct blending; opaque surfaces need a single double-sided pass -->
|
|
749
|
+
{#each transparent ? [BackSide, FrontSide] : [DoubleSide] as side, pass_idx (side)}
|
|
750
|
+
<T.Mesh
|
|
751
|
+
geometry={entry.geometry}
|
|
752
|
+
renderOrder={entry.render_order + pass_idx}
|
|
753
|
+
frustumCulled={false}
|
|
754
|
+
>
|
|
755
|
+
<T.MeshStandardMaterial
|
|
756
|
+
{color}
|
|
757
|
+
vertexColors={vertex_colored}
|
|
758
|
+
{transparent}
|
|
759
|
+
{opacity}
|
|
760
|
+
{side}
|
|
761
|
+
depthWrite={!transparent}
|
|
762
|
+
metalness={0.1}
|
|
763
|
+
roughness={0.6}
|
|
764
|
+
/>
|
|
765
|
+
</T.Mesh>
|
|
766
|
+
{/each}
|
|
767
|
+
{/if}
|
|
768
|
+
{/key}
|
|
284
769
|
{/if}
|
|
285
770
|
{/each}
|