@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.
Files changed (112) hide show
  1. package/NOTICE +2 -0
  2. package/contributions/animation-mixers.service.ts +20 -0
  3. package/contributions/animation-timeline.utility.tsx +44 -0
  4. package/contributions/three-integration.service.ts +13 -0
  5. package/dist-node/serving.mjs +405 -0
  6. package/package.json +113 -5
  7. package/serving/animation-live-module.ts +70 -0
  8. package/serving/animation-stamp.ts +88 -0
  9. package/serving/index.ts +14 -0
  10. package/serving/model-import-conversion.ts +344 -0
  11. package/src/adapter/ingest/scene-capture.ts +1 -23
  12. package/src/adapter/renderer-config.ts +3 -4
  13. package/src/adapter/three-contract.ts +72 -0
  14. package/src/animation/live-mixers.ts +55 -0
  15. package/src/ecs/object-marks.ts +1 -1
  16. package/src/ecs/user-data.ts +0 -16
  17. package/src/host-hierarchy-objects.ts +31 -0
  18. package/src/kit/animation/three-clips-subject.ts +190 -0
  19. package/src/kit/asset-compare.ts +294 -0
  20. package/src/kit/asset-preview-command.ts +265 -0
  21. package/src/kit/asset-preview-framing.ts +357 -0
  22. package/src/kit/asset-preview.ts +2802 -0
  23. package/src/kit/asset-workflow/model-inspection.ts +830 -0
  24. package/src/kit/authoring/component-instance-root.ts +171 -0
  25. package/src/kit/authoring/design-time-settle.ts +343 -0
  26. package/src/kit/authoring/live-object-transform.ts +62 -0
  27. package/src/kit/authoring/object3d-document-session-registry.ts +154 -0
  28. package/src/kit/authoring/object3d-document-session.ts +1965 -0
  29. package/src/kit/authoring/object3d-gesture-controller.ts +113 -0
  30. package/src/kit/authoring/quarks-particle-systems.ts +19 -0
  31. package/src/kit/authoring/shell-viewport-policy.ts +48 -0
  32. package/src/kit/authoring/source-object3d-authoring-adapter.ts +526 -0
  33. package/src/kit/authoring/three-projection-core.ts +226 -0
  34. package/src/kit/authoring/viewport-pick-context.ts +39 -0
  35. package/src/kit/authoring/viewport-raycast.ts +240 -0
  36. package/src/kit/authoring/world-hidden-viewport.ts +95 -0
  37. package/src/kit/camera-authoring.ts +175 -0
  38. package/src/kit/components/CameraInfo.tsx +56 -0
  39. package/src/kit/components/InspectorObjectPreview.tsx +57 -0
  40. package/src/kit/components/Object3DDocumentToolbar.tsx +549 -0
  41. package/src/kit/components/Object3DDocumentViewport.tsx +58 -0
  42. package/src/kit/components/StageHost.tsx +2547 -0
  43. package/src/kit/components/StageOverlays.tsx +21 -0
  44. package/src/kit/components/StatsOverlay.tsx +78 -0
  45. package/src/kit/components/ToolObject3DPreview.tsx +39 -0
  46. package/src/kit/components/ViewportFurniture.tsx +655 -0
  47. package/src/kit/components/ViewportOverlay.tsx +215 -0
  48. package/src/kit/components/ViewportShadingMenu.tsx +340 -0
  49. package/src/kit/components/ViewportViewMenu.tsx +155 -0
  50. package/src/kit/components/asset-viewers/EntityModelDocument.tsx +121 -0
  51. package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
  52. package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +395 -0
  53. package/src/kit/components/asset-viewers/LutAssetDocument.tsx +444 -0
  54. package/src/kit/components/asset-viewers/ModelAssetDocument.tsx +105 -0
  55. package/src/kit/components/asset-viewers/Object3DPreview.tsx +356 -0
  56. package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +527 -0
  57. package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
  58. package/src/kit/components/asset-viewers/three-asset-viewers.tsx +132 -0
  59. package/src/kit/components/object3d-contribution-surfaces.tsx +33 -0
  60. package/src/kit/components/stage-keyboard.tsx +40 -0
  61. package/src/kit/components/stage-overlay-set.tsx +105 -0
  62. package/src/kit/components/stage-presence-markers.ts +482 -0
  63. package/src/kit/components/stage-transform-chrome.ts +30 -0
  64. package/src/kit/components/stage-transform-tools.tsx +73 -0
  65. package/src/kit/components/stage-view-name.ts +30 -0
  66. package/src/kit/components/standard-viewport-dressing.ts +1042 -0
  67. package/src/kit/components/world-root-binding.ts +64 -0
  68. package/src/kit/constraint-helper.ts +338 -0
  69. package/src/kit/editor-shell-store.ts +814 -0
  70. package/src/kit/editor-viewport.ts +6621 -0
  71. package/src/kit/entity-lod.ts +31 -0
  72. package/src/kit/entity-object.ts +92 -0
  73. package/src/kit/hierarchy-mark-reader.ts +74 -0
  74. package/src/kit/instanced-presentation.ts +164 -0
  75. package/src/kit/live-module-source.ts +230 -0
  76. package/src/kit/model-thumbnail.ts +539 -0
  77. package/src/kit/play-camera-flight.ts +300 -0
  78. package/src/kit/projection/three.ts +898 -0
  79. package/src/kit/reflection-probe-helper.ts +142 -0
  80. package/src/kit/scene-document-viewport.ts +51 -0
  81. package/src/kit/scene-framing.ts +315 -0
  82. package/src/kit/scene-view-fog.ts +89 -0
  83. package/src/kit/spatial-handle-visuals.ts +332 -0
  84. package/src/kit/stories/three-story-model.ts +66 -0
  85. package/src/kit/three-canvas-render.ts +44 -0
  86. package/src/kit/three-hierarchy-row-media.ts +26 -0
  87. package/src/kit/three-inspection-media.ts +73 -0
  88. package/src/kit/three-integration.ts +86 -0
  89. package/src/kit/three-state.ts +33 -0
  90. package/src/kit/three-viewport/bone-selection-highlight.ts +119 -0
  91. package/src/kit/three-viewport/camera-fit.ts +41 -0
  92. package/src/kit/three-viewport/interactive-renderer.ts +132 -0
  93. package/src/kit/three-viewport/selection-brackets.ts +355 -0
  94. package/src/kit/three-viewport/selection-outline.ts +333 -0
  95. package/src/kit/three-viewport/skeleton-helper.ts +61 -0
  96. package/src/kit/three-viewport/source-color.ts +197 -0
  97. package/src/kit/three-viewport/studio-environment.ts +96 -0
  98. package/src/kit/trigger-volume-helper.ts +116 -0
  99. package/src/kit/viewport-actions.ts +128 -0
  100. package/src/kit/viewport-authoring-policy.ts +154 -0
  101. package/src/kit/viewport-commands.ts +318 -0
  102. package/src/kit/viewport-hotkeys.ts +119 -0
  103. package/src/kit/viewport-shading-boundary.ts +12 -0
  104. package/src/kit/viewport-status-facet.ts +53 -0
  105. package/src/object3d-contributions.ts +494 -0
  106. package/src/render/viewport-shading.ts +6 -2
  107. package/src/viewport/content-bounds.ts +38 -4
  108. package/src/viewport/environment.ts +16 -0
  109. package/src/viewport-api.ts +92 -0
  110. package/src/viewport-door.ts +237 -0
  111. package/src/animation/animation-clock.ts +0 -479
  112. package/src/animation/runtime-inspection.ts +0 -45
@@ -0,0 +1,300 @@
1
+ /**
2
+ * THE PLAY ENTRY CAMERA FLIGHT — the Three integration's half of Play's immersive entry
3
+ * (`@volter/editor-sdk/kit/play-camera-flight`). The kit's transition owns the phases and the
4
+ * chrome; this owns the camera: the editor viewport flies along a Catmull-Rom spline from its
5
+ * preview pose to the AUTHORED game camera's pose, retargets to the game's live camera once it
6
+ * boots so the cross-fade is pixel-continuous, and restores the preview pose when Play ends. The
7
+ * flight reads the authored viewport's rig through the viewport door and rides its frame hook, so
8
+ * flight pacing rides the editor's own frame clock.
9
+ */
10
+ import { registerPlayCameraFlight, type LiveCameraLookup } from '@volter/editor-sdk/kit/play-camera-flight';
11
+ import { onViewportFrame, viewportRig } from '../viewport-door';
12
+ import * as THREE from 'three';
13
+ import { threeStoreForHost } from './three-state';
14
+
15
+ /** Flight time scales mildly with distance so short hops feel snappy and
16
+ * cross-scene flights stay graceful, clamped to a tight band. */
17
+ export function flightDurationMs(distance: number): number {
18
+ return Math.min(1600, Math.max(800, 900 + distance * 25));
19
+ }
20
+
21
+ export function easeInOutCubic(t: number): number {
22
+ return t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;
23
+ }
24
+
25
+ /**
26
+ * Control points for the camera flight spline: start, two interior points
27
+ * forming a gentle arc, end. The arc lifts along world-up and bows slightly
28
+ * sideways, both proportional to (but clamped against) the travel distance —
29
+ * a short hop barely arcs, a long flight gets a visible swoop, and a
30
+ * degenerate zero-length flight returns a straight (stationary) curve.
31
+ */
32
+ export function computeFlightControlPoints(
33
+ start: THREE.Vector3,
34
+ end: THREE.Vector3,
35
+ up: THREE.Vector3 = new THREE.Vector3(0, 1, 0),
36
+ ): THREE.Vector3[] {
37
+ const travel = new THREE.Vector3().subVectors(end, start);
38
+ const distance = travel.length();
39
+ if (distance < 1e-6) {
40
+ return [start.clone(), start.clone(), end.clone(), end.clone()];
41
+ }
42
+ const lift = Math.min(distance * 0.18, 6);
43
+ // Side bias perpendicular to travel and up — gives the arc a slight bank
44
+ // instead of a purely vertical hump. Falls back to zero when travel is
45
+ // parallel to up (straight vertical flights just lift).
46
+ const side = new THREE.Vector3().crossVectors(travel, up);
47
+ if (side.lengthSq() > 1e-9) side.normalize().multiplyScalar(Math.min(distance * 0.08, 2.5));
48
+ else side.set(0, 0, 0);
49
+ const liftVec = up.clone().normalize().multiplyScalar(lift);
50
+ const p1 = start.clone().addScaledVector(travel, 0.3).add(liftVec).add(side);
51
+ const p2 = end
52
+ .clone()
53
+ .addScaledVector(travel, -0.3)
54
+ .addScaledVector(liftVec, 0.6)
55
+ .addScaledVector(side, 0.5);
56
+ return [start.clone(), p1, p2, end.clone()];
57
+ }
58
+
59
+ /** The narrow store surface `resolveAuthoredCameraTarget` reads. */
60
+ export interface PlayCameraSourceStore {
61
+ readonly objectMap: ReadonlyMap<string, THREE.Object3D>;
62
+ }
63
+
64
+ export interface AuthoredCameraTarget {
65
+ entityId: string;
66
+ position: [number, number, number];
67
+ quaternion: [number, number, number, number];
68
+ fov: number;
69
+ }
70
+
71
+ /** Editor-only overlays (helpers, gizmos) live on layer 31 — never flight targets. */
72
+ const EDITOR_ONLY_LAYER_MASK = 1 << 31;
73
+
74
+ function isEditorOnlyOrGizmo(obj: THREE.Object3D): boolean {
75
+ return (obj.layers.mask & EDITOR_ONLY_LAYER_MASK) !== 0 || /gizmo/i.test(obj.name);
76
+ }
77
+
78
+ /** Depth-first search for the first real camera in a subtree, skipping
79
+ * editor-only/gizmo branches wholesale. */
80
+ function findCameraInSubtree(obj: THREE.Object3D): THREE.Camera | null {
81
+ if (isEditorOnlyOrGizmo(obj)) return null;
82
+ if ((obj as Partial<THREE.Camera>).isCamera) return obj as THREE.Camera;
83
+ for (const child of obj.children) {
84
+ const found = findCameraInSubtree(child);
85
+ if (found) return found;
86
+ }
87
+ return null;
88
+ }
89
+
90
+ function targetFromObject(
91
+ entityId: string,
92
+ obj: THREE.Object3D,
93
+ fov: number,
94
+ ): AuthoredCameraTarget {
95
+ const position = new THREE.Vector3();
96
+ const quaternion = new THREE.Quaternion();
97
+ obj.getWorldPosition(position);
98
+ obj.getWorldQuaternion(quaternion);
99
+ return {
100
+ entityId,
101
+ position: position.toArray() as [number, number, number],
102
+ quaternion: quaternion.toArray() as [number, number, number, number],
103
+ fov,
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Find the authored game camera in the CURRENT (pre-play) editor scene.
109
+ *
110
+ * A world's source IS its document, so there is no descriptor to read a
111
+ * declared camera out of: the design session adopts the mounted fiber scene
112
+ * into the store, which makes the authored camera (e.g. drei
113
+ * `<PerspectiveCamera makeDefault>`) a live `THREE.Camera` in the mapped
114
+ * object graphs. Take the first one outside editor-only (layer-31) and gizmo
115
+ * branches, at its live world transform.
116
+ *
117
+ * `null` when no camera is found — play then keeps today's behavior (game
118
+ * starts at the preview pose, no flight).
119
+ */
120
+ export function resolveAuthoredCameraTarget(
121
+ store: PlayCameraSourceStore,
122
+ ): AuthoredCameraTarget | null {
123
+ for (const [id, obj] of store.objectMap) {
124
+ const camera = findCameraInSubtree(obj);
125
+ if (!camera) continue;
126
+ const perspective = camera as Partial<THREE.PerspectiveCamera>;
127
+ return targetFromObject(id, camera, perspective.isPerspectiveCamera ? perspective.fov! : 60);
128
+ }
129
+ return null;
130
+ }
131
+
132
+ interface FlightRuntime {
133
+ startPos: THREE.Vector3;
134
+ startQuat: THREE.Quaternion;
135
+ startFov: number;
136
+ endPos: THREE.Vector3;
137
+ endQuat: THREE.Quaternion;
138
+ endFov: number;
139
+ curve: THREE.CatmullRomCurve3;
140
+ startedAt: number;
141
+ durationMs: number;
142
+ landed: boolean;
143
+ }
144
+
145
+ function rebuildCurve(flight: FlightRuntime): void {
146
+ flight.curve = new THREE.CatmullRomCurve3(
147
+ computeFlightControlPoints(flight.startPos, flight.endPos),
148
+ );
149
+ }
150
+
151
+ interface ActiveFlight {
152
+ readonly runtime: FlightRuntime;
153
+ readonly snapshot: { position: THREE.Vector3; target: THREE.Vector3; up: THREE.Vector3; fov: number };
154
+ readonly onLanded: () => void;
155
+ /** Live game camera, registered at game-ready — the flight retargets to it
156
+ * each frame so the hand-off converges on whatever the game actually does
157
+ * with its camera during boot (follow rigs, SceneCamera components…). */
158
+ getLiveCamera: LiveCameraLookup | null;
159
+ /** Driving the camera: from `begin` until the game is on screen (`settle`) or Play ends. */
160
+ driving: boolean;
161
+ }
162
+
163
+ let _flight: ActiveFlight | null = null;
164
+
165
+ function begin(onLanded: () => void): boolean {
166
+ const viewport = viewportRig();
167
+ const objects = threeStoreForHost()?.objectMap;
168
+ const target = objects ? resolveAuthoredCameraTarget({ objectMap: objects }) : null;
169
+ if (!target || !viewport) return false;
170
+ const startPos = viewport.camera.position.clone();
171
+ const endPos = new THREE.Vector3().fromArray(target.position);
172
+ _flight = {
173
+ runtime: {
174
+ startPos,
175
+ startQuat: viewport.camera.quaternion.clone(),
176
+ startFov: viewport.camera.fov,
177
+ endPos,
178
+ endQuat: new THREE.Quaternion().fromArray(target.quaternion),
179
+ endFov: target.fov,
180
+ curve: new THREE.CatmullRomCurve3(computeFlightControlPoints(startPos, endPos)),
181
+ startedAt: performance.now(),
182
+ durationMs: flightDurationMs(startPos.distanceTo(endPos)),
183
+ landed: false,
184
+ },
185
+ snapshot: {
186
+ position: viewport.camera.position.clone(),
187
+ target: viewport.orbit.target.clone(),
188
+ up: viewport.camera.up.clone(),
189
+ fov: viewport.camera.fov,
190
+ },
191
+ onLanded,
192
+ getLiveCamera: null,
193
+ driving: true,
194
+ };
195
+ viewport.orbit.enabled = false;
196
+ return true;
197
+ }
198
+
199
+ /** After the flight, leave OrbitControls in a sane state at the landed pose
200
+ * so a `set-viewport-tab scene` during play still orbits correctly. */
201
+ function settle(): void {
202
+ const viewport = viewportRig();
203
+ const active = _flight;
204
+ if (!active) return;
205
+ active.driving = false;
206
+ if (!viewport) return;
207
+ const camera = viewport.camera;
208
+ const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
209
+ const distance = Math.max(1, active.snapshot.position.distanceTo(active.snapshot.target));
210
+ viewport.orbit.target.copy(camera.position).addScaledVector(forward, distance);
211
+ // The orbit re-aims with the camera's up: the landed pose's own, so its roll stands.
212
+ camera.up.set(0, 1, 0).applyQuaternion(camera.quaternion);
213
+ viewport.orbit.enabled = true;
214
+ }
215
+
216
+ /** Restore the pre-play camera pose. */
217
+ function end(): void {
218
+ const active = _flight;
219
+ if (!active) return;
220
+ _flight = null;
221
+ const viewport = viewportRig();
222
+ if (!viewport) return;
223
+ const snap = active.snapshot;
224
+ viewport.camera.position.copy(snap.position);
225
+ viewport.orbit.target.copy(snap.target);
226
+ viewport.camera.up.copy(snap.up);
227
+ viewport.camera.lookAt(snap.target);
228
+ if (viewport.camera.fov !== snap.fov) {
229
+ viewport.camera.fov = snap.fov;
230
+ viewport.camera.updateProjectionMatrix();
231
+ }
232
+ viewport.orbit.enabled = true;
233
+ viewport.orbit.update();
234
+ }
235
+
236
+ const _liveCamPos = new THREE.Vector3();
237
+ const _liveCamQuat = new THREE.Quaternion();
238
+
239
+ /**
240
+ * Per-frame flight driver — called from the world root's stage's RAF loop right after
241
+ * `viewport.update(dt)` so the flight has final say over the camera pose.
242
+ */
243
+ onViewportFrame(playTransitionFrame);
244
+
245
+ function playTransitionFrame(): void {
246
+ const active = _flight;
247
+ const viewport = viewportRig();
248
+ if (!active || !viewport || !active.driving) return;
249
+ const flight = active.runtime;
250
+
251
+ // Retarget to the live game camera once it exists (`track`).
252
+ // The kit hands the lane's lookup over unread; only an `Object3D` is a camera to converge on.
253
+ const found = active.getLiveCamera?.() ?? null;
254
+ const liveCamera = (found as Partial<THREE.Object3D> | null)?.isObject3D ? (found as THREE.Object3D) : null;
255
+ if (liveCamera) {
256
+ liveCamera.getWorldPosition(_liveCamPos);
257
+ liveCamera.getWorldQuaternion(_liveCamQuat);
258
+ if (_liveCamPos.distanceToSquared(flight.endPos) > 1e-8) {
259
+ flight.endPos.copy(_liveCamPos);
260
+ rebuildCurve(flight);
261
+ }
262
+ flight.endQuat.copy(_liveCamQuat);
263
+ const liveFov = (liveCamera as THREE.PerspectiveCamera).fov;
264
+ if (typeof liveFov === 'number') flight.endFov = liveFov;
265
+ }
266
+
267
+ const camera = viewport.camera;
268
+ if (flight.landed) {
269
+ // Holding/cross-fading: pin to the (possibly live-tracked) end pose.
270
+ camera.position.copy(flight.endPos);
271
+ camera.quaternion.copy(flight.endQuat);
272
+ if (camera.fov !== flight.endFov) {
273
+ camera.fov = flight.endFov;
274
+ camera.updateProjectionMatrix();
275
+ }
276
+ return;
277
+ }
278
+
279
+ const t = Math.min(1, (performance.now() - flight.startedAt) / flight.durationMs);
280
+ const eased = easeInOutCubic(t);
281
+ camera.position.copy(flight.curve.getPoint(eased));
282
+ camera.quaternion.slerpQuaternions(flight.startQuat, flight.endQuat, eased);
283
+ camera.fov = flight.startFov + (flight.endFov - flight.startFov) * eased;
284
+ camera.updateProjectionMatrix();
285
+ if (t >= 1) {
286
+ flight.landed = true;
287
+ active.onLanded();
288
+ }
289
+ }
290
+
291
+ export function registerThreePlayCameraFlight(): () => void {
292
+ return registerPlayCameraFlight({
293
+ begin,
294
+ track: (liveCamera) => {
295
+ if (_flight) _flight.getLiveCamera = liveCamera;
296
+ },
297
+ settle,
298
+ end,
299
+ });
300
+ }