@volter/editor-threejs 0.5.65 → 0.5.67
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/NOTICE +2 -0
- package/contributions/animation-mixers.service.ts +20 -0
- package/contributions/animation-timeline.utility.tsx +44 -0
- package/contributions/three-integration.service.ts +13 -0
- package/dist-node/serving.mjs +405 -0
- package/package.json +113 -5
- package/serving/animation-live-module.ts +70 -0
- package/serving/animation-stamp.ts +88 -0
- package/serving/index.ts +14 -0
- package/serving/model-import-conversion.ts +344 -0
- package/src/adapter/ingest/scene-capture.ts +1 -23
- package/src/adapter/renderer-config.ts +3 -4
- package/src/adapter/three-contract.ts +72 -0
- package/src/animation/live-mixers.ts +55 -0
- package/src/ecs/object-marks.ts +1 -1
- package/src/ecs/user-data.ts +0 -16
- package/src/host-hierarchy-objects.ts +31 -0
- package/src/kit/animation/three-clips-subject.ts +190 -0
- package/src/kit/asset-compare.ts +294 -0
- package/src/kit/asset-preview-command.ts +265 -0
- package/src/kit/asset-preview-framing.ts +357 -0
- package/src/kit/asset-preview.ts +2802 -0
- package/src/kit/asset-workflow/model-inspection.ts +830 -0
- package/src/kit/authoring/component-instance-root.ts +171 -0
- package/src/kit/authoring/design-time-settle.ts +343 -0
- package/src/kit/authoring/live-object-transform.ts +62 -0
- package/src/kit/authoring/object3d-document-session-registry.ts +154 -0
- package/src/kit/authoring/object3d-document-session.ts +1965 -0
- package/src/kit/authoring/object3d-gesture-controller.ts +113 -0
- package/src/kit/authoring/quarks-particle-systems.ts +19 -0
- package/src/kit/authoring/shell-viewport-policy.ts +48 -0
- package/src/kit/authoring/source-object3d-authoring-adapter.ts +526 -0
- package/src/kit/authoring/three-projection-core.ts +226 -0
- package/src/kit/authoring/viewport-pick-context.ts +39 -0
- package/src/kit/authoring/viewport-raycast.ts +240 -0
- package/src/kit/authoring/world-hidden-viewport.ts +95 -0
- package/src/kit/camera-authoring.ts +175 -0
- package/src/kit/components/CameraInfo.tsx +56 -0
- package/src/kit/components/InspectorObjectPreview.tsx +57 -0
- package/src/kit/components/Object3DDocumentToolbar.tsx +549 -0
- package/src/kit/components/Object3DDocumentViewport.tsx +58 -0
- package/src/kit/components/StageHost.tsx +2547 -0
- package/src/kit/components/StageOverlays.tsx +21 -0
- package/src/kit/components/StatsOverlay.tsx +78 -0
- package/src/kit/components/ToolObject3DPreview.tsx +39 -0
- package/src/kit/components/ViewportFurniture.tsx +655 -0
- package/src/kit/components/ViewportOverlay.tsx +215 -0
- package/src/kit/components/ViewportShadingMenu.tsx +340 -0
- package/src/kit/components/ViewportViewMenu.tsx +155 -0
- package/src/kit/components/asset-viewers/EntityModelDocument.tsx +121 -0
- package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
- package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +395 -0
- package/src/kit/components/asset-viewers/LutAssetDocument.tsx +444 -0
- package/src/kit/components/asset-viewers/ModelAssetDocument.tsx +105 -0
- package/src/kit/components/asset-viewers/Object3DPreview.tsx +356 -0
- package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +527 -0
- package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
- package/src/kit/components/asset-viewers/three-asset-viewers.tsx +132 -0
- package/src/kit/components/object3d-contribution-surfaces.tsx +33 -0
- package/src/kit/components/stage-keyboard.tsx +40 -0
- package/src/kit/components/stage-overlay-set.tsx +105 -0
- package/src/kit/components/stage-presence-markers.ts +482 -0
- package/src/kit/components/stage-transform-chrome.ts +30 -0
- package/src/kit/components/stage-transform-tools.tsx +73 -0
- package/src/kit/components/stage-view-name.ts +30 -0
- package/src/kit/components/standard-viewport-dressing.ts +1042 -0
- package/src/kit/components/world-root-binding.ts +64 -0
- package/src/kit/constraint-helper.ts +338 -0
- package/src/kit/editor-shell-store.ts +814 -0
- package/src/kit/editor-viewport.ts +6621 -0
- package/src/kit/entity-lod.ts +31 -0
- package/src/kit/entity-object.ts +92 -0
- package/src/kit/hierarchy-mark-reader.ts +74 -0
- package/src/kit/instanced-presentation.ts +164 -0
- package/src/kit/live-module-source.ts +230 -0
- package/src/kit/model-thumbnail.ts +539 -0
- package/src/kit/play-camera-flight.ts +300 -0
- package/src/kit/projection/three.ts +898 -0
- package/src/kit/reflection-probe-helper.ts +142 -0
- package/src/kit/scene-document-viewport.ts +51 -0
- package/src/kit/scene-framing.ts +315 -0
- package/src/kit/scene-view-fog.ts +89 -0
- package/src/kit/spatial-handle-visuals.ts +332 -0
- package/src/kit/stories/three-story-model.ts +66 -0
- package/src/kit/three-canvas-render.ts +44 -0
- package/src/kit/three-hierarchy-row-media.ts +26 -0
- package/src/kit/three-inspection-media.ts +73 -0
- package/src/kit/three-integration.ts +86 -0
- package/src/kit/three-state.ts +33 -0
- package/src/kit/three-viewport/bone-selection-highlight.ts +119 -0
- package/src/kit/three-viewport/camera-fit.ts +41 -0
- package/src/kit/three-viewport/interactive-renderer.ts +132 -0
- package/src/kit/three-viewport/selection-brackets.ts +355 -0
- package/src/kit/three-viewport/selection-outline.ts +333 -0
- package/src/kit/three-viewport/skeleton-helper.ts +61 -0
- package/src/kit/three-viewport/source-color.ts +197 -0
- package/src/kit/three-viewport/studio-environment.ts +96 -0
- package/src/kit/trigger-volume-helper.ts +116 -0
- package/src/kit/viewport-actions.ts +128 -0
- package/src/kit/viewport-authoring-policy.ts +154 -0
- package/src/kit/viewport-commands.ts +318 -0
- package/src/kit/viewport-hotkeys.ts +119 -0
- package/src/kit/viewport-shading-boundary.ts +12 -0
- package/src/kit/viewport-status-facet.ts +53 -0
- package/src/object3d-contributions.ts +494 -0
- package/src/render/viewport-shading.ts +6 -2
- package/src/viewport/content-bounds.ts +38 -4
- package/src/viewport/environment.ts +16 -0
- package/src/viewport-api.ts +92 -0
- package/src/viewport-door.ts +237 -0
- package/src/animation/animation-clock.ts +0 -479
- package/src/animation/runtime-inspection.ts +0 -45
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE WORLD ROOT'S CONTENT BINDING, as the stage host sees it.
|
|
3
|
+
*
|
|
4
|
+
* A stage shows either one Object3D a caller builds (`build` content) or the
|
|
5
|
+
* project's world (`world-root` content): the manifest's roots mounted by a
|
|
6
|
+
* design session, drawn by the world's own image pipeline, with the presenter
|
|
7
|
+
* Play adopts live roots through. The host owns the surface's place, the frame
|
|
8
|
+
* session, the per-stage door, the furniture and the overlays for both. What
|
|
9
|
+
* mounts a world is the contributing package's — it runs the game runtime,
|
|
10
|
+
* which a modeling host never carries — so the package hands the host this
|
|
11
|
+
* binding and the host loads it only for a stage that shows a world.
|
|
12
|
+
*/
|
|
13
|
+
import type { ComponentType, RefObject } from 'react';
|
|
14
|
+
import type * as THREE from 'three';
|
|
15
|
+
import type { EditorShellStore } from '../editor-shell-store';
|
|
16
|
+
import type { EditorStats } from '@volter/editor-sdk/kit/editor-runtime';
|
|
17
|
+
import type { EditorViewport } from '../editor-viewport';
|
|
18
|
+
import type { ViewportPresentation, ViewportRoot } from '../../viewport-api';
|
|
19
|
+
|
|
20
|
+
export interface WorldRootStageOptions {
|
|
21
|
+
/** The session's store: the world is the session's own subject. */
|
|
22
|
+
readonly store: EditorShellStore;
|
|
23
|
+
readonly documentId: string;
|
|
24
|
+
readonly container: HTMLDivElement;
|
|
25
|
+
readonly canvas: HTMLCanvasElement;
|
|
26
|
+
readonly renderer: THREE.WebGLRenderer;
|
|
27
|
+
readonly stats: EditorStats;
|
|
28
|
+
/** The camera-authoring preview pane, read per frame (a React ref). */
|
|
29
|
+
readonly cameraPreview: () => HTMLDivElement | null;
|
|
30
|
+
readonly onMountStatus: (status: 'mounting' | 'ready') => void;
|
|
31
|
+
readonly onRootIds: (rootIds: readonly string[]) => void;
|
|
32
|
+
/** This stage's frame hook on the per-stage viewport door. */
|
|
33
|
+
readonly runFrame: (deltaSeconds: number) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface WorldRootStage {
|
|
37
|
+
readonly scene: THREE.Scene;
|
|
38
|
+
readonly viewport: EditorViewport;
|
|
39
|
+
frame(timeMs: number, resumed: boolean): void;
|
|
40
|
+
present(roots: readonly ViewportRoot[]): ViewportPresentation | null;
|
|
41
|
+
setHelper(kind: string, object: THREE.Object3D | null): void;
|
|
42
|
+
dispose(): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface WorldRootOverlayProps {
|
|
46
|
+
readonly store: EditorShellStore;
|
|
47
|
+
/** The stage's own document — its stage context is read by this id. */
|
|
48
|
+
readonly documentId: string;
|
|
49
|
+
readonly cameraPreviewRef: RefObject<HTMLDivElement | null>;
|
|
50
|
+
readonly mountStatus: 'mounting' | 'ready';
|
|
51
|
+
readonly rootIds: readonly string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface WorldRootStageBinding {
|
|
55
|
+
/** The world's own drawing surface, built to the world's render settings. */
|
|
56
|
+
mountWorldRootSurface(
|
|
57
|
+
canvasHost: HTMLDivElement,
|
|
58
|
+
container: HTMLDivElement,
|
|
59
|
+
displayName: string,
|
|
60
|
+
): { canvas: HTMLCanvasElement; renderer: THREE.WebGLRenderer; lease: null };
|
|
61
|
+
installWorldRootStage(options: WorldRootStageOptions): WorldRootStage;
|
|
62
|
+
/** What the stage shows over a world: its mount state and camera authoring. */
|
|
63
|
+
readonly Overlays: ComponentType<WorldRootOverlayProps>;
|
|
64
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { EDITOR_LAYER } from '@volter/editor-threejs/viewport/editor-layers';
|
|
2
|
+
import { type ConstraintMark, constraintsOf } from '@volter/editor-threejs/adapter/constraint';
|
|
3
|
+
import { setUserData } from '@volter/editor-threejs/ecs/user-data';
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
|
|
6
|
+
const CHAIN = new THREE.Color(0x43c6ff);
|
|
7
|
+
const TARGET = new THREE.Color(0xffb84a);
|
|
8
|
+
const POLE = new THREE.Color(0xd875ff);
|
|
9
|
+
const ERROR = new THREE.Color(0xff4d68);
|
|
10
|
+
const HOVER = new THREE.Color(0xffffff);
|
|
11
|
+
|
|
12
|
+
export type ConstraintControlKind = 'target' | 'pole';
|
|
13
|
+
|
|
14
|
+
/** A pickable viewport effector backed by the constraint's real hierarchy
|
|
15
|
+
* object. The helper mesh is only its editor projection; selection and
|
|
16
|
+
* transforms always go through `object`. */
|
|
17
|
+
export interface ConstraintControl {
|
|
18
|
+
readonly kind: ConstraintControlKind;
|
|
19
|
+
readonly label: string;
|
|
20
|
+
readonly object: THREE.Object3D;
|
|
21
|
+
readonly mesh: THREE.Mesh;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ConstraintVisual {
|
|
25
|
+
readonly mark: ConstraintMark;
|
|
26
|
+
readonly chain: THREE.Line;
|
|
27
|
+
readonly targetLine: THREE.Line;
|
|
28
|
+
readonly poleLine: THREE.Line;
|
|
29
|
+
readonly joints: THREE.Mesh[];
|
|
30
|
+
readonly target: THREE.Mesh;
|
|
31
|
+
readonly pole: THREE.Mesh;
|
|
32
|
+
readonly targetHit: THREE.Mesh;
|
|
33
|
+
readonly poleHit: THREE.Mesh;
|
|
34
|
+
readonly positions: THREE.Vector3[];
|
|
35
|
+
readonly targetPosition: THREE.Vector3;
|
|
36
|
+
readonly polePosition: THREE.Vector3;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function line(color: THREE.Color): THREE.Line {
|
|
40
|
+
return new THREE.Line(
|
|
41
|
+
new THREE.BufferGeometry(),
|
|
42
|
+
new THREE.LineBasicMaterial({
|
|
43
|
+
color,
|
|
44
|
+
depthTest: false,
|
|
45
|
+
depthWrite: false,
|
|
46
|
+
transparent: true,
|
|
47
|
+
toneMapped: false,
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function point(geometry: THREE.BufferGeometry, color: THREE.Color): THREE.Mesh {
|
|
53
|
+
return new THREE.Mesh(
|
|
54
|
+
geometry,
|
|
55
|
+
new THREE.MeshBasicMaterial({
|
|
56
|
+
color,
|
|
57
|
+
depthTest: false,
|
|
58
|
+
depthWrite: false,
|
|
59
|
+
transparent: true,
|
|
60
|
+
toneMapped: false,
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function setLinePoints(target: THREE.Line, points: readonly THREE.Vector3[]): void {
|
|
66
|
+
target.visible = points.length > 1;
|
|
67
|
+
if (!target.visible) return;
|
|
68
|
+
let position = target.geometry.getAttribute('position') as THREE.BufferAttribute | undefined;
|
|
69
|
+
if (!position || position.count !== points.length) {
|
|
70
|
+
position = new THREE.Float32BufferAttribute(points.length * 3, 3);
|
|
71
|
+
target.geometry.setAttribute('position', position);
|
|
72
|
+
}
|
|
73
|
+
for (let index = 0; index < points.length; index++) {
|
|
74
|
+
position.setXYZ(index, points[index]!.x, points[index]!.y, points[index]!.z);
|
|
75
|
+
}
|
|
76
|
+
position.needsUpdate = true;
|
|
77
|
+
target.geometry.computeBoundingSphere();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Editor-owned projection of project-owned spatial constraints. It uses the
|
|
82
|
+
* real live bones and ordinary target/pole Object3Ds, but every line and point
|
|
83
|
+
* it creates stays on EDITOR_LAYER and out of the authored hierarchy.
|
|
84
|
+
*/
|
|
85
|
+
export class ConstraintHelper extends THREE.Group {
|
|
86
|
+
private selected = false;
|
|
87
|
+
private hoveredControl: THREE.Mesh | null = null;
|
|
88
|
+
private selectedObjects: ReadonlySet<THREE.Object3D> = new Set();
|
|
89
|
+
private visuals: ConstraintVisual[] = [];
|
|
90
|
+
|
|
91
|
+
constructor(readonly source: THREE.Object3D) {
|
|
92
|
+
super();
|
|
93
|
+
this.name = 'Constraint Helper';
|
|
94
|
+
this.renderOrder = 999;
|
|
95
|
+
setUserData(this, 'editorHelper', true);
|
|
96
|
+
setUserData(this, 'editorHelperType', 'constraints');
|
|
97
|
+
this.layers.set(EDITOR_LAYER);
|
|
98
|
+
this.refreshStructure();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
setSelected(selected: boolean): void {
|
|
102
|
+
this.selected = selected;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
setSelectedObjects(objects: ReadonlySet<THREE.Object3D>): void {
|
|
106
|
+
this.selectedObjects = objects;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The orange target and purple pole are effectors, not decorative joints.
|
|
110
|
+
* Expose only those two meshes to the viewport's editor-helper picker. */
|
|
111
|
+
controlMeshes(): THREE.Mesh[] {
|
|
112
|
+
return this.visuals.flatMap((visual) =>
|
|
113
|
+
[visual.targetHit, visual.poleHit].filter((mesh) => mesh.visible),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** True when this helper is already the native viewport presentation for
|
|
118
|
+
* the object. Such nodes must not also receive the legacy corner-bracket
|
|
119
|
+
* fallback: the constraint lines/handle are their visible representation. */
|
|
120
|
+
presents(object: THREE.Object3D): boolean {
|
|
121
|
+
if (object === this.source) return true;
|
|
122
|
+
return this.visuals.some((visual) => {
|
|
123
|
+
const snapshot = visual.mark.getSnapshot();
|
|
124
|
+
return object === snapshot.target || object === snapshot.pole;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
controlFor(mesh: THREE.Object3D): ConstraintControl | null {
|
|
129
|
+
for (const visual of this.visuals) {
|
|
130
|
+
const snapshot = visual.mark.getSnapshot();
|
|
131
|
+
if ((mesh === visual.target || mesh === visual.targetHit) && snapshot.target) {
|
|
132
|
+
return {
|
|
133
|
+
kind: 'target',
|
|
134
|
+
label: snapshot.target.name || `${visual.mark.config.label} target`,
|
|
135
|
+
object: snapshot.target,
|
|
136
|
+
mesh: visual.target,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if ((mesh === visual.pole || mesh === visual.poleHit) && snapshot.pole) {
|
|
140
|
+
return {
|
|
141
|
+
kind: 'pole',
|
|
142
|
+
label: snapshot.pole.name || `${visual.mark.config.label} pole`,
|
|
143
|
+
object: snapshot.pole,
|
|
144
|
+
mesh: visual.pole,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
setHoveredControl(mesh: THREE.Mesh | null): void {
|
|
152
|
+
this.hoveredControl = mesh;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
update(camera: THREE.Camera): void {
|
|
156
|
+
this.refreshStructure();
|
|
157
|
+
for (const visual of this.visuals) this.updateVisual(visual, camera);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
dispose(): void {
|
|
161
|
+
this.clearVisuals();
|
|
162
|
+
this.removeFromParent();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private refreshStructure(): void {
|
|
166
|
+
const marks = constraintsOf(this.source);
|
|
167
|
+
const unchanged =
|
|
168
|
+
marks.length === this.visuals.length &&
|
|
169
|
+
marks.every((mark, index) => {
|
|
170
|
+
const snapshot = mark.getSnapshot();
|
|
171
|
+
const pointCount =
|
|
172
|
+
snapshot.chain.length > 0 ? snapshot.chain.length : snapshot.constrained ? 1 : 0;
|
|
173
|
+
return (
|
|
174
|
+
this.visuals[index]?.mark === mark && this.visuals[index]?.positions.length === pointCount
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
if (unchanged) return;
|
|
178
|
+
this.clearVisuals();
|
|
179
|
+
this.visuals = marks.map((mark) => this.makeVisual(mark));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private makeVisual(mark: ConstraintMark): ConstraintVisual {
|
|
183
|
+
const snapshot = mark.getSnapshot();
|
|
184
|
+
const points =
|
|
185
|
+
snapshot.chain.length > 0
|
|
186
|
+
? snapshot.chain
|
|
187
|
+
: snapshot.constrained
|
|
188
|
+
? [snapshot.constrained]
|
|
189
|
+
: [];
|
|
190
|
+
const chain = line(CHAIN);
|
|
191
|
+
const targetLine = line(TARGET);
|
|
192
|
+
const poleLine = line(POLE);
|
|
193
|
+
const joints = points.map(() => point(new THREE.SphereGeometry(0.035, 10, 7), CHAIN));
|
|
194
|
+
const target = point(new THREE.OctahedronGeometry(0.085), TARGET);
|
|
195
|
+
const pole = point(new THREE.TetrahedronGeometry(0.075), POLE);
|
|
196
|
+
// Gizmos in established editors use a larger invisible picking volume
|
|
197
|
+
// than their painted handle. Preserve the compact octahedron/tetrahedron
|
|
198
|
+
// while giving each a forgiving screen hit target that wins before model
|
|
199
|
+
// geometry behind it.
|
|
200
|
+
const hitMaterial = new THREE.MeshBasicMaterial({
|
|
201
|
+
transparent: true,
|
|
202
|
+
opacity: 0,
|
|
203
|
+
depthTest: false,
|
|
204
|
+
depthWrite: false,
|
|
205
|
+
colorWrite: false,
|
|
206
|
+
});
|
|
207
|
+
const targetHit = new THREE.Mesh(new THREE.SphereGeometry(0.16, 10, 7), hitMaterial);
|
|
208
|
+
const poleHit = new THREE.Mesh(new THREE.SphereGeometry(0.15, 10, 7), hitMaterial.clone());
|
|
209
|
+
for (const object of [
|
|
210
|
+
chain,
|
|
211
|
+
targetLine,
|
|
212
|
+
poleLine,
|
|
213
|
+
...joints,
|
|
214
|
+
target,
|
|
215
|
+
pole,
|
|
216
|
+
targetHit,
|
|
217
|
+
poleHit,
|
|
218
|
+
]) {
|
|
219
|
+
object.renderOrder = this.renderOrder;
|
|
220
|
+
object.layers.set(EDITOR_LAYER);
|
|
221
|
+
this.add(object);
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
mark,
|
|
225
|
+
chain,
|
|
226
|
+
targetLine,
|
|
227
|
+
poleLine,
|
|
228
|
+
joints,
|
|
229
|
+
target,
|
|
230
|
+
pole,
|
|
231
|
+
targetHit,
|
|
232
|
+
poleHit,
|
|
233
|
+
positions: points.map(() => new THREE.Vector3()),
|
|
234
|
+
targetPosition: new THREE.Vector3(),
|
|
235
|
+
polePosition: new THREE.Vector3(),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: each branch updates one independent handle in a compact per-frame projection.
|
|
240
|
+
private updateVisual(visual: ConstraintVisual, camera: THREE.Camera): void {
|
|
241
|
+
const snapshot = visual.mark.getSnapshot();
|
|
242
|
+
const points =
|
|
243
|
+
snapshot.chain.length > 0
|
|
244
|
+
? snapshot.chain
|
|
245
|
+
: snapshot.constrained
|
|
246
|
+
? [snapshot.constrained]
|
|
247
|
+
: [];
|
|
248
|
+
for (let index = 0; index < points.length; index++) {
|
|
249
|
+
points[index]!.getWorldPosition(visual.positions[index]!);
|
|
250
|
+
}
|
|
251
|
+
setLinePoints(visual.chain, visual.positions);
|
|
252
|
+
for (let index = 0; index < visual.joints.length; index++) {
|
|
253
|
+
const joint = visual.joints[index]!;
|
|
254
|
+
const position = visual.positions[index];
|
|
255
|
+
joint.visible = Boolean(position);
|
|
256
|
+
if (position) {
|
|
257
|
+
joint.position.copy(position);
|
|
258
|
+
joint.scale.setScalar(this.handleScale(camera, position));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const tip = visual.positions.at(-1);
|
|
263
|
+
const targetPosition = snapshot.target?.getWorldPosition(visual.targetPosition) ?? null;
|
|
264
|
+
const polePosition = snapshot.pole?.getWorldPosition(visual.polePosition) ?? null;
|
|
265
|
+
setLinePoints(visual.targetLine, tip && targetPosition ? [tip, targetPosition] : []);
|
|
266
|
+
setLinePoints(
|
|
267
|
+
visual.poleLine,
|
|
268
|
+
visual.positions[0] && polePosition ? [visual.positions[0], polePosition] : [],
|
|
269
|
+
);
|
|
270
|
+
visual.target.visible = targetPosition !== null;
|
|
271
|
+
if (targetPosition) {
|
|
272
|
+
visual.target.position.copy(targetPosition);
|
|
273
|
+
visual.target.scale.setScalar(
|
|
274
|
+
this.handleScale(camera, targetPosition) *
|
|
275
|
+
(this.hoveredControl === visual.target ? 1.22 : 1),
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
visual.targetHit.visible = targetPosition !== null;
|
|
279
|
+
if (targetPosition) {
|
|
280
|
+
visual.targetHit.position.copy(targetPosition);
|
|
281
|
+
visual.targetHit.scale.copy(visual.target.scale);
|
|
282
|
+
}
|
|
283
|
+
visual.pole.visible = polePosition !== null;
|
|
284
|
+
if (polePosition) {
|
|
285
|
+
visual.pole.position.copy(polePosition);
|
|
286
|
+
visual.pole.scale.setScalar(
|
|
287
|
+
this.handleScale(camera, polePosition) * (this.hoveredControl === visual.pole ? 1.22 : 1),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
visual.poleHit.visible = polePosition !== null;
|
|
291
|
+
if (polePosition) {
|
|
292
|
+
visual.poleHit.position.copy(polePosition);
|
|
293
|
+
visual.poleHit.scale.copy(visual.pole.scale);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const failed = snapshot.status === 'error' || snapshot.status === 'unresolved';
|
|
297
|
+
const opacity = this.selected ? 1 : 0.52;
|
|
298
|
+
for (const object of [visual.chain, ...visual.joints]) {
|
|
299
|
+
const material = object.material as THREE.LineBasicMaterial | THREE.MeshBasicMaterial;
|
|
300
|
+
material.color.copy(failed ? ERROR : CHAIN);
|
|
301
|
+
material.opacity = opacity;
|
|
302
|
+
}
|
|
303
|
+
const targetActive =
|
|
304
|
+
this.hoveredControl === visual.target ||
|
|
305
|
+
(snapshot.target !== null && this.selectedObjects.has(snapshot.target));
|
|
306
|
+
const poleActive =
|
|
307
|
+
this.hoveredControl === visual.pole ||
|
|
308
|
+
(snapshot.pole !== null && this.selectedObjects.has(snapshot.pole));
|
|
309
|
+
(visual.target.material as THREE.MeshBasicMaterial).color.copy(targetActive ? HOVER : TARGET);
|
|
310
|
+
(visual.pole.material as THREE.MeshBasicMaterial).color.copy(poleActive ? HOVER : POLE);
|
|
311
|
+
(visual.targetLine.material as THREE.LineBasicMaterial).color.copy(TARGET);
|
|
312
|
+
(visual.poleLine.material as THREE.LineBasicMaterial).color.copy(POLE);
|
|
313
|
+
(visual.targetLine.material as THREE.LineBasicMaterial).opacity = opacity;
|
|
314
|
+
(visual.poleLine.material as THREE.LineBasicMaterial).opacity = opacity;
|
|
315
|
+
(visual.target.material as THREE.MeshBasicMaterial).opacity = targetActive ? 1 : opacity;
|
|
316
|
+
(visual.pole.material as THREE.MeshBasicMaterial).opacity = poleActive ? 1 : opacity;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Keep control points legible like ordinary editor gizmos while retaining
|
|
320
|
+
* enough world-scale variation to read depth. */
|
|
321
|
+
private handleScale(camera: THREE.Camera, position: THREE.Vector3): number {
|
|
322
|
+
return THREE.MathUtils.clamp(camera.position.distanceTo(position) / 8, 0.65, 4);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private clearVisuals(): void {
|
|
326
|
+
for (const child of [...this.children]) {
|
|
327
|
+
child.removeFromParent();
|
|
328
|
+
const renderable = child as THREE.Line | THREE.Mesh;
|
|
329
|
+
renderable.geometry.dispose();
|
|
330
|
+
const materials = Array.isArray(renderable.material)
|
|
331
|
+
? renderable.material
|
|
332
|
+
: [renderable.material];
|
|
333
|
+
for (const material of materials) material.dispose();
|
|
334
|
+
}
|
|
335
|
+
this.visuals = [];
|
|
336
|
+
this.hoveredControl = null;
|
|
337
|
+
}
|
|
338
|
+
}
|