@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.
- package/package.json +2 -13
- package/src/lib/components/Avatar/Avatar.svelte +82 -0
- package/src/lib/components/Avatar/AvatarFileInput.svelte +85 -0
- package/src/lib/components/Avatar/AvatarPopover.svelte +34 -0
- package/src/lib/components/Avatar/index.ts +4 -0
- package/src/lib/components/Avatar/types.ts +24 -0
- package/src/lib/components/BrushSizeSlider/BrushSizeSlider.svelte +174 -0
- package/src/lib/components/BrushSizeSlider/index.ts +1 -0
- package/src/lib/components/Button/Button.svelte +182 -0
- package/src/lib/components/Button/ConfirmActionButton.svelte +98 -0
- package/src/lib/components/Button/IconButton.svelte +121 -0
- package/src/lib/components/Button/RadioButton.svelte +93 -0
- package/src/lib/components/Button/index.ts +5 -0
- package/src/lib/components/Button/types.ts +54 -0
- package/src/lib/components/CardFan/CardFan.svelte +165 -0
- package/src/lib/components/CardFan/index.ts +2 -0
- package/src/lib/components/CardFan/types.ts +6 -0
- package/src/lib/components/CodeBlock/Code.svelte +7 -0
- package/src/lib/components/CodeBlock/CodeBlock.svelte +102 -0
- package/src/lib/components/CodeBlock/index.ts +3 -0
- package/src/lib/components/CodeBlock/types.ts +10 -0
- package/src/lib/components/ColorMode/ColorMode.svelte +8 -0
- package/src/lib/components/ColorMode/index.ts +2 -0
- package/src/lib/components/ColorMode/types.ts +12 -0
- package/src/lib/components/ColorPicker/ColorPicker.svelte +838 -0
- package/src/lib/components/ColorPicker/ColorPickerSwatch.svelte +32 -0
- package/src/lib/components/ColorPicker/index.ts +3 -0
- package/src/lib/components/ColorPicker/types.ts +51 -0
- package/src/lib/components/ContextMenu/ContextMenu.svelte +86 -0
- package/src/lib/components/ContextMenu/index.ts +2 -0
- package/src/lib/components/ContextMenu/types.ts +15 -0
- package/src/lib/components/DrawingSliders/DrawingSliders.svelte +379 -0
- package/src/lib/components/DrawingSliders/index.ts +1 -0
- package/src/lib/components/Editor/Editor.svelte +825 -0
- package/src/lib/components/Editor/index.ts +1 -0
- package/src/lib/components/FogSliders/FogSliders.svelte +33 -0
- package/src/lib/components/FogSliders/index.ts +1 -0
- package/src/lib/components/Hr/Hr.svelte +15 -0
- package/src/lib/components/Hr/index.ts +1 -0
- package/src/lib/components/Icon/Icon.svelte +6 -0
- package/src/lib/components/Icon/index.ts +2 -0
- package/src/lib/components/Icon/types.ts +20 -0
- package/src/lib/components/Input/DualInputSlider.svelte +126 -0
- package/src/lib/components/Input/FileInput.svelte +176 -0
- package/src/lib/components/Input/FormControl.svelte +150 -0
- package/src/lib/components/Input/FormError.svelte +37 -0
- package/src/lib/components/Input/Input.svelte +56 -0
- package/src/lib/components/Input/InputCheckbox.svelte +99 -0
- package/src/lib/components/Input/InputSlider.svelte +86 -0
- package/src/lib/components/Input/Label.svelte +19 -0
- package/src/lib/components/Input/index.ts +9 -0
- package/src/lib/components/Input/types.ts +39 -0
- package/src/lib/components/Link/Link.svelte +41 -0
- package/src/lib/components/Link/LinkBox.svelte +20 -0
- package/src/lib/components/Link/LinkOverlay.svelte +23 -0
- package/src/lib/components/Link/index.ts +4 -0
- package/src/lib/components/Link/types.ts +17 -0
- package/src/lib/components/Loading/Loader.svelte +60 -0
- package/src/lib/components/Loading/Skeleton.svelte +9 -0
- package/src/lib/components/Loading/index.ts +2 -0
- package/src/lib/components/Logo/Logo.svelte +16 -0
- package/src/lib/components/Logo/index.ts +1 -0
- package/src/lib/components/MarkerTooltip/MarkerTooltip.svelte +435 -0
- package/src/lib/components/MarkerTooltip/index.ts +1 -0
- package/src/lib/components/Menu/SelectorMenu.svelte +280 -0
- package/src/lib/components/Menu/index.ts +2 -0
- package/src/lib/components/Menu/types.ts +17 -0
- package/src/lib/components/MyCounterButton.svelte +11 -0
- package/src/lib/components/Panel/index.ts +2 -0
- package/src/lib/components/Panel/panel.svelte +18 -0
- package/src/lib/components/Panel/types.ts +8 -0
- package/src/lib/components/PersistButton/PersistButton.svelte +100 -0
- package/src/lib/components/PersistButton/index.ts +1 -0
- package/src/lib/components/Popover/Popover.svelte +81 -0
- package/src/lib/components/Popover/index.ts +2 -0
- package/src/lib/components/Popover/types.ts +19 -0
- package/src/lib/components/PropsTable/PropsTable.svelte +107 -0
- package/src/lib/components/RadialMenu/EffectPreview.svelte +36 -0
- package/src/lib/components/RadialMenu/EffectPreviewScene.svelte +194 -0
- package/src/lib/components/RadialMenu/RadialMenu.svelte +503 -0
- package/src/lib/components/RadialMenu/RadialMenuItem.svelte +176 -0
- package/src/lib/components/RadialMenu/index.ts +2 -0
- package/src/lib/components/RadialMenu/types.ts +35 -0
- package/src/lib/components/Select/Select.svelte +342 -0
- package/src/lib/components/Select/index.ts +2 -0
- package/src/lib/components/Select/types.ts +22 -0
- package/src/lib/components/Spacer/Spacer.svelte +14 -0
- package/src/lib/components/Spacer/index.ts +2 -0
- package/src/lib/components/Spacer/types.ts +5 -0
- package/src/lib/components/Stage/components/AnnotationLayer/AnnotationLayer.svelte +445 -0
- package/src/lib/components/Stage/components/AnnotationLayer/AnnotationMaterial.svelte +167 -0
- package/src/lib/components/Stage/components/AnnotationLayer/types.ts +196 -0
- package/src/lib/components/Stage/components/CursorLayer/CursorLayer.svelte +148 -0
- package/src/lib/components/Stage/components/CursorLayer/cursor.svg +26 -0
- package/src/lib/components/Stage/components/CursorLayer/index.ts +2 -0
- package/src/lib/components/Stage/components/CursorLayer/types.ts +23 -0
- package/src/lib/components/Stage/components/DrawingLayer/DrawingMaterial.svelte +364 -0
- package/src/lib/components/Stage/components/DrawingLayer/types.ts +65 -0
- package/src/lib/components/Stage/components/EdgeOverlayLayer/EdgeOverlayLayer.svelte +72 -0
- package/src/lib/components/Stage/components/EdgeOverlayLayer/types.ts +34 -0
- package/src/lib/components/Stage/components/FogLayer/FogLayer.svelte +75 -0
- package/src/lib/components/Stage/components/FogLayer/types.ts +51 -0
- package/src/lib/components/Stage/components/FogOfWarLayer/FogOfWarLayer.svelte +249 -0
- package/src/lib/components/Stage/components/FogOfWarLayer/FogOfWarMaterial.svelte +200 -0
- package/src/lib/components/Stage/components/FogOfWarLayer/types.ts +116 -0
- package/src/lib/components/Stage/components/GridLayer/GridLayer.svelte +20 -0
- package/src/lib/components/Stage/components/GridLayer/GridMaterial.svelte +69 -0
- package/src/lib/components/Stage/components/GridLayer/types.ts +79 -0
- package/src/lib/components/Stage/components/LayerInput/LayerInput.svelte +300 -0
- package/src/lib/components/Stage/components/MapLayer/MapLayer.svelte +196 -0
- package/src/lib/components/Stage/components/MapLayer/dataSources/GifDataSource.ts +265 -0
- package/src/lib/components/Stage/components/MapLayer/dataSources/IMapDataSource.ts +55 -0
- package/src/lib/components/Stage/components/MapLayer/dataSources/ImageDataSource.ts +87 -0
- package/src/lib/components/Stage/components/MapLayer/dataSources/VideoDataSource.ts +150 -0
- package/src/lib/components/Stage/components/MapLayer/dataSources/dataSourceFactory.ts +48 -0
- package/src/lib/components/Stage/components/MapLayer/dataSources/index.ts +16 -0
- package/src/lib/components/Stage/components/MapLayer/types.ts +58 -0
- package/src/lib/components/Stage/components/MarkerLayer/MarkerLayer.svelte +398 -0
- package/src/lib/components/Stage/components/MarkerLayer/MarkerToken.svelte +262 -0
- package/src/lib/components/Stage/components/MarkerLayer/types.ts +126 -0
- package/src/lib/components/Stage/components/MeasurementLayer/MeasurementLayer.svelte +364 -0
- package/src/lib/components/Stage/components/MeasurementLayer/MeasurementManager.svelte +473 -0
- package/src/lib/components/Stage/components/MeasurementLayer/measurements/BaseMeasurement.ts +427 -0
- package/src/lib/components/Stage/components/MeasurementLayer/measurements/BeamMeasurement.ts +105 -0
- package/src/lib/components/Stage/components/MeasurementLayer/measurements/CircleMeasurement.ts +98 -0
- package/src/lib/components/Stage/components/MeasurementLayer/measurements/ConeMeasurement.ts +163 -0
- package/src/lib/components/Stage/components/MeasurementLayer/measurements/LineMeasurement.ts +102 -0
- package/src/lib/components/Stage/components/MeasurementLayer/measurements/RectangleMeasurement.ts +120 -0
- package/src/lib/components/Stage/components/MeasurementLayer/measurements/index.ts +7 -0
- package/src/lib/components/Stage/components/MeasurementLayer/types.ts +94 -0
- package/src/lib/components/Stage/components/MeasurementLayer/utils/canvasDrawing.ts +357 -0
- package/src/lib/components/Stage/components/MeasurementLayer/utils/distanceCalculations.ts +170 -0
- package/src/lib/components/Stage/components/ParticleSystem/ParticleSystem.svelte +220 -0
- package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/ash.png +0 -0
- package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/leaves.png +0 -0
- package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/rain.png +0 -0
- package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/snow.png +0 -0
- package/src/lib/components/Stage/components/ParticleSystem/rng.js +20 -0
- package/src/lib/components/Stage/components/ParticleSystem/types.ts +95 -0
- package/src/lib/components/Stage/components/PerformanceDebugger/PerformanceDebugger.svelte +144 -0
- package/src/lib/components/Stage/components/PerformanceDebugger/index.ts +1 -0
- package/src/lib/components/Stage/components/PerformanceOverlay/PerformanceOverlay.svelte +208 -0
- package/src/lib/components/Stage/components/PerformanceOverlay/index.ts +1 -0
- package/src/lib/components/Stage/components/PointerInputManager/PointerInputManager.svelte +201 -0
- package/src/lib/components/Stage/components/Scene/Scene.svelte +651 -0
- package/src/lib/components/Stage/components/Scene/luts.ts +24 -0
- package/src/lib/components/Stage/components/Scene/types.ts +225 -0
- package/src/lib/components/Stage/components/Stage/Stage.svelte +332 -0
- package/src/lib/components/Stage/components/Stage/types.ts +136 -0
- package/src/lib/components/Stage/components/WeatherLayer/WeatherLayer.svelte +135 -0
- package/src/lib/components/Stage/components/WeatherLayer/presets/AshPreset.ts +71 -0
- package/src/lib/components/Stage/components/WeatherLayer/presets/LeavesPreset.ts +70 -0
- package/src/lib/components/Stage/components/WeatherLayer/presets/RainPreset.ts +68 -0
- package/src/lib/components/Stage/components/WeatherLayer/presets/SnowPreset.ts +70 -0
- package/src/lib/components/Stage/components/WeatherLayer/presets/index.ts +6 -0
- package/src/lib/components/Stage/components/WeatherLayer/types.ts +35 -0
- package/src/lib/components/Stage/helpers/clippingPlaneStore.svelte.ts +28 -0
- package/src/lib/components/Stage/helpers/debugState.svelte.ts +18 -0
- package/src/lib/components/Stage/helpers/grid.ts +548 -0
- package/src/lib/components/Stage/helpers/lazyBrush.ts +171 -0
- package/src/lib/components/Stage/helpers/performanceMetrics.svelte.ts +220 -0
- package/src/lib/components/Stage/helpers/utils.ts +21 -0
- package/src/lib/components/Stage/index.ts +49 -0
- package/src/lib/components/Stage/shaders/AnnotationEffects.frag +1070 -0
- package/src/lib/components/Stage/shaders/Annotations.frag +29 -0
- package/src/lib/components/Stage/shaders/Drawing.frag +83 -0
- package/src/lib/components/Stage/shaders/Drawing.vert +5 -0
- package/src/lib/components/Stage/shaders/Fog.frag +147 -0
- package/src/lib/components/Stage/shaders/FractalNoise.frag +96 -0
- package/src/lib/components/Stage/shaders/GridShader.frag +174 -0
- package/src/lib/components/Stage/shaders/Overlay.frag +23 -0
- package/src/lib/components/Stage/shaders/Overlay.vert +0 -0
- package/src/lib/components/Stage/shaders/Particles.frag +27 -0
- package/src/lib/components/Stage/shaders/Particles.vert +51 -0
- package/src/lib/components/Stage/shaders/ToolOutline.frag +59 -0
- package/src/lib/components/Stage/shaders/default.vert +8 -0
- package/src/lib/components/Stage/types.ts +4 -0
- package/src/lib/components/Table/Table.svelte +16 -0
- package/src/lib/components/Table/Td.svelte +17 -0
- package/src/lib/components/Table/Th.svelte +18 -0
- package/src/lib/components/Table/index.ts +4 -0
- package/src/lib/components/Table/types.ts +14 -0
- package/src/lib/components/Text/Text.svelte +23 -0
- package/src/lib/components/Text/index.ts +2 -0
- package/src/lib/components/Text/types.ts +12 -0
- package/src/lib/components/Title/Title.svelte +54 -0
- package/src/lib/components/Title/index.ts +2 -0
- package/src/lib/components/Title/types.ts +9 -0
- package/src/lib/components/Toast/Toast.svelte +155 -0
- package/src/lib/components/Toast/index.ts +5 -0
- package/src/lib/components/Toast/toastCookie.ts +24 -0
- package/src/lib/components/Toast/types.ts +6 -0
- package/src/lib/components/ToolTip/ToolTip.svelte +70 -0
- package/src/lib/components/ToolTip/index.ts +2 -0
- package/src/lib/components/ToolTip/types.ts +14 -0
- package/src/lib/components/index.ts +32 -0
- package/src/lib/components/types.ts +0 -0
- package/src/lib/index.ts +2 -0
- package/src/lib/styles/globals.css +108 -0
- package/src/lib/styles/normalize.css +9 -0
- package/src/lib/styles/reset.css +133 -0
- package/src/lib/styles/utilities.css +179 -0
- package/src/lib/styles/vars.css +1103 -0
- package/src/lib/types/awareness.ts +17 -0
- 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
|
+
}
|
package/src/lib/components/Stage/components/MeasurementLayer/measurements/CircleMeasurement.ts
ADDED
|
@@ -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
|
+
}
|