matterviz 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/dist/EmptyState.svelte +10 -2
  2. package/dist/FilePicker.svelte +123 -82
  3. package/dist/Icon.svelte +18 -12
  4. package/dist/MillerIndexInput.svelte +27 -21
  5. package/dist/api/optimade.js +6 -6
  6. package/dist/app.css +216 -207
  7. package/dist/brillouin/BrillouinZone.svelte +292 -149
  8. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  9. package/dist/brillouin/BrillouinZoneControls.svelte +32 -5
  10. package/dist/brillouin/BrillouinZoneExportPane.svelte +69 -42
  11. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
  12. package/dist/brillouin/BrillouinZoneInfoPane.svelte +99 -68
  13. package/dist/brillouin/BrillouinZoneScene.svelte +275 -163
  14. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  15. package/dist/brillouin/BrillouinZoneTooltip.svelte +17 -7
  16. package/dist/brillouin/compute.js +11 -6
  17. package/dist/chempot-diagram/ChemPotDiagram.svelte +162 -27
  18. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +451 -281
  19. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2148 -1642
  20. package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -5
  21. package/dist/chempot-diagram/async-compute.svelte.d.ts +3 -0
  22. package/dist/chempot-diagram/async-compute.svelte.js +77 -0
  23. package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
  24. package/dist/chempot-diagram/chempot-worker.js +11 -0
  25. package/dist/chempot-diagram/color.js +1 -2
  26. package/dist/chempot-diagram/compute.d.ts +10 -0
  27. package/dist/chempot-diagram/compute.js +250 -88
  28. package/dist/chempot-diagram/index.d.ts +2 -1
  29. package/dist/chempot-diagram/index.js +2 -1
  30. package/dist/chempot-diagram/temperature.js +8 -9
  31. package/dist/chempot-diagram/types.d.ts +3 -0
  32. package/dist/chempot-diagram/types.js +1 -0
  33. package/dist/colors/index.d.ts +1 -1
  34. package/dist/colors/index.js +5 -3
  35. package/dist/composition/BarChart.svelte +128 -55
  36. package/dist/composition/BubbleChart.svelte +102 -49
  37. package/dist/composition/Composition.svelte +100 -79
  38. package/dist/composition/Formula.svelte +108 -62
  39. package/dist/composition/FormulaFilter.svelte +665 -537
  40. package/dist/composition/PieChart.svelte +183 -108
  41. package/dist/composition/format.d.ts +5 -0
  42. package/dist/composition/format.js +20 -3
  43. package/dist/composition/parse.js +14 -9
  44. package/dist/convex-hull/ConvexHull.svelte +93 -40
  45. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
  46. package/dist/convex-hull/ConvexHull2D.svelte +549 -360
  47. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  48. package/dist/convex-hull/ConvexHull3D.svelte +1296 -827
  49. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  50. package/dist/convex-hull/ConvexHull4D.svelte +1004 -688
  51. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  52. package/dist/convex-hull/ConvexHullControls.svelte +115 -28
  53. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +1 -1
  54. package/dist/convex-hull/ConvexHullInfoPane.svelte +29 -3
  55. package/dist/convex-hull/ConvexHullStats.svelte +425 -328
  56. package/dist/convex-hull/ConvexHullTooltip.svelte +40 -16
  57. package/dist/convex-hull/GasPressureControls.svelte +104 -61
  58. package/dist/convex-hull/StructurePopup.svelte +25 -4
  59. package/dist/convex-hull/TemperatureSlider.svelte +45 -25
  60. package/dist/convex-hull/barycentric-coords.js +13 -7
  61. package/dist/convex-hull/demo-temperature.js +8 -4
  62. package/dist/convex-hull/gas-thermodynamics.js +17 -12
  63. package/dist/convex-hull/helpers.d.ts +9 -0
  64. package/dist/convex-hull/helpers.js +77 -34
  65. package/dist/convex-hull/thermodynamics.js +61 -56
  66. package/dist/convex-hull/types.d.ts +9 -14
  67. package/dist/convex-hull/types.js +0 -17
  68. package/dist/coordination/CoordinationBarPlot.svelte +227 -154
  69. package/dist/element/BohrAtom.svelte +55 -12
  70. package/dist/element/ElementHeading.svelte +7 -2
  71. package/dist/element/ElementPhoto.svelte +15 -9
  72. package/dist/element/ElementStats.svelte +10 -4
  73. package/dist/element/ElementTile.svelte +137 -73
  74. package/dist/element/Nucleus.svelte +39 -11
  75. package/dist/element/data.js +1 -1
  76. package/dist/feedback/ClickFeedback.svelte +16 -5
  77. package/dist/feedback/DragOverlay.svelte +10 -2
  78. package/dist/feedback/Spinner.svelte +4 -2
  79. package/dist/feedback/StatusMessage.svelte +8 -2
  80. package/dist/fermi-surface/FermiSlice.svelte +118 -88
  81. package/dist/fermi-surface/FermiSurface.svelte +328 -187
  82. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  83. package/dist/fermi-surface/FermiSurfaceControls.svelte +113 -46
  84. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  85. package/dist/fermi-surface/FermiSurfaceScene.svelte +535 -342
  86. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  87. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +14 -5
  88. package/dist/fermi-surface/compute.js +16 -20
  89. package/dist/fermi-surface/parse.js +24 -14
  90. package/dist/fermi-surface/symmetry.js +2 -7
  91. package/dist/fermi-surface/types.d.ts +3 -5
  92. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1019 -765
  93. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +1 -1
  94. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +76 -22
  95. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +2 -3
  96. package/dist/icons.js +47 -0
  97. package/dist/index.d.ts +2 -1
  98. package/dist/index.js +2 -1
  99. package/dist/io/decompress.js +1 -1
  100. package/dist/io/export.d.ts +3 -0
  101. package/dist/io/export.js +129 -143
  102. package/dist/io/is-binary.js +2 -3
  103. package/dist/io/url-drop.js +1 -2
  104. package/dist/isosurface/Isosurface.svelte +202 -148
  105. package/dist/isosurface/IsosurfaceControls.svelte +46 -28
  106. package/dist/isosurface/parse.js +34 -29
  107. package/dist/isosurface/slice.js +5 -10
  108. package/dist/isosurface/types.d.ts +2 -1
  109. package/dist/isosurface/types.js +61 -12
  110. package/dist/labels.js +11 -8
  111. package/dist/layout/FullscreenToggle.svelte +11 -2
  112. package/dist/layout/InfoCard.svelte +38 -6
  113. package/dist/layout/InfoTag.svelte +63 -32
  114. package/dist/layout/PropertyFilter.svelte +82 -37
  115. package/dist/layout/SettingsSection.svelte +85 -55
  116. package/dist/layout/SubpageGrid.svelte +10 -2
  117. package/dist/layout/json-tree/JsonNode.svelte +183 -138
  118. package/dist/layout/json-tree/JsonTree.svelte +499 -413
  119. package/dist/layout/json-tree/JsonValue.svelte +127 -99
  120. package/dist/layout/json-tree/utils.js +4 -2
  121. package/dist/marching-cubes.js +25 -2
  122. package/dist/math.d.ts +13 -17
  123. package/dist/math.js +133 -67
  124. package/dist/overlays/ContextMenu.svelte +65 -40
  125. package/dist/overlays/DraggablePane.svelte +211 -139
  126. package/dist/periodic-table/PeriodicTable.svelte +278 -145
  127. package/dist/periodic-table/PeriodicTableControls.svelte +178 -128
  128. package/dist/periodic-table/PropertySelect.svelte +25 -7
  129. package/dist/periodic-table/TableInset.svelte +8 -3
  130. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +446 -309
  131. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  132. package/dist/phase-diagram/PhaseDiagramControls.svelte +102 -43
  133. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  134. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +63 -40
  135. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +71 -28
  136. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +1 -1
  137. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +158 -101
  138. package/dist/phase-diagram/TdbInfoPanel.svelte +28 -4
  139. package/dist/phase-diagram/build-diagram.js +9 -9
  140. package/dist/phase-diagram/colors.js +1 -3
  141. package/dist/phase-diagram/parse.js +10 -9
  142. package/dist/phase-diagram/svg-to-diagram.js +53 -49
  143. package/dist/phase-diagram/utils.d.ts +1 -0
  144. package/dist/phase-diagram/utils.js +80 -25
  145. package/dist/plot/AxisLabel.svelte +28 -3
  146. package/dist/plot/BarPlot.svelte +1182 -734
  147. package/dist/plot/BarPlot.svelte.d.ts +2 -2
  148. package/dist/plot/BarPlotControls.svelte +31 -5
  149. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  150. package/dist/plot/ColorBar.svelte +479 -329
  151. package/dist/plot/ColorScaleSelect.svelte +27 -6
  152. package/dist/plot/ElementScatter.svelte +36 -15
  153. package/dist/plot/FillArea.svelte +152 -95
  154. package/dist/plot/Histogram.svelte +934 -571
  155. package/dist/plot/Histogram.svelte.d.ts +1 -1
  156. package/dist/plot/HistogramControls.svelte +53 -9
  157. package/dist/plot/HistogramControls.svelte.d.ts +1 -1
  158. package/dist/plot/InteractiveAxisLabel.svelte +34 -11
  159. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
  160. package/dist/plot/Line.svelte +63 -28
  161. package/dist/plot/PlotControls.svelte +157 -114
  162. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  163. package/dist/plot/PlotLegend.svelte +174 -91
  164. package/dist/plot/PlotTooltip.svelte +45 -6
  165. package/dist/plot/PortalSelect.svelte +175 -147
  166. package/dist/plot/ReferenceLine.svelte +76 -22
  167. package/dist/plot/ReferenceLine3D.svelte +132 -107
  168. package/dist/plot/ReferencePlane.svelte +146 -121
  169. package/dist/plot/ScatterPlot.svelte +1681 -1091
  170. package/dist/plot/ScatterPlot.svelte.d.ts +2 -2
  171. package/dist/plot/ScatterPlot3D.svelte +256 -131
  172. package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
  173. package/dist/plot/ScatterPlot3DControls.svelte +113 -63
  174. package/dist/plot/ScatterPlot3DControls.svelte.d.ts +2 -1
  175. package/dist/plot/ScatterPlot3DScene.svelte +608 -403
  176. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
  177. package/dist/plot/ScatterPlotControls.svelte +65 -25
  178. package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
  179. package/dist/plot/ScatterPoint.svelte +98 -26
  180. package/dist/plot/ScatterPoint.svelte.d.ts +1 -0
  181. package/dist/plot/SpacegroupBarPlot.svelte +142 -85
  182. package/dist/plot/Surface3D.svelte +159 -108
  183. package/dist/plot/ZeroLines.svelte +55 -3
  184. package/dist/plot/ZoomRect.svelte +4 -2
  185. package/dist/plot/axis-utils.js +1 -3
  186. package/dist/plot/data-cleaning.js +12 -28
  187. package/dist/plot/data-transform.js +2 -1
  188. package/dist/plot/fill-utils.js +2 -0
  189. package/dist/plot/layout.d.ts +4 -1
  190. package/dist/plot/layout.js +33 -14
  191. package/dist/plot/reference-line.d.ts +2 -2
  192. package/dist/plot/reference-line.js +7 -5
  193. package/dist/plot/scales.js +24 -36
  194. package/dist/plot/types.d.ts +11 -23
  195. package/dist/plot/types.js +6 -11
  196. package/dist/plot/utils/label-placement.d.ts +32 -15
  197. package/dist/plot/utils/label-placement.js +227 -66
  198. package/dist/plot/utils/series-visibility.js +2 -3
  199. package/dist/rdf/RdfPlot.svelte +143 -91
  200. package/dist/rdf/calc-rdf.js +4 -5
  201. package/dist/sanitize.d.ts +4 -0
  202. package/dist/sanitize.js +107 -0
  203. package/dist/settings.d.ts +18 -6
  204. package/dist/settings.js +46 -16
  205. package/dist/spectral/Bands.svelte +632 -453
  206. package/dist/spectral/BandsAndDos.svelte +90 -49
  207. package/dist/spectral/BrillouinBandsDos.svelte +151 -93
  208. package/dist/spectral/Dos.svelte +389 -258
  209. package/dist/spectral/helpers.js +55 -43
  210. package/dist/state.svelte.d.ts +1 -1
  211. package/dist/state.svelte.js +3 -2
  212. package/dist/structure/Arrow.svelte +59 -20
  213. package/dist/structure/AtomLegend.svelte +215 -134
  214. package/dist/structure/Bond.svelte +73 -47
  215. package/dist/structure/CanvasTooltip.svelte +10 -2
  216. package/dist/structure/CellSelect.svelte +72 -45
  217. package/dist/structure/Cylinder.svelte +33 -17
  218. package/dist/structure/Lattice.svelte +88 -33
  219. package/dist/structure/Structure.svelte +1063 -797
  220. package/dist/structure/Structure.svelte.d.ts +1 -1
  221. package/dist/structure/StructureControls.svelte +349 -118
  222. package/dist/structure/StructureExportPane.svelte +124 -89
  223. package/dist/structure/StructureExportPane.svelte.d.ts +1 -1
  224. package/dist/structure/StructureInfoPane.svelte +304 -237
  225. package/dist/structure/StructureScene.svelte +879 -443
  226. package/dist/structure/StructureScene.svelte.d.ts +15 -7
  227. package/dist/structure/atom-properties.js +8 -8
  228. package/dist/structure/bonding.js +6 -7
  229. package/dist/structure/export.js +14 -29
  230. package/dist/structure/ferrox-wasm.js +1 -1
  231. package/dist/structure/index.d.ts +13 -3
  232. package/dist/structure/index.js +83 -23
  233. package/dist/structure/measure.d.ts +2 -2
  234. package/dist/structure/measure.js +4 -44
  235. package/dist/structure/parse.js +113 -141
  236. package/dist/structure/partial-occupancy.js +7 -10
  237. package/dist/structure/pbc.d.ts +1 -0
  238. package/dist/structure/pbc.js +16 -6
  239. package/dist/structure/supercell.d.ts +2 -2
  240. package/dist/structure/supercell.js +12 -22
  241. package/dist/structure/validation.js +1 -2
  242. package/dist/symmetry/SymmetryStats.svelte +84 -41
  243. package/dist/symmetry/WyckoffTable.svelte +26 -6
  244. package/dist/symmetry/cell-transform.js +5 -3
  245. package/dist/symmetry/index.js +8 -7
  246. package/dist/symmetry/spacegroups.js +148 -148
  247. package/dist/table/HeatmapTable.svelte +790 -554
  248. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  249. package/dist/table/ToggleMenu.svelte +125 -92
  250. package/dist/table/index.js +2 -4
  251. package/dist/theme/ThemeControl.svelte +21 -12
  252. package/dist/time.js +4 -1
  253. package/dist/tooltip/TooltipContent.svelte +33 -8
  254. package/dist/trajectory/Trajectory.svelte +758 -558
  255. package/dist/trajectory/TrajectoryError.svelte +14 -3
  256. package/dist/trajectory/TrajectoryExportPane.svelte +137 -83
  257. package/dist/trajectory/TrajectoryInfoPane.svelte +272 -143
  258. package/dist/trajectory/extract.js +10 -26
  259. package/dist/trajectory/format-detect.js +5 -5
  260. package/dist/trajectory/frame-reader.d.ts +1 -1
  261. package/dist/trajectory/frame-reader.js +5 -12
  262. package/dist/trajectory/helpers.d.ts +0 -1
  263. package/dist/trajectory/helpers.js +2 -17
  264. package/dist/trajectory/index.js +14 -12
  265. package/dist/trajectory/parse/ase.js +5 -4
  266. package/dist/trajectory/parse/hdf5.js +26 -18
  267. package/dist/trajectory/parse/index.js +13 -18
  268. package/dist/trajectory/parse/lammps.js +17 -7
  269. package/dist/trajectory/parse/vasp.js +5 -2
  270. package/dist/trajectory/parse/xyz.js +8 -7
  271. package/dist/trajectory/plotting.js +13 -8
  272. package/dist/utils.d.ts +1 -0
  273. package/dist/utils.js +13 -0
  274. package/dist/xrd/XrdPlot.svelte +337 -247
  275. package/dist/xrd/broadening.js +14 -9
  276. package/dist/xrd/calc-xrd.js +12 -18
  277. package/dist/xrd/parse.d.ts +1 -1
  278. package/dist/xrd/parse.js +17 -17
  279. package/package.json +99 -103
  280. package/readme.md +1 -1
  281. /package/dist/theme/{themes.js → themes.mjs} +0 -0
@@ -1,268 +1,426 @@
1
- <script lang="ts">import { AXIS_COLORS, NEG_AXIS_COLORS } from '../colors';
2
- import * as math from '../math';
3
- import { DEFAULTS } from '../settings';
4
- import { Arrow, Cylinder } from '../structure';
5
- import { T, useThrelte } from '@threlte/core';
6
- import * as extras from '@threlte/extras';
7
- import * as d3_sc from 'd3-scale-chromatic';
8
- import { SvelteMap } from 'svelte/reactivity';
9
- import { BackSide, BufferAttribute, BufferGeometry, Color, DoubleSide, FrontSide, Matrix4, Plane, Vector3, } from 'three';
10
- import * as constants from './constants';
11
- import { IDENTITY_4x4, OH_SYMMETRY_MATRICES } from './symmetry';
12
- let { fermi_data = $bindable(), bz_data = $bindable(), camera_position = $bindable(), camera_projection = $bindable(`perspective`),
13
- // Fermi surface styling
14
- color_property = `band`, color_scale = `interpolateViridis`, representation = `solid`, surface_opacity = $bindable(0.8), selected_bands,
15
- // BZ styling
16
- show_bz = true, bz_color = `#888888`, bz_opacity = 0.1, bz_edge_color = `#333333`, bz_edge_width = 0.02, show_vectors = true, tile_bz = false,
17
- // Clipping plane
18
- clip_enabled = false, clip_axis = `z`, clip_position = 0, clip_flip = false, vector_scale = 1.0,
19
- // Camera controls
20
- rotation_damping = DEFAULTS.structure.rotation_damping, max_zoom = DEFAULTS.structure.max_zoom, min_zoom = DEFAULTS.structure.min_zoom, rotate_speed = DEFAULTS.structure.rotate_speed, zoom_speed = DEFAULTS.structure.zoom_speed, pan_speed = DEFAULTS.structure.pan_speed, zoom_to_cursor = DEFAULTS.structure.zoom_to_cursor, fov = DEFAULTS.structure.fov, initial_zoom = DEFAULTS.structure.initial_zoom, ambient_light = DEFAULTS.structure.ambient_light, directional_light = DEFAULTS.structure.directional_light, gizmo = DEFAULTS.structure.show_gizmo, auto_rotate = DEFAULTS.structure.auto_rotate, camera_is_moving = $bindable(false), scene = $bindable(), camera = $bindable(), hover_data = $bindable(null), } = $props();
21
- const threlte = useThrelte();
22
- // Compute scene size for clipping (also used for camera positioning later)
23
- function get_scene_size() {
24
- if (!fermi_data?.k_lattice)
25
- return 10;
26
- const mags = fermi_data.k_lattice.map((vec) => Math.hypot(...vec));
27
- return mags.reduce((sum, mag) => sum + mag, 0) / 3;
28
- }
29
- // Compute clipping plane based on axis and position
30
- // Plane equation: dot(normal, point) + constant >= 0 means point is visible
31
- const clip_plane = $derived.by(() => {
32
- if (!clip_enabled)
33
- return null;
34
- const axis_idx = { x: 0, y: 1, z: 2 }[clip_axis];
35
- const normal_arr = [0, 0, 0];
36
- normal_arr[axis_idx] = clip_flip ? -1 : 1;
37
- const scaled_position = clip_position * get_scene_size();
1
+ <script lang="ts">
2
+ import type { BrillouinZoneData } from '../brillouin'
3
+ import { AXIS_COLORS, NEG_AXIS_COLORS } from '../colors'
4
+ import type { Matrix4Tuple, Vec3 } from '../math'
5
+ import * as math from '../math'
6
+ import type { CameraProjection } from '../settings'
7
+ import { DEFAULTS } from '../settings'
8
+ import { Arrow, Cylinder } from '../structure'
9
+ import { T, useThrelte } from '@threlte/core'
10
+ import * as extras from '@threlte/extras'
11
+ import * as d3_sc from 'd3-scale-chromatic'
12
+ import type { ComponentProps } from 'svelte'
13
+ import { SvelteMap } from 'svelte/reactivity'
14
+ import type { Camera, Scene } from 'three'
15
+ import {
16
+ BackSide,
17
+ BufferAttribute,
18
+ BufferGeometry,
19
+ Color,
20
+ DoubleSide,
21
+ FrontSide,
22
+ Matrix4,
23
+ Plane,
24
+ Vector3,
25
+ } from 'three'
26
+ import * as constants from './constants'
27
+ import { IDENTITY_4x4, OH_SYMMETRY_MATRICES } from './symmetry'
28
+ import type {
29
+ ColorProperty,
30
+ FermiHoverData,
31
+ FermiSurfaceData,
32
+ Isosurface,
33
+ RepresentationMode,
34
+ } from './types'
35
+
36
+ // Threlte pointer event type for mesh interactions
37
+ type ThreltePointerEvent = { point: Vector3; nativeEvent: PointerEvent }
38
+
39
+ let {
40
+ fermi_data = $bindable(),
41
+ bz_data = $bindable(),
42
+ camera_position = $bindable(),
43
+ camera_projection = $bindable(`perspective`),
44
+ // Fermi surface styling
45
+ color_property = `band`,
46
+ color_scale = `interpolateViridis`,
47
+ representation = `solid`,
48
+ surface_opacity = $bindable(0.8),
49
+ selected_bands,
50
+ // BZ styling
51
+ show_bz = true,
52
+ bz_color = `#888888`,
53
+ bz_opacity = 0.1,
54
+ bz_edge_color = `#333333`,
55
+ bz_edge_width = 0.02,
56
+ show_vectors = true,
57
+ tile_bz = false,
58
+ // Clipping plane
59
+ clip_enabled = false,
60
+ clip_axis = `z`,
61
+ clip_position = 0,
62
+ clip_flip = false,
63
+ vector_scale = 1.0,
64
+ // Camera controls
65
+ rotation_damping = DEFAULTS.structure.rotation_damping,
66
+ max_zoom = DEFAULTS.structure.max_zoom,
67
+ min_zoom = DEFAULTS.structure.min_zoom,
68
+ rotate_speed = DEFAULTS.structure.rotate_speed,
69
+ zoom_speed = DEFAULTS.structure.zoom_speed,
70
+ pan_speed = DEFAULTS.structure.pan_speed,
71
+ zoom_to_cursor = DEFAULTS.structure.zoom_to_cursor,
72
+ fov = DEFAULTS.structure.fov,
73
+ initial_zoom = DEFAULTS.structure.initial_zoom,
74
+ ambient_light = DEFAULTS.structure.ambient_light,
75
+ directional_light = DEFAULTS.structure.directional_light,
76
+ gizmo = DEFAULTS.structure.show_gizmo,
77
+ auto_rotate = DEFAULTS.structure.auto_rotate,
78
+ camera_is_moving = $bindable(false),
79
+ scene = $bindable(),
80
+ camera = $bindable(),
81
+ hover_data = $bindable<FermiHoverData | null>(null),
82
+ }: {
83
+ fermi_data?: FermiSurfaceData
84
+ bz_data?: BrillouinZoneData
85
+ camera_position?: Vec3 | undefined
86
+ camera_projection?: CameraProjection
87
+ color_property?: ColorProperty
88
+ color_scale?: string
89
+ representation?: RepresentationMode
90
+ surface_opacity?: number
91
+ selected_bands?: number[]
92
+ show_bz?: boolean
93
+ bz_color?: string
94
+ bz_opacity?: number
95
+ bz_edge_color?: string
96
+ bz_edge_width?: number
97
+ show_vectors?: boolean
98
+ tile_bz?: boolean
99
+ clip_enabled?: boolean
100
+ clip_axis?: `x` | `y` | `z`
101
+ clip_position?: number
102
+ clip_flip?: boolean
103
+ vector_scale?: number
104
+ rotation_damping?: number
105
+ max_zoom?: number
106
+ min_zoom?: number
107
+ rotate_speed?: number
108
+ zoom_speed?: number
109
+ pan_speed?: number
110
+ zoom_to_cursor?: boolean
111
+ fov?: number
112
+ initial_zoom?: number
113
+ ambient_light?: number
114
+ directional_light?: number
115
+ gizmo?: boolean | ComponentProps<typeof extras.Gizmo>
116
+ auto_rotate?: number
117
+ camera_is_moving?: boolean
118
+ scene?: Scene
119
+ camera?: Camera
120
+ hover_data?: FermiHoverData | null
121
+ } = $props()
122
+
123
+ const threlte = useThrelte()
124
+
125
+ // Compute scene size for clipping (also used for camera positioning later)
126
+ function get_scene_size(): number {
127
+ if (!fermi_data?.k_lattice) return 10
128
+ const mags = fermi_data.k_lattice.map((vec) => Math.hypot(...vec))
129
+ return mags.reduce((sum, mag) => sum + mag, 0) / 3
130
+ }
131
+
132
+ // Compute clipping plane based on axis and position
133
+ // Plane equation: dot(normal, point) + constant >= 0 means point is visible
134
+ const clip_plane = $derived.by(() => {
135
+ if (!clip_enabled) return null
136
+
137
+ const axis_idx = { x: 0, y: 1, z: 2 }[clip_axis]
138
+ const normal_arr: Vec3 = [0, 0, 0]
139
+ normal_arr[axis_idx] = clip_flip ? -1 : 1
140
+
141
+ const scaled_position = clip_position * get_scene_size()
38
142
  // constant = -position for normal case (keep points >= position)
39
143
  // constant = +position for flipped case (keep points <= position)
40
- const constant = clip_flip ? scaled_position : -scaled_position;
41
- return new Plane(new Vector3(...normal_arr), constant);
42
- });
43
- // Apply clipping plane to renderer
44
- $effect(() => {
45
- if (!threlte.renderer)
46
- return;
144
+ const constant = clip_flip ? scaled_position : -scaled_position
145
+
146
+ return new Plane(new Vector3(...normal_arr), constant)
147
+ })
148
+
149
+ // Apply clipping plane to renderer
150
+ $effect(() => {
151
+ if (!threlte.renderer) return
152
+
47
153
  if (clip_plane) {
48
- threlte.renderer.clippingPlanes = [clip_plane];
49
- threlte.renderer.localClippingEnabled = true;
154
+ threlte.renderer.clippingPlanes = [clip_plane]
155
+ threlte.renderer.localClippingEnabled = true
156
+ } else {
157
+ threlte.renderer.clippingPlanes = []
158
+ threlte.renderer.localClippingEnabled = false
50
159
  }
51
- else {
52
- threlte.renderer.clippingPlanes = [];
53
- threlte.renderer.localClippingEnabled = false;
54
- }
55
- });
56
- $effect(() => {
57
- scene = threlte.scene;
58
- camera = threlte.camera.current;
160
+ })
161
+
162
+ $effect(() => {
163
+ scene = threlte.scene
164
+ camera = threlte.camera.current
59
165
  if (threlte.renderer) {
60
- // Enable object sorting for proper depth ordering of transparent surfaces
61
- threlte.renderer.sortObjects = true;
62
- Object.assign(threlte.renderer.domElement, { __renderer: threlte.renderer });
166
+ // Enable object sorting for proper depth ordering of transparent surfaces
167
+ threlte.renderer.sortObjects = true
168
+ Object.assign(threlte.renderer.domElement, { __renderer: threlte.renderer })
63
169
  }
64
- });
65
- extras.interactivity();
66
- // Get color interpolator from d3
67
- const get_interpolator = (name) => {
68
- const fn = d3_sc[name];
69
- return typeof fn === `function` ? fn : d3_sc.interpolateViridis;
70
- };
71
- // Filter surfaces based on selected bands
72
- let visible_surfaces = $derived(fermi_data?.isosurfaces.filter((surface) => selected_bands === undefined || selected_bands.includes(surface.band_index)) ?? []);
73
- // Compute average vertex distance from origin for each surface (used for render ordering)
74
- // Smaller distance = inner surface = render first (lower renderOrder)
75
- // Larger distance = outer surface = render last (higher renderOrder)
76
- function compute_surface_radius(surface) {
77
- if (surface.vertices.length === 0)
78
- return 0;
79
- let sum = 0;
170
+ })
171
+
172
+ extras.interactivity()
173
+
174
+ // Get color interpolator from d3
175
+ const get_interpolator = (name: string): (t: number) => string => {
176
+ const fn = d3_sc[name as keyof typeof d3_sc]
177
+ return typeof fn === `function` ? fn : d3_sc.interpolateViridis
178
+ }
179
+
180
+ // Filter surfaces based on selected bands
181
+ let visible_surfaces = $derived(
182
+ fermi_data?.isosurfaces.filter((surface) =>
183
+ selected_bands === undefined || selected_bands.includes(surface.band_index)
184
+ ) ?? [],
185
+ )
186
+
187
+ // Compute average vertex distance from origin for each surface (used for render ordering)
188
+ // Smaller distance = inner surface = render first (lower renderOrder)
189
+ // Larger distance = outer surface = render last (higher renderOrder)
190
+ function compute_surface_radius(surface: Isosurface): number {
191
+ if (surface.vertices.length === 0) return 0
192
+ let sum = 0
80
193
  for (const vertex of surface.vertices) {
81
- sum += Math.hypot(vertex[0], vertex[1], vertex[2]);
194
+ sum += Math.hypot(vertex[0], vertex[1], vertex[2])
82
195
  }
83
- return sum / surface.vertices.length;
84
- }
85
- // Map from surface to its render order based on size (inner surfaces first)
86
- let surface_render_orders = $derived.by(() => {
87
- const order_map = new SvelteMap();
88
- if (visible_surfaces.length === 0)
89
- return order_map;
196
+ return sum / surface.vertices.length
197
+ }
198
+
199
+ // Map from surface to its render order based on size (inner surfaces first)
200
+ let surface_render_orders = $derived.by((): Map<Isosurface, number> => {
201
+ const order_map = new SvelteMap<Isosurface, number>()
202
+ if (visible_surfaces.length === 0) return order_map
203
+
90
204
  // Compute radius for each surface and sort by it
91
205
  const surfaces_with_radius = visible_surfaces.map((surface) => ({
92
- surface,
93
- radius: compute_surface_radius(surface),
94
- }));
95
- surfaces_with_radius.sort((a, b) => a.radius - b.radius);
206
+ surface,
207
+ radius: compute_surface_radius(surface),
208
+ }))
209
+ surfaces_with_radius.sort((a, b) => a.radius - b.radius)
210
+
96
211
  // Assign render order: smaller radius (inner) = lower order = rendered first
97
212
  for (let idx = 0; idx < surfaces_with_radius.length; idx++) {
98
- order_map.set(surfaces_with_radius[idx].surface, idx);
213
+ order_map.set(surfaces_with_radius[idx].surface, idx)
99
214
  }
100
- return order_map;
101
- });
102
- // Compute property range for color scaling
103
- let property_range = $derived.by(() => {
215
+ return order_map
216
+ })
217
+
218
+ // Compute property range for color scaling
219
+ let property_range = $derived.by((): [number, number] => {
104
220
  if (color_property !== `velocity` && color_property !== `custom`) {
105
- return [0, 1];
221
+ return [0, 1]
106
222
  }
107
- let min_val = Infinity;
108
- let max_val = -Infinity;
223
+ let [min_val, max_val] = [Infinity, -Infinity]
109
224
  for (const surface of visible_surfaces) {
110
- if (!surface.properties)
111
- continue;
112
- for (const prop of surface.properties) {
113
- if (prop < min_val)
114
- min_val = prop;
115
- if (prop > max_val)
116
- max_val = prop;
117
- }
225
+ if (!surface.properties) continue
226
+ for (const prop of surface.properties) {
227
+ if (prop < min_val) min_val = prop
228
+ if (prop > max_val) max_val = prop
229
+ }
118
230
  }
119
- if (min_val === Infinity)
120
- return [0, 1];
121
- return [min_val, max_val];
122
- });
123
- // Get color for a surface/vertex
124
- function get_surface_color(surface, vertex_idx) {
231
+ if (min_val === Infinity) return [0, 1]
232
+ return [min_val, max_val]
233
+ })
234
+
235
+ // Get color for a surface/vertex
236
+ function get_surface_color(surface: Isosurface, vertex_idx?: number): string {
125
237
  if (color_property === `band`) {
126
- return constants.BAND_COLORS[surface.band_index % constants.BAND_COLORS.length];
238
+ return constants.BAND_COLORS[surface.band_index % constants.BAND_COLORS.length]
127
239
  }
128
- if ((color_property === `velocity` || color_property === `custom`) &&
129
- surface.properties &&
130
- vertex_idx !== undefined) {
131
- const prop = surface.properties[vertex_idx];
132
- const [min_val, max_val] = property_range;
133
- const normalized = max_val > min_val
134
- ? (prop - min_val) / (max_val - min_val)
135
- : 0.5;
136
- return get_interpolator(color_scale)(normalized);
240
+ if (
241
+ (color_property === `velocity` || color_property === `custom`) &&
242
+ surface.properties &&
243
+ vertex_idx !== undefined
244
+ ) {
245
+ const prop = surface.properties[vertex_idx]
246
+ const [min_val, max_val] = property_range
247
+ const normalized = max_val > min_val
248
+ ? (prop - min_val) / (max_val - min_val)
249
+ : 0.5
250
+ return get_interpolator(color_scale)(normalized)
137
251
  }
138
252
  // Spin coloring
139
253
  if (color_property === `spin` && surface.spin) {
140
- return surface.spin === `up` ? `#e41a1c` : `#377eb8`;
254
+ return surface.spin === `up` ? `#e41a1c` : `#377eb8`
141
255
  }
142
- return constants.BAND_COLORS[surface.band_index % constants.BAND_COLORS.length];
143
- }
144
- // Create geometry for an isosurface
145
- function create_isosurface_geometry(surface) {
146
- if (surface.vertices.length === 0 || surface.faces.length === 0)
147
- return null;
148
- const positions = [];
149
- const normals = [];
150
- const colors = [];
256
+ return constants.BAND_COLORS[surface.band_index % constants.BAND_COLORS.length]
257
+ }
258
+
259
+ // Create geometry for an isosurface
260
+ function create_isosurface_geometry(
261
+ surface: Isosurface,
262
+ ): { geometry: BufferGeometry; dispose: () => void } | null {
263
+ if (surface.vertices.length === 0 || surface.faces.length === 0) return null
264
+
265
+ const positions: number[] = []
266
+ const normals: number[] = []
267
+ const colors: number[] = []
268
+
151
269
  const use_vertex_colors = color_property === `velocity` ||
152
- color_property === `custom`;
153
- const n_vertices = surface.vertices.length;
270
+ color_property === `custom`
271
+
272
+ const n_vertices = surface.vertices.length
273
+
154
274
  for (const face of surface.faces) {
155
- if (face.length < 3)
156
- continue;
157
- // Fan triangulation: for polygon with N vertices, create N-2 triangles
158
- // e.g. quad [0,1,2,3] becomes triangles [0,1,2] and [0,2,3]
159
- for (let fan_idx = 1; fan_idx < face.length - 1; fan_idx++) {
160
- const idx0 = face[0];
161
- const idx1 = face[fan_idx];
162
- const idx2 = face[fan_idx + 1];
163
- // Validate face indices are within bounds (protects against malformed JSON files)
164
- if (idx0 < 0 || idx0 >= n_vertices ||
165
- idx1 < 0 || idx1 >= n_vertices ||
166
- idx2 < 0 || idx2 >= n_vertices) {
167
- continue;
168
- }
169
- const v0 = surface.vertices[idx0];
170
- const v1 = surface.vertices[idx1];
171
- const v2 = surface.vertices[idx2];
172
- positions.push(...v0, ...v1, ...v2);
173
- // Use per-vertex normals if available, otherwise compute face normal
174
- if (surface.normals && surface.normals.length > 0) {
175
- const n0 = surface.normals[idx0] ?? [0, 0, 1];
176
- const n1 = surface.normals[idx1] ?? [0, 0, 1];
177
- const n2 = surface.normals[idx2] ?? [0, 0, 1];
178
- normals.push(...n0, ...n1, ...n2);
179
- }
180
- else {
181
- const e1 = math.subtract(v1, v0);
182
- const e2 = math.subtract(v2, v0);
183
- const normal = math.cross_3d(e1, e2);
184
- const len = Math.hypot(...normal);
185
- const n = len > 1e-10 ? normal.map((x) => x / len) : [0, 0, 1];
186
- normals.push(...n, ...n, ...n);
187
- }
188
- // Per-vertex colors for this triangle
189
- if (use_vertex_colors) {
190
- for (const vert_idx of [idx0, idx1, idx2]) {
191
- const color_str = get_surface_color(surface, vert_idx);
192
- const color = new Color(color_str);
193
- colors.push(color.r, color.g, color.b);
194
- }
195
- }
275
+ if (face.length < 3) continue
276
+
277
+ // Fan triangulation: for polygon with N vertices, create N-2 triangles
278
+ // e.g. quad [0,1,2,3] becomes triangles [0,1,2] and [0,2,3]
279
+ for (let fan_idx = 1; fan_idx < face.length - 1; fan_idx++) {
280
+ const idx0 = face[0]
281
+ const idx1 = face[fan_idx]
282
+ const idx2 = face[fan_idx + 1]
283
+
284
+ // Validate face indices are within bounds (protects against malformed JSON files)
285
+ if (
286
+ idx0 < 0 || idx0 >= n_vertices ||
287
+ idx1 < 0 || idx1 >= n_vertices ||
288
+ idx2 < 0 || idx2 >= n_vertices
289
+ ) {
290
+ continue
291
+ }
292
+
293
+ const v0 = surface.vertices[idx0]
294
+ const v1 = surface.vertices[idx1]
295
+ const v2 = surface.vertices[idx2]
296
+
297
+ positions.push(...v0, ...v1, ...v2)
298
+
299
+ // Use per-vertex normals if available, otherwise compute face normal
300
+ if (surface.normals && surface.normals.length > 0) {
301
+ const n0 = surface.normals[idx0] ?? [0, 0, 1]
302
+ const n1 = surface.normals[idx1] ?? [0, 0, 1]
303
+ const n2 = surface.normals[idx2] ?? [0, 0, 1]
304
+ normals.push(...n0, ...n1, ...n2)
305
+ } else {
306
+ const e1: Vec3 = math.subtract(v1, v0)
307
+ const e2: Vec3 = math.subtract(v2, v0)
308
+ const normal = math.cross_3d(e1, e2)
309
+ const len = Math.hypot(...normal)
310
+ const n = len > 1e-10 ? normal.map((x) => x / len) : [0, 0, 1]
311
+ normals.push(...n, ...n, ...n)
196
312
  }
313
+
314
+ // Per-vertex colors for this triangle
315
+ if (use_vertex_colors) {
316
+ for (const vert_idx of [idx0, idx1, idx2]) {
317
+ const color_str = get_surface_color(surface, vert_idx)
318
+ const color = new Color(color_str)
319
+ colors.push(color.r, color.g, color.b)
320
+ }
321
+ }
322
+ }
197
323
  }
198
- const geometry = new BufferGeometry();
199
- geometry.setAttribute(`position`, new BufferAttribute(new Float32Array(positions), 3));
200
- geometry.setAttribute(`normal`, new BufferAttribute(new Float32Array(normals), 3));
324
+
325
+ const geometry = new BufferGeometry()
326
+ geometry.setAttribute(
327
+ `position`,
328
+ new BufferAttribute(new Float32Array(positions), 3),
329
+ )
330
+ geometry.setAttribute(`normal`, new BufferAttribute(new Float32Array(normals), 3))
331
+
201
332
  if (use_vertex_colors) {
202
- geometry.setAttribute(`color`, new BufferAttribute(new Float32Array(colors), 3));
333
+ geometry.setAttribute(`color`, new BufferAttribute(new Float32Array(colors), 3))
203
334
  }
204
- geometry.computeBoundingSphere();
205
- return { geometry, dispose: () => geometry.dispose() };
206
- }
207
- let geometry_cache = $derived.by(() => {
208
- const cache = new SvelteMap();
335
+
336
+ geometry.computeBoundingSphere()
337
+
338
+ return { geometry, dispose: () => geometry.dispose() }
339
+ }
340
+
341
+ // Memoized geometry cache - pre-compute geometries to avoid recomputation on every render
342
+ type GeometryData = { geometry: BufferGeometry; dispose: () => void }
343
+ let geometry_cache = $derived.by((): Map<string, GeometryData | null> => {
344
+ const cache = new SvelteMap<string, GeometryData | null>()
209
345
  for (let idx = 0; idx < visible_surfaces.length; idx++) {
210
- const surface = visible_surfaces[idx];
211
- const key = `${surface.band_index}-${surface.spin}-${idx}`;
212
- cache.set(key, create_isosurface_geometry(surface));
346
+ const surface = visible_surfaces[idx]
347
+ const key = `${surface.band_index}-${surface.spin}-${idx}`
348
+ cache.set(key, create_isosurface_geometry(surface))
213
349
  }
214
- return cache;
215
- });
216
- // Cleanup geometries when cache changes
217
- $effect(() => {
218
- const current_cache = geometry_cache;
350
+ return cache
351
+ })
352
+
353
+ // Cleanup geometries when cache changes
354
+ $effect(() => {
355
+ const current_cache = geometry_cache
219
356
  return () => {
220
- for (const geo_data of current_cache.values()) {
221
- geo_data?.dispose();
222
- }
223
- };
224
- });
225
- // Count total triangles and auto-disable tiling for very large surfaces
226
- let total_triangles = $derived(visible_surfaces.reduce((sum, surface) => sum + surface.faces.length, 0));
227
- let effective_tile_bz = $derived(tile_bz && total_triangles < constants.MAX_TRIANGLES_FOR_TILING);
228
- // Warn user when tiling is auto-disabled
229
- $effect(() => {
357
+ for (const geo_data of current_cache.values()) {
358
+ geo_data?.dispose()
359
+ }
360
+ }
361
+ })
362
+
363
+ // Count total triangles and auto-disable tiling for very large surfaces
364
+ let total_triangles = $derived(
365
+ visible_surfaces.reduce((sum, surface) => sum + surface.faces.length, 0),
366
+ )
367
+ let effective_tile_bz = $derived(
368
+ tile_bz && total_triangles < constants.MAX_TRIANGLES_FOR_TILING,
369
+ )
370
+
371
+ // Warn user when tiling is auto-disabled
372
+ $effect(() => {
230
373
  if (tile_bz && !effective_tile_bz && total_triangles > 0) {
231
- console.warn(`Fermi surface has ${total_triangles} triangles, auto-disabled BZ tiling for performance`);
374
+ console.warn(
375
+ `Fermi surface has ${total_triangles} triangles, auto-disabled BZ tiling for performance`,
376
+ )
232
377
  }
233
- });
234
- // Compute rotation target from surfaces or BZ
235
- const rotation_target = $derived.by(() => {
378
+ })
379
+
380
+ // Compute rotation target from surfaces or BZ
381
+ const rotation_target = $derived.by((): Vec3 => {
236
382
  if (bz_data?.vertices && bz_data.vertices.length > 0) {
237
- const sum = bz_data.vertices.reduce((acc, vert) => math.add(acc, vert), [0, 0, 0]);
238
- return math.scale(sum, 1 / bz_data.vertices.length);
383
+ const sum = bz_data.vertices.reduce(
384
+ (acc, vert) => math.add(acc, vert),
385
+ [0, 0, 0] as Vec3,
386
+ )
387
+ return math.scale(sum, 1 / bz_data.vertices.length)
239
388
  }
240
- return [0, 0, 0];
241
- });
242
- // Scene size for camera positioning (uses helper function defined earlier)
243
- const scene_size = $derived(get_scene_size());
244
- const computed_camera_position = $derived.by(() => camera_position || [10, 3, 8].map((x) => x * Math.max(1, scene_size)));
245
- const gizmo_props = $derived({
389
+ return [0, 0, 0]
390
+ })
391
+
392
+ // Scene size for camera positioning (uses helper function defined earlier)
393
+ const scene_size = $derived(get_scene_size())
394
+
395
+ const computed_camera_position = $derived.by(
396
+ () =>
397
+ camera_position || ([10, 3, 8].map((x) => x * Math.max(1, scene_size)) as Vec3),
398
+ )
399
+
400
+ const gizmo_props = $derived({
246
401
  background: { enabled: false },
247
402
  className: `responsive-gizmo`,
248
- ...Object.fromEntries([...AXIS_COLORS, ...NEG_AXIS_COLORS].map(([axis, color, hover]) => [
403
+ ...Object.fromEntries(
404
+ [...AXIS_COLORS, ...NEG_AXIS_COLORS].map(([axis, color, hover]) => [
249
405
  axis,
250
406
  {
251
- color,
252
- labelColor: `#111`,
253
- opacity: axis.startsWith(`n`) ? 0.9 : 0.8,
254
- hover: {
255
- color: hover,
256
- labelColor: `#222`,
257
- opacity: axis.startsWith(`n`) ? 1 : 0.9,
258
- },
407
+ color,
408
+ labelColor: `#111`,
409
+ opacity: axis.startsWith(`n`) ? 0.9 : 0.8,
410
+ hover: {
411
+ color: hover,
412
+ labelColor: `#222`,
413
+ opacity: axis.startsWith(`n`) ? 1 : 0.9,
414
+ },
259
415
  },
260
- ])),
416
+ ]),
417
+ ),
261
418
  ...(typeof gizmo === `object` ? gizmo : {}),
262
419
  offset: { left: 5, bottom: 5 },
263
- });
264
- const is_ortho = $derived(camera_projection === `orthographic`);
265
- const orbit_controls_props = $derived({
420
+ })
421
+
422
+ const is_ortho = $derived(camera_projection === `orthographic`)
423
+ const orbit_controls_props = $derived({
266
424
  position: [0, 0, 0],
267
425
  target: rotation_target,
268
426
  enableRotate: rotate_speed > 0,
@@ -280,141 +438,176 @@ const orbit_controls_props = $derived({
280
438
  dampingFactor: rotation_damping,
281
439
  onstart: () => (camera_is_moving = true),
282
440
  onend: () => (camera_is_moving = false),
283
- });
284
- const vector_colors = [`red`, `green`, `blue`];
285
- const vector_labels = [`b₁`, `b₂`, `b₃`];
286
- // Create BZ geometry
287
- const bz_geometry = $derived.by(() => {
288
- if (!bz_data || bz_data.faces.length === 0)
289
- return null;
290
- const positions = [];
291
- const normals = [];
441
+ })
442
+
443
+ const vector_colors = [`red`, `green`, `blue`]
444
+ const vector_labels = [`b₁`, `b₂`, `b₃`]
445
+
446
+ // Create BZ geometry
447
+ const bz_geometry = $derived.by(() => {
448
+ if (!bz_data || bz_data.faces.length === 0) return null
449
+
450
+ const positions: number[] = []
451
+ const normals: number[] = []
452
+
292
453
  for (const face of bz_data.faces) {
293
- if (face.length < 3)
294
- continue;
295
- for (let face_idx = 1; face_idx < face.length - 1; face_idx++) {
296
- const indices = [face[0], face[face_idx], face[face_idx + 1]];
297
- if (indices.some((idx) => idx < 0 || idx >= bz_data.vertices.length))
298
- continue;
299
- const [v0, v1, v2] = indices.map((idx) => bz_data.vertices[idx]);
300
- positions.push(...v0, ...v1, ...v2);
301
- const e1 = math.subtract(v1, v0);
302
- const e2 = math.subtract(v2, v0);
303
- const normal_vec = math.cross_3d(e1, e2);
304
- const len = Math.hypot(...normal_vec);
305
- const norm = len > 1e-10 ? normal_vec.map((x) => x / len) : [0, 0, 0];
306
- normals.push(...norm, ...norm, ...norm);
307
- }
454
+ if (face.length < 3) continue
455
+
456
+ for (let face_idx = 1; face_idx < face.length - 1; face_idx++) {
457
+ const indices = [face[0], face[face_idx], face[face_idx + 1]]
458
+ if (indices.some((idx) => idx < 0 || idx >= bz_data.vertices.length)) continue
459
+ const [v0, v1, v2] = indices.map((idx) => bz_data.vertices[idx])
460
+ positions.push(...v0, ...v1, ...v2)
461
+
462
+ const e1: Vec3 = math.subtract(v1, v0)
463
+ const e2: Vec3 = math.subtract(v2, v0)
464
+ const normal_vec = math.cross_3d(e1, e2)
465
+ const len = Math.hypot(...normal_vec)
466
+ const norm = len > 1e-10 ? normal_vec.map((x) => x / len) : [0, 0, 0]
467
+ normals.push(...norm, ...norm, ...norm)
468
+ }
308
469
  }
309
- const geometry = new BufferGeometry();
310
- geometry.setAttribute(`position`, new BufferAttribute(new Float32Array(positions), 3));
311
- geometry.setAttribute(`normal`, new BufferAttribute(new Float32Array(normals), 3));
312
- geometry.computeBoundingSphere();
313
- return geometry;
314
- });
315
- $effect(() => {
316
- const prev_geometry = bz_geometry;
317
- return () => prev_geometry?.dispose();
318
- });
319
- // Get material props for two-pass transparent rendering
320
- // Pass 1 (back faces): renders interior/back of surfaces first
321
- // Pass 2 (front faces): renders exterior/front of surfaces on top
322
- // This avoids z-fighting while showing both sides correctly
323
- const get_material_props = (surface_color, use_vertex_colors, surface_idx, pass) => {
324
- const is_transparent = surface_opacity < 1;
470
+
471
+ const geometry = new BufferGeometry()
472
+ geometry.setAttribute(
473
+ `position`,
474
+ new BufferAttribute(new Float32Array(positions), 3),
475
+ )
476
+ geometry.setAttribute(`normal`, new BufferAttribute(new Float32Array(normals), 3))
477
+ geometry.computeBoundingSphere()
478
+ return geometry
479
+ })
480
+
481
+ $effect(() => {
482
+ const prev_geometry = bz_geometry
483
+ return () => prev_geometry?.dispose()
484
+ })
485
+
486
+ // Get material props for two-pass transparent rendering
487
+ // Pass 1 (back faces): renders interior/back of surfaces first
488
+ // Pass 2 (front faces): renders exterior/front of surfaces on top
489
+ // This avoids z-fighting while showing both sides correctly
490
+ const get_material_props = (
491
+ surface_color: string,
492
+ use_vertex_colors: boolean,
493
+ surface_idx: number,
494
+ pass: `front` | `back`,
495
+ ) => {
496
+ const is_transparent = surface_opacity < 1
325
497
  const base = {
326
- transparent: is_transparent,
327
- opacity: surface_opacity,
328
- // Two-pass: back faces first (pass=back), front faces second (pass=front)
329
- // For opaque: just use DoubleSide
330
- side: is_transparent ? (pass === `back` ? BackSide : FrontSide) : DoubleSide,
331
- depthWrite: true,
332
- depthTest: true,
333
- // Polygon offset helps separate overlapping geometry
334
- polygonOffset: true,
335
- polygonOffsetFactor: 1 + surface_idx * 0.5,
336
- polygonOffsetUnits: 1 + surface_idx * 0.5,
337
- };
498
+ transparent: is_transparent,
499
+ opacity: surface_opacity,
500
+ // Two-pass: back faces first (pass=back), front faces second (pass=front)
501
+ // For opaque: just use DoubleSide
502
+ side: is_transparent ? (pass === `back` ? BackSide : FrontSide) : DoubleSide,
503
+ depthWrite: true,
504
+ depthTest: true,
505
+ // Polygon offset helps separate overlapping geometry
506
+ polygonOffset: true,
507
+ polygonOffsetFactor: 1 + surface_idx * 0.5,
508
+ polygonOffsetUnits: 1 + surface_idx * 0.5,
509
+ }
510
+
338
511
  if (use_vertex_colors) {
339
- return { ...base, vertexColors: true };
512
+ return { ...base, vertexColors: true }
340
513
  }
341
- return { ...base, color: surface_color };
342
- };
343
- // Compute inverse of k_lattice for Cartesian->fractional conversion (cached)
344
- const k_lattice_inv = $derived.by(() => {
345
- if (!fermi_data?.k_lattice)
346
- return null;
514
+ return { ...base, color: surface_color }
515
+ }
516
+
517
+ // Compute inverse of k_lattice for Cartesian->fractional conversion (cached)
518
+ const k_lattice_inv = $derived.by(() => {
519
+ if (!fermi_data?.k_lattice) return null
347
520
  try {
348
- return math.matrix_inverse_3x3(fermi_data.k_lattice);
521
+ return math.matrix_inverse_3x3(fermi_data.k_lattice)
522
+ } catch {
523
+ return null
349
524
  }
350
- catch {
351
- return null;
352
- }
353
- });
354
- // Throttle state for pointer move events to avoid O(n) vertex lookups causing jank
355
- let last_hover_time = 0;
356
- // Convert Cartesian k-coordinates to fractional (reciprocal lattice units)
357
- // Returns null if k_lattice is unavailable or inversion failed
358
- function cartesian_to_fractional(cart) {
359
- if (!k_lattice_inv)
360
- return null;
361
- return math.mat3x3_vec3_multiply(k_lattice_inv, cart);
362
- }
363
- // Find index of nearest vertex to a point in a surface
364
- function find_nearest_vertex(surface, point) {
365
- let min_dist = Infinity;
366
- let nearest_idx = 0;
525
+ })
526
+
527
+ // Throttle state for pointer move events to avoid O(n) vertex lookups causing jank
528
+ let last_hover_time = 0
529
+
530
+ // Convert Cartesian k-coordinates to fractional (reciprocal lattice units)
531
+ // Returns null if k_lattice is unavailable or inversion failed
532
+ function cartesian_to_fractional(cart: Vec3): Vec3 | null {
533
+ if (!k_lattice_inv) return null
534
+ return math.mat3x3_vec3_multiply(k_lattice_inv, cart)
535
+ }
536
+
537
+ // Find index of nearest vertex to a point in a surface
538
+ function find_nearest_vertex(surface: Isosurface, point: Vec3): number {
539
+ let [min_dist, nearest_idx] = [Infinity, 0]
367
540
  for (let idx = 0; idx < surface.vertices.length; idx++) {
368
- const vertex = surface.vertices[idx];
369
- const dist = Math.hypot(point[0] - vertex[0], point[1] - vertex[1], point[2] - vertex[2]);
370
- if (dist < min_dist) {
371
- min_dist = dist;
372
- nearest_idx = idx;
373
- }
541
+ const vertex = surface.vertices[idx]
542
+ const dist = Math.hypot(
543
+ point[0] - vertex[0],
544
+ point[1] - vertex[1],
545
+ point[2] - vertex[2],
546
+ )
547
+ if (dist < min_dist) {
548
+ min_dist = dist
549
+ nearest_idx = idx
550
+ }
374
551
  }
375
- return nearest_idx;
376
- }
377
- // Create hover data from pointer event on a surface
378
- function create_hover_data(event, surface, surface_color, sym_idx, sym_matrix) {
552
+ return nearest_idx
553
+ }
554
+
555
+ // Create hover data from pointer event on a surface
556
+ function create_hover_data(
557
+ event: ThreltePointerEvent,
558
+ surface: Isosurface,
559
+ surface_color: string,
560
+ sym_idx: number,
561
+ sym_matrix: Matrix4Tuple,
562
+ ): FermiHoverData {
379
563
  // event.point is in world space (after sym_matrix transformation)
380
- const position_cartesian = [event.point.x, event.point.y, event.point.z];
381
- const position_fractional = cartesian_to_fractional(position_cartesian);
564
+ const position_cartesian: Vec3 = [event.point.x, event.point.y, event.point.z]
565
+ const position_fractional = cartesian_to_fractional(position_cartesian)
566
+
382
567
  // Transform world-space point to local space for nearest-vertex lookup
383
568
  // surface.vertices are in local space (raw geometry before sym_matrix)
384
- const local_point = event.point.clone();
385
- const inv_matrix = new Matrix4().fromArray(sym_matrix).invert();
386
- local_point.applyMatrix4(inv_matrix);
387
- const local_position = [local_point.x, local_point.y, local_point.z];
569
+ const local_point = event.point.clone()
570
+ const inv_matrix = new Matrix4().fromArray(sym_matrix).invert()
571
+ local_point.applyMatrix4(inv_matrix)
572
+ const local_position: Vec3 = [local_point.x, local_point.y, local_point.z]
573
+
388
574
  // Find nearest vertex for property lookup (in local space)
389
- const nearest_idx = find_nearest_vertex(surface, local_position);
390
- const property_value = surface.properties?.[nearest_idx];
391
- const has_velocities = fermi_data?.metadata?.has_velocities;
575
+ const nearest_idx = find_nearest_vertex(surface, local_position)
576
+ const property_value = surface.properties?.[nearest_idx]
577
+ const has_velocities = fermi_data?.metadata?.has_velocities
392
578
  const property_name = property_value != null
393
- ? (has_velocities ? `velocity` : `custom`)
394
- : undefined;
395
- const { clientX, clientY } = event.nativeEvent;
579
+ ? (has_velocities ? `velocity` : `custom`)
580
+ : undefined
581
+
582
+ const { clientX, clientY } = event.nativeEvent
396
583
  return {
397
- band_index: surface.band_index,
398
- spin: surface.spin,
399
- position_cartesian,
400
- position_fractional,
401
- screen_position: { x: clientX, y: clientY },
402
- surface_color,
403
- property_value,
404
- property_name,
405
- is_tiled: effective_tile_bz,
406
- symmetry_index: sym_idx,
407
- };
408
- }
409
- // Throttled handler for pointer move events
410
- // Skips expensive nearest-vertex lookups if called too frequently
411
- function handle_pointer_move(event, surface, surface_color, sym_idx, sym_matrix) {
412
- const now = performance.now();
413
- if (now - last_hover_time < constants.HOVER_THROTTLE_MS)
414
- return;
415
- last_hover_time = now;
416
- hover_data = create_hover_data(event, surface, surface_color, sym_idx, sym_matrix);
417
- }
584
+ band_index: surface.band_index,
585
+ spin: surface.spin,
586
+ position_cartesian,
587
+ position_fractional,
588
+ screen_position: { x: clientX, y: clientY },
589
+ surface_color,
590
+ property_value,
591
+ property_name,
592
+ is_tiled: effective_tile_bz,
593
+ symmetry_index: sym_idx,
594
+ }
595
+ }
596
+
597
+ // Throttled handler for pointer move events
598
+ // Skips expensive nearest-vertex lookups if called too frequently
599
+ function handle_pointer_move(
600
+ event: ThreltePointerEvent,
601
+ surface: Isosurface,
602
+ surface_color: string,
603
+ sym_idx: number,
604
+ sym_matrix: Matrix4Tuple,
605
+ ): void {
606
+ const now = performance.now()
607
+ if (now - last_hover_time < constants.HOVER_THROTTLE_MS) return
608
+ last_hover_time = now
609
+ hover_data = create_hover_data(event, surface, surface_color, sym_idx, sym_matrix)
610
+ }
418
611
  </script>
419
612
 
420
613
  {#if camera_projection === `perspective`}