@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,249 @@
1
+ <script lang="ts">
2
+ import * as THREE from 'three';
3
+ import { getContext, onDestroy } from 'svelte';
4
+ import { T, type Props as ThrelteProps } from '@threlte/core';
5
+ import { ToolType } from '../DrawingLayer/types';
6
+ import { type FogOfWarLayerProps } from './types';
7
+ import type { Size } from '../../types';
8
+ import type { Callbacks } from '../Stage/types';
9
+ import LayerInput from '../LayerInput/LayerInput.svelte';
10
+ import toolOutlineVertexShader from '../../shaders/default.vert?raw';
11
+ import toolOutlineFragmentShader from '../../shaders/ToolOutline.frag?raw';
12
+ import { SceneLayer } from '../Scene/types';
13
+ import FogOfWarMaterial from './FogOfWarMaterial.svelte';
14
+
15
+ interface Props extends ThrelteProps<typeof THREE.Mesh> {
16
+ props: FogOfWarLayerProps;
17
+ isActive: boolean;
18
+ mapSize: Size | null;
19
+ }
20
+
21
+ const { props, isActive, mapSize, ...meshProps }: Props = $props();
22
+
23
+ const onFogUpdate = getContext<Callbacks>('callbacks').onFogUpdate;
24
+
25
+ // Convert percentage-based tool.size to texture pixels for outline
26
+ // This must match the conversion in FogOfWarMaterial
27
+ const toolSizePixels = $derived.by(() => {
28
+ if (!mapSize) return props.tool.size;
29
+ const textureSize = Math.min(mapSize.width, mapSize.height);
30
+ return Math.round(textureSize * (props.tool.size / 100));
31
+ });
32
+
33
+ let mesh: THREE.Mesh = $state(new THREE.Mesh());
34
+ let outlineMesh: THREE.Mesh = $state(new THREE.Mesh());
35
+ let material: FogOfWarMaterial | undefined = $state();
36
+ let drawing = false;
37
+ let hasFinishedDrawing = false;
38
+
39
+ // Export drawing state so parent can check it
40
+ export function isDrawing() {
41
+ return drawing;
42
+ }
43
+
44
+ // If mouse leaves the drawing area, we need to reset the start position
45
+ // when it re-enters the drawing area to prevent the drawing from "jumping"
46
+ // to the new point
47
+ let lastPos: THREE.Vector2 | null = null;
48
+
49
+ // Add outline material
50
+ // Initial value will be updated by $effect below
51
+ const outlineMaterial = new THREE.ShaderMaterial({
52
+ uniforms: {
53
+ uStart: { value: new THREE.Vector2(Infinity, Infinity) },
54
+ uEnd: { value: new THREE.Vector2(Infinity, Infinity) },
55
+ uBrushSize: { value: props.tool.size }, // Will be updated to pixels in $effect
56
+ uTextureSize: { value: new THREE.Vector2(0, 0) },
57
+ uShapeType: { value: props.tool.type },
58
+ uOutlineColor: { value: new THREE.Color(props.outline.color) },
59
+ uOutlineOpacity: { value: props.outline.opacity },
60
+ uOutlineThickness: { value: props.outline.thickness }
61
+ },
62
+ vertexShader: toolOutlineVertexShader,
63
+ fragmentShader: toolOutlineFragmentShader,
64
+ transparent: true,
65
+ depthTest: false
66
+ });
67
+
68
+ onDestroy(() => {
69
+ outlineMaterial.dispose();
70
+ });
71
+
72
+ // Whenever the tool becomes inactive, reset the drawing state and hide outline
73
+ $effect(() => {
74
+ if (!isActive) {
75
+ lastPos = null;
76
+ drawing = false;
77
+ material?.revertChanges();
78
+ outlineMesh.visible = false;
79
+ // Reset cursor position so it doesn't appear at old location when reactivated
80
+ outlineMaterial.uniforms.uStart.value.set(Infinity, Infinity);
81
+ outlineMaterial.uniforms.uEnd.value.set(Infinity, Infinity);
82
+ }
83
+ });
84
+
85
+ // Update outline material uniforms (but don't control visibility here - let draw() handle it)
86
+ $effect(() => {
87
+ if (!mapSize) return;
88
+
89
+ // Use .set() to avoid allocating new objects
90
+ outlineMaterial.uniforms.uTextureSize.value.set(mapSize.width, mapSize.height);
91
+ outlineMaterial.uniforms.uOutlineColor.value.set(props.outline.color);
92
+ outlineMaterial.uniforms.uOutlineThickness.value = props.outline.thickness;
93
+ outlineMaterial.uniforms.uOutlineOpacity.value = props.outline.opacity;
94
+ outlineMaterial.uniforms.uShapeType.value = props.tool.type;
95
+ outlineMaterial.uniforms.uBrushSize.value = toolSizePixels;
96
+ });
97
+
98
+ function onMouseDown(e: Event, p: THREE.Vector2 | null) {
99
+ e.preventDefault();
100
+ lastPos = p;
101
+ drawing = true;
102
+ hasFinishedDrawing = false;
103
+
104
+ draw(e, p);
105
+ }
106
+
107
+ function onMouseUp(_e: Event, p: THREE.Vector2 | null) {
108
+ // If using shapes, draw the shape outline when the mouse button is released
109
+ if (props.tool.type === ToolType.Ellipse || props.tool.type === ToolType.Rectangle) {
110
+ if (p && drawing && lastPos) {
111
+ material?.drawPath(p, lastPos, true);
112
+ outlineMesh.visible = false;
113
+ hasFinishedDrawing = true;
114
+ }
115
+ }
116
+
117
+ if (hasFinishedDrawing) {
118
+ onFogUpdate(toPng());
119
+ }
120
+
121
+ // Reset the drawing state
122
+ lastPos = null;
123
+ drawing = false;
124
+ hasFinishedDrawing = false;
125
+ }
126
+
127
+ function onMouseLeave() {
128
+ lastPos = null;
129
+ drawing = false;
130
+ outlineMesh.visible = false;
131
+ material?.revertChanges();
132
+
133
+ // Hide cursor when mouse leaves
134
+ outlineMaterial.uniforms.uStart.value.set(Infinity, Infinity);
135
+ outlineMaterial.uniforms.uEnd.value.set(Infinity, Infinity);
136
+ }
137
+
138
+ function draw(e: Event, p: THREE.Vector2 | null) {
139
+ // If the mouse is not within the drawing area, do nothing
140
+ if (!p) {
141
+ // Move cursor off-screen when mouse is outside
142
+ outlineMaterial.uniforms.uStart.value.set(Infinity, Infinity);
143
+ outlineMaterial.uniforms.uEnd.value.set(Infinity, Infinity);
144
+ return;
145
+ }
146
+
147
+ if (props.tool.type === ToolType.Ellipse || props.tool.type === ToolType.Rectangle) {
148
+ // Shapes don't use lazy brush
149
+ outlineMaterial.uniforms.uStart.value.copy(p);
150
+ outlineMaterial.uniforms.uEnd.value.copy(lastPos ?? p);
151
+
152
+ // When using shapes, draw the shape outline while the mouse button is held down
153
+ if (drawing) {
154
+ outlineMesh.visible = true;
155
+ material?.drawPath(p, lastPos);
156
+ }
157
+ } else {
158
+ // For freehand tools, always show the cursor
159
+ outlineMesh.visible = true;
160
+
161
+ // For freehand drawing
162
+ if (drawing) {
163
+ if (!lastPos) {
164
+ lastPos = p.clone();
165
+ }
166
+ material?.drawPath(p, lastPos, true);
167
+ hasFinishedDrawing = true;
168
+ lastPos = p.clone();
169
+ }
170
+
171
+ // Always show cursor at current position
172
+ outlineMaterial.uniforms.uStart.value.copy(p);
173
+ outlineMaterial.uniforms.uEnd.value.copy(p);
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Clears all fog, revealing the entire map underneath
179
+ */
180
+ export function clearFog() {
181
+ material?.clear();
182
+ onFogUpdate(toPng());
183
+ }
184
+
185
+ /**
186
+ * Resets the fog to fill the entire layer
187
+ */
188
+ export function resetFog() {
189
+ material?.fill();
190
+ onFogUpdate(toPng());
191
+ }
192
+
193
+ /**
194
+ * Serializes the fog of war image data into a binary buffer
195
+ * @return A binary buffer
196
+ */
197
+ export async function toPng(): Promise<Blob> {
198
+ return (await material?.toPng()) ?? new Blob();
199
+ }
200
+
201
+ /**
202
+ * Exports the fog of war state as RLE-encoded data
203
+ * @returns RLE encoded Uint8Array
204
+ */
205
+ export async function toRLE(): Promise<Uint8Array> {
206
+ return (await material?.toRLE()) ?? new Uint8Array();
207
+ }
208
+
209
+ /**
210
+ * Loads RLE-encoded data into the fog of war
211
+ * @param rleData RLE encoded data
212
+ * @param width Image width
213
+ * @param height Image height
214
+ */
215
+ export async function fromRLE(rleData: Uint8Array, width: number, height: number) {
216
+ return material?.fromRLE(rleData, width, height);
217
+ }
218
+ </script>
219
+
220
+ <LayerInput
221
+ id="fogOfWar"
222
+ {isActive}
223
+ layerSize={mapSize}
224
+ target={mesh}
225
+ {onMouseDown}
226
+ onMouseMove={draw}
227
+ {onMouseUp}
228
+ {onMouseLeave}
229
+ />
230
+
231
+ <!--
232
+ Invisible mesh used for input detection.
233
+ The plane geometry is larger than the map size to allow cursor
234
+ events to be detected outside of the fog of war layer.
235
+ -->
236
+ <T.Mesh bind:ref={mesh} name="fogOfWarInput" layer={SceneLayer.Input}>
237
+ <T.MeshBasicMaterial visible={false} />
238
+ <T.PlaneGeometry args={[10, 10]} />
239
+ </T.Mesh>
240
+
241
+ <T.Mesh bind:ref={outlineMesh} name="fogOfWarToolOutline" layers={[SceneLayer.Overlay]}>
242
+ <T is={outlineMaterial} transparent={true} opacity={0.0} depthTest={false} />
243
+ <T.PlaneGeometry />
244
+ </T.Mesh>
245
+
246
+ <T.Mesh name="fogOfWar" {...meshProps} layers={[SceneLayer.Main]}>
247
+ <FogOfWarMaterial bind:this={material} {props} {mapSize} />
248
+ <T.PlaneGeometry />
249
+ </T.Mesh>
@@ -0,0 +1,200 @@
1
+ <script lang="ts">
2
+ import * as THREE from 'three';
3
+ import { T, useTask } from '@threlte/core';
4
+ import { getContext, onDestroy } from 'svelte';
5
+ import DrawingMaterial from '../DrawingLayer/DrawingMaterial.svelte';
6
+ import { type FogOfWarLayerProps } from './types';
7
+ import type { Size } from '../../types';
8
+ import { clippingPlaneStore } from '../../helpers/clippingPlaneStore.svelte';
9
+ import { InitialState } from '../DrawingLayer/types';
10
+ import { StageMode } from '../Stage/types';
11
+ import fogVertexShader from '../../shaders/default.vert?raw';
12
+ import fogFragmentShader from '../../shaders/Fog.frag?raw';
13
+
14
+ interface Props {
15
+ props: FogOfWarLayerProps;
16
+ mapSize: Size | null;
17
+ }
18
+
19
+ const { props, mapSize }: Props = $props();
20
+
21
+ const stage = getContext<{ mode: StageMode }>('stage');
22
+ let drawMaterial: DrawingMaterial;
23
+
24
+ // Convert percentage-based tool.size to texture pixels
25
+ // tool.size is a percentage (5-20), mapSize gives texture dimensions
26
+ const toolSizePixels = $derived.by(() => {
27
+ if (!mapSize) return props.tool.size;
28
+ const textureSize = Math.min(mapSize.width, mapSize.height);
29
+ return Math.round(textureSize * (props.tool.size / 100));
30
+ });
31
+
32
+ // Create derived props with converted tool size
33
+ const drawingProps = $derived({
34
+ ...props,
35
+ tool: {
36
+ ...props.tool,
37
+ size: toolSizePixels
38
+ }
39
+ });
40
+
41
+ // Material used for rendering the fog of war
42
+ let fogMaterial = new THREE.ShaderMaterial({
43
+ uniforms: {
44
+ uMaskTexture: { value: null },
45
+ uTime: { value: 0.0 },
46
+ uBaseColor: { value: new THREE.Color(props.noise.baseColor) },
47
+ uFogColor1: { value: new THREE.Color(props.noise.fogColor1) },
48
+ uFogColor2: { value: new THREE.Color(props.noise.fogColor2) },
49
+ uFogColor3: { value: new THREE.Color(props.noise.fogColor3) },
50
+ uFogColor4: { value: new THREE.Color(props.noise.fogColor4) },
51
+ uFogSpeed: { value: props.noise.speed },
52
+ uEdgeMinMipMapLevel: { value: props.edge.minMipMapLevel },
53
+ uEdgeMaxMipMapLevel: { value: props.edge.maxMipMapLevel },
54
+ uEdgeFrequency: { value: props.edge.frequency },
55
+ uEdgeAmplitude: { value: props.edge.amplitude },
56
+ uEdgeOffset: { value: props.edge.offset },
57
+ uEdgeSpeed: { value: props.edge.speed },
58
+ uPersistence: { value: props.noise.persistence },
59
+ uLacunarity: { value: props.noise.lacunarity },
60
+ uFrequency: { value: props.noise.frequency },
61
+ uOffset: { value: props.noise.offset },
62
+ uAmplitude: { value: props.noise.amplitude },
63
+ uLevels: { value: props.noise.levels },
64
+ uOpacity: { value: stage.mode === StageMode.DM ? props.opacity.dm : props.opacity.player },
65
+ uClippingPlanes: new THREE.Uniform(
66
+ clippingPlaneStore.value.map((p) => new THREE.Vector4(p.normal.x, p.normal.y, p.normal.z, p.constant))
67
+ )
68
+ },
69
+ transparent: true,
70
+ fragmentShader: fogFragmentShader,
71
+ vertexShader: fogVertexShader
72
+ });
73
+
74
+ // Track opacity separately to ensure it updates properly
75
+ let currentOpacity = $derived(stage.mode === StageMode.DM ? props.opacity.dm : props.opacity.player);
76
+
77
+ // Update opacity when it changes
78
+ $effect(() => {
79
+ fogMaterial.uniforms.uOpacity.value = currentOpacity;
80
+ fogMaterial.uniformsNeedUpdate = true;
81
+ });
82
+
83
+ // Whenever the fog of war props change, we need to update the material
84
+ $effect(() => {
85
+ // Use .set() to avoid allocating new Color objects
86
+ fogMaterial.uniforms.uBaseColor.value.set(props.noise.baseColor);
87
+ fogMaterial.uniforms.uFogColor1.value.set(props.noise.fogColor1);
88
+ fogMaterial.uniforms.uFogColor2.value.set(props.noise.fogColor2);
89
+ fogMaterial.uniforms.uFogColor3.value.set(props.noise.fogColor3);
90
+ fogMaterial.uniforms.uFogColor4.value.set(props.noise.fogColor4);
91
+
92
+ fogMaterial.uniforms.uEdgeMinMipMapLevel.value = props.edge.minMipMapLevel;
93
+ fogMaterial.uniforms.uEdgeMaxMipMapLevel.value = props.edge.maxMipMapLevel;
94
+ fogMaterial.uniforms.uEdgeFrequency.value = props.edge.frequency;
95
+ fogMaterial.uniforms.uEdgeAmplitude.value = props.edge.amplitude;
96
+ fogMaterial.uniforms.uEdgeOffset.value = props.edge.offset;
97
+ fogMaterial.uniforms.uEdgeSpeed.value = props.edge.speed;
98
+ fogMaterial.uniforms.uFogSpeed.value = props.noise.speed;
99
+ fogMaterial.uniforms.uFrequency.value = props.noise.frequency;
100
+ fogMaterial.uniforms.uPersistence.value = props.noise.persistence;
101
+ fogMaterial.uniforms.uLacunarity.value = props.noise.lacunarity;
102
+ fogMaterial.uniforms.uLevels.value = props.noise.levels;
103
+ fogMaterial.uniforms.uOffset.value = props.noise.offset;
104
+ fogMaterial.uniforms.uAmplitude.value = props.noise.amplitude;
105
+
106
+ // Update clipping planes in place
107
+ const planes = clippingPlaneStore.value;
108
+ for (let i = 0; i < planes.length; i++) {
109
+ const p = planes[i];
110
+ fogMaterial.uniforms.uClippingPlanes.value[i].set(p.normal.x, p.normal.y, p.normal.z, p.constant);
111
+ }
112
+
113
+ // Force shader to update with new uniform values
114
+ fogMaterial.uniformsNeedUpdate = true;
115
+ });
116
+
117
+ useTask((delta) => {
118
+ fogMaterial.uniforms.uTime.value += delta;
119
+ });
120
+
121
+ /**
122
+ * Reverts the changes made to the fog of war
123
+ */
124
+ export function revertChanges() {
125
+ drawMaterial.revert();
126
+ }
127
+
128
+ /**
129
+ * Clears the fog of war
130
+ */
131
+ export function clear() {
132
+ drawMaterial.clear();
133
+ }
134
+
135
+ /**
136
+ * Fills the fog of war
137
+ */
138
+ export function fill() {
139
+ drawMaterial.fill();
140
+ }
141
+
142
+ /**
143
+ * Draws a path on the fog of war
144
+ * @param start The start position of the path
145
+ * @param last The last position of the path
146
+ * @param persist Whether to persist the current state
147
+ */
148
+ export function drawPath(start: THREE.Vector2, last: THREE.Vector2 | null = null, persist: boolean = false) {
149
+ drawMaterial.drawPath(start, last, persist);
150
+ }
151
+
152
+ /**
153
+ * Serializes the current fog of war state to a binary buffer
154
+ * @returns A binary buffer representation of the fog of war textuare
155
+ */
156
+ export async function toPng(): Promise<Blob> {
157
+ return drawMaterial.toPng();
158
+ }
159
+
160
+ /**
161
+ * Exports the fog of war state as RLE-encoded data
162
+ * @returns RLE encoded Uint8Array
163
+ */
164
+ export async function toRLE(): Promise<Uint8Array> {
165
+ return drawMaterial.toRLE();
166
+ }
167
+
168
+ /**
169
+ * Loads RLE-encoded data into the fog of war
170
+ * @param rleData RLE encoded data
171
+ * @param width Image width
172
+ * @param height Image height
173
+ */
174
+ export async function fromRLE(rleData: Uint8Array, width: number, height: number) {
175
+ return drawMaterial.fromRLE(rleData, width, height);
176
+ }
177
+
178
+ onDestroy(() => {
179
+ fogMaterial.dispose();
180
+ });
181
+ </script>
182
+
183
+ <DrawingMaterial
184
+ bind:this={drawMaterial}
185
+ props={drawingProps}
186
+ size={mapSize}
187
+ initialState={InitialState.Fill}
188
+ onRender={(texture) => {
189
+ fogMaterial.uniforms.uMaskTexture.value = texture;
190
+ fogMaterial.uniformsNeedUpdate = true;
191
+ }}
192
+ />
193
+
194
+ {#snippet attachMaterial()}
195
+ {fogMaterial}
196
+ {/snippet}
197
+
198
+ <T is={fogMaterial}>
199
+ {@render attachMaterial()}
200
+ </T>
@@ -0,0 +1,116 @@
1
+ import type { DrawingLayerProps } from '../DrawingLayer/types';
2
+
3
+ export interface FogOfWarLayerProps extends DrawingLayerProps {
4
+ /**
5
+ * The outline settings
6
+ */
7
+ outline: {
8
+ /**
9
+ * The color of the outline
10
+ */
11
+ color: string;
12
+
13
+ /**
14
+ * The opacity of the outline
15
+ */
16
+ opacity: number;
17
+
18
+ /**
19
+ * The thickness of the outline
20
+ */
21
+ thickness: number;
22
+ };
23
+
24
+ /**
25
+ * The edge settings
26
+ */
27
+ edge: {
28
+ /**
29
+ * The minimum mip map level to use for the edge mask
30
+ */
31
+ minMipMapLevel: number;
32
+
33
+ /**
34
+ * The maximum mip map level to use for the edge mask
35
+ */
36
+ maxMipMapLevel: number;
37
+
38
+ /**
39
+ * The frequency of the noise added to the mask edge
40
+ */
41
+ frequency: { x: number; y: number; z: number; w: number };
42
+
43
+ /**
44
+ * The amplitude of the noise added to the mask edge
45
+ */
46
+ amplitude: { x: number; y: number; z: number; w: number };
47
+
48
+ /**
49
+ * The offset of the noise added to the mask edge
50
+ */
51
+ offset: number;
52
+
53
+ /**
54
+ * The speed of the noise added to the mask edge
55
+ */
56
+ speed: number;
57
+ };
58
+
59
+ /**
60
+ * The noise settings
61
+ */
62
+ noise: {
63
+ /**
64
+ * Color of the fog
65
+ */
66
+ baseColor: string;
67
+ fogColor1: string;
68
+ fogColor2: string;
69
+ fogColor3: string;
70
+ fogColor4: string;
71
+
72
+ /**
73
+ * Controls the speed of the fog
74
+ */
75
+ speed: { x: number; y: number; z: number; w: number };
76
+
77
+ /**
78
+ * Controls the frequency of the fog
79
+ */
80
+ frequency: { x: number; y: number; z: number; w: number };
81
+
82
+ /**
83
+ * Controls the persistence of the fog
84
+ */
85
+ persistence: { x: number; y: number; z: number; w: number };
86
+
87
+ /**
88
+ * Controls the lacunarity of the fog
89
+ */
90
+ lacunarity: { x: number; y: number; z: number; w: number };
91
+
92
+ /**
93
+ * Controls the levels of the fog
94
+ */
95
+ levels: { x: number; y: number; z: number; w: number };
96
+
97
+ /**
98
+ * Controls the offset of the fog
99
+ */
100
+ offset: { x: number; y: number; z: number; w: number };
101
+
102
+ /**
103
+ * Controls the amplitude of the fog
104
+ */
105
+ amplitude: { x: number; y: number; z: number; w: number };
106
+ };
107
+ }
108
+
109
+ export interface FogOfWarExports {
110
+ clearFog: () => void;
111
+ resetFog: () => void;
112
+ toPng: () => Promise<Blob>;
113
+ toRLE: () => Promise<Uint8Array>;
114
+ fromRLE: (rleData: Uint8Array, width: number, height: number) => Promise<void>;
115
+ isDrawing: () => boolean;
116
+ }
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import * as THREE from 'three';
3
+ import { T, type Props as ThrelteProps } from '@threlte/core';
4
+ import { type GridLayerProps } from './types';
5
+ import { type DisplayProps } from '../Stage/types';
6
+ import GridMaterial from './GridMaterial.svelte';
7
+
8
+ interface Props extends ThrelteProps<typeof THREE.Mesh> {
9
+ grid: GridLayerProps;
10
+ display: DisplayProps;
11
+ sceneZoom: number;
12
+ }
13
+
14
+ const { grid, display, sceneZoom, ...meshProps }: Props = $props();
15
+ </script>
16
+
17
+ <T.Mesh name="gridLayer" scale={[display.resolution.x, display.resolution.y, 1]} {...meshProps}>
18
+ <GridMaterial {grid} {display} {sceneZoom} />
19
+ <T.PlaneGeometry />
20
+ </T.Mesh>
@@ -0,0 +1,69 @@
1
+ <script lang="ts">
2
+ import * as THREE from 'three';
3
+ import type { GridLayerProps } from './types';
4
+ import type { DisplayProps } from '../Stage/types';
5
+ import fragmentShader from '../../shaders/GridShader.frag?raw';
6
+ import vertexShader from '../../shaders/default.vert?raw';
7
+ import { T, useThrelte } from '@threlte/core';
8
+
9
+ interface Props {
10
+ grid: GridLayerProps;
11
+ display: DisplayProps;
12
+ sceneZoom: number;
13
+ }
14
+
15
+ let { invalidate } = useThrelte();
16
+ const { grid, display, sceneZoom }: Props = $props();
17
+
18
+ let material = new THREE.ShaderMaterial({
19
+ uniforms: {
20
+ uOpacity: new THREE.Uniform(grid.opacity),
21
+ uGridType: new THREE.Uniform(grid.gridType),
22
+ uGridMode: new THREE.Uniform(grid.gridMode || 0),
23
+ uSpacing_in: new THREE.Uniform(grid.spacing),
24
+ uPadding_px: new THREE.Uniform(display.padding),
25
+ uLineThickness: new THREE.Uniform(grid.lineThickness),
26
+ uLineColor: new THREE.Uniform(new THREE.Color(grid.lineColor)),
27
+ uShadowOpacity: new THREE.Uniform(grid.shadowOpacity),
28
+ uShadowBlur: new THREE.Uniform(grid.shadowBlur),
29
+ uShadowSpread: new THREE.Uniform(grid.shadowSpread),
30
+ uShadowColor: new THREE.Uniform(new THREE.Color(grid.shadowColor)),
31
+ uSceneScale: new THREE.Uniform(1),
32
+ uResolution_px: new THREE.Uniform(new THREE.Vector2(0, 0)),
33
+ uDisplaySize_in: new THREE.Uniform(new THREE.Vector2(0, 0)),
34
+ uFixedGridCountX: new THREE.Uniform(grid.fixedGridCount?.x || 24),
35
+ uFixedGridCountY: new THREE.Uniform(grid.fixedGridCount?.y || 17)
36
+ }
37
+ });
38
+
39
+ $effect(() => {
40
+ material.uniforms.uOpacity.value = grid.opacity;
41
+ material.uniforms.uSceneScale.value = sceneZoom;
42
+ material.uniforms.uGridType.value = grid.gridType;
43
+ material.uniforms.uGridMode.value = grid.gridMode || 0;
44
+ material.uniforms.uSpacing_in.value = grid.spacing;
45
+ material.uniforms.uPadding_px.value = display.padding;
46
+ material.uniforms.uLineThickness.value = grid.lineThickness;
47
+ // Use .set() to avoid allocating new objects
48
+ material.uniforms.uLineColor.value.set(grid.lineColor);
49
+ material.uniforms.uShadowOpacity.value = grid.shadowOpacity;
50
+ material.uniforms.uShadowBlur.value = grid.shadowBlur;
51
+ material.uniforms.uShadowSpread.value = grid.shadowSpread;
52
+ material.uniforms.uShadowColor.value.set(grid.shadowColor);
53
+ material.uniforms.uResolution_px.value.set(display.resolution.x, display.resolution.y);
54
+ material.uniforms.uDisplaySize_in.value.set(display.size.x, display.size.y);
55
+ material.uniforms.uFixedGridCountX.value = grid.fixedGridCount?.x || 24;
56
+ material.uniforms.uFixedGridCountY.value = grid.fixedGridCount?.y || 17;
57
+
58
+ invalidate();
59
+ });
60
+ </script>
61
+
62
+ {#snippet attachMaterial()}
63
+ {material}
64
+ {/snippet}
65
+
66
+ <!-- Export the material to be used in the parent component -->
67
+ <T is={material} {fragmentShader} {vertexShader} transparent={true} depthTest={false}>
68
+ {@render attachMaterial()}
69
+ </T>