@pascal-app/editor 0.7.0 → 0.9.1
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 +12 -13
- package/src/components/editor/alignment-3d-guide-layer.tsx +160 -0
- package/src/components/editor/custom-camera-controls.tsx +755 -25
- package/src/components/editor/editor-layout-mobile.tsx +3 -3
- package/src/components/editor/editor-layout-v2.tsx +62 -54
- package/src/components/editor/export-manager.tsx +32 -3
- package/src/components/editor/first-person/build-collider-world.test.ts +149 -0
- package/src/components/editor/first-person/build-collider-world.ts +203 -87
- package/src/components/editor/first-person/bvh-ecctrl.tsx +127 -62
- package/src/components/editor/first-person-controls.tsx +836 -31
- package/src/components/editor/floating-action-menu.tsx +287 -196
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-panel.tsx +5837 -12006
- package/src/components/editor/grid.tsx +51 -17
- package/src/components/editor/group-move-handle.tsx +316 -0
- package/src/components/editor/group-rotate-handle.tsx +382 -0
- package/src/components/editor/group-transform-shared.test.ts +205 -0
- package/src/components/editor/group-transform-shared.ts +256 -0
- package/src/components/editor/handles/handle-arrow.tsx +523 -0
- package/src/components/editor/handles/use-handle-drag.ts +213 -0
- package/src/components/editor/index.tsx +234 -190
- package/src/components/editor/measurement-pill.tsx +75 -0
- package/src/components/editor/node-arrow-handles.tsx +1453 -0
- package/src/components/editor/selection-manager.tsx +435 -158
- package/src/components/editor/site-edge-labels.tsx +44 -20
- package/src/components/editor/slab-hole-highlights.tsx +466 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +539 -0
- package/src/components/editor/thumbnail-generator.tsx +27 -64
- package/src/components/editor/use-floorplan-background-placement.ts +122 -35
- package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
- package/src/components/editor/use-floorplan-scene-data.ts +14 -2
- package/src/components/editor/wall-measurement-label.tsx +11 -6
- package/src/components/editor/wall-move-side-handles.tsx +855 -0
- package/src/components/editor/wall-opening-highlights.tsx +181 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +150 -0
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +6 -1
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +227 -0
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +616 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +75 -0
- package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
- package/src/components/editor-2d/renderers/floorplan-draft-layer.tsx +6 -1
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -61
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +59 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +1963 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +29 -14
- package/src/components/editor-2d/svg-paths.ts +10 -2
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +30 -9
- package/src/components/systems/ceiling/ceiling-system.tsx +12 -8
- package/src/components/systems/roof/roof-edit-system.tsx +86 -12
- package/src/components/systems/stair/stair-edit-system.tsx +2 -2
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -0
- package/src/components/systems/zone/zone-system.tsx +0 -0
- package/src/components/tools/elevator/elevator-defaults.ts +8 -0
- package/src/components/tools/elevator/elevator-tool.tsx +349 -0
- package/src/components/tools/elevator/move-elevator-tool.tsx +252 -0
- package/src/components/tools/fence/fence-drafting.ts +64 -22
- package/src/components/tools/item/move-tool.tsx +40 -107
- package/src/components/tools/item/placement-math.test.ts +10 -0
- package/src/components/tools/item/placement-math.ts +33 -9
- package/src/components/tools/item/placement-strategies.ts +201 -25
- package/src/components/tools/item/placement-types.ts +10 -1
- package/src/components/tools/item/use-draft-node.ts +20 -2
- package/src/components/tools/item/use-placement-coordinator.tsx +700 -299
- package/src/components/tools/registry/move-registry-node-tool.tsx +602 -0
- package/src/components/tools/roof/roof-tool.tsx +108 -12
- package/src/components/tools/select/box-select-state.ts +77 -0
- package/src/components/tools/select/box-select-tool.tsx +324 -543
- package/src/components/tools/select/plane-box-select-tool.tsx +577 -0
- package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
- package/src/components/tools/select/select-candidates.test.ts +109 -0
- package/src/components/tools/select/select-candidates.ts +85 -0
- package/src/components/tools/shared/affordance-dispatch.ts +30 -0
- package/src/components/tools/shared/drag-bounding-box.tsx +171 -0
- package/src/components/tools/shared/floor-stack-preview.ts +25 -0
- package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
- package/src/components/tools/shared/placement-box-geometry.ts +123 -0
- package/src/components/tools/shared/placement-box.tsx +123 -0
- package/src/components/tools/shared/polygon-editor.tsx +617 -167
- package/src/components/tools/shared/segment-angle.ts +57 -1
- package/src/components/tools/site/site-boundary-editor.tsx +464 -12
- package/src/components/tools/site/site-flag-model.ts +18 -0
- package/src/components/tools/stair/stair-defaults.ts +1 -1
- package/src/components/tools/stair/stair-tool.tsx +269 -35
- package/src/components/tools/tool-manager.tsx +172 -56
- package/src/components/tools/wall/wall-drafting.ts +98 -98
- package/src/components/tools/wall/wall-snap-geometry.test.ts +79 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +270 -0
- package/src/components/tools/zone/zone-tool.tsx +36 -11
- package/src/components/ui/action-menu/action-button.tsx +21 -1
- package/src/components/ui/action-menu/camera-actions.tsx +0 -0
- package/src/components/ui/action-menu/control-modes.tsx +5 -76
- package/src/components/ui/action-menu/furnish-tools.tsx +6 -92
- package/src/components/ui/action-menu/index.tsx +33 -196
- package/src/components/ui/action-menu/structure-tools.tsx +13 -88
- package/src/components/ui/action-menu/view-toggles.tsx +262 -37
- package/src/components/ui/command-palette/editor-commands.tsx +24 -17
- package/src/components/ui/command-palette/index.tsx +6 -256
- package/src/components/ui/controls/material-paint-panel.tsx +83 -0
- package/src/components/ui/controls/material-picker.tsx +17 -35
- package/src/components/ui/controls/metric-control.tsx +33 -11
- package/src/components/ui/floating-level-selector.tsx +48 -11
- package/src/components/ui/helpers/helper-manager.tsx +21 -17
- package/src/components/ui/helpers/registered-tool-helper.tsx +25 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +1743 -315
- package/src/components/ui/item-catalog/item-catalog.tsx +91 -46
- package/src/components/ui/level-duplicate-dialog.tsx +5 -6
- package/src/components/ui/panels/node-display.ts +1 -0
- package/src/components/ui/panels/paint-panel.tsx +66 -41
- package/src/components/ui/panels/panel-manager.tsx +33 -74
- package/src/components/ui/panels/panel-wrapper.tsx +217 -12
- package/src/components/ui/panels/parametric-inspector.tsx +469 -0
- package/src/components/ui/panels/reference-panel.tsx +28 -13
- package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
- package/src/components/ui/primitives/number-input.tsx +1 -1
- package/src/components/ui/primitives/sidebar.tsx +0 -0
- package/src/components/ui/scene-loader.tsx +1 -3
- package/src/components/ui/sidebar/app-sidebar.tsx +0 -0
- package/src/components/ui/sidebar/icon-rail.tsx +0 -0
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +404 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +59 -9
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +6 -1
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +259 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +83 -0
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +89 -0
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +83 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +8 -12
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +93 -0
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +41 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +128 -0
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +83 -0
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +68 -34
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +52 -3
- package/src/components/ui/sidebar/tab-bar.tsx +61 -1
- package/src/components/ui/slider.tsx +1 -1
- package/src/components/viewer-overlay.tsx +169 -52
- package/src/components/viewer-zone-system.tsx +1 -1
- package/src/hooks/use-auto-save.ts +24 -3
- package/src/hooks/use-ceiling-events.ts +175 -0
- package/src/hooks/use-drag-action.ts +124 -0
- package/src/hooks/use-keyboard.ts +125 -62
- package/src/hooks/use-selection.ts +64 -0
- package/src/index.tsx +243 -4
- package/src/lib/constants.ts +6 -2
- package/src/lib/editor-api.ts +62 -0
- package/src/lib/elevator-support.ts +96 -0
- package/src/lib/floorplan/apply-alignment.ts +111 -0
- package/src/lib/floorplan/index.ts +7 -0
- package/src/lib/floorplan/selection-tool.ts +40 -0
- package/src/lib/floorplan/stairs.ts +12 -1
- package/src/lib/fresh-planar-placement.test.ts +102 -0
- package/src/lib/fresh-planar-placement.ts +63 -0
- package/src/lib/history.ts +2 -1
- package/src/lib/level-duplication.test.ts +0 -2
- package/src/lib/level-duplication.ts +55 -43
- package/src/lib/level-name.ts +11 -0
- package/src/lib/material-paint.ts +212 -20
- package/src/lib/placement-metadata.ts +29 -0
- package/src/lib/planar-cursor-placement.test.ts +43 -0
- package/src/lib/planar-cursor-placement.ts +42 -0
- package/src/lib/roof-duplication.ts +2 -2
- package/src/lib/scene-bounds.ts +1 -1
- package/src/lib/scene-clipboard.ts +267 -0
- package/src/lib/scene.ts +13 -4
- package/src/lib/sfx-bus.ts +25 -1
- package/src/lib/sfx-player.ts +75 -27
- package/src/lib/site-boundary.ts +1 -0
- package/src/lib/stair-duplication.ts +2 -2
- package/src/lib/stair-levels.test.ts +146 -0
- package/src/lib/stair-levels.ts +177 -0
- package/src/lib/world-grid-snap.ts +199 -0
- package/src/lib/zone-content.ts +130 -0
- package/src/store/use-alignment-guides.ts +21 -0
- package/src/store/use-editor.tsx +342 -91
- package/src/store/use-placement-preview.ts +31 -0
- package/src/store/use-wall-move-ghosts.ts +36 -0
- package/src/store/use-wall-snap-indicator.ts +32 -0
- package/tsconfig.json +2 -1
- package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
- package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
- package/src/components/feedback-dialog.tsx +0 -265
- package/src/components/pascal-radio.tsx +0 -280
- package/src/components/preview-button.tsx +0 -16
- package/src/components/tools/building/move-building-tool.tsx +0 -157
- package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
- package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -257
- package/src/components/tools/column/column-tool.tsx +0 -97
- package/src/components/tools/column/move-column-tool.tsx +0 -105
- package/src/components/tools/door/door-math.ts +0 -110
- package/src/components/tools/door/door-tool.tsx +0 -324
- package/src/components/tools/door/move-door-tool.tsx +0 -412
- package/src/components/tools/fence/curve-fence-tool.tsx +0 -179
- package/src/components/tools/fence/fence-tool.tsx +0 -346
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -438
- package/src/components/tools/fence/move-fence-tool.tsx +0 -298
- package/src/components/tools/item/item-tool.tsx +0 -26
- package/src/components/tools/roof/move-roof-tool.tsx +0 -359
- package/src/components/tools/slab/move-slab-tool.tsx +0 -182
- package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
- package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
- package/src/components/tools/slab/slab-tool.tsx +0 -322
- package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
- package/src/components/tools/spawn/spawn-tool.tsx +0 -130
- package/src/components/tools/wall/curve-wall-tool.tsx +0 -176
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -423
- package/src/components/tools/wall/move-wall-tool.tsx +0 -356
- package/src/components/tools/wall/wall-tool.tsx +0 -332
- package/src/components/tools/window/move-window-tool.tsx +0 -447
- package/src/components/tools/window/window-math.ts +0 -117
- package/src/components/tools/window/window-tool.tsx +0 -332
- package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
- package/src/components/ui/helpers/slab-helper.tsx +0 -20
- package/src/components/ui/helpers/wall-helper.tsx +0 -20
- package/src/components/ui/panels/ceiling-panel.tsx +0 -250
- package/src/components/ui/panels/column-panel.tsx +0 -715
- package/src/components/ui/panels/door-panel.tsx +0 -1292
- package/src/components/ui/panels/fence-panel.tsx +0 -227
- package/src/components/ui/panels/item-panel.tsx +0 -306
- package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
- package/src/components/ui/panels/roof-panel.tsx +0 -230
- package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
- package/src/components/ui/panels/slab-panel.tsx +0 -250
- package/src/components/ui/panels/spawn-panel.tsx +0 -155
- package/src/components/ui/panels/stair-panel.tsx +0 -525
- package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
- package/src/components/ui/panels/wall-panel.tsx +0 -177
- package/src/components/ui/panels/window-panel.tsx +0 -960
- package/src/components/ui/viewer-toolbar.tsx +0 -436
- package/src/contexts/presets-context.tsx +0 -121
- package/src/hooks/use-contextual-tools.ts +0 -61
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { type Cursor, emitter } from '@pascal-app/core'
|
|
4
|
+
import type { ThreeEvent } from '@react-three/fiber'
|
|
5
|
+
import { type ReactNode, useEffect, useMemo, useRef } from 'react'
|
|
6
|
+
import {
|
|
7
|
+
BoxGeometry,
|
|
8
|
+
type BufferGeometry,
|
|
9
|
+
CircleGeometry,
|
|
10
|
+
Color,
|
|
11
|
+
CylinderGeometry,
|
|
12
|
+
DoubleSide,
|
|
13
|
+
ExtrudeGeometry,
|
|
14
|
+
type Group,
|
|
15
|
+
Shape,
|
|
16
|
+
TorusGeometry,
|
|
17
|
+
} from 'three'
|
|
18
|
+
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
|
19
|
+
import { MeshBasicNodeMaterial } from 'three/webgpu'
|
|
20
|
+
import { EDITOR_LAYER } from '../../../lib/constants'
|
|
21
|
+
|
|
22
|
+
export const ARROW_SCALE = 0.65
|
|
23
|
+
export const ARROW_COLOR = '#8381ed'
|
|
24
|
+
export const ARROW_HOVER_COLOR = '#a5b4fc'
|
|
25
|
+
export const NO_RAYCAST = () => null
|
|
26
|
+
export const HIT_AREA_MARGIN = 0.035
|
|
27
|
+
|
|
28
|
+
const HIT_AREA_RENDER_ORDER = 1011
|
|
29
|
+
const HIT_AREA_THICKNESS = 0.08
|
|
30
|
+
const CHEVRON_MIN_X = -0.2
|
|
31
|
+
const CHEVRON_MAX_X = 0.22
|
|
32
|
+
const CHEVRON_HALF_WIDTH = 0.12
|
|
33
|
+
const CHEVRON_NOTCH_X = -0.04
|
|
34
|
+
const CHEVRON_SHAFT_HALF_WIDTH = 0.035
|
|
35
|
+
const CHEVRON_DEPTH = 0.08
|
|
36
|
+
const CHEVRON_BEVEL_THICKNESS = 0.035
|
|
37
|
+
const CHEVRON_BEVEL_SIZE = 0.03
|
|
38
|
+
const CHEVRON_BEVEL_SEGMENTS = 10
|
|
39
|
+
const MOVE_CROSS_HALF_LENGTH = 0.36
|
|
40
|
+
const MOVE_CROSS_SHAFT_HALF_WIDTH = 0.03
|
|
41
|
+
const MOVE_CROSS_HEAD_HALF_WIDTH = 0.12
|
|
42
|
+
const MOVE_CROSS_HEAD_INSET = 0.2
|
|
43
|
+
const MOVE_CROSS_DEPTH = 0.06
|
|
44
|
+
const MOVE_CROSS_BEVEL_THICKNESS = 0.018
|
|
45
|
+
const MOVE_CROSS_BEVEL_SIZE = 0.012
|
|
46
|
+
const MOVE_CROSS_BEVEL_SEGMENTS = 6
|
|
47
|
+
const ROTATE_HANDLE_RADIUS = 0.2
|
|
48
|
+
const ROTATE_HANDLE_HALF_SWEEP = Math.PI / 3
|
|
49
|
+
const ROTATE_RIBBON_HALF_WIDTH = 0.02
|
|
50
|
+
const ROTATE_HEAD_HALF_WIDTH = 0.045
|
|
51
|
+
const TRACKER_CUBE_SIZE = 0.16
|
|
52
|
+
export const CORNER_HEX_RADIUS = 0.16
|
|
53
|
+
|
|
54
|
+
export type HandleArrowShape = 'chevron' | 'cross' | 'curved-arrow' | 'tracker' | 'corner-picker'
|
|
55
|
+
export type HandleArrowInputShape = HandleArrowShape | 'arrow' | 'move-cross'
|
|
56
|
+
|
|
57
|
+
export type HandleArrowPlacement = {
|
|
58
|
+
position: readonly [number, number, number]
|
|
59
|
+
rotation?: readonly [number, number, number]
|
|
60
|
+
baseScale: number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type PointerHandler = (event: ThreeEvent<PointerEvent>) => void
|
|
64
|
+
|
|
65
|
+
export type HandleArrowProps = {
|
|
66
|
+
shape: HandleArrowInputShape
|
|
67
|
+
placement: HandleArrowPlacement
|
|
68
|
+
hover: boolean
|
|
69
|
+
cursor: Cursor
|
|
70
|
+
onHoverChange: (hovered: boolean) => void
|
|
71
|
+
onPointerDown: PointerHandler
|
|
72
|
+
activeCursor?: Cursor
|
|
73
|
+
children?: ReactNode
|
|
74
|
+
hoverScale?: number
|
|
75
|
+
indicatorRotation?: readonly [number, number, number]
|
|
76
|
+
onPointerEnter?: PointerHandler
|
|
77
|
+
onPointerLeave?: PointerHandler
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function normalizeHandleArrowShape(shape: HandleArrowInputShape, cursor: Cursor): HandleArrowShape {
|
|
81
|
+
if (shape === 'arrow') return 'chevron'
|
|
82
|
+
if (shape === 'move-cross') return 'cross'
|
|
83
|
+
if (shape === 'chevron' && cursor === 'move') return 'cross'
|
|
84
|
+
return shape
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Two-headed curved-arrow silhouette for whole-node rotation handles
|
|
88
|
+
// (today: the elevator's corner rotate gizmo). Symmetric arc centred on
|
|
89
|
+
// +X with sweeps to +/-halfSweep, arrowhead wings + tangentially-extended
|
|
90
|
+
// tips at each end. Drawn in 2D then extruded and rotated to lie in the
|
|
91
|
+
// XZ plane - same final-orientation contract as the chevron, so the
|
|
92
|
+
// outer rotation Y and inner-rotation chain in the renderer are reused
|
|
93
|
+
// unchanged.
|
|
94
|
+
export function createRotateArrowHandleGeometry() {
|
|
95
|
+
const R = 0.2
|
|
96
|
+
const ribbonHalfWidth = 0.02
|
|
97
|
+
const halfSweep = Math.PI / 3
|
|
98
|
+
const headHalfWidth = 0.045
|
|
99
|
+
const headOvershoot = 0.075
|
|
100
|
+
const rIn = R - ribbonHalfWidth
|
|
101
|
+
const rOut = R + ribbonHalfWidth
|
|
102
|
+
const a1 = halfSweep
|
|
103
|
+
const a2 = -halfSweep
|
|
104
|
+
|
|
105
|
+
const tip1: [number, number] = [
|
|
106
|
+
R * Math.cos(a1) - headOvershoot * Math.sin(a1),
|
|
107
|
+
R * Math.sin(a1) + headOvershoot * Math.cos(a1),
|
|
108
|
+
]
|
|
109
|
+
const tip2: [number, number] = [
|
|
110
|
+
R * Math.cos(a2) + headOvershoot * Math.sin(a2),
|
|
111
|
+
R * Math.sin(a2) - headOvershoot * Math.cos(a2),
|
|
112
|
+
]
|
|
113
|
+
const innerWing1: [number, number] = [
|
|
114
|
+
(rIn - headHalfWidth) * Math.cos(a1),
|
|
115
|
+
(rIn - headHalfWidth) * Math.sin(a1),
|
|
116
|
+
]
|
|
117
|
+
const outerWing1: [number, number] = [
|
|
118
|
+
(rOut + headHalfWidth) * Math.cos(a1),
|
|
119
|
+
(rOut + headHalfWidth) * Math.sin(a1),
|
|
120
|
+
]
|
|
121
|
+
const innerWing2: [number, number] = [
|
|
122
|
+
(rIn - headHalfWidth) * Math.cos(a2),
|
|
123
|
+
(rIn - headHalfWidth) * Math.sin(a2),
|
|
124
|
+
]
|
|
125
|
+
const outerWing2: [number, number] = [
|
|
126
|
+
(rOut + headHalfWidth) * Math.cos(a2),
|
|
127
|
+
(rOut + headHalfWidth) * Math.sin(a2),
|
|
128
|
+
]
|
|
129
|
+
const innerCorner1: [number, number] = [rIn * Math.cos(a1), rIn * Math.sin(a1)]
|
|
130
|
+
const outerCorner1: [number, number] = [rOut * Math.cos(a1), rOut * Math.sin(a1)]
|
|
131
|
+
const innerCorner2: [number, number] = [rIn * Math.cos(a2), rIn * Math.sin(a2)]
|
|
132
|
+
const outerCorner2: [number, number] = [rOut * Math.cos(a2), rOut * Math.sin(a2)]
|
|
133
|
+
|
|
134
|
+
const shape = new Shape()
|
|
135
|
+
shape.moveTo(innerCorner1[0], innerCorner1[1])
|
|
136
|
+
shape.lineTo(innerWing1[0], innerWing1[1])
|
|
137
|
+
shape.lineTo(tip1[0], tip1[1])
|
|
138
|
+
shape.lineTo(outerWing1[0], outerWing1[1])
|
|
139
|
+
shape.lineTo(outerCorner1[0], outerCorner1[1])
|
|
140
|
+
shape.absarc(0, 0, rOut, a1, a2, true)
|
|
141
|
+
shape.lineTo(outerWing2[0], outerWing2[1])
|
|
142
|
+
shape.lineTo(tip2[0], tip2[1])
|
|
143
|
+
shape.lineTo(innerWing2[0], innerWing2[1])
|
|
144
|
+
shape.lineTo(innerCorner2[0], innerCorner2[1])
|
|
145
|
+
shape.absarc(0, 0, rIn, a2, a1, false)
|
|
146
|
+
shape.closePath()
|
|
147
|
+
|
|
148
|
+
const geometry = new ExtrudeGeometry(shape, {
|
|
149
|
+
depth: 0.06,
|
|
150
|
+
bevelEnabled: true,
|
|
151
|
+
bevelThickness: 0.018,
|
|
152
|
+
bevelSize: 0.012,
|
|
153
|
+
bevelOffset: 0,
|
|
154
|
+
bevelSegments: 6,
|
|
155
|
+
curveSegments: 24,
|
|
156
|
+
steps: 1,
|
|
157
|
+
})
|
|
158
|
+
geometry.translate(0, 0, -0.03)
|
|
159
|
+
geometry.rotateX(-Math.PI / 2)
|
|
160
|
+
geometry.computeVertexNormals()
|
|
161
|
+
geometry.computeBoundingSphere()
|
|
162
|
+
return geometry
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Reused chevron+shaft silhouette. The chevron points along +X by default;
|
|
166
|
+
// callers rotate it around Y for Z-axis handles and into a vertical frame for
|
|
167
|
+
// Y-axis handles.
|
|
168
|
+
export function createArrowHandleGeometry() {
|
|
169
|
+
const shape = new Shape()
|
|
170
|
+
shape.moveTo(CHEVRON_MAX_X, 0)
|
|
171
|
+
shape.lineTo(CHEVRON_NOTCH_X, CHEVRON_HALF_WIDTH)
|
|
172
|
+
shape.lineTo(CHEVRON_NOTCH_X, CHEVRON_SHAFT_HALF_WIDTH)
|
|
173
|
+
shape.lineTo(CHEVRON_MIN_X, CHEVRON_SHAFT_HALF_WIDTH)
|
|
174
|
+
shape.lineTo(CHEVRON_MIN_X, -CHEVRON_SHAFT_HALF_WIDTH)
|
|
175
|
+
shape.lineTo(CHEVRON_NOTCH_X, -CHEVRON_SHAFT_HALF_WIDTH)
|
|
176
|
+
shape.lineTo(CHEVRON_NOTCH_X, -CHEVRON_HALF_WIDTH)
|
|
177
|
+
shape.lineTo(CHEVRON_MAX_X, 0)
|
|
178
|
+
const geometry = new ExtrudeGeometry(shape, {
|
|
179
|
+
depth: CHEVRON_DEPTH,
|
|
180
|
+
bevelEnabled: true,
|
|
181
|
+
bevelThickness: CHEVRON_BEVEL_THICKNESS,
|
|
182
|
+
bevelSize: CHEVRON_BEVEL_SIZE,
|
|
183
|
+
bevelOffset: 0,
|
|
184
|
+
bevelSegments: CHEVRON_BEVEL_SEGMENTS,
|
|
185
|
+
curveSegments: 16,
|
|
186
|
+
steps: 1,
|
|
187
|
+
})
|
|
188
|
+
geometry.translate(0, 0, -CHEVRON_DEPTH / 2)
|
|
189
|
+
geometry.rotateX(-Math.PI / 2)
|
|
190
|
+
geometry.computeVertexNormals()
|
|
191
|
+
geometry.computeBoundingSphere()
|
|
192
|
+
return geometry
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function createDoubleArrowShape(): Shape {
|
|
196
|
+
const shape = new Shape()
|
|
197
|
+
shape.moveTo(MOVE_CROSS_HALF_LENGTH, 0)
|
|
198
|
+
shape.lineTo(MOVE_CROSS_HEAD_INSET, MOVE_CROSS_HEAD_HALF_WIDTH)
|
|
199
|
+
shape.lineTo(MOVE_CROSS_HEAD_INSET, MOVE_CROSS_SHAFT_HALF_WIDTH)
|
|
200
|
+
shape.lineTo(-MOVE_CROSS_HEAD_INSET, MOVE_CROSS_SHAFT_HALF_WIDTH)
|
|
201
|
+
shape.lineTo(-MOVE_CROSS_HEAD_INSET, MOVE_CROSS_HEAD_HALF_WIDTH)
|
|
202
|
+
shape.lineTo(-MOVE_CROSS_HALF_LENGTH, 0)
|
|
203
|
+
shape.lineTo(-MOVE_CROSS_HEAD_INSET, -MOVE_CROSS_HEAD_HALF_WIDTH)
|
|
204
|
+
shape.lineTo(-MOVE_CROSS_HEAD_INSET, -MOVE_CROSS_SHAFT_HALF_WIDTH)
|
|
205
|
+
shape.lineTo(MOVE_CROSS_HEAD_INSET, -MOVE_CROSS_SHAFT_HALF_WIDTH)
|
|
206
|
+
shape.lineTo(MOVE_CROSS_HEAD_INSET, -MOVE_CROSS_HEAD_HALF_WIDTH)
|
|
207
|
+
shape.closePath()
|
|
208
|
+
return shape
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function createMoveCrossHandleGeometry() {
|
|
212
|
+
const shape = createDoubleArrowShape()
|
|
213
|
+
const extrudeOpts = {
|
|
214
|
+
depth: MOVE_CROSS_DEPTH,
|
|
215
|
+
bevelEnabled: true,
|
|
216
|
+
bevelThickness: MOVE_CROSS_BEVEL_THICKNESS,
|
|
217
|
+
bevelSize: MOVE_CROSS_BEVEL_SIZE,
|
|
218
|
+
bevelOffset: 0,
|
|
219
|
+
bevelSegments: MOVE_CROSS_BEVEL_SEGMENTS,
|
|
220
|
+
curveSegments: 8,
|
|
221
|
+
steps: 1,
|
|
222
|
+
}
|
|
223
|
+
const armX = new ExtrudeGeometry(shape, extrudeOpts)
|
|
224
|
+
armX.translate(0, 0, -MOVE_CROSS_DEPTH / 2)
|
|
225
|
+
armX.rotateX(-Math.PI / 2)
|
|
226
|
+
const armZ = armX.clone()
|
|
227
|
+
armZ.rotateY(Math.PI / 2)
|
|
228
|
+
const merged = mergeGeometries([armX, armZ], false)
|
|
229
|
+
if (!merged) {
|
|
230
|
+
armZ.dispose()
|
|
231
|
+
armX.computeVertexNormals()
|
|
232
|
+
armX.computeBoundingSphere()
|
|
233
|
+
return armX
|
|
234
|
+
}
|
|
235
|
+
armX.dispose()
|
|
236
|
+
armZ.dispose()
|
|
237
|
+
merged.computeVertexNormals()
|
|
238
|
+
merged.computeBoundingSphere()
|
|
239
|
+
return merged
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function createArrowHitAreaGeometry() {
|
|
243
|
+
const length = CHEVRON_MAX_X - CHEVRON_MIN_X + HIT_AREA_MARGIN * 2
|
|
244
|
+
const centerX = (CHEVRON_MIN_X + CHEVRON_MAX_X) / 2
|
|
245
|
+
const geometry = new CylinderGeometry(
|
|
246
|
+
CHEVRON_HALF_WIDTH + HIT_AREA_MARGIN,
|
|
247
|
+
CHEVRON_HALF_WIDTH + HIT_AREA_MARGIN,
|
|
248
|
+
length,
|
|
249
|
+
16,
|
|
250
|
+
)
|
|
251
|
+
geometry.rotateZ(-Math.PI / 2)
|
|
252
|
+
geometry.translate(centerX, 0, 0)
|
|
253
|
+
geometry.computeBoundingSphere()
|
|
254
|
+
return geometry
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function createMoveCrossHitAreaGeometry() {
|
|
258
|
+
const geometry = new CylinderGeometry(
|
|
259
|
+
MOVE_CROSS_HALF_LENGTH + HIT_AREA_MARGIN,
|
|
260
|
+
MOVE_CROSS_HALF_LENGTH + HIT_AREA_MARGIN,
|
|
261
|
+
HIT_AREA_THICKNESS,
|
|
262
|
+
32,
|
|
263
|
+
)
|
|
264
|
+
geometry.computeBoundingSphere()
|
|
265
|
+
return geometry
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function createRotateArrowHitAreaGeometry() {
|
|
269
|
+
const halfSweep = ROTATE_HANDLE_HALF_SWEEP + HIT_AREA_MARGIN / ROTATE_HANDLE_RADIUS
|
|
270
|
+
const geometry = new TorusGeometry(
|
|
271
|
+
ROTATE_HANDLE_RADIUS,
|
|
272
|
+
ROTATE_RIBBON_HALF_WIDTH + ROTATE_HEAD_HALF_WIDTH + HIT_AREA_MARGIN,
|
|
273
|
+
10,
|
|
274
|
+
64,
|
|
275
|
+
halfSweep * 2,
|
|
276
|
+
)
|
|
277
|
+
geometry.rotateZ(-halfSweep)
|
|
278
|
+
geometry.rotateX(-Math.PI / 2)
|
|
279
|
+
geometry.computeBoundingSphere()
|
|
280
|
+
return geometry
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function createTrackerHitAreaGeometry() {
|
|
284
|
+
const size = TRACKER_CUBE_SIZE + HIT_AREA_MARGIN * 2
|
|
285
|
+
const geometry = new BoxGeometry(size, size, size)
|
|
286
|
+
geometry.computeBoundingSphere()
|
|
287
|
+
return geometry
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function createEndpointHitAreaGeometry(radius: number) {
|
|
291
|
+
const geometry = new CylinderGeometry(
|
|
292
|
+
radius + HIT_AREA_MARGIN,
|
|
293
|
+
radius + HIT_AREA_MARGIN,
|
|
294
|
+
HIT_AREA_THICKNESS,
|
|
295
|
+
24,
|
|
296
|
+
)
|
|
297
|
+
geometry.rotateX(Math.PI / 2)
|
|
298
|
+
geometry.computeBoundingSphere()
|
|
299
|
+
return geometry
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function createHandleArrowGeometry(shape: HandleArrowShape) {
|
|
303
|
+
if (shape === 'chevron') return createArrowHandleGeometry()
|
|
304
|
+
if (shape === 'cross') return createMoveCrossHandleGeometry()
|
|
305
|
+
if (shape === 'curved-arrow') return createRotateArrowHandleGeometry()
|
|
306
|
+
if (shape === 'tracker') {
|
|
307
|
+
const geometry = new BoxGeometry(TRACKER_CUBE_SIZE, TRACKER_CUBE_SIZE, TRACKER_CUBE_SIZE)
|
|
308
|
+
geometry.computeBoundingSphere()
|
|
309
|
+
return geometry
|
|
310
|
+
}
|
|
311
|
+
const geometry = new CircleGeometry(CORNER_HEX_RADIUS, 6)
|
|
312
|
+
geometry.computeBoundingSphere()
|
|
313
|
+
return geometry
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function createHandleArrowHitGeometry(shape: HandleArrowShape) {
|
|
317
|
+
if (shape === 'chevron') return createArrowHitAreaGeometry()
|
|
318
|
+
if (shape === 'cross') return createMoveCrossHitAreaGeometry()
|
|
319
|
+
if (shape === 'curved-arrow') return createRotateArrowHitAreaGeometry()
|
|
320
|
+
if (shape === 'tracker') return createTrackerHitAreaGeometry()
|
|
321
|
+
const geometry = new CircleGeometry(CORNER_HEX_RADIUS, 6)
|
|
322
|
+
geometry.computeBoundingSphere()
|
|
323
|
+
return geometry
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let sharedHitAreaMaterial: MeshBasicNodeMaterial | null = null
|
|
327
|
+
let sharedHitAreaMaterialRefs = 0
|
|
328
|
+
|
|
329
|
+
function createInvisibleHitAreaMaterial() {
|
|
330
|
+
return new MeshBasicNodeMaterial({
|
|
331
|
+
color: new Color('#ffffff'),
|
|
332
|
+
colorWrite: false,
|
|
333
|
+
depthTest: false,
|
|
334
|
+
depthWrite: false,
|
|
335
|
+
opacity: 0,
|
|
336
|
+
side: DoubleSide,
|
|
337
|
+
transparent: true,
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function useInvisibleHitAreaMaterial(): MeshBasicNodeMaterial {
|
|
342
|
+
const materialRef = useRef<MeshBasicNodeMaterial | null>(null)
|
|
343
|
+
if (!materialRef.current) {
|
|
344
|
+
sharedHitAreaMaterial ??= createInvisibleHitAreaMaterial()
|
|
345
|
+
materialRef.current = sharedHitAreaMaterial
|
|
346
|
+
}
|
|
347
|
+
useEffect(() => {
|
|
348
|
+
sharedHitAreaMaterialRefs += 1
|
|
349
|
+
return () => {
|
|
350
|
+
sharedHitAreaMaterialRefs -= 1
|
|
351
|
+
if (sharedHitAreaMaterialRefs <= 0 && sharedHitAreaMaterial) {
|
|
352
|
+
sharedHitAreaMaterial.dispose()
|
|
353
|
+
sharedHitAreaMaterial = null
|
|
354
|
+
sharedHitAreaMaterialRefs = 0
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}, [])
|
|
358
|
+
return materialRef.current
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export function InvisibleHandleHitArea({
|
|
362
|
+
geometry,
|
|
363
|
+
material,
|
|
364
|
+
onPointerDown,
|
|
365
|
+
onPointerEnter,
|
|
366
|
+
onPointerLeave,
|
|
367
|
+
scale,
|
|
368
|
+
}: {
|
|
369
|
+
geometry: BufferGeometry
|
|
370
|
+
material: MeshBasicNodeMaterial
|
|
371
|
+
onPointerDown: PointerHandler
|
|
372
|
+
onPointerEnter: PointerHandler
|
|
373
|
+
onPointerLeave: PointerHandler
|
|
374
|
+
scale: number
|
|
375
|
+
}) {
|
|
376
|
+
return (
|
|
377
|
+
<mesh
|
|
378
|
+
frustumCulled={false}
|
|
379
|
+
geometry={geometry}
|
|
380
|
+
layers={EDITOR_LAYER}
|
|
381
|
+
material={material}
|
|
382
|
+
onPointerDown={onPointerDown}
|
|
383
|
+
onPointerEnter={onPointerEnter}
|
|
384
|
+
onPointerLeave={onPointerLeave}
|
|
385
|
+
renderOrder={HIT_AREA_RENDER_ORDER}
|
|
386
|
+
scale={scale}
|
|
387
|
+
/>
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function useArrowMaterial(): MeshBasicNodeMaterial {
|
|
392
|
+
return useMemo(
|
|
393
|
+
() =>
|
|
394
|
+
new MeshBasicNodeMaterial({
|
|
395
|
+
color: new Color(ARROW_COLOR),
|
|
396
|
+
side: DoubleSide,
|
|
397
|
+
// `depthTest: false` keeps the chevron drawing on top of any
|
|
398
|
+
// geometry under it; `depthWrite: true` puts the chevron's depth
|
|
399
|
+
// into the scenePass buffer so the ink-edge shader's depth
|
|
400
|
+
// Laplacian fires on its silhouette from every angle. Without
|
|
401
|
+
// depthWrite, only the normal-discontinuity branch can detect
|
|
402
|
+
// the chevron, and that signal collapses when the arrow's faces
|
|
403
|
+
// happen to align with whatever sits behind them in screen space
|
|
404
|
+
// - which is why the lines used to drop out depending on the view.
|
|
405
|
+
depthTest: false,
|
|
406
|
+
depthWrite: true,
|
|
407
|
+
transparent: true,
|
|
408
|
+
opacity: 1,
|
|
409
|
+
}),
|
|
410
|
+
[],
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function useHandleArrowMaterial(shape: HandleArrowShape): MeshBasicNodeMaterial {
|
|
415
|
+
return useMemo(
|
|
416
|
+
() =>
|
|
417
|
+
new MeshBasicNodeMaterial({
|
|
418
|
+
color: new Color(ARROW_COLOR),
|
|
419
|
+
side: DoubleSide,
|
|
420
|
+
transparent: true,
|
|
421
|
+
opacity: shape === 'corner-picker' ? 0.95 : 1,
|
|
422
|
+
depthTest: false,
|
|
423
|
+
depthWrite: shape !== 'corner-picker',
|
|
424
|
+
}),
|
|
425
|
+
[shape],
|
|
426
|
+
)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function indicatorRenderOrder(shape: HandleArrowShape) {
|
|
430
|
+
return shape === 'tracker' || shape === 'corner-picker' ? 1003 : 1010
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export function HandleArrow({
|
|
434
|
+
shape,
|
|
435
|
+
placement,
|
|
436
|
+
hover,
|
|
437
|
+
cursor,
|
|
438
|
+
activeCursor,
|
|
439
|
+
children,
|
|
440
|
+
hoverScale = 1.12,
|
|
441
|
+
indicatorRotation,
|
|
442
|
+
onHoverChange,
|
|
443
|
+
onPointerDown,
|
|
444
|
+
onPointerEnter,
|
|
445
|
+
onPointerLeave,
|
|
446
|
+
}: HandleArrowProps) {
|
|
447
|
+
const visualShape = normalizeHandleArrowShape(shape, cursor)
|
|
448
|
+
const geometry = useMemo(() => createHandleArrowGeometry(visualShape), [visualShape])
|
|
449
|
+
const hitGeometry = useMemo(() => createHandleArrowHitGeometry(visualShape), [visualShape])
|
|
450
|
+
const indicatorMaterial = useHandleArrowMaterial(visualShape)
|
|
451
|
+
const hitMaterial = useInvisibleHitAreaMaterial()
|
|
452
|
+
const rootRef = useRef<Group>(null)
|
|
453
|
+
const rotation: [number, number, number] = placement.rotation
|
|
454
|
+
? [placement.rotation[0], placement.rotation[1], placement.rotation[2]]
|
|
455
|
+
: [0, 0, 0]
|
|
456
|
+
const localRotation: [number, number, number] = indicatorRotation
|
|
457
|
+
? [indicatorRotation[0], indicatorRotation[1], indicatorRotation[2]]
|
|
458
|
+
: [0, 0, 0]
|
|
459
|
+
const scale = (hover ? hoverScale : 1) * placement.baseScale
|
|
460
|
+
const hitScale = visualShape === 'corner-picker' ? scale : placement.baseScale
|
|
461
|
+
|
|
462
|
+
useEffect(() => {
|
|
463
|
+
indicatorMaterial.color.set(hover ? ARROW_HOVER_COLOR : ARROW_COLOR)
|
|
464
|
+
}, [indicatorMaterial, hover])
|
|
465
|
+
useEffect(() => {
|
|
466
|
+
const hideForCapture = () => {
|
|
467
|
+
if (rootRef.current) rootRef.current.visible = false
|
|
468
|
+
}
|
|
469
|
+
const restoreAfterCapture = () => {
|
|
470
|
+
if (rootRef.current) rootRef.current.visible = true
|
|
471
|
+
}
|
|
472
|
+
emitter.on('thumbnail:before-capture', hideForCapture)
|
|
473
|
+
emitter.on('thumbnail:after-capture', restoreAfterCapture)
|
|
474
|
+
return () => {
|
|
475
|
+
emitter.off('thumbnail:before-capture', hideForCapture)
|
|
476
|
+
emitter.off('thumbnail:after-capture', restoreAfterCapture)
|
|
477
|
+
}
|
|
478
|
+
}, [])
|
|
479
|
+
useEffect(() => () => geometry.dispose(), [geometry])
|
|
480
|
+
useEffect(() => () => hitGeometry.dispose(), [hitGeometry])
|
|
481
|
+
useEffect(() => () => indicatorMaterial.dispose(), [indicatorMaterial])
|
|
482
|
+
|
|
483
|
+
const handleEnter: PointerHandler = (event) => {
|
|
484
|
+
event.stopPropagation()
|
|
485
|
+
onHoverChange(true)
|
|
486
|
+
if (!activeCursor || document.body.style.cursor !== activeCursor) {
|
|
487
|
+
document.body.style.cursor = cursor
|
|
488
|
+
}
|
|
489
|
+
onPointerEnter?.(event)
|
|
490
|
+
}
|
|
491
|
+
const handleLeave: PointerHandler = (event) => {
|
|
492
|
+
event.stopPropagation()
|
|
493
|
+
onHoverChange(false)
|
|
494
|
+
if (document.body.style.cursor === cursor) {
|
|
495
|
+
document.body.style.cursor = ''
|
|
496
|
+
}
|
|
497
|
+
onPointerLeave?.(event)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return (
|
|
501
|
+
<group position={placement.position} ref={rootRef} rotation={rotation}>
|
|
502
|
+
{children}
|
|
503
|
+
<group rotation={localRotation}>
|
|
504
|
+
<InvisibleHandleHitArea
|
|
505
|
+
geometry={hitGeometry}
|
|
506
|
+
material={hitMaterial}
|
|
507
|
+
onPointerDown={onPointerDown}
|
|
508
|
+
onPointerEnter={handleEnter}
|
|
509
|
+
onPointerLeave={handleLeave}
|
|
510
|
+
scale={hitScale}
|
|
511
|
+
/>
|
|
512
|
+
<mesh
|
|
513
|
+
frustumCulled={false}
|
|
514
|
+
geometry={geometry}
|
|
515
|
+
material={indicatorMaterial}
|
|
516
|
+
raycast={NO_RAYCAST}
|
|
517
|
+
renderOrder={indicatorRenderOrder(visualShape)}
|
|
518
|
+
scale={scale}
|
|
519
|
+
/>
|
|
520
|
+
</group>
|
|
521
|
+
</group>
|
|
522
|
+
)
|
|
523
|
+
}
|