@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,651 @@
1
+ <script lang="ts">
2
+ import * as THREE from 'three';
3
+ import { getContext, onMount, onDestroy, untrack } from 'svelte';
4
+ import { T, useThrelte, useTask } from '@threlte/core';
5
+ import {
6
+ EffectComposer,
7
+ EffectPass,
8
+ RenderPass,
9
+ BloomEffect,
10
+ VignetteEffect,
11
+ ChromaticAberrationEffect,
12
+ BlendFunction,
13
+ ToneMappingEffect,
14
+ ToneMappingMode,
15
+ LUT3DEffect
16
+ } from 'postprocessing';
17
+ import { getLUT } from './luts';
18
+ import { type Callbacks, type StageProps } from '../Stage/types';
19
+ import { MapLayerType, type MapLayerExports } from '../MapLayer/types';
20
+ import { clippingPlaneStore, updateClippingPlanes } from '../../helpers/clippingPlaneStore.svelte';
21
+ import { beginFrame, endFrame, startTiming, endTiming, logMetrics } from '../../helpers/performanceMetrics.svelte';
22
+ import { debugState } from '../../helpers/debugState.svelte';
23
+ import { getGridCellSize as getGridCellSizeHelper } from '../../helpers/grid';
24
+ import { SceneLayer, SceneLayerOrder, SceneLoadingState } from './types';
25
+ import type { AnnotationExports } from '../AnnotationLayer/types';
26
+ import AnnotationLayer from '../AnnotationLayer/AnnotationLayer.svelte';
27
+ import CursorLayer from '../CursorLayer/CursorLayer.svelte';
28
+ import type { CursorData } from '../CursorLayer/types';
29
+ import EdgeOverlayLayer from '../EdgeOverlayLayer/EdgeOverlayLayer.svelte';
30
+ import GridLayer from '../GridLayer/GridLayer.svelte';
31
+ import MapLayer from '../MapLayer/MapLayer.svelte';
32
+ import MarkerLayer from '../MarkerLayer/MarkerLayer.svelte';
33
+ import MeasurementLayer from '../MeasurementLayer/MeasurementLayer.svelte';
34
+ import type { MarkerLayerExports } from '../MarkerLayer/types';
35
+ import WeatherLayer from '../WeatherLayer/WeatherLayer.svelte';
36
+
37
+ interface Props {
38
+ props: StageProps;
39
+ cursors?: CursorData[];
40
+ trackLocalCursor?: boolean;
41
+ receivedMeasurement?: {
42
+ startPoint: { x: number; y: number };
43
+ endPoint: { x: number; y: number };
44
+ type: number;
45
+ beamWidth?: number;
46
+ coneAngle?: number;
47
+ // Visual properties
48
+ color?: string;
49
+ thickness?: number;
50
+ outlineColor?: string;
51
+ outlineThickness?: number;
52
+ opacity?: number;
53
+ markerSize?: number;
54
+ // Timing properties
55
+ autoHideDelay?: number;
56
+ fadeoutTime?: number;
57
+ // Distance properties
58
+ showDistance?: boolean;
59
+ snapToGrid?: boolean;
60
+ enableDMG252?: boolean;
61
+ } | null;
62
+ }
63
+
64
+ let { props, receivedMeasurement = null, cursors = [], trackLocalCursor = false }: Props = $props();
65
+
66
+ const { scene, renderer, camera, size, autoRender, renderStage } = useThrelte();
67
+
68
+ const callbacks = getContext<Callbacks>('callbacks');
69
+ const onSceneUpdate = callbacks.onSceneUpdate;
70
+
71
+ // Type definition for MeasurementLayer exports
72
+ type MeasurementLayerExports = {
73
+ getCurrentMeasurement: () => {
74
+ startPoint: THREE.Vector2 | null;
75
+ endPoint: THREE.Vector2 | null;
76
+ type: number;
77
+ } | null;
78
+ isCurrentlyDrawing: () => boolean;
79
+ };
80
+
81
+ let annotationsLayer: AnnotationExports;
82
+ let mapLayer: MapLayerExports;
83
+ let markerLayer: MarkerLayerExports;
84
+ let measurementLayer: MeasurementLayerExports | null = $state(null);
85
+ let needsResize = true;
86
+ let loadingState = SceneLoadingState.LoadingMap;
87
+
88
+ // Local cursor tracking
89
+ let raycaster = new THREE.Raycaster();
90
+ raycaster.layers.enable(SceneLayer.Main);
91
+
92
+ // Pre-allocated objects for mouse tracking to avoid GC pressure
93
+ const mouseNDC = new THREE.Vector2();
94
+ const intersectionPlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
95
+ const intersectionPoint = new THREE.Vector3();
96
+
97
+ let composer = new EffectComposer(renderer);
98
+
99
+ onMount(() => {
100
+ let before = autoRender.current;
101
+ autoRender.set(false);
102
+ renderer.autoClear = false;
103
+ renderer.setClearColor(0, 0);
104
+ // Cap pixel ratio for performance on weak GPUs (e.g., Mac Mini)
105
+ const maxDpr = props.display.maxPixelRatio ?? 2;
106
+ const dpr = Math.min(window.devicePixelRatio, maxDpr);
107
+ renderer.setPixelRatio(dpr);
108
+ renderer.localClippingEnabled = true;
109
+
110
+ // Add mouse tracking if enabled
111
+ if (trackLocalCursor && callbacks.onCursorMove) {
112
+ const handleMouseMove = (event: MouseEvent) => {
113
+ // Convert mouse position to normalized device coordinates
114
+ const rect = renderer.domElement.getBoundingClientRect();
115
+ mouseNDC.set(
116
+ ((event.clientX - rect.left) / rect.width) * 2 - 1,
117
+ -((event.clientY - rect.top) / rect.height) * 2 + 1
118
+ );
119
+
120
+ // Update raycaster with camera and mouse position
121
+ raycaster.setFromCamera(mouseNDC, $camera);
122
+
123
+ // Find intersection with the z=0 plane using ray.intersectPlane (no allocations)
124
+ const hit = raycaster.ray.intersectPlane(intersectionPlane, intersectionPoint);
125
+
126
+ if (hit) {
127
+ // Account for scene transform (offset and zoom)
128
+ const adjustedPos = {
129
+ x: (intersectionPoint.x - props.scene.offset.x) / props.scene.zoom,
130
+ y: (intersectionPoint.y - props.scene.offset.y) / props.scene.zoom,
131
+ z: 0
132
+ };
133
+
134
+ callbacks.onCursorMove?.(adjustedPos);
135
+ }
136
+ };
137
+
138
+ renderer.domElement.addEventListener('mousemove', handleMouseMove);
139
+
140
+ return () => {
141
+ autoRender.set(before);
142
+ renderer.domElement.removeEventListener('mousemove', handleMouseMove);
143
+ };
144
+ }
145
+
146
+ return () => {
147
+ autoRender.set(before);
148
+ };
149
+ });
150
+
151
+ onDestroy(() => {
152
+ composer.dispose();
153
+ });
154
+
155
+ // Setup camera and renderer in effect
156
+ $effect(() => {
157
+ if (!camera) return;
158
+
159
+ // Configure camera to see both layers
160
+ $camera.layers.disableAll();
161
+ $camera.layers.enable(SceneLayer.Main);
162
+ $camera.layers.enable(SceneLayer.Overlay);
163
+ });
164
+
165
+ // Whenever the scene or display properties change, update the clipping planes
166
+ $effect(() => {
167
+ updateClippingPlanes(props.scene, props.display);
168
+ untrack(() => (renderer.clippingPlanes = clippingPlaneStore.value));
169
+ });
170
+
171
+ // Update needsResize when map URL changes
172
+ $effect(() => {
173
+ const mapUrl = props.map.url;
174
+ if (mapUrl) {
175
+ needsResize = true;
176
+ }
177
+ });
178
+
179
+ // Effect to update post-processing settings when props change
180
+ $effect(() => {
181
+ const postProcessing = $state.snapshot(props.postProcessing);
182
+
183
+ // Need to convert the LUT to a LookupTexture
184
+ Promise.resolve(getLUT(postProcessing.lut.url))
185
+ .then((lut) => {
186
+ composer.dispose();
187
+ composer = new EffectComposer(renderer);
188
+
189
+ const effects = [];
190
+
191
+ const renderPass = new RenderPass(scene, $camera);
192
+ composer.addPass(renderPass);
193
+
194
+ if (postProcessing.enabled) {
195
+ if (postProcessing.bloom.enabled) {
196
+ const bloomEffect = new BloomEffect({
197
+ intensity: postProcessing.bloom.intensity,
198
+ mipmapBlur: postProcessing.bloom.mipmapBlur,
199
+ radius: postProcessing.bloom.radius,
200
+ levels: postProcessing.bloom.levels,
201
+ luminanceThreshold: postProcessing.bloom.threshold,
202
+ luminanceSmoothing: postProcessing.bloom.smoothing
203
+ });
204
+ effects.push(bloomEffect);
205
+ }
206
+
207
+ if (postProcessing.chromaticAberration.enabled) {
208
+ const chromaticAberrationEffect = new ChromaticAberrationEffect({
209
+ offset: new THREE.Vector2(postProcessing.chromaticAberration.offset),
210
+ radialModulation: true,
211
+ modulationOffset: 0.025
212
+ });
213
+ effects.push(chromaticAberrationEffect);
214
+ }
215
+
216
+ if (postProcessing.vignette.enabled) {
217
+ const vignetteEffect = new VignetteEffect({
218
+ offset: postProcessing.vignette.offset,
219
+ darkness: postProcessing.vignette.darkness,
220
+ blendFunction: BlendFunction.NORMAL
221
+ });
222
+ effects.push(vignetteEffect);
223
+ }
224
+
225
+ if (postProcessing.lut.enabled && postProcessing.lut.url !== null) {
226
+ const lutEffect = new LUT3DEffect(new THREE.Data3DTexture(), {
227
+ blendFunction: BlendFunction.SET
228
+ });
229
+ lutEffect.setSize($size.width, $size.height);
230
+
231
+ if (!lut) return;
232
+ lutEffect.lut.dispose();
233
+ lutEffect.lut = lut;
234
+
235
+ effects.push(lutEffect);
236
+ }
237
+
238
+ // Add final tonemapping pass
239
+ const toneMappingEffect = new ToneMappingEffect({
240
+ mode:
241
+ postProcessing.enabled && postProcessing.toneMapping.enabled
242
+ ? postProcessing.toneMapping.mode
243
+ : ToneMappingMode.LINEAR
244
+ });
245
+ effects.push(toneMappingEffect);
246
+
247
+ const effectPass = new EffectPass($camera, ...effects);
248
+ composer.addPass(effectPass);
249
+ }
250
+ })
251
+ .catch((error) => console.error(error));
252
+ });
253
+
254
+ $effect(() => {
255
+ const renderSize = new THREE.Vector2();
256
+ renderer.getSize(renderSize);
257
+
258
+ // Only update render/composer size if it doesn't match the canvas size
259
+ // This check must be done here; it does not work when placed in $effect
260
+ if (
261
+ renderSize.width !== $size.width ||
262
+ renderSize.height !== $size.height ||
263
+ composer.outputBuffer.width !== $size.width ||
264
+ composer.outputBuffer.height !== $size.height
265
+ ) {
266
+ needsResize = true;
267
+ }
268
+ });
269
+
270
+ // Check if any post-processing effects are active
271
+ const hasActiveEffects = $derived(() => {
272
+ const pp = props.postProcessing;
273
+ if (!pp.enabled) return false;
274
+ return pp.bloom.enabled || pp.chromaticAberration.enabled || pp.vignette.enabled || pp.lut.enabled;
275
+ });
276
+
277
+ // Custom render task
278
+ useTask(
279
+ (dt) => {
280
+ if (!scene || !renderer || !camera) return;
281
+
282
+ const enableMetrics = debugState.enableMetrics;
283
+ const frameStart = enableMetrics ? beginFrame() : 0;
284
+
285
+ if (needsResize) {
286
+ needsResize = false;
287
+ renderer.setSize($size.width, $size.height);
288
+ composer.setSize($size.width, $size.height);
289
+ if (props.scene.autoFit) {
290
+ fit();
291
+ }
292
+ }
293
+
294
+ renderer.clear();
295
+
296
+ // Render main scene with post-processing (or bypass if no effects active)
297
+ camera.current.layers.set(SceneLayer.Main);
298
+
299
+ let composerTime = 0;
300
+ if (hasActiveEffects()) {
301
+ const composerStart = enableMetrics ? startTiming() : 0;
302
+ composer.render(dt);
303
+ composerTime = enableMetrics ? endTiming(composerStart) : 0;
304
+ } else {
305
+ const composerStart = enableMetrics ? startTiming() : 0;
306
+ renderer.render(scene, camera.current);
307
+ composerTime = enableMetrics ? endTiming(composerStart) : 0;
308
+ }
309
+
310
+ // Render overlays (grid/ping) without post-processing
311
+ camera.current.layers.set(SceneLayer.Overlay);
312
+
313
+ const overlayStart = enableMetrics ? startTiming() : 0;
314
+ renderer.render(scene, camera.current);
315
+ const overlayTime = enableMetrics ? endTiming(overlayStart) : 0;
316
+
317
+ // Reset camera back to main layer
318
+ camera.current.layers.set(SceneLayer.Main);
319
+
320
+ // Update metrics if enabled
321
+ if (enableMetrics) {
322
+ endFrame(frameStart, renderer, { composerTime, overlayTime });
323
+
324
+ if (debugState.logMetricsToConsole) {
325
+ logMetrics(props.debug.loggingRate);
326
+ }
327
+ }
328
+
329
+ // If scene was resized, need to wait for prop update to finish
330
+ if (loadingState === SceneLoadingState.Resizing) {
331
+ setLoadingState(SceneLoadingState.Rendering);
332
+ } else if (loadingState === SceneLoadingState.Rendering) {
333
+ setLoadingState(SceneLoadingState.Initialized);
334
+ }
335
+ },
336
+ { stage: renderStage }
337
+ );
338
+
339
+ function setLoadingState(state: SceneLoadingState) {
340
+ loadingState = state;
341
+ if (state === SceneLoadingState.Initialized) {
342
+ callbacks.onStageInitialized();
343
+ }
344
+ }
345
+
346
+ export function fill() {
347
+ const canvasAspectRatio = renderer.domElement.clientWidth / renderer.domElement.clientHeight;
348
+ let sceneAspectRatio = props.display.resolution.x / props.display.resolution.y;
349
+ let sceneWidth = props.display.resolution.x;
350
+ let sceneHeight = props.display.resolution.y;
351
+
352
+ // Swap dimensions if rotated 90 or 270 degrees
353
+ if (props.scene.rotation === 90 || props.scene.rotation === 270) {
354
+ sceneAspectRatio = props.display.resolution.y / props.display.resolution.x;
355
+ sceneWidth = props.display.resolution.y;
356
+ sceneHeight = props.display.resolution.x;
357
+ }
358
+
359
+ let newZoom: number;
360
+ if (sceneAspectRatio > canvasAspectRatio) {
361
+ newZoom = renderer.domElement.clientHeight / sceneHeight;
362
+ } else {
363
+ newZoom = renderer.domElement.clientWidth / sceneWidth;
364
+ }
365
+
366
+ onSceneUpdate({ x: 0, y: 0 }, newZoom);
367
+ }
368
+
369
+ export function fit() {
370
+ const canvasAspectRatio = $size.width / $size.height;
371
+ let sceneAspectRatio = props.display.resolution.x / props.display.resolution.y;
372
+ let sceneWidth = props.display.resolution.x;
373
+ let sceneHeight = props.display.resolution.y;
374
+
375
+ // Swap dimensions if rotated 90 or 270 degrees
376
+ if (props.scene.rotation === 90 || props.scene.rotation === 270) {
377
+ sceneAspectRatio = props.display.resolution.y / props.display.resolution.x;
378
+ sceneWidth = props.display.resolution.y;
379
+ sceneHeight = props.display.resolution.x;
380
+ }
381
+
382
+ let newZoom: number;
383
+ if (sceneAspectRatio < canvasAspectRatio) {
384
+ newZoom = $size.height / sceneHeight;
385
+ } else {
386
+ newZoom = $size.width / sceneWidth;
387
+ }
388
+
389
+ onSceneUpdate({ x: 0, y: 0 }, newZoom);
390
+ }
391
+
392
+ export async function generateThumbnail(quality: number = 0.5): Promise<Blob> {
393
+ const texture = mapLayer.getCompositeMapTexture();
394
+
395
+ if (!texture) return new Blob();
396
+
397
+ // Store original scene state
398
+ const originalScene = scene;
399
+ const originalCamera = camera.current;
400
+ const originalSize = $size;
401
+
402
+ const displayWidth = props.display.resolution.x;
403
+ const displayHeight = props.display.resolution.y;
404
+
405
+ // Handle both image and video textures
406
+ let imageWidth: number;
407
+ let imageHeight: number;
408
+
409
+ const textureImage = texture.image as HTMLVideoElement | HTMLImageElement;
410
+ if (textureImage instanceof HTMLVideoElement) {
411
+ // For video textures, use videoWidth and videoHeight
412
+ imageWidth = textureImage.videoWidth || displayWidth;
413
+ imageHeight = textureImage.videoHeight || displayHeight;
414
+ } else {
415
+ // For image textures, use width and height
416
+ imageWidth = textureImage.width;
417
+ imageHeight = textureImage.height;
418
+ }
419
+
420
+ // Create a temporary scene and camera for rendering
421
+ const tempScene = new THREE.Scene();
422
+ const tempCamera = new THREE.OrthographicCamera(
423
+ -displayWidth / 2,
424
+ displayWidth / 2,
425
+ displayHeight / 2,
426
+ -displayHeight / 2,
427
+ 0.1,
428
+ 1000
429
+ );
430
+ tempCamera.position.z = 100;
431
+
432
+ // Create a quad to render the texture
433
+ const geometry = new THREE.PlaneGeometry(1, 1);
434
+ const material = new THREE.MeshBasicMaterial({ map: texture.clone() });
435
+ const quad = new THREE.Mesh(geometry, material);
436
+ quad.position.set(props.map.offset.x, props.map.offset.y, 0);
437
+ quad.rotation.z = (props.map.rotation / 180.0) * Math.PI;
438
+ quad.scale.set(imageWidth * props.map.zoom, imageHeight * props.map.zoom, 1);
439
+ tempScene.add(quad);
440
+
441
+ // Temporarily replace scene and camera
442
+ composer.setMainScene(tempScene);
443
+ composer.setMainCamera(tempCamera);
444
+ renderer.setSize(displayWidth, displayHeight);
445
+ composer.setSize(displayWidth, displayHeight);
446
+
447
+ // Temporarily disable clipping planes
448
+ renderer.clippingPlanes = [];
449
+
450
+ // Render to the offscreen canvas
451
+ composer.render();
452
+
453
+ const offscreenCanvas = new OffscreenCanvas(displayWidth, displayHeight);
454
+ const context = offscreenCanvas.getContext('2d');
455
+ context?.drawImage(renderer.domElement, 0, 0, displayWidth, displayHeight);
456
+
457
+ // Restore original state
458
+ composer.setMainScene(originalScene);
459
+ composer.setMainCamera(originalCamera);
460
+ renderer.setSize(originalSize.width, originalSize.height);
461
+ composer.setSize(originalSize.width, originalSize.height);
462
+ renderer.clippingPlanes = clippingPlaneStore.value;
463
+
464
+ // Clean up
465
+ geometry.dispose();
466
+ material.dispose();
467
+
468
+ return offscreenCanvas.convertToBlob({ type: 'image/jpeg', quality });
469
+ }
470
+
471
+ export const annotations = {
472
+ clear: (layerId: string) => annotationsLayer.clear(layerId),
473
+ toRLE: () => annotationsLayer?.toRLE(),
474
+ fromRLE: (rleData: Uint8Array, width: number, height: number) => annotationsLayer?.fromRLE(rleData, width, height),
475
+ loadMask: (layerId: string, rleData: Uint8Array) => annotationsLayer?.loadMask(layerId, rleData),
476
+ isDrawing: () => annotationsLayer?.isDrawing() ?? false
477
+ };
478
+
479
+ export const map = {
480
+ fill: () => mapLayer.fill(),
481
+ fit: () => mapLayer.fit(),
482
+ getSize: () => mapLayer?.mapSize ?? null
483
+ };
484
+
485
+ // References to the layer doesn't exist until the component is mounted,
486
+ // so we need create these wrapper functions
487
+ export const fogOfWar = {
488
+ clear: () => mapLayer.fogOfWar.clear(),
489
+ reset: () => mapLayer.fogOfWar.reset(),
490
+ toPng: () => mapLayer.fogOfWar.toPng(),
491
+ toRLE: () => mapLayer.fogOfWar.toRLE(),
492
+ fromRLE: (rleData: Uint8Array, width: number, height: number) => mapLayer.fogOfWar.fromRLE(rleData, width, height),
493
+ isDrawing: () => mapLayer?.fogOfWar?.isDrawing() ?? false
494
+ };
495
+
496
+ // Export marker state getters
497
+ export const markers = {
498
+ get isHoveringMarker() {
499
+ return markerLayer?.markerState?.isHovering ?? false;
500
+ },
501
+ get isDraggingMarker() {
502
+ return markerLayer?.markerState?.isDragging ?? false;
503
+ },
504
+ get hoveredMarker() {
505
+ return markerLayer?.markerState?.hoveredMarker ?? null;
506
+ },
507
+ get selectedMarker() {
508
+ return markerLayer?.markerState?.selectedMarker ?? null;
509
+ },
510
+ maintainHover: (maintain: boolean) => {
511
+ markerLayer?.maintainHover?.(maintain);
512
+ },
513
+ onSceneChange: () => {
514
+ markerLayer?.onSceneChange?.();
515
+ }
516
+ };
517
+
518
+ // Export measurement layer methods
519
+ export const measurement = {
520
+ getCurrentMeasurement: () => measurementLayer?.getCurrentMeasurement?.() ?? null,
521
+ isDrawing: () => measurementLayer?.isCurrentlyDrawing?.() ?? false
522
+ };
523
+
524
+ export function getMarkerSizeInScreenSpace(markerSize = 1) {
525
+ const worldGridSize = getGridCellSizeHelper(props.grid, props.display);
526
+ const worldMarkerDiameter = worldGridSize * markerSize * 0.9;
527
+ const zoomedMarkerDiameter = worldMarkerDiameter * props.scene.zoom;
528
+ const screenMarkerDiameter = (zoomedMarkerDiameter / props.display.resolution.x) * $size.width;
529
+
530
+ return screenMarkerDiameter;
531
+ }
532
+
533
+ export function getMarkerScreenPosition(marker: { position?: { x: number; y: number } }) {
534
+ if (!marker?.position) return null;
535
+
536
+ // Create a vector at the marker's local position
537
+ const vector = new THREE.Vector3(marker.position.x, marker.position.y, 0);
538
+
539
+ // Apply scene transformations to get world position
540
+ // The markers are rendered inside a T.Object3D with position and scale transforms
541
+ vector.x = vector.x * props.scene.zoom + props.scene.offset.x;
542
+ vector.y = vector.y * props.scene.zoom + props.scene.offset.y;
543
+
544
+ // Project world position to screen space
545
+ vector.project(camera.current);
546
+
547
+ // Convert from normalized device coordinates (-1 to 1) to screen coordinates
548
+ const x = (vector.x * 0.5 + 0.5) * $size.width;
549
+ const y = (-vector.y * 0.5 + 0.5) * $size.height;
550
+
551
+ return { x, y };
552
+ }
553
+ </script>
554
+
555
+ <T.OrthographicCamera
556
+ makeDefault
557
+ near={0.1}
558
+ far={1000}
559
+ rotation={[0, 0, (props.scene.rotation * Math.PI) / 180]}
560
+ position={[0, 0, 100]}
561
+ ></T.OrthographicCamera>
562
+
563
+ <!-- Scene -->
564
+ <T.Object3D position={[props.scene.offset.x, props.scene.offset.y, 0]} scale={[props.scene.zoom, props.scene.zoom, 1]}>
565
+ <MapLayer
566
+ bind:this={mapLayer}
567
+ {props}
568
+ onMapLoading={() => {
569
+ callbacks.onStageLoading();
570
+ setLoadingState(SceneLoadingState.LoadingMap);
571
+ }}
572
+ onMapLoaded={() => {
573
+ needsResize = true;
574
+ if (loadingState === SceneLoadingState.LoadingMap) {
575
+ setLoadingState(SceneLoadingState.Resizing);
576
+ }
577
+ }}
578
+ />
579
+
580
+ <WeatherLayer
581
+ {props}
582
+ size={props.display.resolution}
583
+ layers={[SceneLayer.Main]}
584
+ renderOrder={SceneLayerOrder.Weather}
585
+ />
586
+
587
+ <!-- Map overlays that scale with the scene -->
588
+ <GridLayer
589
+ grid={props.grid}
590
+ display={props.display}
591
+ sceneZoom={props.scene.zoom}
592
+ layers={[SceneLayer.Overlay]}
593
+ renderOrder={SceneLayerOrder.Grid}
594
+ />
595
+
596
+ <EdgeOverlayLayer
597
+ props={props.edgeOverlay}
598
+ display={props.display}
599
+ visible={props.edgeOverlay.enabled}
600
+ layers={[SceneLayer.Overlay]}
601
+ renderOrder={SceneLayerOrder.EdgeOverlay}
602
+ />
603
+
604
+ <AnnotationLayer
605
+ bind:this={annotationsLayer}
606
+ props={props.annotations}
607
+ mode={props.mode}
608
+ isActive={props.activeLayer === MapLayerType.Annotation}
609
+ sceneZoom={props.scene.zoom}
610
+ display={props.display}
611
+ />
612
+
613
+ <MarkerLayer
614
+ bind:this={markerLayer}
615
+ {props}
616
+ isActive={props.activeLayer === MapLayerType.Marker || props.activeLayer === MapLayerType.None}
617
+ grid={props.grid}
618
+ display={props.display}
619
+ />
620
+
621
+ {#if props.measurement}
622
+ <MeasurementLayer
623
+ bind:this={measurementLayer}
624
+ props={props.measurement}
625
+ isActive={props.activeLayer === MapLayerType.Measurement}
626
+ display={props.display}
627
+ grid={props.grid}
628
+ sceneRotation={props.scene.rotation}
629
+ onMeasurementStart={callbacks.onMeasurementStart}
630
+ onMeasurementUpdate={callbacks.onMeasurementUpdate}
631
+ onMeasurementEnd={callbacks.onMeasurementEnd}
632
+ {receivedMeasurement}
633
+ />
634
+ {:else}
635
+ <!-- MeasurementLayer skipped: props.measurement is undefined -->
636
+ {/if}
637
+
638
+ <!-- Cursor Layer for rendering remote cursors -->
639
+ <CursorLayer
640
+ props={{
641
+ cursors: cursors,
642
+ showLabels: true,
643
+ fadeOutDelay: 5000,
644
+ fadeOutDuration: 500,
645
+ gridSpacing: props.grid.spacing,
646
+ displaySize: props.display.size,
647
+ displayResolution: props.display.resolution,
648
+ sceneZoom: props.scene.zoom
649
+ }}
650
+ />
651
+ </T.Object3D>
@@ -0,0 +1,24 @@
1
+ import { LookupTexture } from 'postprocessing';
2
+ import * as THREE from 'three';
3
+ import { LUTCubeLoader } from 'three/examples/jsm/loaders/LUTCubeLoader';
4
+
5
+ const loadingManager = new THREE.LoadingManager();
6
+ const lutLoader = new LUTCubeLoader(loadingManager);
7
+
8
+ /**
9
+ * Gets a LUT
10
+ * @param url The URL of the LUT to get
11
+ * @returns A promise that resolves to the LookupTexture
12
+ */
13
+ export const getLUT = async (url: string | null): Promise<LookupTexture | null> => {
14
+ if (!url) return null;
15
+ const result = await lutLoader.loadAsync(url);
16
+ try {
17
+ const lookupTexture = LookupTexture.from(result.texture3D);
18
+ lookupTexture.colorSpace = THREE.LinearSRGBColorSpace;
19
+ return lookupTexture;
20
+ } catch (error) {
21
+ console.error(error);
22
+ return null;
23
+ }
24
+ };