@tableslayer/ui 0.1.3 → 0.1.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 (205) hide show
  1. package/package.json +2 -13
  2. package/src/lib/components/Avatar/Avatar.svelte +82 -0
  3. package/src/lib/components/Avatar/AvatarFileInput.svelte +85 -0
  4. package/src/lib/components/Avatar/AvatarPopover.svelte +34 -0
  5. package/src/lib/components/Avatar/index.ts +4 -0
  6. package/src/lib/components/Avatar/types.ts +24 -0
  7. package/src/lib/components/BrushSizeSlider/BrushSizeSlider.svelte +174 -0
  8. package/src/lib/components/BrushSizeSlider/index.ts +1 -0
  9. package/src/lib/components/Button/Button.svelte +182 -0
  10. package/src/lib/components/Button/ConfirmActionButton.svelte +98 -0
  11. package/src/lib/components/Button/IconButton.svelte +121 -0
  12. package/src/lib/components/Button/RadioButton.svelte +93 -0
  13. package/src/lib/components/Button/index.ts +5 -0
  14. package/src/lib/components/Button/types.ts +54 -0
  15. package/src/lib/components/CardFan/CardFan.svelte +165 -0
  16. package/src/lib/components/CardFan/index.ts +2 -0
  17. package/src/lib/components/CardFan/types.ts +6 -0
  18. package/src/lib/components/CodeBlock/Code.svelte +7 -0
  19. package/src/lib/components/CodeBlock/CodeBlock.svelte +102 -0
  20. package/src/lib/components/CodeBlock/index.ts +3 -0
  21. package/src/lib/components/CodeBlock/types.ts +10 -0
  22. package/src/lib/components/ColorMode/ColorMode.svelte +8 -0
  23. package/src/lib/components/ColorMode/index.ts +2 -0
  24. package/src/lib/components/ColorMode/types.ts +12 -0
  25. package/src/lib/components/ColorPicker/ColorPicker.svelte +838 -0
  26. package/src/lib/components/ColorPicker/ColorPickerSwatch.svelte +32 -0
  27. package/src/lib/components/ColorPicker/index.ts +3 -0
  28. package/src/lib/components/ColorPicker/types.ts +51 -0
  29. package/src/lib/components/ContextMenu/ContextMenu.svelte +86 -0
  30. package/src/lib/components/ContextMenu/index.ts +2 -0
  31. package/src/lib/components/ContextMenu/types.ts +15 -0
  32. package/src/lib/components/DrawingSliders/DrawingSliders.svelte +379 -0
  33. package/src/lib/components/DrawingSliders/index.ts +1 -0
  34. package/src/lib/components/Editor/Editor.svelte +825 -0
  35. package/src/lib/components/Editor/index.ts +1 -0
  36. package/src/lib/components/FogSliders/FogSliders.svelte +33 -0
  37. package/src/lib/components/FogSliders/index.ts +1 -0
  38. package/src/lib/components/Hr/Hr.svelte +15 -0
  39. package/src/lib/components/Hr/index.ts +1 -0
  40. package/src/lib/components/Icon/Icon.svelte +6 -0
  41. package/src/lib/components/Icon/index.ts +2 -0
  42. package/src/lib/components/Icon/types.ts +20 -0
  43. package/src/lib/components/Input/DualInputSlider.svelte +126 -0
  44. package/src/lib/components/Input/FileInput.svelte +176 -0
  45. package/src/lib/components/Input/FormControl.svelte +150 -0
  46. package/src/lib/components/Input/FormError.svelte +37 -0
  47. package/src/lib/components/Input/Input.svelte +56 -0
  48. package/src/lib/components/Input/InputCheckbox.svelte +99 -0
  49. package/src/lib/components/Input/InputSlider.svelte +86 -0
  50. package/src/lib/components/Input/Label.svelte +19 -0
  51. package/src/lib/components/Input/index.ts +9 -0
  52. package/src/lib/components/Input/types.ts +39 -0
  53. package/src/lib/components/Link/Link.svelte +41 -0
  54. package/src/lib/components/Link/LinkBox.svelte +20 -0
  55. package/src/lib/components/Link/LinkOverlay.svelte +23 -0
  56. package/src/lib/components/Link/index.ts +4 -0
  57. package/src/lib/components/Link/types.ts +17 -0
  58. package/src/lib/components/Loading/Loader.svelte +60 -0
  59. package/src/lib/components/Loading/Skeleton.svelte +9 -0
  60. package/src/lib/components/Loading/index.ts +2 -0
  61. package/src/lib/components/Logo/Logo.svelte +16 -0
  62. package/src/lib/components/Logo/index.ts +1 -0
  63. package/src/lib/components/MarkerTooltip/MarkerTooltip.svelte +435 -0
  64. package/src/lib/components/MarkerTooltip/index.ts +1 -0
  65. package/src/lib/components/Menu/SelectorMenu.svelte +280 -0
  66. package/src/lib/components/Menu/index.ts +2 -0
  67. package/src/lib/components/Menu/types.ts +17 -0
  68. package/src/lib/components/MyCounterButton.svelte +11 -0
  69. package/src/lib/components/Panel/index.ts +2 -0
  70. package/src/lib/components/Panel/panel.svelte +18 -0
  71. package/src/lib/components/Panel/types.ts +8 -0
  72. package/src/lib/components/PersistButton/PersistButton.svelte +100 -0
  73. package/src/lib/components/PersistButton/index.ts +1 -0
  74. package/src/lib/components/Popover/Popover.svelte +81 -0
  75. package/src/lib/components/Popover/index.ts +2 -0
  76. package/src/lib/components/Popover/types.ts +19 -0
  77. package/src/lib/components/PropsTable/PropsTable.svelte +107 -0
  78. package/src/lib/components/RadialMenu/EffectPreview.svelte +36 -0
  79. package/src/lib/components/RadialMenu/EffectPreviewScene.svelte +194 -0
  80. package/src/lib/components/RadialMenu/RadialMenu.svelte +503 -0
  81. package/src/lib/components/RadialMenu/RadialMenuItem.svelte +176 -0
  82. package/src/lib/components/RadialMenu/index.ts +2 -0
  83. package/src/lib/components/RadialMenu/types.ts +35 -0
  84. package/src/lib/components/Select/Select.svelte +342 -0
  85. package/src/lib/components/Select/index.ts +2 -0
  86. package/src/lib/components/Select/types.ts +22 -0
  87. package/src/lib/components/Spacer/Spacer.svelte +14 -0
  88. package/src/lib/components/Spacer/index.ts +2 -0
  89. package/src/lib/components/Spacer/types.ts +5 -0
  90. package/src/lib/components/Stage/components/AnnotationLayer/AnnotationLayer.svelte +445 -0
  91. package/src/lib/components/Stage/components/AnnotationLayer/AnnotationMaterial.svelte +167 -0
  92. package/src/lib/components/Stage/components/AnnotationLayer/types.ts +196 -0
  93. package/src/lib/components/Stage/components/CursorLayer/CursorLayer.svelte +148 -0
  94. package/src/lib/components/Stage/components/CursorLayer/cursor.svg +26 -0
  95. package/src/lib/components/Stage/components/CursorLayer/index.ts +2 -0
  96. package/src/lib/components/Stage/components/CursorLayer/types.ts +23 -0
  97. package/src/lib/components/Stage/components/DrawingLayer/DrawingMaterial.svelte +364 -0
  98. package/src/lib/components/Stage/components/DrawingLayer/types.ts +65 -0
  99. package/src/lib/components/Stage/components/EdgeOverlayLayer/EdgeOverlayLayer.svelte +72 -0
  100. package/src/lib/components/Stage/components/EdgeOverlayLayer/types.ts +34 -0
  101. package/src/lib/components/Stage/components/FogLayer/FogLayer.svelte +75 -0
  102. package/src/lib/components/Stage/components/FogLayer/types.ts +51 -0
  103. package/src/lib/components/Stage/components/FogOfWarLayer/FogOfWarLayer.svelte +249 -0
  104. package/src/lib/components/Stage/components/FogOfWarLayer/FogOfWarMaterial.svelte +200 -0
  105. package/src/lib/components/Stage/components/FogOfWarLayer/types.ts +116 -0
  106. package/src/lib/components/Stage/components/GridLayer/GridLayer.svelte +20 -0
  107. package/src/lib/components/Stage/components/GridLayer/GridMaterial.svelte +69 -0
  108. package/src/lib/components/Stage/components/GridLayer/types.ts +79 -0
  109. package/src/lib/components/Stage/components/LayerInput/LayerInput.svelte +300 -0
  110. package/src/lib/components/Stage/components/MapLayer/MapLayer.svelte +196 -0
  111. package/src/lib/components/Stage/components/MapLayer/dataSources/GifDataSource.ts +265 -0
  112. package/src/lib/components/Stage/components/MapLayer/dataSources/IMapDataSource.ts +55 -0
  113. package/src/lib/components/Stage/components/MapLayer/dataSources/ImageDataSource.ts +87 -0
  114. package/src/lib/components/Stage/components/MapLayer/dataSources/VideoDataSource.ts +150 -0
  115. package/src/lib/components/Stage/components/MapLayer/dataSources/dataSourceFactory.ts +48 -0
  116. package/src/lib/components/Stage/components/MapLayer/dataSources/index.ts +16 -0
  117. package/src/lib/components/Stage/components/MapLayer/types.ts +58 -0
  118. package/src/lib/components/Stage/components/MarkerLayer/MarkerLayer.svelte +398 -0
  119. package/src/lib/components/Stage/components/MarkerLayer/MarkerToken.svelte +262 -0
  120. package/src/lib/components/Stage/components/MarkerLayer/types.ts +126 -0
  121. package/src/lib/components/Stage/components/MeasurementLayer/MeasurementLayer.svelte +364 -0
  122. package/src/lib/components/Stage/components/MeasurementLayer/MeasurementManager.svelte +473 -0
  123. package/src/lib/components/Stage/components/MeasurementLayer/measurements/BaseMeasurement.ts +427 -0
  124. package/src/lib/components/Stage/components/MeasurementLayer/measurements/BeamMeasurement.ts +105 -0
  125. package/src/lib/components/Stage/components/MeasurementLayer/measurements/CircleMeasurement.ts +98 -0
  126. package/src/lib/components/Stage/components/MeasurementLayer/measurements/ConeMeasurement.ts +163 -0
  127. package/src/lib/components/Stage/components/MeasurementLayer/measurements/LineMeasurement.ts +102 -0
  128. package/src/lib/components/Stage/components/MeasurementLayer/measurements/RectangleMeasurement.ts +120 -0
  129. package/src/lib/components/Stage/components/MeasurementLayer/measurements/index.ts +7 -0
  130. package/src/lib/components/Stage/components/MeasurementLayer/types.ts +94 -0
  131. package/src/lib/components/Stage/components/MeasurementLayer/utils/canvasDrawing.ts +357 -0
  132. package/src/lib/components/Stage/components/MeasurementLayer/utils/distanceCalculations.ts +170 -0
  133. package/src/lib/components/Stage/components/ParticleSystem/ParticleSystem.svelte +220 -0
  134. package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/ash.png +0 -0
  135. package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/leaves.png +0 -0
  136. package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/rain.png +0 -0
  137. package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/snow.png +0 -0
  138. package/src/lib/components/Stage/components/ParticleSystem/rng.js +20 -0
  139. package/src/lib/components/Stage/components/ParticleSystem/types.ts +95 -0
  140. package/src/lib/components/Stage/components/PerformanceDebugger/PerformanceDebugger.svelte +144 -0
  141. package/src/lib/components/Stage/components/PerformanceDebugger/index.ts +1 -0
  142. package/src/lib/components/Stage/components/PerformanceOverlay/PerformanceOverlay.svelte +208 -0
  143. package/src/lib/components/Stage/components/PerformanceOverlay/index.ts +1 -0
  144. package/src/lib/components/Stage/components/PointerInputManager/PointerInputManager.svelte +201 -0
  145. package/src/lib/components/Stage/components/Scene/Scene.svelte +651 -0
  146. package/src/lib/components/Stage/components/Scene/luts.ts +24 -0
  147. package/src/lib/components/Stage/components/Scene/types.ts +225 -0
  148. package/src/lib/components/Stage/components/Stage/Stage.svelte +332 -0
  149. package/src/lib/components/Stage/components/Stage/types.ts +136 -0
  150. package/src/lib/components/Stage/components/WeatherLayer/WeatherLayer.svelte +135 -0
  151. package/src/lib/components/Stage/components/WeatherLayer/presets/AshPreset.ts +71 -0
  152. package/src/lib/components/Stage/components/WeatherLayer/presets/LeavesPreset.ts +70 -0
  153. package/src/lib/components/Stage/components/WeatherLayer/presets/RainPreset.ts +68 -0
  154. package/src/lib/components/Stage/components/WeatherLayer/presets/SnowPreset.ts +70 -0
  155. package/src/lib/components/Stage/components/WeatherLayer/presets/index.ts +6 -0
  156. package/src/lib/components/Stage/components/WeatherLayer/types.ts +35 -0
  157. package/src/lib/components/Stage/helpers/clippingPlaneStore.svelte.ts +28 -0
  158. package/src/lib/components/Stage/helpers/debugState.svelte.ts +18 -0
  159. package/src/lib/components/Stage/helpers/grid.ts +548 -0
  160. package/src/lib/components/Stage/helpers/lazyBrush.ts +171 -0
  161. package/src/lib/components/Stage/helpers/performanceMetrics.svelte.ts +220 -0
  162. package/src/lib/components/Stage/helpers/utils.ts +21 -0
  163. package/src/lib/components/Stage/index.ts +49 -0
  164. package/src/lib/components/Stage/shaders/AnnotationEffects.frag +1070 -0
  165. package/src/lib/components/Stage/shaders/Annotations.frag +29 -0
  166. package/src/lib/components/Stage/shaders/Drawing.frag +83 -0
  167. package/src/lib/components/Stage/shaders/Drawing.vert +5 -0
  168. package/src/lib/components/Stage/shaders/Fog.frag +147 -0
  169. package/src/lib/components/Stage/shaders/FractalNoise.frag +96 -0
  170. package/src/lib/components/Stage/shaders/GridShader.frag +174 -0
  171. package/src/lib/components/Stage/shaders/Overlay.frag +23 -0
  172. package/src/lib/components/Stage/shaders/Overlay.vert +0 -0
  173. package/src/lib/components/Stage/shaders/Particles.frag +27 -0
  174. package/src/lib/components/Stage/shaders/Particles.vert +51 -0
  175. package/src/lib/components/Stage/shaders/ToolOutline.frag +59 -0
  176. package/src/lib/components/Stage/shaders/default.vert +8 -0
  177. package/src/lib/components/Stage/types.ts +4 -0
  178. package/src/lib/components/Table/Table.svelte +16 -0
  179. package/src/lib/components/Table/Td.svelte +17 -0
  180. package/src/lib/components/Table/Th.svelte +18 -0
  181. package/src/lib/components/Table/index.ts +4 -0
  182. package/src/lib/components/Table/types.ts +14 -0
  183. package/src/lib/components/Text/Text.svelte +23 -0
  184. package/src/lib/components/Text/index.ts +2 -0
  185. package/src/lib/components/Text/types.ts +12 -0
  186. package/src/lib/components/Title/Title.svelte +54 -0
  187. package/src/lib/components/Title/index.ts +2 -0
  188. package/src/lib/components/Title/types.ts +9 -0
  189. package/src/lib/components/Toast/Toast.svelte +155 -0
  190. package/src/lib/components/Toast/index.ts +5 -0
  191. package/src/lib/components/Toast/toastCookie.ts +24 -0
  192. package/src/lib/components/Toast/types.ts +6 -0
  193. package/src/lib/components/ToolTip/ToolTip.svelte +70 -0
  194. package/src/lib/components/ToolTip/index.ts +2 -0
  195. package/src/lib/components/ToolTip/types.ts +14 -0
  196. package/src/lib/components/index.ts +32 -0
  197. package/src/lib/components/types.ts +0 -0
  198. package/src/lib/index.ts +2 -0
  199. package/src/lib/styles/globals.css +108 -0
  200. package/src/lib/styles/normalize.css +9 -0
  201. package/src/lib/styles/reset.css +133 -0
  202. package/src/lib/styles/utilities.css +179 -0
  203. package/src/lib/styles/vars.css +1103 -0
  204. package/src/lib/types/awareness.ts +17 -0
  205. package/src/lib/utils/rle.ts +217 -0
@@ -0,0 +1,220 @@
1
+ <script lang="ts">
2
+ import * as THREE from 'three';
3
+ import { T, useTask, useLoader } from '@threlte/core';
4
+ import { ParticleData } from './types';
5
+ import type { ParticleSystemProps } from './types';
6
+ import { RNG } from './rng';
7
+
8
+ import fragmentShader from '../../shaders/Particles.frag?raw';
9
+ import vertexShader from '../../shaders/Particles.vert?raw';
10
+ import { DEG2RAD } from 'three/src/math/MathUtils';
11
+ import { onDestroy, untrack } from 'svelte';
12
+
13
+ interface Props {
14
+ props: ParticleSystemProps;
15
+ opacity?: number;
16
+ intensity?: number;
17
+ }
18
+
19
+ const { props, opacity, intensity }: Props = $props();
20
+
21
+ let mesh: THREE.Mesh | undefined = $state(undefined);
22
+ let geometry: THREE.BufferGeometry | undefined = $state(undefined);
23
+
24
+ $effect.pre(() => {
25
+ const rng = new RNG(0);
26
+ const count = Math.round(props.maxParticleCount * (intensity ?? 1));
27
+
28
+ // Initialize particle attributes - 4 vertices per quad
29
+ const positions = new Float32Array(count * 12); // 4 vertices * 3 coords
30
+ const centers = new Float32Array(count * 8); // 1 center * 2 coords
31
+ const uvs = new Float32Array(count * 8); // 4 vertices * 2 coords
32
+ const indices = new Uint32Array(count * 6); // 2 triangles * 3 vertices
33
+ const ageOffsets = new Float32Array(count * 4); // 4 vertices
34
+
35
+ const particle = ParticleData[props.type];
36
+
37
+ // Initialize particles
38
+ for (let i = 0; i < count; i++) {
39
+ const radius = rng.random() * (props.spawnArea.maxRadius - props.spawnArea.minRadius) + props.spawnArea.minRadius;
40
+ const angle = rng.random() * 2 * Math.PI;
41
+ const x = radius * Math.cos(angle);
42
+ const y = radius * Math.sin(angle);
43
+ const z = -1.001;
44
+
45
+ // Quad vertex positions (same for all 4 corners initially)
46
+ const baseIdx = i * 12;
47
+
48
+ // Generate random size for this particle
49
+ let size = rng.random() * (props.size.max - props.size.min) + props.size.min;
50
+ let width = size * props.scale.x;
51
+ let height = size * props.scale.y;
52
+
53
+ const rotation = new THREE.Euler(0, 0, props.rotation.offset * DEG2RAD);
54
+ rotation.z += props.rotation.alignRadially ? angle : 0;
55
+ rotation.z += props.rotation.randomize ? rng.random() * 360 : 0;
56
+
57
+ const v1 = new THREE.Vector3(-width / 2, -height / 2, 0);
58
+ const v2 = new THREE.Vector3(width / 2, -height / 2, 0);
59
+ const v3 = new THREE.Vector3(-width / 2, height / 2, 0);
60
+ const v4 = new THREE.Vector3(width / 2, height / 2, 0);
61
+
62
+ v1.applyEuler(rotation);
63
+ v2.applyEuler(rotation);
64
+ v3.applyEuler(rotation);
65
+ v4.applyEuler(rotation);
66
+
67
+ v1.add(new THREE.Vector3(x, y, z));
68
+ v2.add(new THREE.Vector3(x, y, z));
69
+ v3.add(new THREE.Vector3(x, y, z));
70
+ v4.add(new THREE.Vector3(x, y, z));
71
+
72
+ // Set quad corners with size offset
73
+ positions[baseIdx] = v1.x; // Bottom left x
74
+ positions[baseIdx + 1] = v1.y; // Bottom left y
75
+ positions[baseIdx + 2] = v1.z; // Bottom left z
76
+
77
+ positions[baseIdx + 3] = v2.x; // Bottom right x
78
+ positions[baseIdx + 4] = v2.y; // Bottom right y
79
+ positions[baseIdx + 5] = v2.z; // Bottom right z
80
+
81
+ positions[baseIdx + 6] = v3.x; // Top left x
82
+ positions[baseIdx + 7] = v3.y; // Top left y
83
+ positions[baseIdx + 8] = v3.z; // Top left z
84
+
85
+ positions[baseIdx + 9] = v4.x; // Top right x
86
+ positions[baseIdx + 10] = v4.y; // Top right y
87
+ positions[baseIdx + 11] = v4.z; // Top right z
88
+
89
+ // Set center position
90
+ const centerIdx = i * 8;
91
+ centers[centerIdx] = x;
92
+ centers[centerIdx + 1] = y;
93
+ centers[centerIdx + 2] = x;
94
+ centers[centerIdx + 3] = y;
95
+ centers[centerIdx + 4] = x;
96
+ centers[centerIdx + 5] = y;
97
+ centers[centerIdx + 6] = x;
98
+ centers[centerIdx + 7] = y;
99
+
100
+ // Calculate random frame from texture atlas
101
+ const frame = Math.floor(rng.random() * (particle.columns * particle.rows));
102
+ const col = frame % particle.columns;
103
+ const row = Math.floor(frame / particle.columns);
104
+
105
+ // Calculate UV coordinates for this frame
106
+ const uv0 = new THREE.Vector2(col / particle.columns, row / particle.rows);
107
+ const uv1 = new THREE.Vector2((col + 1) / particle.columns, (row + 1) / particle.rows);
108
+
109
+ // UV coordinates for quad
110
+ const uvBaseIdx = i * 8;
111
+ uvs[uvBaseIdx] = uv0.x; // bottom left
112
+ uvs[uvBaseIdx + 1] = uv0.y;
113
+ uvs[uvBaseIdx + 2] = uv1.x; // bottom right
114
+ uvs[uvBaseIdx + 3] = uv0.y;
115
+ uvs[uvBaseIdx + 4] = uv0.x; // top left
116
+ uvs[uvBaseIdx + 5] = uv1.y;
117
+ uvs[uvBaseIdx + 6] = uv1.x; // top right
118
+ uvs[uvBaseIdx + 7] = uv1.y;
119
+
120
+ // Indices for two triangles
121
+ const indexBaseIdx = i * 6;
122
+ const vertexBaseIdx = i * 4;
123
+ indices[indexBaseIdx] = vertexBaseIdx;
124
+ indices[indexBaseIdx + 1] = vertexBaseIdx + 1;
125
+ indices[indexBaseIdx + 2] = vertexBaseIdx + 2;
126
+ indices[indexBaseIdx + 3] = vertexBaseIdx + 1;
127
+ indices[indexBaseIdx + 4] = vertexBaseIdx + 3;
128
+ indices[indexBaseIdx + 5] = vertexBaseIdx + 2;
129
+
130
+ // Random attributes (same for all vertices of quad)
131
+ const ageOffset = rng.random() * props.lifetime;
132
+
133
+ for (let v = 0; v < 4; v++) {
134
+ ageOffsets[i * 4 + v] = ageOffset;
135
+ }
136
+ }
137
+
138
+ untrack(() => {
139
+ geometry = new THREE.BufferGeometry();
140
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
141
+ geometry.setAttribute('center', new THREE.BufferAttribute(centers, 2));
142
+ geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
143
+ geometry.setAttribute('ageOffset', new THREE.BufferAttribute(ageOffsets, 1));
144
+ geometry.setIndex(new THREE.BufferAttribute(indices, 1));
145
+ });
146
+
147
+ return () => {
148
+ geometry?.dispose();
149
+ };
150
+ });
151
+
152
+ const material = new THREE.ShaderMaterial();
153
+ const loader = useLoader(THREE.TextureLoader);
154
+
155
+ // Track current texture for disposal
156
+ let currentTexture: THREE.Texture | null = $state(null);
157
+
158
+ // Add cleanup on component destruction
159
+ onDestroy(() => {
160
+ geometry?.dispose();
161
+ if (currentTexture) {
162
+ currentTexture.dispose();
163
+ currentTexture = null;
164
+ }
165
+ });
166
+
167
+ // Create a derived value for uniforms that updates when props change
168
+ const uniforms = $derived({
169
+ uTime: { value: 0 },
170
+ uTexture: { value: currentTexture },
171
+ uOpacity: { value: opacity },
172
+ uColor: { value: new THREE.Color(props.color) },
173
+
174
+ uLifetime: { value: props.lifetime },
175
+ uAngularVelocity: { value: props.rotation.velocity },
176
+ uInitialVelocity: { value: props.initialVelocity },
177
+ uLinearForceAmplitude: { value: props.force.linear },
178
+ uExponentialForceAmplitude: { value: props.force.exponential },
179
+ uSinusoidalForceAmplitude: { value: props.force.sinusoidal.amplitude },
180
+ uSinusoidalForceFrequency: { value: props.force.sinusoidal.frequency },
181
+ uFadeInTime: { value: props.fadeInTime },
182
+ uFadeOutTime: { value: props.fadeOutTime },
183
+ uScale: { value: props.scale }
184
+ });
185
+
186
+ $effect(() => {
187
+ loader.load(ParticleData[props.type].url).then((newTexture) => {
188
+ untrack(() => {
189
+ currentTexture?.dispose();
190
+ currentTexture = newTexture;
191
+
192
+ if (material.uniforms.uTexture) {
193
+ material.uniforms.uTexture.value = newTexture;
194
+ }
195
+ });
196
+ });
197
+ });
198
+
199
+ // Update material uniforms whenever they change
200
+ $effect(() => {
201
+ Object.assign(material.uniforms, uniforms);
202
+ });
203
+
204
+ useTask((dt) => {
205
+ material.uniforms.uTime.value += dt;
206
+ });
207
+ </script>
208
+
209
+ <T.Mesh bind:ref={mesh} {geometry}>
210
+ <T.ShaderMaterial
211
+ is={material}
212
+ {vertexShader}
213
+ {fragmentShader}
214
+ transparent={true}
215
+ depthWrite={false}
216
+ depthTest={false}
217
+ blending={THREE.NormalBlending}
218
+ side={THREE.DoubleSide}
219
+ />
220
+ </T.Mesh>
@@ -0,0 +1,20 @@
1
+ export class RNG {
2
+ m_w = 123456789;
3
+ m_z = 987654321;
4
+ mask = 0xffffffff;
5
+
6
+ constructor(seed) {
7
+ this.m_w = (123456789 + seed) & this.mask;
8
+ this.m_z = (987654321 - seed) & this.mask;
9
+ }
10
+
11
+ // Returns number between 0 (inclusive) and 1.0 (exclusive),
12
+ // just like Math.random().
13
+ random() {
14
+ this.m_z = (36969 * (this.m_z & 65535) + (this.m_z >> 16)) & this.mask;
15
+ this.m_w = (18000 * (this.m_w & 65535) + (this.m_w >> 16)) & this.mask;
16
+ let result = ((this.m_z << 16) + (this.m_w & 65535)) >>> 0;
17
+ result /= 4294967296;
18
+ return result;
19
+ }
20
+ }
@@ -0,0 +1,95 @@
1
+ import ash from './particles/atlases/ash.png';
2
+ import leaves from './particles/atlases/leaves.png';
3
+ import rain from './particles/atlases/rain.png';
4
+ import snow from './particles/atlases/snow.png';
5
+
6
+ export enum ParticleType {
7
+ Snow = 1,
8
+ Rain = 2,
9
+ Leaves = 3,
10
+ Ash = 4
11
+ }
12
+
13
+ export const ParticleData = {
14
+ [ParticleType.Snow]: {
15
+ url: snow,
16
+ size: 768,
17
+ columns: 3,
18
+ rows: 3
19
+ },
20
+ [ParticleType.Rain]: {
21
+ url: rain,
22
+ size: 768,
23
+ columns: 3,
24
+ rows: 3
25
+ },
26
+ [ParticleType.Leaves]: {
27
+ url: leaves,
28
+ size: 768,
29
+ columns: 3,
30
+ rows: 3
31
+ },
32
+ [ParticleType.Ash]: {
33
+ url: ash,
34
+ size: 768,
35
+ columns: 3,
36
+ rows: 3
37
+ }
38
+ } as const;
39
+
40
+ export interface ParticleSystemProps {
41
+ maxParticleCount: number;
42
+ type: ParticleType;
43
+ lifetime: number;
44
+ color: string;
45
+ opacity: number;
46
+ fadeInTime: number;
47
+ fadeOutTime: number;
48
+ initialVelocity: {
49
+ x: number;
50
+ y: number;
51
+ z: number;
52
+ };
53
+ force: {
54
+ linear: {
55
+ x: number;
56
+ y: number;
57
+ z: number;
58
+ };
59
+ exponential: {
60
+ x: number;
61
+ y: number;
62
+ z: number;
63
+ };
64
+ sinusoidal: {
65
+ amplitude: {
66
+ x: number;
67
+ y: number;
68
+ z: number;
69
+ };
70
+ frequency: {
71
+ x: number;
72
+ y: number;
73
+ z: number;
74
+ };
75
+ };
76
+ };
77
+ rotation: {
78
+ alignRadially: boolean;
79
+ offset: number;
80
+ velocity: number;
81
+ randomize: boolean;
82
+ };
83
+ scale: {
84
+ x: number;
85
+ y: number;
86
+ };
87
+ size: {
88
+ min: number;
89
+ max: number;
90
+ };
91
+ spawnArea: {
92
+ minRadius: number;
93
+ maxRadius: number;
94
+ };
95
+ }
@@ -0,0 +1,144 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import PerformanceOverlay from '../PerformanceOverlay/PerformanceOverlay.svelte';
4
+ import { resetMetrics } from '../../helpers/performanceMetrics.svelte';
5
+ import { debugState, setDebugEnabled } from '../../helpers/debugState.svelte';
6
+
7
+ interface Props {
8
+ /** Key combination to toggle (default: F9) */
9
+ shortcut?: string;
10
+ /** Callback when debug state changes (optional, for external notification) */
11
+ onToggle?: (enabled: boolean) => void;
12
+ /** Disabled layers for A/B testing */
13
+ disabledLayers?: string[];
14
+ }
15
+
16
+ const { shortcut = 'F9', onToggle, disabledLayers = [] }: Props = $props();
17
+
18
+ let showHelp = $state(false);
19
+
20
+ // Use the global debug state
21
+ const enabled = $derived(debugState.enableMetrics);
22
+
23
+ const parseShortcut = (shortcut: string) => {
24
+ const parts = shortcut.split('+').map((p) => p.trim());
25
+ const modifiers = ['shift', 'ctrl', 'control', 'alt', 'meta', 'cmd'];
26
+ const keyPart = parts.find((p) => !modifiers.includes(p.toLowerCase())) || '';
27
+
28
+ return {
29
+ shift: parts.some((p) => p.toLowerCase() === 'shift'),
30
+ ctrl: parts.some((p) => p.toLowerCase() === 'ctrl' || p.toLowerCase() === 'control'),
31
+ alt: parts.some((p) => p.toLowerCase() === 'alt'),
32
+ meta: parts.some((p) => p.toLowerCase() === 'meta' || p.toLowerCase() === 'cmd'),
33
+ // Preserve case for function keys (F9, F10, etc.)
34
+ key: keyPart.match(/^[fF]\d+$/) ? keyPart.toUpperCase() : keyPart.toLowerCase()
35
+ };
36
+ };
37
+
38
+ const shortcutConfig = parseShortcut(shortcut);
39
+
40
+ const formatShortcut = () => {
41
+ const parts = [];
42
+ if (shortcutConfig.shift) parts.push('Shift');
43
+ if (shortcutConfig.ctrl) parts.push('Ctrl');
44
+ if (shortcutConfig.alt) parts.push('Alt');
45
+ if (shortcutConfig.meta) parts.push('Cmd');
46
+ // Function keys display as-is (F9), others uppercase
47
+ parts.push(shortcutConfig.key.match(/^F\d+$/) ? shortcutConfig.key : shortcutConfig.key.toUpperCase());
48
+ return parts.join(' + ');
49
+ };
50
+
51
+ const isFormElement = (element: Element | null): boolean => {
52
+ if (!element) return false;
53
+ const tagName = element.tagName;
54
+ return (
55
+ tagName === 'INPUT' ||
56
+ tagName === 'TEXTAREA' ||
57
+ tagName === 'SELECT' ||
58
+ (element as HTMLElement).isContentEditable
59
+ );
60
+ };
61
+
62
+ const handleKeyDown = (e: KeyboardEvent) => {
63
+ // Don't trigger while typing in form elements
64
+ if (isFormElement(document.activeElement)) return;
65
+
66
+ // Function keys (F1-F12) are case-sensitive in e.key, others we compare lowercase
67
+ const isFunctionKey = shortcutConfig.key.match(/^F\d+$/);
68
+ const keyMatches = isFunctionKey ? e.key === shortcutConfig.key : e.key.toLowerCase() === shortcutConfig.key;
69
+
70
+ const matchesShortcut =
71
+ e.shiftKey === shortcutConfig.shift &&
72
+ e.ctrlKey === shortcutConfig.ctrl &&
73
+ e.altKey === shortcutConfig.alt &&
74
+ e.metaKey === shortcutConfig.meta &&
75
+ keyMatches;
76
+
77
+ if (matchesShortcut) {
78
+ e.preventDefault();
79
+ const newEnabled = !debugState.enableMetrics;
80
+ setDebugEnabled(newEnabled);
81
+ onToggle?.(newEnabled);
82
+
83
+ if (newEnabled) {
84
+ resetMetrics();
85
+ showHelp = true;
86
+ setTimeout(() => (showHelp = false), 2000);
87
+ }
88
+ }
89
+ };
90
+
91
+ onMount(() => {
92
+ window.addEventListener('keydown', handleKeyDown);
93
+ return () => window.removeEventListener('keydown', handleKeyDown);
94
+ });
95
+
96
+ /** Get debug props to spread into Stage props */
97
+ export const getDebugProps = () => ({
98
+ enableStats: debugState.enableMetrics,
99
+ loggingRate: 1000,
100
+ enableMetrics: debugState.enableMetrics,
101
+ logMetricsToConsole: debugState.logMetricsToConsole,
102
+ disabledLayers
103
+ });
104
+ </script>
105
+
106
+ {#if enabled}
107
+ <PerformanceOverlay visible={true} {disabledLayers} />
108
+ {/if}
109
+
110
+ {#if showHelp}
111
+ <div class="performanceDebugger__help">
112
+ Performance metrics enabled. Press {formatShortcut()} to disable.
113
+ </div>
114
+ {/if}
115
+
116
+ <style>
117
+ .performanceDebugger__help {
118
+ position: absolute;
119
+ bottom: 1rem;
120
+ left: 50%;
121
+ transform: translateX(-50%);
122
+ background: var(--fg);
123
+ color: var(--bg);
124
+ font-family: var(--font-sans);
125
+ font-size: 0.75rem;
126
+ padding: 0.5rem 1rem;
127
+ border-radius: var(--radius-2);
128
+ box-shadow: var(--shadow-2);
129
+ z-index: 1000;
130
+ pointer-events: none;
131
+ animation: performanceDebuggerFadeIn 0.2s var(--ease-out-3);
132
+ }
133
+
134
+ @keyframes performanceDebuggerFadeIn {
135
+ from {
136
+ opacity: 0;
137
+ transform: translateX(-50%) translateY(0.5rem);
138
+ }
139
+ to {
140
+ opacity: 1;
141
+ transform: translateX(-50%) translateY(0);
142
+ }
143
+ }
144
+ </style>
@@ -0,0 +1 @@
1
+ export { default as PerformanceDebugger } from './PerformanceDebugger.svelte';
@@ -0,0 +1,208 @@
1
+ <script lang="ts">
2
+ import {
3
+ performanceMetrics,
4
+ getAverageFps,
5
+ get1PercentLowFps,
6
+ getFrameTimes
7
+ } from '../../helpers/performanceMetrics.svelte';
8
+
9
+ interface Props {
10
+ visible?: boolean;
11
+ disabledLayers?: string[];
12
+ }
13
+
14
+ const { visible = true, disabledLayers = [] }: Props = $props();
15
+
16
+ const metrics = $derived(performanceMetrics.value);
17
+ const frameTimes = $derived(getFrameTimes());
18
+ const avgFps = $derived(getAverageFps());
19
+ const lowFps = $derived(get1PercentLowFps());
20
+
21
+ // Graph dimensions
22
+ const GRAPH_WIDTH = 200;
23
+ const GRAPH_HEIGHT = 60;
24
+ const MAX_FRAME_TIME = 50; // Cap at 50ms (20fps) for graph scale
25
+
26
+ // Generate SVG path for frame time graph
27
+ const graphPath = $derived(() => {
28
+ if (frameTimes.length < 2) return '';
29
+
30
+ const points = frameTimes.map((ft, i) => {
31
+ const x = (i / (frameTimes.length - 1)) * GRAPH_WIDTH;
32
+ const y = GRAPH_HEIGHT - Math.min(ft / MAX_FRAME_TIME, 1) * GRAPH_HEIGHT;
33
+ return `${x},${y}`;
34
+ });
35
+
36
+ return `M${points.join(' L')}`;
37
+ });
38
+
39
+ // Color based on FPS
40
+ const fpsColor = $derived(() => {
41
+ const fps = metrics.fps;
42
+ if (fps >= 55) return 'var(--fgSuccess)';
43
+ if (fps >= 30) return 'var(--fgWarning)';
44
+ return 'var(--fgDanger)';
45
+ });
46
+ </script>
47
+
48
+ {#if visible}
49
+ <div class="perfOverlay">
50
+ <div class="perfOverlay__header">Performance</div>
51
+
52
+ <div class="perfOverlay__section">
53
+ <div class="perfOverlay__row">
54
+ <span class="perfOverlay__label">FPS</span>
55
+ <span class="perfOverlay__value" style="color: {fpsColor()}">{metrics.fps.toFixed(0)}</span>
56
+ </div>
57
+ <div class="perfOverlay__row">
58
+ <span class="perfOverlay__label">Avg</span>
59
+ <span class="perfOverlay__value">{avgFps.toFixed(0)}</span>
60
+ </div>
61
+ <div class="perfOverlay__row">
62
+ <span class="perfOverlay__label">1% Low</span>
63
+ <span class="perfOverlay__value">{lowFps.toFixed(0)}</span>
64
+ </div>
65
+ </div>
66
+
67
+ <div class="perfOverlay__graph">
68
+ <svg width={GRAPH_WIDTH} height={GRAPH_HEIGHT} class="perfOverlay__svg">
69
+ <!-- 60fps line (16.67ms) -->
70
+ <line
71
+ x1="0"
72
+ y1={GRAPH_HEIGHT - (16.67 / MAX_FRAME_TIME) * GRAPH_HEIGHT}
73
+ x2={GRAPH_WIDTH}
74
+ y2={GRAPH_HEIGHT - (16.67 / MAX_FRAME_TIME) * GRAPH_HEIGHT}
75
+ stroke="var(--fgSuccess)"
76
+ stroke-opacity="0.3"
77
+ stroke-width="1"
78
+ stroke-dasharray="4,4"
79
+ />
80
+ <!-- 30fps line (33.33ms) -->
81
+ <line
82
+ x1="0"
83
+ y1={GRAPH_HEIGHT - (33.33 / MAX_FRAME_TIME) * GRAPH_HEIGHT}
84
+ x2={GRAPH_WIDTH}
85
+ y2={GRAPH_HEIGHT - (33.33 / MAX_FRAME_TIME) * GRAPH_HEIGHT}
86
+ stroke="var(--fgWarning)"
87
+ stroke-opacity="0.3"
88
+ stroke-width="1"
89
+ stroke-dasharray="4,4"
90
+ />
91
+ <!-- Frame time path -->
92
+ <path d={graphPath()} fill="none" stroke="var(--fgPrimary)" stroke-width="1.5" />
93
+ </svg>
94
+ </div>
95
+
96
+ <div class="perfOverlay__section">
97
+ <div class="perfOverlay__row">
98
+ <span class="perfOverlay__label">Frame</span>
99
+ <span class="perfOverlay__value">{metrics.frameTime.toFixed(2)}ms</span>
100
+ </div>
101
+ <div class="perfOverlay__row">
102
+ <span class="perfOverlay__label">Composer</span>
103
+ <span class="perfOverlay__value">{metrics.composerTime.toFixed(2)}ms</span>
104
+ </div>
105
+ <div class="perfOverlay__row">
106
+ <span class="perfOverlay__label">Overlay</span>
107
+ <span class="perfOverlay__value">{metrics.overlayTime.toFixed(2)}ms</span>
108
+ </div>
109
+ </div>
110
+
111
+ <div class="perfOverlay__section">
112
+ <div class="perfOverlay__row">
113
+ <span class="perfOverlay__label">Draw calls</span>
114
+ <span class="perfOverlay__value">{metrics.drawCalls}</span>
115
+ </div>
116
+ <div class="perfOverlay__row">
117
+ <span class="perfOverlay__label">Triangles</span>
118
+ <span class="perfOverlay__value">{metrics.triangles}</span>
119
+ </div>
120
+ <div class="perfOverlay__row">
121
+ <span class="perfOverlay__label">Textures</span>
122
+ <span class="perfOverlay__value">{metrics.textures}</span>
123
+ </div>
124
+ <div class="perfOverlay__row">
125
+ <span class="perfOverlay__label">Geometries</span>
126
+ <span class="perfOverlay__value">{metrics.geometries}</span>
127
+ </div>
128
+ </div>
129
+
130
+ {#if disabledLayers.length > 0}
131
+ <div class="perfOverlay__section">
132
+ <div class="perfOverlay__sublabel">Disabled layers</div>
133
+ {#each disabledLayers as layer}
134
+ <div class="perfOverlay__disabledLayer">{layer}</div>
135
+ {/each}
136
+ </div>
137
+ {/if}
138
+ </div>
139
+ {/if}
140
+
141
+ <style>
142
+ .perfOverlay {
143
+ position: absolute;
144
+ top: 0.5rem;
145
+ left: 0.5rem;
146
+ background: rgba(0, 0, 0, 0.85);
147
+ color: var(--fg);
148
+ font-family: var(--font-mono);
149
+ font-size: 0.75rem;
150
+ padding: 0.5rem;
151
+ border-radius: var(--radius-2);
152
+ z-index: 1000;
153
+ pointer-events: none;
154
+ min-width: 14rem;
155
+ }
156
+
157
+ .perfOverlay__header {
158
+ font-weight: var(--font-weight-7);
159
+ margin-bottom: 0.5rem;
160
+ padding-bottom: 0.25rem;
161
+ border-bottom: 1px solid var(--fgMuted);
162
+ }
163
+
164
+ .perfOverlay__section {
165
+ margin-bottom: 0.5rem;
166
+ }
167
+
168
+ .perfOverlay__section:last-child {
169
+ margin-bottom: 0;
170
+ }
171
+
172
+ .perfOverlay__row {
173
+ display: flex;
174
+ justify-content: space-between;
175
+ line-height: var(--font-lineheight-2);
176
+ }
177
+
178
+ .perfOverlay__label {
179
+ color: var(--fgMuted);
180
+ }
181
+
182
+ .perfOverlay__value {
183
+ font-weight: var(--font-weight-5);
184
+ }
185
+
186
+ .perfOverlay__sublabel {
187
+ color: var(--fgMuted);
188
+ font-size: 0.625rem;
189
+ margin-bottom: 0.125rem;
190
+ }
191
+
192
+ .perfOverlay__disabledLayer {
193
+ color: var(--fgWarning);
194
+ font-size: 0.625rem;
195
+ padding-left: 0.5rem;
196
+ }
197
+
198
+ .perfOverlay__graph {
199
+ margin: 0.5rem 0;
200
+ background: rgba(255, 255, 255, 0.05);
201
+ border-radius: var(--radius-1);
202
+ overflow: hidden;
203
+ }
204
+
205
+ .perfOverlay__svg {
206
+ display: block;
207
+ }
208
+ </style>
@@ -0,0 +1 @@
1
+ export { default as PerformanceOverlay } from './PerformanceOverlay.svelte';