@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,427 @@
1
+ import * as THREE from 'three';
2
+ import { hexDistance, pixelToHex, type HexCoordinate } from '../../../helpers/grid';
3
+ import type { GridLayerProps } from '../../GridLayer/types';
4
+ import { GridType } from '../../GridLayer/types';
5
+ import { SceneLayer, SceneLayerOrder } from '../../Scene/types';
6
+ import type { DisplayProps } from '../../Stage/types';
7
+ import { MeasurementType, type MeasurementLayerProps } from '../types';
8
+ import { createTextCanvas } from '../utils/canvasDrawing';
9
+ import { calculateLineDistance } from '../utils/distanceCalculations';
10
+
11
+ // =============================================================================
12
+ // MEASUREMENT CONSTANTS
13
+ // =============================================================================
14
+
15
+ /** Distance in pixels to offset text labels from measurement end points */
16
+ const TEXT_OFFSET_DISTANCE = 150;
17
+
18
+ /** Divisor for calculating font size based on display resolution */
19
+ const FONT_SIZE_DIVISOR = 15;
20
+
21
+ /** Decimal places for distance display */
22
+ const DISTANCE_DECIMAL_PLACES = 1;
23
+
24
+ /**
25
+ * Interface defining the contract that all measurement implementations must follow.
26
+ * Provides methods for lifecycle management, rendering, and data access for measurements.
27
+ */
28
+ export interface IMeasurement {
29
+ /** The Three.js group object containing the measurement visualization */
30
+ object: THREE.Group;
31
+ /** The rendered shape object in the Three.js scene */
32
+ shapeMesh: THREE.Mesh;
33
+ /** The rendered text object in the Three.js scene */
34
+ textMesh: THREE.Mesh;
35
+ /** Starting point of the measurement in world coordinates */
36
+ startPoint: THREE.Vector2;
37
+ /** Current end point of the measurement in world coordinates */
38
+ endPoint: THREE.Vector2;
39
+
40
+ /**
41
+ * Updates the measurement with a new end point, typically called during mouse movement.
42
+ * @param {THREE.Vector2} endPoint - The new end point coordinates in world space
43
+ * @param {number} sceneRotation - Optional scene rotation in degrees
44
+ * @returns {void}
45
+ */
46
+ update(endPoint: THREE.Vector2, sceneRotation?: number): void;
47
+
48
+ /**
49
+ * Renders only the shape portion of the measurement (without text labels).
50
+ * @returns {void}
51
+ */
52
+ renderShape(): void;
53
+
54
+ /**
55
+ * Cleans up all Three.js resources and removes the measurement from memory.
56
+ * Should be called when the measurement is no longer needed.
57
+ * @returns {void}
58
+ */
59
+ dispose(): void;
60
+ }
61
+
62
+ /**
63
+ * Abstract base class providing common functionality for all measurement types.
64
+ * Handles shared behavior like distance calculation, text rendering, resource management,
65
+ * and coordinate transformations while allowing subclasses to implement specific shapes.
66
+ */
67
+ export abstract class BaseMeasurement implements IMeasurement {
68
+ /** Unique identifier for this measurement instance */
69
+ public id: string;
70
+ /** The type of measurement (line, circle, rectangle, etc.) */
71
+ public type: MeasurementType;
72
+ /** Starting point of the measurement in world coordinates */
73
+ public startPoint: THREE.Vector2;
74
+ /** Current end point of the measurement in world coordinates */
75
+ public endPoint: THREE.Vector2;
76
+ /** Timestamp when the measurement was created */
77
+ public createdAt: number;
78
+ /** Primary color for the measurement visualization */
79
+ public color: string;
80
+ /** Opacity level for the measurement (0.0 to 1.0) */
81
+ public opacity: number;
82
+ /** Line thickness for drawing measurement elements */
83
+ public thickness: number;
84
+ /** Diameter of the measurement markers/points */
85
+ public markerSize: number;
86
+ /** Thickness of outline strokes around measurement elements */
87
+ public outlineThickness: number;
88
+ /** Color used for outline strokes */
89
+ public outlineColor: string;
90
+ /** Whether to display distance text labels */
91
+ public showDistance: boolean;
92
+ /** Whether to snap measurement points to the grid */
93
+ public snapToGrid: boolean;
94
+ /** Whether to use DMG 252 measurement calculations */
95
+ public enableDMG252: boolean;
96
+
97
+ /** Display properties containing resolution and size information */
98
+ protected displayProps: DisplayProps;
99
+ /** Grid properties containing spacing and units information */
100
+ protected gridProps: GridLayerProps;
101
+ /** Scene rotation in degrees for text alignment */
102
+ protected sceneRotation: number = 0;
103
+ /** The group object in the Three.js scene */
104
+ public object: THREE.Group;
105
+ /** The rendered shape object in the Three.js scene */
106
+ public shapeMesh: THREE.Mesh;
107
+ /** The rendered text object in the Three.js scene */
108
+ public textMesh: THREE.Mesh;
109
+
110
+ /** Flag indicating whether this measurement has been disposed */
111
+ protected isDisposed = false;
112
+
113
+ /**
114
+ * Creates a new measurement instance with the specified properties.
115
+ * Initializes all measurement parameters and sets up the coordinate system.
116
+ *
117
+ * @param {MeasurementType} type - The specific type of measurement being created
118
+ * @param {THREE.Vector2} startPoint - Initial starting point in world coordinates
119
+ * @param {MeasurementLayerProps} measurementProps - Configuration properties for the measurement
120
+ * @param {DisplayProps} displayProps - Display settings including resolution and dimensions
121
+ * @param {GridLayerProps} gridProps - Grid configuration for snapping and units
122
+ */
123
+ constructor(
124
+ type: MeasurementType,
125
+ startPoint: THREE.Vector2,
126
+ measurementProps: MeasurementLayerProps,
127
+ displayProps: DisplayProps,
128
+ gridProps: GridLayerProps
129
+ ) {
130
+ this.id = crypto.randomUUID();
131
+ this.type = type;
132
+ this.startPoint = startPoint.clone();
133
+ this.endPoint = startPoint.clone();
134
+ this.createdAt = Date.now();
135
+ this.color = measurementProps.color;
136
+ this.opacity = measurementProps.opacity;
137
+ this.thickness = measurementProps.thickness;
138
+ this.markerSize = measurementProps.markerSize;
139
+ this.outlineThickness = measurementProps.outlineThickness;
140
+ this.outlineColor = measurementProps.outlineColor;
141
+ this.showDistance = measurementProps.showDistance;
142
+ this.snapToGrid = measurementProps.snapToGrid;
143
+ this.enableDMG252 = measurementProps.enableDMG252;
144
+ this.displayProps = displayProps;
145
+ this.gridProps = gridProps;
146
+
147
+ this.object = new THREE.Group();
148
+ this.object.userData.measurementId = this.id;
149
+
150
+ // Create meshes and materials once, only update the texture when the measurement is updated
151
+ const shapeMaterial = new THREE.MeshBasicMaterial({
152
+ map: null,
153
+ transparent: true,
154
+ opacity: this.opacity,
155
+ side: THREE.DoubleSide,
156
+ depthWrite: false,
157
+ depthTest: false,
158
+ toneMapped: false // Prevent tone mapping from making white appear gray
159
+ });
160
+
161
+ this.shapeMesh = new THREE.Mesh(undefined, shapeMaterial);
162
+ this.shapeMesh.layers.set(SceneLayer.Overlay);
163
+ this.shapeMesh.renderOrder = SceneLayerOrder.Measurement;
164
+
165
+ // Create an empty text mesh that will be updated when measurement has actual distance
166
+ const emptyCanvas = document.createElement('canvas');
167
+ emptyCanvas.width = 1;
168
+ emptyCanvas.height = 1;
169
+ const emptyTexture = new THREE.CanvasTexture(emptyCanvas);
170
+ const textMaterial = new THREE.MeshBasicMaterial({
171
+ map: emptyTexture,
172
+ transparent: true,
173
+ opacity: this.opacity,
174
+ depthWrite: false,
175
+ depthTest: false,
176
+ toneMapped: false // Prevent tone mapping from making white appear gray
177
+ });
178
+ this.textMesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), textMaterial);
179
+ this.textMesh.layers.set(SceneLayer.Overlay);
180
+ this.textMesh.renderOrder = SceneLayerOrder.Measurement;
181
+ this.textMesh.visible = false; // Start invisible until we have actual content
182
+
183
+ this.object.add(this.shapeMesh);
184
+ this.object.add(this.textMesh);
185
+ }
186
+
187
+ /**
188
+ * Updates the measurement with a new end point and recalculates all derived values.
189
+ * Recreates the shape and text objects to reflect the new measurement geometry.
190
+ * This method is called continuously during measurement creation as the user moves the mouse.
191
+ *
192
+ * @param {THREE.Vector2} endPoint - The new end point coordinates in world space
193
+ * @param {number} sceneRotation - Optional scene rotation in degrees
194
+ * @returns {void}
195
+ */
196
+ update(endPoint: THREE.Vector2, sceneRotation?: number): void {
197
+ if (this.isDisposed) return;
198
+
199
+ this.endPoint = endPoint.clone();
200
+ if (sceneRotation !== undefined) {
201
+ this.sceneRotation = sceneRotation;
202
+ }
203
+
204
+ this.renderShape();
205
+ this.renderText();
206
+ }
207
+
208
+ /**
209
+ * Renders the distance text label for this measurement with standard positioning logic.
210
+ * Positions text 150 pixels away from the end point in the direction of the measurement.
211
+ * All measurement types share this text positioning and styling approach.
212
+ * Returns an empty group if distance display is disabled.
213
+ *
214
+ * @returns {void}
215
+ */
216
+ renderText(): void {
217
+ // Calculate direction from start to end point and normalize
218
+ const direction = this.endPoint.clone().sub(this.startPoint).normalize();
219
+
220
+ // Initial text position at standard offset distance from the end point
221
+ let textPosition = this.endPoint.clone().add(direction.multiplyScalar(TEXT_OFFSET_DISTANCE));
222
+
223
+ // Edge detection and adjustment
224
+ const padding = 150; // Padding from edges to prevent clipping
225
+ const halfWidth = this.displayProps.resolution.x / 2;
226
+ const halfHeight = this.displayProps.resolution.y / 2;
227
+
228
+ // Check and adjust for edge clipping
229
+ if (textPosition.x > halfWidth - padding) {
230
+ textPosition.x = halfWidth - padding;
231
+ } else if (textPosition.x < -halfWidth + padding) {
232
+ textPosition.x = -halfWidth + padding;
233
+ }
234
+
235
+ if (textPosition.y > halfHeight - padding) {
236
+ textPosition.y = halfHeight - padding;
237
+ } else if (textPosition.y < -halfHeight + padding) {
238
+ textPosition.y = -halfHeight + padding;
239
+ }
240
+
241
+ // For hex grids with snapping, we want to show hex count instead of distance
242
+ const isHexGrid = this.gridProps.gridType === GridType.Hex;
243
+ const showHexCount = isHexGrid && this.snapToGrid;
244
+
245
+ let distance: number;
246
+ let displayText: string;
247
+ let displayUnits: string;
248
+
249
+ if (showHexCount) {
250
+ // Calculate hex count directly
251
+ const pixelsPerInchX = this.displayProps.resolution.x / this.displayProps.size.x;
252
+ const hexSizePixels = this.gridProps.spacing * pixelsPerInchX;
253
+ const startHex = pixelToHex(this.startPoint, hexSizePixels) as HexCoordinate & { isGrid2?: boolean };
254
+ const endHex = pixelToHex(this.endPoint, hexSizePixels) as HexCoordinate & { isGrid2?: boolean };
255
+ distance = hexDistance(startHex, endHex);
256
+
257
+ // Format as integer hex count
258
+ displayText = Math.round(distance).toString();
259
+ displayUnits = distance === 1 ? 'hex' : 'hexes';
260
+ } else {
261
+ // Standard distance calculation
262
+ distance = calculateLineDistance(
263
+ this.startPoint,
264
+ this.endPoint,
265
+ this.gridProps.spacing,
266
+ this.displayProps.size,
267
+ this.displayProps.resolution,
268
+ this.gridProps.gridType,
269
+ this.snapToGrid,
270
+ this.enableDMG252,
271
+ this.gridProps.worldGridSize || 5,
272
+ this.gridProps.worldGridUnits || 'FT'
273
+ );
274
+
275
+ // Format number to only show decimals if needed
276
+ displayText = distance % 1 === 0 ? distance.toString() : distance.toFixed(DISTANCE_DECIMAL_PLACES);
277
+ displayUnits = this.gridProps.worldGridUnits || 'FT';
278
+ }
279
+
280
+ // Don't render text for zero distance
281
+ if (distance === 0) {
282
+ this.textMesh.visible = false;
283
+ return;
284
+ }
285
+
286
+ // Show the text mesh since we have content
287
+ this.textMesh.visible = true;
288
+
289
+ const fontSize = this.displayProps.resolution.y / FONT_SIZE_DIVISOR;
290
+ const textCanvas = createTextCanvas(
291
+ displayText,
292
+ fontSize,
293
+ this.color,
294
+ this.outlineColor,
295
+ this.outlineThickness,
296
+ displayUnits
297
+ );
298
+
299
+ // Create texture from canvas
300
+ const texture = new THREE.CanvasTexture(textCanvas);
301
+ texture.needsUpdate = true;
302
+
303
+ // Update the text mesh geometry to match canvas size
304
+ if (this.textMesh.geometry) {
305
+ this.textMesh.geometry.dispose();
306
+ }
307
+ this.textMesh.geometry = new THREE.PlaneGeometry(textCanvas.width, textCanvas.height);
308
+
309
+ if (this.textMesh.material instanceof THREE.MeshBasicMaterial) {
310
+ // Dispose the old texture before assigning new one to prevent memory leak
311
+ if (this.textMesh.material.map) {
312
+ this.textMesh.material.map.dispose();
313
+ }
314
+ this.textMesh.material.map = texture;
315
+ this.textMesh.material.map.needsUpdate = true;
316
+ }
317
+
318
+ this.textMesh.position.set(textPosition.x, textPosition.y, 0);
319
+
320
+ // Apply counter-rotation to keep text aligned to browser bottom
321
+ // Similar to how markers handle rotation
322
+ const normalizedRotation = ((this.sceneRotation % 360) + 360) % 360;
323
+ const needsFlip =
324
+ (normalizedRotation > 85 && normalizedRotation < 95) || (normalizedRotation > 265 && normalizedRotation < 275);
325
+ const counterRotation = needsFlip
326
+ ? -((this.sceneRotation + 180) * Math.PI) / 180
327
+ : -(this.sceneRotation * Math.PI) / 180;
328
+
329
+ this.textMesh.rotation.z = counterRotation;
330
+ }
331
+
332
+ /**
333
+ * Abstract method that must be implemented by each measurement type to render its specific shape.
334
+ * Should create and return a Three.js object representing the measurement's visual geometry
335
+ * (line, circle, rectangle, etc.) based on the current start and end points.
336
+ * The mesh should be stored in the `this.mesh` property.
337
+ *
338
+ * @returns {void}
339
+ */
340
+ abstract renderShape(): void;
341
+
342
+ /**
343
+ * Creates a Three.js mesh displaying text with consistent styling across all measurements.
344
+ * Generates a canvas-based texture with the specified text and applies standard material settings
345
+ * for proper transparency, layering, and visual appearance.
346
+ *
347
+ * @param {string} text - The text content to display
348
+ * @param {THREE.Vector2} position - World coordinates where the text should be positioned
349
+ * @returns {THREE.Mesh} A Three.js mesh containing the rendered text
350
+ */
351
+ protected createTextMesh(text: string, position: THREE.Vector2): THREE.Mesh {
352
+ const fontSize = this.displayProps.resolution.y / FONT_SIZE_DIVISOR;
353
+
354
+ const canvas = createTextCanvas(text, fontSize, this.color, this.outlineColor, this.outlineThickness);
355
+
356
+ // Create texture from canvas
357
+ const texture = new THREE.CanvasTexture(canvas);
358
+ texture.needsUpdate = true;
359
+
360
+ // Create plane geometry for text with world dimensions
361
+ const geometry = new THREE.PlaneGeometry(canvas.width, canvas.height);
362
+ const material = new THREE.MeshBasicMaterial({
363
+ map: texture,
364
+ transparent: true,
365
+ opacity: this.opacity,
366
+ toneMapped: false // Prevent tone mapping from making white appear gray
367
+ });
368
+
369
+ const textMesh = new THREE.Mesh(geometry, material);
370
+ textMesh.layers.set(SceneLayer.Overlay);
371
+ textMesh.renderOrder = SceneLayerOrder.Measurement;
372
+ textMesh.position.set(position.x, position.y, 0);
373
+
374
+ return textMesh;
375
+ }
376
+
377
+ /**
378
+ * Updates the shape mesh with a new geometry and texture.
379
+ * @param {THREE.PlaneGeometry} geometry - The new geometry to use
380
+ * @param {THREE.CanvasTexture} texture - The new texture to use
381
+ * @returns {void}
382
+ */
383
+ protected updateShapeMesh(geometry: THREE.PlaneGeometry, texture: THREE.CanvasTexture): void {
384
+ this.shapeMesh.geometry?.dispose();
385
+ this.shapeMesh.geometry = geometry;
386
+ if (this.shapeMesh.material instanceof THREE.MeshBasicMaterial) {
387
+ // Dispose the old texture before assigning new one to prevent memory leak
388
+ if (this.shapeMesh.material.map) {
389
+ this.shapeMesh.material.map.dispose();
390
+ }
391
+ this.shapeMesh.material.map = texture;
392
+ this.shapeMesh.material.map.needsUpdate = true;
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Completely destroys this measurement instance and cleans up all associated resources.
398
+ * Removes objects from the scene, disposes of Three.js resources, and marks the measurement as disposed.
399
+ * Should be called when the measurement is no longer needed to prevent memory leaks.
400
+ * @returns {void}
401
+ */
402
+ dispose(): void {
403
+ this.isDisposed = true;
404
+
405
+ this.shapeMesh.removeFromParent();
406
+ if (this.shapeMesh instanceof THREE.Mesh) {
407
+ this.shapeMesh.geometry?.dispose();
408
+ // Dispose texture before disposing material
409
+ if (this.shapeMesh.material instanceof THREE.MeshBasicMaterial && this.shapeMesh.material.map) {
410
+ this.shapeMesh.material.map.dispose();
411
+ }
412
+ (this.shapeMesh.material as THREE.Material)?.dispose();
413
+ }
414
+
415
+ this.textMesh.removeFromParent();
416
+ if (this.textMesh instanceof THREE.Mesh) {
417
+ this.textMesh.geometry?.dispose();
418
+ // Dispose texture before disposing material
419
+ if (this.textMesh.material instanceof THREE.MeshBasicMaterial && this.textMesh.material.map) {
420
+ this.textMesh.material.map.dispose();
421
+ }
422
+ (this.textMesh.material as THREE.Material)?.dispose();
423
+ }
424
+
425
+ this.object.removeFromParent();
426
+ }
427
+ }
@@ -0,0 +1,105 @@
1
+ // prettier-ignore
2
+ import * as THREE from 'three';
3
+ // prettier-ignore
4
+ import type { DisplayProps } from '../../Stage/types';
5
+ // prettier-ignore
6
+ import type { GridLayerProps } from '../../GridLayer/types';
7
+ import { MeasurementType, type MeasurementLayerProps } from '../types';
8
+ import { drawCircle, drawRectangle } from '../utils/canvasDrawing';
9
+ import { BaseMeasurement } from './BaseMeasurement';
10
+
11
+ export class BeamMeasurement extends BaseMeasurement {
12
+ private beamWidth: number;
13
+
14
+ constructor(
15
+ startPoint: THREE.Vector2,
16
+ measurementProps: MeasurementLayerProps,
17
+ displayProps: DisplayProps,
18
+ gridProps: GridLayerProps
19
+ ) {
20
+ super(MeasurementType.Beam, startPoint, measurementProps, displayProps, gridProps);
21
+ this.beamWidth = measurementProps.beamWidth;
22
+ }
23
+
24
+ renderShape(): void {
25
+ // Calculate beam parameters
26
+ const direction = this.endPoint.clone().sub(this.startPoint);
27
+ const length = direction.length();
28
+ const angle = Math.atan2(direction.y, direction.x);
29
+
30
+ // Convert beam width from world units to pixels
31
+ const pixelsPerInch = this.displayProps.resolution.x / this.displayProps.size.x;
32
+ const beamWidthInches = (this.beamWidth * this.gridProps.spacing) / this.gridProps.worldGridSize;
33
+ const beamWidthPixels = beamWidthInches * pixelsPerInch;
34
+
35
+ // Create canvas for the beam
36
+ const canvas = document.createElement('canvas');
37
+ const context = canvas.getContext('2d', { colorSpace: 'srgb' })!;
38
+
39
+ // Calculate canvas size - accommodate beam dimensions plus padding
40
+ const padding = Math.max(this.markerSize + this.outlineThickness + beamWidthPixels, 40);
41
+ const canvasWidth = length + padding * 2;
42
+ const canvasHeight = beamWidthPixels + padding * 2;
43
+
44
+ canvas.width = Math.max(canvasWidth, 100);
45
+ canvas.height = Math.max(canvasHeight, 100);
46
+
47
+ // Clear canvas
48
+ context.clearRect(0, 0, canvas.width, canvas.height);
49
+
50
+ // Save context and apply rotation
51
+ context.save();
52
+ context.translate(canvas.width / 2, canvas.height / 2);
53
+
54
+ // Calculate rectangle dimensions centered on canvas
55
+ const rectX = -length / 2;
56
+ const rectY = -beamWidthPixels / 2;
57
+ const rectWidth = length;
58
+ const rectHeight = beamWidthPixels;
59
+
60
+ // Draw beam rectangle using utility function
61
+ if (rectWidth > 0 && rectHeight > 0) {
62
+ drawRectangle(
63
+ context,
64
+ rectX,
65
+ rectY,
66
+ rectWidth,
67
+ rectHeight,
68
+ this.color,
69
+ this.thickness,
70
+ this.color,
71
+ 0.2,
72
+ this.outlineThickness > 0 ? this.outlineColor : undefined,
73
+ this.outlineThickness > 0 ? this.outlineThickness : undefined
74
+ );
75
+ }
76
+
77
+ // Draw start point circle
78
+ drawCircle(context, rectX, 0, this.markerSize / 2, this.color, this.outlineColor, this.outlineThickness);
79
+
80
+ // Draw end point circle
81
+ drawCircle(
82
+ context,
83
+ rectX + rectWidth,
84
+ 0,
85
+ this.markerSize / 2,
86
+ this.color,
87
+ this.outlineColor,
88
+ this.outlineThickness
89
+ );
90
+
91
+ // Restore context
92
+ context.restore();
93
+
94
+ const texture = new THREE.CanvasTexture(canvas);
95
+ texture.needsUpdate = true;
96
+
97
+ this.updateShapeMesh(new THREE.PlaneGeometry(canvas.width, canvas.height), texture);
98
+
99
+ // Position the beam at the center point between start and end
100
+ const centerX = (this.startPoint.x + this.endPoint.x) / 2;
101
+ const centerY = (this.startPoint.y + this.endPoint.y) / 2;
102
+ this.shapeMesh.position.set(centerX, centerY, 0);
103
+ this.shapeMesh.rotation.z = angle;
104
+ }
105
+ }
@@ -0,0 +1,98 @@
1
+ // prettier-ignore
2
+ import * as THREE from 'three';
3
+ // prettier-ignore
4
+ import type { DisplayProps } from '../../Stage/types';
5
+ // prettier-ignore
6
+ import type { GridLayerProps } from '../../GridLayer/types';
7
+ import { MeasurementType, type MeasurementLayerProps } from '../types';
8
+ import { drawCircle } from '../utils/canvasDrawing';
9
+ import { BaseMeasurement } from './BaseMeasurement';
10
+
11
+ export class CircleMeasurement extends BaseMeasurement {
12
+ constructor(
13
+ startPoint: THREE.Vector2,
14
+ measurementProps: MeasurementLayerProps,
15
+ displayProps: DisplayProps,
16
+ gridProps: GridLayerProps
17
+ ) {
18
+ super(MeasurementType.Circle, startPoint, measurementProps, displayProps, gridProps);
19
+ }
20
+
21
+ renderShape(): void {
22
+ // Calculate radius in pixels
23
+ const radiusPixels = this.startPoint.distanceTo(this.endPoint);
24
+
25
+ // Create canvas for the circle
26
+ const canvas = document.createElement('canvas');
27
+ const context = canvas.getContext('2d', { colorSpace: 'srgb' })!;
28
+
29
+ // Calculate canvas size - need to accommodate the full circle plus outline and points
30
+ const padding = Math.max(this.markerSize + this.outlineThickness, 40);
31
+ const canvasSize = (radiusPixels + padding) * 2;
32
+
33
+ canvas.width = Math.max(canvasSize, 100);
34
+ canvas.height = Math.max(canvasSize, 100);
35
+
36
+ // Clear canvas
37
+ context.clearRect(0, 0, canvas.width, canvas.height);
38
+
39
+ // Canvas center coordinates
40
+ const canvasCenterX = canvas.width / 2;
41
+ const canvasCenterY = canvas.height / 2;
42
+
43
+ // Draw the large circle with dashed pattern
44
+ // First draw the fill
45
+ context.fillStyle = this.color;
46
+ context.globalAlpha = 0.2;
47
+ context.beginPath();
48
+ context.arc(canvasCenterX, canvasCenterY, radiusPixels, 0, Math.PI * 2);
49
+ context.fill();
50
+ context.globalAlpha = 1.0;
51
+
52
+ // Draw the outline with dashed pattern if needed
53
+ if (this.outlineThickness > 0) {
54
+ context.strokeStyle = this.outlineColor;
55
+ context.lineWidth = this.thickness + this.outlineThickness * 2;
56
+ context.setLineDash([20, 10]); // Dashed pattern
57
+ context.beginPath();
58
+ context.arc(canvasCenterX, canvasCenterY, radiusPixels, 0, Math.PI * 2);
59
+ context.stroke();
60
+ }
61
+
62
+ // Draw the main stroke with dashed pattern
63
+ context.strokeStyle = this.color;
64
+ context.lineWidth = this.thickness;
65
+ context.setLineDash([20, 10]); // Dashed pattern
66
+ context.beginPath();
67
+ context.arc(canvasCenterX, canvasCenterY, radiusPixels, 0, Math.PI * 2);
68
+ context.stroke();
69
+
70
+ // Reset dash pattern for other elements
71
+ context.setLineDash([]);
72
+
73
+ // Draw center point
74
+ drawCircle(
75
+ context,
76
+ canvasCenterX,
77
+ canvasCenterY,
78
+ this.markerSize / 2,
79
+ this.color,
80
+ this.outlineColor,
81
+ this.outlineThickness
82
+ );
83
+
84
+ // Draw radius indicator point at the edge of the circle
85
+ // Calculate the position of the end point relative to start point, then place on circle edge
86
+ const direction = this.endPoint.clone().sub(this.startPoint).normalize();
87
+ const edgeX = canvasCenterX + direction.x * radiusPixels;
88
+ const edgeY = canvasCenterY - direction.y * radiusPixels; // Invert Y for canvas coordinates
89
+ drawCircle(context, edgeX, edgeY, this.markerSize / 2, this.color, this.outlineColor, this.outlineThickness);
90
+
91
+ // Create texture from canvas
92
+ const shapeTexture = new THREE.CanvasTexture(canvas);
93
+ shapeTexture.needsUpdate = true;
94
+
95
+ this.updateShapeMesh(new THREE.PlaneGeometry(canvas.width, canvas.height), shapeTexture);
96
+ this.shapeMesh.position.set(this.startPoint.x, this.startPoint.y, 0);
97
+ }
98
+ }