@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,445 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
import { getContext, onDestroy } from 'svelte';
|
|
4
|
+
import { T } from '@threlte/core';
|
|
5
|
+
import { type AnnotationLayerData, type AnnotationsLayerProps, AnnotationEffect } from './types';
|
|
6
|
+
import { StageMode, type Callbacks, type DisplayProps } from '../Stage/types';
|
|
7
|
+
import LayerInput from '../LayerInput/LayerInput.svelte';
|
|
8
|
+
import { SceneLayer, SceneLayerOrder } from '../Scene/types';
|
|
9
|
+
import AnnotationMaterial from './AnnotationMaterial.svelte';
|
|
10
|
+
import { LazyBrushManager } from '../../helpers/lazyBrush';
|
|
11
|
+
import { ToolType } from '../DrawingLayer/types';
|
|
12
|
+
import toolOutlineVertexShader from '../../shaders/default.vert?raw';
|
|
13
|
+
import toolOutlineFragmentShader from '../../shaders/ToolOutline.frag?raw';
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
props: AnnotationsLayerProps;
|
|
17
|
+
mode: StageMode;
|
|
18
|
+
isActive: boolean;
|
|
19
|
+
display: DisplayProps;
|
|
20
|
+
sceneZoom: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { props, mode, isActive, display, sceneZoom }: Props = $props();
|
|
24
|
+
|
|
25
|
+
const onAnnotationUpdate = getContext<Callbacks>('callbacks').onAnnotationUpdate;
|
|
26
|
+
|
|
27
|
+
// Outline styling (same as fog of war)
|
|
28
|
+
const OUTLINE_COLOR = '#FFFFFF';
|
|
29
|
+
const OUTLINE_OPACITY = 1;
|
|
30
|
+
const OUTLINE_THICKNESS = 2;
|
|
31
|
+
|
|
32
|
+
// Pre-allocated vector for display center offset to avoid GC pressure
|
|
33
|
+
const displayCenterOffset = new THREE.Vector2();
|
|
34
|
+
|
|
35
|
+
// Convert percentage-based lineWidth to texture pixels for outline
|
|
36
|
+
const lineWidthPixels = $derived.by(() => {
|
|
37
|
+
const textureSize = Math.min(display.resolution.x, display.resolution.y);
|
|
38
|
+
return Math.round(textureSize * ((props.lineWidth ?? 2.0) / 100));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
let mesh: THREE.Mesh = $state(new THREE.Mesh());
|
|
42
|
+
let outlineMesh: THREE.Mesh = $state(new THREE.Mesh());
|
|
43
|
+
let drawing = false;
|
|
44
|
+
|
|
45
|
+
// Export drawing state so parent can check it
|
|
46
|
+
export function isDrawing() {
|
|
47
|
+
return drawing;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// If mouse leaves the drawing area, we need to reset the start position
|
|
51
|
+
// when it re-enters the drawing area to prevent the drawing from "jumping"
|
|
52
|
+
// to the new point
|
|
53
|
+
let lastPos: THREE.Vector2 | null = null;
|
|
54
|
+
|
|
55
|
+
// Track the last screen position for the persist button
|
|
56
|
+
let lastScreenPos: { x: number; y: number } | null = null;
|
|
57
|
+
|
|
58
|
+
// Track if cursors are hidden to avoid redundant resets
|
|
59
|
+
let cursorsHidden = false;
|
|
60
|
+
|
|
61
|
+
// Initialize lazy brush for smooth drawing
|
|
62
|
+
// Base values for zoom level 1.0
|
|
63
|
+
const BASE_RADIUS = 20;
|
|
64
|
+
const BASE_FRICTION = 0.05;
|
|
65
|
+
|
|
66
|
+
// Smoothing disabled by default for more responsive drawing
|
|
67
|
+
const smoothingEnabled = $derived(props.smoothingEnabled ?? false);
|
|
68
|
+
|
|
69
|
+
const lazyBrush = new LazyBrushManager({
|
|
70
|
+
radius: BASE_RADIUS,
|
|
71
|
+
enabled: smoothingEnabled,
|
|
72
|
+
friction: BASE_FRICTION
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Outline material for brush cursor (same as fog of war)
|
|
76
|
+
const outlineMaterial = new THREE.ShaderMaterial({
|
|
77
|
+
uniforms: {
|
|
78
|
+
uStart: { value: new THREE.Vector2(Infinity, Infinity) },
|
|
79
|
+
uEnd: { value: new THREE.Vector2(Infinity, Infinity) },
|
|
80
|
+
uBrushSize: { value: lineWidthPixels },
|
|
81
|
+
uTextureSize: { value: new THREE.Vector2(display.resolution.x, display.resolution.y) },
|
|
82
|
+
uShapeType: { value: ToolType.Brush },
|
|
83
|
+
uOutlineColor: { value: new THREE.Color(OUTLINE_COLOR) },
|
|
84
|
+
uOutlineOpacity: { value: OUTLINE_OPACITY },
|
|
85
|
+
uOutlineThickness: { value: OUTLINE_THICKNESS }
|
|
86
|
+
},
|
|
87
|
+
vertexShader: toolOutlineVertexShader,
|
|
88
|
+
fragmentShader: toolOutlineFragmentShader,
|
|
89
|
+
transparent: true,
|
|
90
|
+
depthTest: false
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
onDestroy(() => {
|
|
94
|
+
outlineMaterial.dispose();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Adjust lazy brush settings based on zoom level and smoothing toggle
|
|
98
|
+
$effect(() => {
|
|
99
|
+
// Scale radius inversely with zoom - less smoothing when zoomed in
|
|
100
|
+
// At zoom 2x, radius is 25 (half)
|
|
101
|
+
// At zoom 0.5x, radius is 100 (double)
|
|
102
|
+
const adjustedRadius = BASE_RADIUS / sceneZoom;
|
|
103
|
+
|
|
104
|
+
// Adjust friction based on zoom - more responsive when zoomed in
|
|
105
|
+
// At high zoom (>2), reduce friction for more immediate response
|
|
106
|
+
// At low zoom (<0.5), increase friction for smoother lines
|
|
107
|
+
const adjustedFriction =
|
|
108
|
+
sceneZoom > 2 ? BASE_FRICTION * 0.5 : sceneZoom < 0.5 ? BASE_FRICTION * 1.5 : BASE_FRICTION;
|
|
109
|
+
|
|
110
|
+
lazyBrush.updateConfig({
|
|
111
|
+
radius: Math.max(5, Math.min(100, adjustedRadius)), // Clamp between 5 and 100
|
|
112
|
+
friction: adjustedFriction,
|
|
113
|
+
enabled: smoothingEnabled
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Update outline material uniforms and center offset when props change
|
|
118
|
+
$effect(() => {
|
|
119
|
+
// Use .set() to avoid allocating new objects
|
|
120
|
+
outlineMaterial.uniforms.uTextureSize.value.set(display.resolution.x, display.resolution.y);
|
|
121
|
+
outlineMaterial.uniforms.uBrushSize.value = lineWidthPixels;
|
|
122
|
+
displayCenterOffset.set(display.resolution.x / 2, display.resolution.y / 2);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Hide outline when tool is not active
|
|
126
|
+
$effect(() => {
|
|
127
|
+
if (!isActive || !props.activeLayer) {
|
|
128
|
+
outlineMesh.visible = false;
|
|
129
|
+
outlineMaterial.uniforms.uStart.value.set(Infinity, Infinity);
|
|
130
|
+
outlineMaterial.uniforms.uEnd.value.set(Infinity, Infinity);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Reference to the child layers
|
|
135
|
+
let layers: AnnotationMaterial[] = $state([]);
|
|
136
|
+
|
|
137
|
+
// Get the currently active layer
|
|
138
|
+
let activeLayer = $derived(
|
|
139
|
+
layers.find((layer) => {
|
|
140
|
+
if (!layer) return false;
|
|
141
|
+
return layer.getId() === props.activeLayer;
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Whenever the tool type changes, we need to reset the drawing state
|
|
146
|
+
$effect(() => {
|
|
147
|
+
if (!isActive) {
|
|
148
|
+
lastPos = null;
|
|
149
|
+
drawing = false;
|
|
150
|
+
lazyBrush.reset();
|
|
151
|
+
// Reset cursor for all layers to ensure no ghosting
|
|
152
|
+
if (!cursorsHidden) {
|
|
153
|
+
layers.forEach((layer) => {
|
|
154
|
+
if (layer) {
|
|
155
|
+
layer.revertChanges();
|
|
156
|
+
layer.resetCursor();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
cursorsHidden = true;
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
// Tool is active again
|
|
163
|
+
cursorsHidden = false;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Also reset cursor when active layer becomes null
|
|
168
|
+
$effect(() => {
|
|
169
|
+
if (!props.activeLayer && layers.length > 0) {
|
|
170
|
+
// No active layer selected, reset all cursors and revert changes
|
|
171
|
+
lastPos = null; // Reset last position to prevent cursor from appearing
|
|
172
|
+
if (!cursorsHidden) {
|
|
173
|
+
layers.forEach((layer) => {
|
|
174
|
+
if (layer) {
|
|
175
|
+
layer.revertChanges();
|
|
176
|
+
layer.resetCursor();
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
cursorsHidden = true;
|
|
180
|
+
}
|
|
181
|
+
} else if (props.activeLayer) {
|
|
182
|
+
// Active layer selected again
|
|
183
|
+
cursorsHidden = false;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
function onMouseDown(e: Event, p: THREE.Vector2 | null) {
|
|
188
|
+
e.preventDefault();
|
|
189
|
+
lastPos = p;
|
|
190
|
+
drawing = true;
|
|
191
|
+
|
|
192
|
+
// Track screen position for persist button
|
|
193
|
+
if (e instanceof MouseEvent) {
|
|
194
|
+
lastScreenPos = { x: e.clientX, y: e.clientY };
|
|
195
|
+
} else if (e instanceof TouchEvent && e.touches[0]) {
|
|
196
|
+
lastScreenPos = { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Start a new stroke with lazy brush
|
|
200
|
+
if (p) {
|
|
201
|
+
// Need to adjust for display offset before starting stroke
|
|
202
|
+
const adjustedP = p.clone();
|
|
203
|
+
adjustedP.add(displayCenterOffset);
|
|
204
|
+
lazyBrush.startStroke(adjustedP);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
draw(e, p);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function onMouseUp() {
|
|
211
|
+
// If we have just finished drawing, save the annotation
|
|
212
|
+
if (props.activeLayer && drawing) {
|
|
213
|
+
onAnnotationUpdate(props.activeLayer, toPng(), lastScreenPos || undefined);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Reset the drawing state
|
|
217
|
+
drawing = false;
|
|
218
|
+
lazyBrush.endStroke();
|
|
219
|
+
// Don't reset lastPos here to prevent cursor jumping
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function onMouseLeave() {
|
|
223
|
+
lastPos = null;
|
|
224
|
+
drawing = false;
|
|
225
|
+
lazyBrush.reset();
|
|
226
|
+
|
|
227
|
+
// Revert changes and hide cursor
|
|
228
|
+
if (activeLayer && !cursorsHidden) {
|
|
229
|
+
activeLayer.revertChanges();
|
|
230
|
+
activeLayer.resetCursor();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Hide outline
|
|
234
|
+
outlineMesh.visible = false;
|
|
235
|
+
outlineMaterial.uniforms.uStart.value.set(Infinity, Infinity);
|
|
236
|
+
outlineMaterial.uniforms.uEnd.value.set(Infinity, Infinity);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function draw(e: Event, p: THREE.Vector2 | null) {
|
|
240
|
+
// Track screen position during drawing for persist button
|
|
241
|
+
if (drawing) {
|
|
242
|
+
if (e instanceof MouseEvent) {
|
|
243
|
+
lastScreenPos = { x: e.clientX, y: e.clientY };
|
|
244
|
+
} else if (e instanceof TouchEvent && e.touches[0]) {
|
|
245
|
+
lastScreenPos = { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// If the mouse is not within the drawing area, hide cursor and outline
|
|
250
|
+
if (!p) {
|
|
251
|
+
// Reset cursor for all layers when mouse leaves (only if not already hidden)
|
|
252
|
+
if (!cursorsHidden) {
|
|
253
|
+
layers.forEach((layer) => {
|
|
254
|
+
if (layer) {
|
|
255
|
+
layer.resetCursor();
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
// Hide outline
|
|
260
|
+
outlineMesh.visible = false;
|
|
261
|
+
outlineMaterial.uniforms.uStart.value.set(Infinity, Infinity);
|
|
262
|
+
outlineMaterial.uniforms.uEnd.value.set(Infinity, Infinity);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Only process if we have an active layer and the annotation tool is active
|
|
267
|
+
if (!activeLayer || !isActive || !props.activeLayer) {
|
|
268
|
+
// Make sure all cursors are hidden when not active (only if not already hidden)
|
|
269
|
+
if (!cursorsHidden) {
|
|
270
|
+
layers.forEach((layer) => {
|
|
271
|
+
if (layer) {
|
|
272
|
+
layer.resetCursor();
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
cursorsHidden = true;
|
|
276
|
+
}
|
|
277
|
+
// Hide outline
|
|
278
|
+
outlineMesh.visible = false;
|
|
279
|
+
outlineMaterial.uniforms.uStart.value.set(Infinity, Infinity);
|
|
280
|
+
outlineMaterial.uniforms.uEnd.value.set(Infinity, Infinity);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Tool is active and we have a valid position
|
|
285
|
+
cursorsHidden = false;
|
|
286
|
+
|
|
287
|
+
p.add(displayCenterOffset);
|
|
288
|
+
|
|
289
|
+
// Show outline at cursor position
|
|
290
|
+
outlineMesh.visible = true;
|
|
291
|
+
outlineMaterial.uniforms.uStart.value.copy(p);
|
|
292
|
+
outlineMaterial.uniforms.uEnd.value.copy(p);
|
|
293
|
+
|
|
294
|
+
// Use lazy brush for smooth drawing when drawing is active
|
|
295
|
+
if (drawing && lazyBrush.enabled) {
|
|
296
|
+
// Update lazy brush and get smoothed points
|
|
297
|
+
const segments = lazyBrush.updateStroke(p);
|
|
298
|
+
|
|
299
|
+
// Draw each segment (segments come as pairs: [lastPoint, newPoint])
|
|
300
|
+
for (let i = 0; i < segments.length; i += 2) {
|
|
301
|
+
if (segments[i] && segments[i + 1]) {
|
|
302
|
+
// Draw from last point to new point
|
|
303
|
+
activeLayer.drawPath(segments[i + 1], segments[i], true);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Update cursor to show brush position
|
|
308
|
+
const brushPos = lazyBrush.getBrushPosition();
|
|
309
|
+
if (brushPos) {
|
|
310
|
+
activeLayer.drawPath(brushPos, brushPos, false);
|
|
311
|
+
// Update outline to follow lazy brush position
|
|
312
|
+
outlineMaterial.uniforms.uStart.value.copy(brushPos);
|
|
313
|
+
outlineMaterial.uniforms.uEnd.value.copy(brushPos);
|
|
314
|
+
} else {
|
|
315
|
+
// Show cursor at actual position if no brush position
|
|
316
|
+
activeLayer.drawPath(p, p, false);
|
|
317
|
+
}
|
|
318
|
+
} else if (drawing && !lazyBrush.enabled) {
|
|
319
|
+
// Drawing but lazy brush is disabled
|
|
320
|
+
if (!lastPos) {
|
|
321
|
+
lastPos = p.clone();
|
|
322
|
+
}
|
|
323
|
+
activeLayer.drawPath(p, lastPos, true);
|
|
324
|
+
lastPos = p.clone();
|
|
325
|
+
} else {
|
|
326
|
+
// Just hovering, show cursor
|
|
327
|
+
activeLayer.drawPath(p, p, false);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function isVisible(layer: AnnotationLayerData) {
|
|
332
|
+
// Don't show DM layers to players
|
|
333
|
+
return !(mode === StageMode.Player && layer.visibility === StageMode.DM);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function hasEffect(layer: AnnotationLayerData) {
|
|
337
|
+
// Check if the layer has an effect (not None)
|
|
338
|
+
return layer.effect?.type !== undefined && layer.effect.type !== AnnotationEffect.None;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Clears the annotation layer
|
|
343
|
+
*/
|
|
344
|
+
export function clear(layerId: string) {
|
|
345
|
+
if (layerId) {
|
|
346
|
+
const layer = layers.find((layer) => layer.getId() === layerId);
|
|
347
|
+
if (layer) {
|
|
348
|
+
layer.clear();
|
|
349
|
+
onAnnotationUpdate(layerId, toPng());
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Serializes the annotation layer image data into a binary buffer
|
|
356
|
+
* @return A binary buffer
|
|
357
|
+
*/
|
|
358
|
+
export async function toPng(): Promise<Blob> {
|
|
359
|
+
// For now, return the active layer's PNG or an empty blob
|
|
360
|
+
return (await activeLayer?.toPng()) ?? new Blob();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Exports the active annotation layer state as RLE-encoded data
|
|
365
|
+
* @returns RLE encoded Uint8Array
|
|
366
|
+
*/
|
|
367
|
+
export async function toRLE(): Promise<Uint8Array> {
|
|
368
|
+
return (await activeLayer?.toRLE()) ?? new Uint8Array();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Loads RLE-encoded data into the active annotation layer
|
|
373
|
+
* @param rleData RLE encoded data
|
|
374
|
+
* @param width Image width
|
|
375
|
+
* @param height Image height
|
|
376
|
+
*/
|
|
377
|
+
export async function fromRLE(rleData: Uint8Array, width: number, height: number) {
|
|
378
|
+
return activeLayer?.fromRLE(rleData, width, height);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Loads RLE-encoded data into a specific annotation layer by ID
|
|
383
|
+
* @param layerId The ID of the annotation layer
|
|
384
|
+
* @param rleData RLE encoded data
|
|
385
|
+
*/
|
|
386
|
+
export async function loadMask(layerId: string, rleData: Uint8Array) {
|
|
387
|
+
// Filter out null entries that may exist before components are rendered
|
|
388
|
+
const layer = layers.find((layer) => layer && layer.getId() === layerId);
|
|
389
|
+
if (layer) {
|
|
390
|
+
return layer.fromRLE(rleData, 1024, 1024);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
</script>
|
|
394
|
+
|
|
395
|
+
<LayerInput
|
|
396
|
+
id="annotation"
|
|
397
|
+
{isActive}
|
|
398
|
+
layerSize={{ width: 1, height: 1 }}
|
|
399
|
+
target={mesh}
|
|
400
|
+
{onMouseDown}
|
|
401
|
+
onMouseMove={draw}
|
|
402
|
+
{onMouseUp}
|
|
403
|
+
{onMouseLeave}
|
|
404
|
+
/>
|
|
405
|
+
|
|
406
|
+
<!--
|
|
407
|
+
Invisible mesh used for input detection.
|
|
408
|
+
The plane geometry is larger than the map size to allow cursor
|
|
409
|
+
events to be detected outside of the fog of war layer.
|
|
410
|
+
-->
|
|
411
|
+
<T.Mesh bind:ref={mesh} name="annotationInput" layers={isActive ? [SceneLayer.Input] : undefined}>
|
|
412
|
+
<T.MeshBasicMaterial visible={false} />
|
|
413
|
+
<T.PlaneGeometry args={[2 * display.resolution.x, 2 * display.resolution.y]} />
|
|
414
|
+
</T.Mesh>
|
|
415
|
+
|
|
416
|
+
<!-- Brush outline for annotation tool -->
|
|
417
|
+
<T.Mesh
|
|
418
|
+
bind:ref={outlineMesh}
|
|
419
|
+
name="annotationToolOutline"
|
|
420
|
+
layers={[SceneLayer.Overlay]}
|
|
421
|
+
scale={[display.resolution.x, display.resolution.y, 1]}
|
|
422
|
+
renderOrder={SceneLayerOrder.Cursor}
|
|
423
|
+
>
|
|
424
|
+
<T is={outlineMaterial} transparent={true} depthTest={false} />
|
|
425
|
+
<T.PlaneGeometry />
|
|
426
|
+
</T.Mesh>
|
|
427
|
+
|
|
428
|
+
<!--
|
|
429
|
+
Effect annotations render on Main layer (under fog, with post-processing).
|
|
430
|
+
Color annotations render on Overlay layer (over fog, no post-processing).
|
|
431
|
+
-->
|
|
432
|
+
<T.Mesh name="annotationLayer" scale={[display.resolution.x, display.resolution.y, 1]}>
|
|
433
|
+
{#each props.layers as layer, index (layer.id)}
|
|
434
|
+
<T.Mesh
|
|
435
|
+
name={layer.id}
|
|
436
|
+
visible={isVisible(layer)}
|
|
437
|
+
position.z={(props.layers.length - index) * 0.001}
|
|
438
|
+
layers={hasEffect(layer) ? [SceneLayer.Main] : [SceneLayer.Overlay]}
|
|
439
|
+
renderOrder={hasEffect(layer) ? SceneLayerOrder.EffectAnnotation : SceneLayerOrder.Annotation}
|
|
440
|
+
>
|
|
441
|
+
<AnnotationMaterial bind:this={layers[index]} props={layer} {display} lineWidth={props.lineWidth} />
|
|
442
|
+
<T.PlaneGeometry />
|
|
443
|
+
</T.Mesh>
|
|
444
|
+
{/each}
|
|
445
|
+
</T.Mesh>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
import { T, useTask } from '@threlte/core';
|
|
4
|
+
import { onDestroy } from 'svelte';
|
|
5
|
+
import chroma from 'chroma-js';
|
|
6
|
+
import DrawingMaterial from '../DrawingLayer/DrawingMaterial.svelte';
|
|
7
|
+
import { type AnnotationLayerData, AnnotationEffect } from './types';
|
|
8
|
+
import { clippingPlaneStore } from '../../helpers/clippingPlaneStore.svelte';
|
|
9
|
+
import { DrawMode, ToolType, InitialState } from '../DrawingLayer/types';
|
|
10
|
+
import type { DisplayProps } from '../Stage/types';
|
|
11
|
+
|
|
12
|
+
import annotationEffectsFragmentShader from '../../shaders/AnnotationEffects.frag?raw';
|
|
13
|
+
import annotationVertexShader from '../../shaders/default.vert?raw';
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
props: AnnotationLayerData;
|
|
17
|
+
display: DisplayProps;
|
|
18
|
+
lineWidth?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { props, display, lineWidth = 2.0 }: Props = $props();
|
|
22
|
+
|
|
23
|
+
const lineWidthPixels = $derived.by(() => {
|
|
24
|
+
const textureSize = Math.min(display.resolution.x, display.resolution.y);
|
|
25
|
+
return Math.round(textureSize * (lineWidth / 100));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
let size = $derived({ width: display.resolution.x, height: display.resolution.y });
|
|
29
|
+
|
|
30
|
+
let drawMaterial: DrawingMaterial;
|
|
31
|
+
|
|
32
|
+
const hexToRGB = (hex: string): THREE.Vector3 => {
|
|
33
|
+
const [r, g, b] = chroma(hex).gl();
|
|
34
|
+
return new THREE.Vector3(r, g, b);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Hardcoded color for Magic effect (saturated purple)
|
|
38
|
+
const MAGIC_EFFECT_COLOR = '#9333ea';
|
|
39
|
+
|
|
40
|
+
// Use hardcoded color for Magic effect, otherwise use layer color
|
|
41
|
+
let colorUniform = $derived(
|
|
42
|
+
props.effect?.type === AnnotationEffect.Magic ? hexToRGB(MAGIC_EFFECT_COLOR) : hexToRGB(props.color)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const getEffectType = () => props.effect?.type ?? AnnotationEffect.None;
|
|
46
|
+
const getEffectSpeed = () => props.effect?.speed ?? 1.0;
|
|
47
|
+
const getEffectIntensity = () => props.effect?.intensity ?? 1.0;
|
|
48
|
+
const getEffectSoftness = () => props.effect?.softness ?? 0.5;
|
|
49
|
+
const getEffectBorder = () => props.effect?.border ?? 0.5;
|
|
50
|
+
const getEffectRoughness = () => props.effect?.roughness ?? 0.0;
|
|
51
|
+
|
|
52
|
+
let material = new THREE.ShaderMaterial({
|
|
53
|
+
defines: {
|
|
54
|
+
NUM_CLIPPING_PLANES: 4
|
|
55
|
+
},
|
|
56
|
+
uniforms: {
|
|
57
|
+
uMaskTexture: { value: null },
|
|
58
|
+
uTime: { value: 0.0 },
|
|
59
|
+
uEffectType: { value: getEffectType() },
|
|
60
|
+
uBaseColor: { value: hexToRGB(props.color) },
|
|
61
|
+
uOpacity: { value: props.opacity },
|
|
62
|
+
uSpeed: { value: getEffectSpeed() },
|
|
63
|
+
uIntensity: { value: getEffectIntensity() },
|
|
64
|
+
uSoftness: { value: getEffectSoftness() },
|
|
65
|
+
uBorder: { value: getEffectBorder() },
|
|
66
|
+
uRoughness: { value: getEffectRoughness() },
|
|
67
|
+
uEdgeMinMipMapLevel: { value: 0 },
|
|
68
|
+
uEdgeMaxMipMapLevel: { value: 4 },
|
|
69
|
+
uClippingPlanes: new THREE.Uniform(
|
|
70
|
+
clippingPlaneStore.value.map((p) => new THREE.Vector4(p.normal.x, p.normal.y, p.normal.z, p.constant))
|
|
71
|
+
)
|
|
72
|
+
},
|
|
73
|
+
transparent: true,
|
|
74
|
+
depthWrite: false, // Prevent transparent overlay from affecting depth buffer
|
|
75
|
+
fragmentShader: annotationEffectsFragmentShader,
|
|
76
|
+
vertexShader: annotationVertexShader
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
$effect(() => {
|
|
80
|
+
material.uniforms.uBaseColor.value.copy(colorUniform);
|
|
81
|
+
material.uniforms.uOpacity.value = props.opacity;
|
|
82
|
+
material.uniforms.uEffectType.value = getEffectType();
|
|
83
|
+
material.uniforms.uSpeed.value = getEffectSpeed();
|
|
84
|
+
material.uniforms.uIntensity.value = getEffectIntensity();
|
|
85
|
+
material.uniforms.uSoftness.value = getEffectSoftness();
|
|
86
|
+
material.uniforms.uBorder.value = getEffectBorder();
|
|
87
|
+
material.uniforms.uRoughness.value = getEffectRoughness();
|
|
88
|
+
// Update clipping planes in place to avoid allocating new Vector4 objects
|
|
89
|
+
const planes = clippingPlaneStore.value;
|
|
90
|
+
for (let i = 0; i < planes.length; i++) {
|
|
91
|
+
const p = planes[i];
|
|
92
|
+
material.uniforms.uClippingPlanes.value[i].set(p.normal.x, p.normal.y, p.normal.z, p.constant);
|
|
93
|
+
}
|
|
94
|
+
material.uniformsNeedUpdate = true;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
useTask((delta) => {
|
|
98
|
+
material.uniforms.uTime.value += delta;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
export const getId = () => props.id;
|
|
102
|
+
|
|
103
|
+
export const revertChanges = () => {
|
|
104
|
+
drawMaterial.revert();
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const resetCursor = () => {
|
|
108
|
+
if (drawMaterial) {
|
|
109
|
+
drawMaterial.resetCursor();
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const clear = () => {
|
|
114
|
+
drawMaterial.clear();
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const drawPath = (start: THREE.Vector2, last: THREE.Vector2 | null = null, persist: boolean = false) => {
|
|
118
|
+
drawMaterial.drawPath(start, last, persist);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const toPng = async (): Promise<Blob> => {
|
|
122
|
+
return drawMaterial.toPng();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export const toRLE = async (): Promise<Uint8Array> => {
|
|
126
|
+
return drawMaterial.toRLE();
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export const fromRLE = async (rleData: Uint8Array, width: number, height: number) => {
|
|
130
|
+
return drawMaterial.fromRLE(rleData, width, height);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
onDestroy(() => {
|
|
134
|
+
material.dispose();
|
|
135
|
+
});
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
<DrawingMaterial
|
|
139
|
+
bind:this={drawMaterial}
|
|
140
|
+
props={{
|
|
141
|
+
url: props.url,
|
|
142
|
+
opacity: {
|
|
143
|
+
dm: props.opacity,
|
|
144
|
+
player: props.opacity
|
|
145
|
+
},
|
|
146
|
+
tool: {
|
|
147
|
+
mode: DrawMode.Draw,
|
|
148
|
+
size: lineWidthPixels,
|
|
149
|
+
type: ToolType.Brush
|
|
150
|
+
}
|
|
151
|
+
}}
|
|
152
|
+
initialState={InitialState.Clear}
|
|
153
|
+
{size}
|
|
154
|
+
onRender={(texture) => {
|
|
155
|
+
material.uniforms.uBaseColor.value.copy(colorUniform);
|
|
156
|
+
material.uniforms.uMaskTexture.value = texture;
|
|
157
|
+
material.uniformsNeedUpdate = true;
|
|
158
|
+
}}
|
|
159
|
+
/>
|
|
160
|
+
|
|
161
|
+
{#snippet attachMaterial()}
|
|
162
|
+
{material}
|
|
163
|
+
{/snippet}
|
|
164
|
+
|
|
165
|
+
<T is={material}>
|
|
166
|
+
{@render attachMaterial()}
|
|
167
|
+
</T>
|