@volter/editor-threejs 0.5.66 → 0.5.68

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 (99) hide show
  1. package/contributions/animation-timeline.utility.tsx +44 -0
  2. package/contributions/three-integration.service.ts +13 -0
  3. package/package.json +96 -6
  4. package/src/adapter/renderer-config.ts +3 -4
  5. package/src/adapter/three-contract.ts +72 -0
  6. package/src/ecs/object-marks.ts +1 -1
  7. package/src/ecs/user-data.ts +0 -9
  8. package/src/host-hierarchy-objects.ts +31 -0
  9. package/src/kit/animation/three-clips-subject.ts +190 -0
  10. package/src/kit/asset-compare.ts +294 -0
  11. package/src/kit/asset-preview-command.ts +265 -0
  12. package/src/kit/asset-preview-framing.ts +357 -0
  13. package/src/kit/asset-preview.ts +2802 -0
  14. package/src/kit/asset-workflow/model-inspection.ts +830 -0
  15. package/src/kit/authoring/component-instance-root.ts +171 -0
  16. package/src/kit/authoring/design-time-settle.ts +343 -0
  17. package/src/kit/authoring/live-object-transform.ts +62 -0
  18. package/src/kit/authoring/object3d-document-session-registry.ts +154 -0
  19. package/src/kit/authoring/object3d-document-session.ts +1965 -0
  20. package/src/kit/authoring/object3d-gesture-controller.ts +113 -0
  21. package/src/kit/authoring/quarks-particle-systems.ts +19 -0
  22. package/src/kit/authoring/shell-viewport-policy.ts +48 -0
  23. package/src/kit/authoring/source-object3d-authoring-adapter.ts +526 -0
  24. package/src/kit/authoring/three-projection-core.ts +226 -0
  25. package/src/kit/authoring/viewport-pick-context.ts +39 -0
  26. package/src/kit/authoring/viewport-raycast.ts +240 -0
  27. package/src/kit/authoring/world-hidden-viewport.ts +95 -0
  28. package/src/kit/camera-authoring.ts +175 -0
  29. package/src/kit/components/CameraInfo.tsx +56 -0
  30. package/src/kit/components/InspectorObjectPreview.tsx +57 -0
  31. package/src/kit/components/Object3DDocumentToolbar.tsx +549 -0
  32. package/src/kit/components/Object3DDocumentViewport.tsx +58 -0
  33. package/src/kit/components/StageHost.tsx +2547 -0
  34. package/src/kit/components/StageOverlays.tsx +21 -0
  35. package/src/kit/components/StatsOverlay.tsx +78 -0
  36. package/src/kit/components/ToolObject3DPreview.tsx +39 -0
  37. package/src/kit/components/ViewportFurniture.tsx +655 -0
  38. package/src/kit/components/ViewportOverlay.tsx +215 -0
  39. package/src/kit/components/ViewportShadingMenu.tsx +340 -0
  40. package/src/kit/components/ViewportViewMenu.tsx +155 -0
  41. package/src/kit/components/asset-viewers/EntityModelDocument.tsx +121 -0
  42. package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
  43. package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +395 -0
  44. package/src/kit/components/asset-viewers/LutAssetDocument.tsx +444 -0
  45. package/src/kit/components/asset-viewers/ModelAssetDocument.tsx +105 -0
  46. package/src/kit/components/asset-viewers/Object3DPreview.tsx +356 -0
  47. package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +527 -0
  48. package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
  49. package/src/kit/components/asset-viewers/three-asset-viewers.tsx +132 -0
  50. package/src/kit/components/object3d-contribution-surfaces.tsx +33 -0
  51. package/src/kit/components/stage-keyboard.tsx +40 -0
  52. package/src/kit/components/stage-overlay-set.tsx +105 -0
  53. package/src/kit/components/stage-presence-markers.ts +482 -0
  54. package/src/kit/components/stage-transform-chrome.ts +30 -0
  55. package/src/kit/components/stage-transform-tools.tsx +73 -0
  56. package/src/kit/components/stage-view-name.ts +30 -0
  57. package/src/kit/components/standard-viewport-dressing.ts +1042 -0
  58. package/src/kit/components/world-root-binding.ts +64 -0
  59. package/src/kit/constraint-helper.ts +338 -0
  60. package/src/kit/editor-shell-store.ts +814 -0
  61. package/src/kit/editor-viewport.ts +6621 -0
  62. package/src/kit/entity-lod.ts +31 -0
  63. package/src/kit/entity-object.ts +92 -0
  64. package/src/kit/hierarchy-mark-reader.ts +74 -0
  65. package/src/kit/instanced-presentation.ts +164 -0
  66. package/src/kit/live-module-source.ts +230 -0
  67. package/src/kit/model-thumbnail.ts +539 -0
  68. package/src/kit/play-camera-flight.ts +300 -0
  69. package/src/kit/projection/three.ts +898 -0
  70. package/src/kit/reflection-probe-helper.ts +142 -0
  71. package/src/kit/scene-document-viewport.ts +51 -0
  72. package/src/kit/scene-framing.ts +315 -0
  73. package/src/kit/scene-view-fog.ts +89 -0
  74. package/src/kit/spatial-handle-visuals.ts +332 -0
  75. package/src/kit/stories/three-story-model.ts +66 -0
  76. package/src/kit/three-canvas-render.ts +44 -0
  77. package/src/kit/three-hierarchy-row-media.ts +26 -0
  78. package/src/kit/three-inspection-media.ts +73 -0
  79. package/src/kit/three-integration.ts +86 -0
  80. package/src/kit/three-state.ts +33 -0
  81. package/src/kit/three-viewport/bone-selection-highlight.ts +119 -0
  82. package/src/kit/three-viewport/camera-fit.ts +41 -0
  83. package/src/kit/three-viewport/interactive-renderer.ts +132 -0
  84. package/src/kit/three-viewport/selection-brackets.ts +355 -0
  85. package/src/kit/three-viewport/selection-outline.ts +333 -0
  86. package/src/kit/three-viewport/skeleton-helper.ts +61 -0
  87. package/src/kit/three-viewport/source-color.ts +197 -0
  88. package/src/kit/three-viewport/studio-environment.ts +96 -0
  89. package/src/kit/trigger-volume-helper.ts +116 -0
  90. package/src/kit/viewport-actions.ts +128 -0
  91. package/src/kit/viewport-authoring-policy.ts +154 -0
  92. package/src/kit/viewport-commands.ts +318 -0
  93. package/src/kit/viewport-hotkeys.ts +119 -0
  94. package/src/kit/viewport-shading-boundary.ts +12 -0
  95. package/src/kit/viewport-status-facet.ts +53 -0
  96. package/src/object3d-contributions.ts +494 -0
  97. package/src/render/viewport-shading.ts +6 -2
  98. package/src/viewport-api.ts +92 -0
  99. package/src/viewport-door.ts +237 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * THE ANIMATION PANEL (`workspace.utility`): the active document's stage transport, drawn by the
3
+ * kit's own strip. A world document's stage attaches every clip its scene can show — a GLTF's
4
+ * own `animations`, and the mixers the game's code made (`animation-mixers.service.ts`) — and
5
+ * this is where a person scrubs and plays them in Edit, as Unity's Animation window does beside
6
+ * its Scene view. While Play drives time the strip goes read-only and says why.
7
+ */
8
+ import { editorHost } from '@volter/editor-sdk/host';
9
+ import { TransportStrip } from '@volter/editor-sdk/kit/transport-strip';
10
+ import { Text } from '@volter/editor-sdk/widgets';
11
+ import { useCallback, useSyncExternalStore } from 'react';
12
+
13
+ export const point = 'workspace.utility';
14
+ export const title = 'Animation';
15
+
16
+ export default function AnimationTimeline() {
17
+ const { documents, transport } = editorHost();
18
+ const activeId = useSyncExternalStore(documents.subscribe, documents.activeId, documents.activeId);
19
+ const handle = useSyncExternalStore(
20
+ transport.subscribe,
21
+ useCallback(() => (activeId ? transport.for(activeId) : null), [activeId, transport]),
22
+ );
23
+ const snapshot = useSyncExternalStore(
24
+ useCallback((listener: () => void) => (handle ? handle.subscribe(listener) : () => {}), [handle]),
25
+ useCallback(() => handle?.snapshot() ?? null, [handle]),
26
+ );
27
+ if (!handle || !snapshot || snapshot.activeSubject === null) {
28
+ return (
29
+ <Text
30
+ as="p"
31
+ variant="caption"
32
+ data-testid="animation-timeline-empty"
33
+ data-document={activeId ?? ''}
34
+ data-transport={handle ? 'yes' : 'no'}
35
+ style={{ padding: 'var(--vgai-space-2) var(--vgai-space-3)' }}
36
+ >
37
+ {handle
38
+ ? 'Nothing in the active document plays an animation.'
39
+ : 'The active document has no stage to animate.'}
40
+ </Text>
41
+ );
42
+ }
43
+ return <TransportStrip transport={handle} />;
44
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The Three integration, installed at boot (`workspace.service`): the Object3D surfaces
3
+ * contributions mount, the viewport's relay verbs, Asset Lab viewers, inspection and hierarchy
4
+ * media, captures, the authoring policy and the session's view-state persistence
5
+ * (`src/kit/three-integration.ts`). Every viewport also ensures it before it mounts.
6
+ */
7
+ import { ensureThreeIntegration } from '../src/kit/three-integration';
8
+
9
+ export const point = 'workspace.service';
10
+
11
+ export function start(): () => void {
12
+ return ensureThreeIntegration();
13
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@volter/editor-threejs",
3
3
  "author": "Volter AI, Inc.",
4
- "version": "0.5.66",
4
+ "version": "0.5.68",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -30,11 +30,96 @@
30
30
  "default": "./src/*"
31
31
  },
32
32
  "./asset-parse-error": "./src/asset-parse-error.ts",
33
- "./loader": "./src/loader.ts"
33
+ "./loader": "./src/loader.ts",
34
+ "./kit/animation/three-clips-subject": "./src/kit/animation/three-clips-subject.ts",
35
+ "./kit/asset-compare": "./src/kit/asset-compare.ts",
36
+ "./kit/asset-preview-command": "./src/kit/asset-preview-command.ts",
37
+ "./kit/asset-preview-framing": "./src/kit/asset-preview-framing.ts",
38
+ "./kit/asset-preview": "./src/kit/asset-preview.ts",
39
+ "./kit/asset-workflow/model-inspection": "./src/kit/asset-workflow/model-inspection.ts",
40
+ "./kit/authoring/component-instance-root": "./src/kit/authoring/component-instance-root.ts",
41
+ "./kit/authoring/design-time-settle": "./src/kit/authoring/design-time-settle.ts",
42
+ "./kit/authoring/live-object-transform": "./src/kit/authoring/live-object-transform.ts",
43
+ "./kit/authoring/object3d-document-session-registry": "./src/kit/authoring/object3d-document-session-registry.ts",
44
+ "./kit/authoring/object3d-document-session": "./src/kit/authoring/object3d-document-session.ts",
45
+ "./kit/authoring/quarks-particle-systems": "./src/kit/authoring/quarks-particle-systems.ts",
46
+ "./kit/authoring/shell-viewport-policy": "./src/kit/authoring/shell-viewport-policy.ts",
47
+ "./kit/authoring/source-object3d-authoring-adapter": "./src/kit/authoring/source-object3d-authoring-adapter.ts",
48
+ "./kit/authoring/three-projection-core": "./src/kit/authoring/three-projection-core.ts",
49
+ "./kit/authoring/viewport-pick-context": "./src/kit/authoring/viewport-pick-context.ts",
50
+ "./kit/authoring/viewport-raycast": "./src/kit/authoring/viewport-raycast.ts",
51
+ "./kit/authoring/world-hidden-viewport": "./src/kit/authoring/world-hidden-viewport.ts",
52
+ "./kit/camera-authoring": "./src/kit/camera-authoring.ts",
53
+ "./kit/components/CameraInfo": "./src/kit/components/CameraInfo.tsx",
54
+ "./kit/components/InspectorObjectPreview": "./src/kit/components/InspectorObjectPreview.tsx",
55
+ "./kit/components/Object3DDocumentToolbar": "./src/kit/components/Object3DDocumentToolbar.tsx",
56
+ "./kit/components/Object3DDocumentViewport": "./src/kit/components/Object3DDocumentViewport.tsx",
57
+ "./kit/components/StageHost": "./src/kit/components/StageHost.tsx",
58
+ "./kit/components/StageOverlays": "./src/kit/components/StageOverlays.tsx",
59
+ "./kit/components/StatsOverlay": "./src/kit/components/StatsOverlay.tsx",
60
+ "./kit/components/ToolObject3DPreview": "./src/kit/components/ToolObject3DPreview.tsx",
61
+ "./kit/components/ViewportFurniture": "./src/kit/components/ViewportFurniture.tsx",
62
+ "./kit/components/ViewportOverlay": "./src/kit/components/ViewportOverlay.tsx",
63
+ "./kit/components/ViewportShadingMenu": "./src/kit/components/ViewportShadingMenu.tsx",
64
+ "./kit/components/ViewportViewMenu": "./src/kit/components/ViewportViewMenu.tsx",
65
+ "./kit/components/asset-viewers/EntityModelDocument": "./src/kit/components/asset-viewers/EntityModelDocument.tsx",
66
+ "./kit/components/asset-viewers/EnvironmentAssetDocument": "./src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx",
67
+ "./kit/components/asset-viewers/LiveModuleDocument": "./src/kit/components/asset-viewers/LiveModuleDocument.tsx",
68
+ "./kit/components/asset-viewers/LutAssetDocument": "./src/kit/components/asset-viewers/LutAssetDocument.tsx",
69
+ "./kit/components/asset-viewers/ModelAssetDocument": "./src/kit/components/asset-viewers/ModelAssetDocument.tsx",
70
+ "./kit/components/asset-viewers/Object3DPreview": "./src/kit/components/asset-viewers/Object3DPreview.tsx",
71
+ "./kit/components/asset-viewers/QuarksAssetDocument": "./src/kit/components/asset-viewers/QuarksAssetDocument.tsx",
72
+ "./kit/components/asset-viewers/ShaderAssetDocument": "./src/kit/components/asset-viewers/ShaderAssetDocument.tsx",
73
+ "./kit/components/asset-viewers/three-asset-viewers": "./src/kit/components/asset-viewers/three-asset-viewers.tsx",
74
+ "./kit/components/stage-keyboard": "./src/kit/components/stage-keyboard.tsx",
75
+ "./kit/components/stage-overlay-set": "./src/kit/components/stage-overlay-set.tsx",
76
+ "./kit/components/stage-presence-markers": "./src/kit/components/stage-presence-markers.ts",
77
+ "./kit/components/stage-transform-chrome": "./src/kit/components/stage-transform-chrome.ts",
78
+ "./kit/components/stage-transform-tools": "./src/kit/components/stage-transform-tools.tsx",
79
+ "./kit/components/standard-viewport-dressing": "./src/kit/components/standard-viewport-dressing.ts",
80
+ "./kit/components/world-root-binding": "./src/kit/components/world-root-binding.ts",
81
+ "./kit/constraint-helper": "./src/kit/constraint-helper.ts",
82
+ "./kit/editor-shell-store": "./src/kit/editor-shell-store.ts",
83
+ "./kit/editor-viewport": "./src/kit/editor-viewport.ts",
84
+ "./kit/entity-lod": "./src/kit/entity-lod.ts",
85
+ "./kit/entity-object": "./src/kit/entity-object.ts",
86
+ "./kit/hierarchy-mark-reader": "./src/kit/hierarchy-mark-reader.ts",
87
+ "./kit/instanced-presentation": "./src/kit/instanced-presentation.ts",
88
+ "./kit/live-module-source": "./src/kit/live-module-source.ts",
89
+ "./kit/model-thumbnail": "./src/kit/model-thumbnail.ts",
90
+ "./kit/projection/three": "./src/kit/projection/three.ts",
91
+ "./kit/reflection-probe-helper": "./src/kit/reflection-probe-helper.ts",
92
+ "./kit/scene-document-viewport": "./src/kit/scene-document-viewport.ts",
93
+ "./kit/scene-framing": "./src/kit/scene-framing.ts",
94
+ "./kit/scene-view-fog": "./src/kit/scene-view-fog.ts",
95
+ "./kit/spatial-handle-visuals": "./src/kit/spatial-handle-visuals.ts",
96
+ "./kit/stories/three-story-model": "./src/kit/stories/three-story-model.ts",
97
+ "./kit/three-canvas-render": "./src/kit/three-canvas-render.ts",
98
+ "./kit/three-hierarchy-row-media": "./src/kit/three-hierarchy-row-media.ts",
99
+ "./kit/three-inspection-media": "./src/kit/three-inspection-media.ts",
100
+ "./kit/three-integration": "./src/kit/three-integration.ts",
101
+ "./kit/three-state": "./src/kit/three-state.ts",
102
+ "./kit/three-viewport/bone-selection-highlight": "./src/kit/three-viewport/bone-selection-highlight.ts",
103
+ "./kit/three-viewport/camera-fit": "./src/kit/three-viewport/camera-fit.ts",
104
+ "./kit/three-viewport/interactive-renderer": "./src/kit/three-viewport/interactive-renderer.ts",
105
+ "./kit/three-viewport/selection-brackets": "./src/kit/three-viewport/selection-brackets.ts",
106
+ "./kit/three-viewport/selection-outline": "./src/kit/three-viewport/selection-outline.ts",
107
+ "./kit/three-viewport/skeleton-helper": "./src/kit/three-viewport/skeleton-helper.ts",
108
+ "./kit/three-viewport/source-color": "./src/kit/three-viewport/source-color.ts",
109
+ "./kit/three-viewport/studio-environment": "./src/kit/three-viewport/studio-environment.ts",
110
+ "./kit/trigger-volume-helper": "./src/kit/trigger-volume-helper.ts",
111
+ "./kit/viewport-actions": "./src/kit/viewport-actions.ts",
112
+ "./kit/viewport-authoring-policy": "./src/kit/viewport-authoring-policy.ts",
113
+ "./kit/viewport-commands": "./src/kit/viewport-commands.ts",
114
+ "./kit/viewport-hotkeys": "./src/kit/viewport-hotkeys.ts",
115
+ "./kit/viewport-shading-boundary": "./src/kit/viewport-shading-boundary.ts",
116
+ "./kit/viewport-status-facet": "./src/kit/viewport-status-facet.ts"
34
117
  },
35
118
  "peerDependencies": {
36
119
  "three": "^0.180.0",
37
- "vite": "^6.4.3"
120
+ "vite": "^6.4.3",
121
+ "react": "^19.0.0",
122
+ "react-dom": "^19.0.0"
38
123
  },
39
124
  "scripts": {
40
125
  "build": "node scripts/build-serving.mjs",
@@ -43,17 +128,22 @@
43
128
  },
44
129
  "vgai": {
45
130
  "contributions": [
46
- "./contributions/animation-mixers.service.ts"
131
+ "./contributions/three-integration.service.ts",
132
+ "./contributions/animation-mixers.service.ts",
133
+ "./contributions/animation-timeline.utility.tsx"
47
134
  ],
48
135
  "serving": "./dist-node/serving.mjs"
49
136
  },
50
137
  "dependencies": {
51
- "@volter/editor-project": "0.5.66",
138
+ "@volter/editor-project": "0.5.68",
52
139
  "@sparkjsdev/spark": "^2.1.0",
53
140
  "three.quarks": "^0.16.0",
54
141
  "zod": "^4.3.6",
55
142
  "typescript": "5.9.3",
56
- "@volter/editor-sdk": "0.5.66"
143
+ "@volter/editor-sdk": "0.5.68",
144
+ "postprocessing": "6.38.3",
145
+ "quarks.core": "0.16.0",
146
+ "@fortawesome/free-solid-svg-icons": "^7.2.0"
57
147
  },
58
148
  "devDependencies": {
59
149
  "esbuild": "0.25.12"
@@ -4,9 +4,8 @@
4
4
  * The SHAPE is the project contract's (`@volter/editor-project/adapter/renderer-config`,
5
5
  * whose header states the rule and the three load-bearing properties); this
6
6
  * file is what writes it onto a live `WebGLRenderer` and hands back the
7
- * restore. `world3d-react/r3f-root-factory.tsx` is the declarer,
8
- * `runtime/create-runtime.ts` the host, the editor's world-root stage the
9
- * applier.
7
+ * restore. A mounted three root declares it (`MountedThreeRoot.rendererConfig`)
8
+ * and the editor's world-root stage applies it.
10
9
  */
11
10
 
12
11
  import type {
@@ -28,7 +27,7 @@ export type {
28
27
  * Apply `config` to `renderer`, returning the restore function that puts back what was there.
29
28
  *
30
29
  * `three` is passed in rather than imported for values so the enum constants come from the HOST's
31
- * three instance — the same identity rule `r3f-root-factory.tsx` follows for the scene and camera.
30
+ * three instance — the same identity rule `r3f-root.tsx` follows for the scene and camera.
32
31
  */
33
32
  export function applyWorldRendererConfig(
34
33
  three: typeof THREE,
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The Three-typed half of the adapter contract. `@volter/editor-project`
3
+ * states the `three` surface with its medium objects opaque (a scene, a
4
+ * camera, a renderer, a hierarchy's scene objects, navigation's debug mesh);
5
+ * this module names them as three.js objects for the editor side that renders
6
+ * and authors them. A `three`-surface adapter hands exactly these objects, so
7
+ * each view below is a narrowing of what the contract already carries, not a
8
+ * conversion.
9
+ */
10
+
11
+ import type {
12
+ AuthoringAdapter,
13
+ HierarchyProvider,
14
+ MountedThreeRoot,
15
+ NavigationAdapter,
16
+ PhysicsAdapter,
17
+ ThreeHostContext,
18
+ } from '@volter/editor-project/adapter';
19
+ import type { NavBakeParams } from '@volter/editor-project/adapter/system-adapter';
20
+ import type * as THREE from 'three';
21
+
22
+ /** Shared GLTF/texture cache a `three` host hands its roots. */
23
+ export interface ThreeAssetCache {
24
+ /** Load an asset by URL. Returns a cached promise if already loading/loaded. */
25
+ load<T = unknown>(url: string): Promise<T>;
26
+ /** Get a previously-loaded asset synchronously. Throws if not yet loaded. */
27
+ get<T = unknown>(url: string): T;
28
+ }
29
+
30
+ /** What a `three`-surface root is handed, Three-typed. */
31
+ export type ThreeRootHostContext = ThreeHostContext<typeof THREE, THREE.WebGLRenderer, ThreeAssetCache>;
32
+
33
+ /** A live, mounted Three world, Three-typed. */
34
+ export type ThreeMountedRoot = MountedThreeRoot<THREE.Scene, THREE.Camera>;
35
+
36
+ /** A mounted `three` root, as the Three objects it hands. */
37
+ export function threeRoot(mounted: MountedThreeRoot): ThreeMountedRoot {
38
+ return mounted as ThreeMountedRoot;
39
+ }
40
+
41
+ /** The hierarchy's scene-object doors, Three-typed. */
42
+ export interface ThreeHierarchy {
43
+ object3D?(id: string): THREE.Object3D | null;
44
+ idForObject3D?(o: THREE.Object3D): string | null;
45
+ }
46
+
47
+ /** A `three`-surface authoring adapter's hierarchy, as the Three objects it hands. */
48
+ export function threeHierarchy(adapter: Pick<AuthoringAdapter, 'hierarchy'>): HierarchyProvider & ThreeHierarchy {
49
+ return adapter.hierarchy as HierarchyProvider & ThreeHierarchy;
50
+ }
51
+
52
+ /** The live Three object a `three`-surface hierarchy hands for `id` (null
53
+ * when it has none, or omits the door). */
54
+ export function threeObject(hierarchy: Pick<HierarchyProvider, 'object3D'>, id: string): THREE.Object3D | null {
55
+ return (hierarchy.object3D?.(id) ?? null) as THREE.Object3D | null;
56
+ }
57
+
58
+ /** A `three`-surface navigation adapter, as the Three objects it trades in. */
59
+ export interface ThreeNavigationAdapter extends NavigationAdapter {
60
+ debugMesh(scene: THREE.Scene): THREE.Object3D | null;
61
+ bake?(meshes: THREE.Mesh[], params?: NavBakeParams): boolean;
62
+ clear?(scene: THREE.Scene): void;
63
+ }
64
+
65
+ export function threeNavigation(adapter: NavigationAdapter): ThreeNavigationAdapter {
66
+ return adapter as ThreeNavigationAdapter;
67
+ }
68
+
69
+ /** A `three`-surface physics adapter's debug-draw object. */
70
+ export function threePhysicsDebugDraw(adapter: PhysicsAdapter): THREE.Object3D | null {
71
+ return (adapter.debugDraw?.() ?? null) as THREE.Object3D | null;
72
+ }
@@ -33,7 +33,7 @@ export type EditorHelperType =
33
33
  | 'trigger-volumes'
34
34
  | 'skeletons'
35
35
  // A package's own helper kind, shown through the editor's viewport door
36
- // (`host.viewport.setHelper`): the host lists the kinds it toggles by name,
36
+ // (the viewport door's `setViewportHelper`): the host lists the kinds it toggles by name,
37
37
  // any other follows the master Helpers toggle.
38
38
  | (string & {});
39
39
 
@@ -40,11 +40,6 @@
40
40
  * - `gaussianSplat` — native Spark splat metadata used for renderer discovery,
41
41
  * inspector facts, bounds, and deterministic disposal.
42
42
  *
43
- * Animation (load-bearing — ED5 disposal contract):
44
- * - `_animMixer` — THREE.AnimationMixer driving this subtree's clips.
45
- * - `_availableClips` — string[] of clip names discovered on the GLTF
46
- * (inspector dropdown).
47
- *
48
43
  * Disposal contract:
49
44
  * - `__sharedGeometry` — `true` when a mesh's geometry is shared/cached and MUST
50
45
  * NOT be disposed by per-object cleanup (P0.2 contract).
@@ -137,8 +132,6 @@ export interface UserDataSchema extends ObjectMarkSchema {
137
132
  _camera: THREE.Camera;
138
133
  _particleSystem: ParticleSystem;
139
134
  gaussianSplat: { src: string; numSplats: number };
140
- _animMixer: THREE.AnimationMixer;
141
- _availableClips: string[];
142
135
  __sharedGeometry: boolean;
143
136
  __shadeOrig: THREE.Material | THREE.Material[];
144
137
  __shadeUnlit: THREE.Material[];
@@ -189,8 +182,6 @@ export const UserDataKeys = {
189
182
  _camera: '_camera',
190
183
  _particleSystem: '_particleSystem',
191
184
  gaussianSplat: 'gaussianSplat',
192
- _animMixer: '_animMixer',
193
- _availableClips: '_availableClips',
194
185
  __sharedGeometry: '__sharedGeometry',
195
186
  __shadeOrig: '__shadeOrig',
196
187
  __shadeUnlit: '__shadeUnlit',
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The live objects behind the hierarchy's node ids, as a media
3
+ * integration provides them: the Three integration answers from the session store's Three half,
4
+ * and a lane that reads a scene's objects (the navmesh bake, Play's camera flight) asks here.
5
+ * With none registered there are no objects.
6
+ */
7
+ import type * as THREE from 'three';
8
+
9
+ /**
10
+ * The world node IS the entity, so a contribution inspecting a live behavior resolves the
11
+ * hierarchy's node id to the object itself. The map is the authored viewport's in Edit and the
12
+ * adopted live scene's in Play; a reader must not hold an object across the hierarchy's
13
+ * `subscribe` firings.
14
+ */
15
+ export interface HostHierarchyObjects {
16
+ object(id: string): THREE.Object3D | null;
17
+ objects(): ReadonlyMap<string, THREE.Object3D>;
18
+ }
19
+
20
+ let registered: HostHierarchyObjects | null = null;
21
+
22
+ export function registerHostHierarchyObjects(provider: HostHierarchyObjects): () => void {
23
+ registered = provider;
24
+ return () => {
25
+ if (registered === provider) registered = null;
26
+ };
27
+ }
28
+
29
+ export function hostHierarchyObjects(): HostHierarchyObjects | null {
30
+ return registered;
31
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * WHAT A THREE WORLD CAN SHOW AT A TIME — the stage transport's subjects for
3
+ * an ordinary three/R3F stage (WORK.md §The stage transport and the animation
4
+ * door, Step 3).
5
+ *
6
+ * TWO DISCOVERIES, because a three world carries clips in two different
7
+ * places and only one of them is the obvious one:
8
+ *
9
+ * 1. `object.animations` — what a GLTF's own `AnimationClip[]` lands in when
10
+ * something assigns it. A subject here owns a NEW `AnimationMixer` on that
11
+ * object, because nothing else is playing it.
12
+ * 2. `liveMixerFor(object)` — the mixer the world's OWN code made for it
13
+ * (`new THREE.AnimationMixer(…)`, drei's `useAnimations`), found through
14
+ * the served animation stamp (`@volter/editor-threejs/animation/live-mixers`).
15
+ * A subject here drives THAT mixer; minting a second mixer over the same
16
+ * object would give one skeleton two writers.
17
+ *
18
+ * DISCOVERY 2 IS NOT OPTIONAL, and this is the measurement that says so:
19
+ * three's `GLTFLoader` never assigns `scene.animations`, so an R3F character
20
+ * loaded with `useGLTF` has an EMPTY `animations` array on its Object3D. A
21
+ * scan that looked only at `animations` would find nothing on exactly the
22
+ * worlds a person most wants to scrub.
23
+ *
24
+ * THE MIXER IS THREE'S OWN and every call here is three's own API — this is a
25
+ * subject over `AnimationMixer`, never a wrapper around it. `setTime` is the
26
+ * one write; the transport owns the position.
27
+ */
28
+ import { liveMixerFor, subscribeLiveMixers } from '@volter/editor-threejs/animation/live-mixers';
29
+ import * as THREE from 'three';
30
+ import type { StageTransport } from '@volter/editor-sdk/kit/animation/stage-transport';
31
+
32
+ /** The display rate a three clip is shown at. Three clips carry a duration in
33
+ * SECONDS and no rate of their own, so the transport needs one to convert a
34
+ * frame for a look that asks in frames. 30 is three's own editor convention
35
+ * (`AnimationClip.CreateFromMorphTargetSequence` defaults to it). */
36
+ const THREE_DISPLAY_FPS = 30;
37
+
38
+ function clamp(value: number, min: number, max: number): number {
39
+ return value < min ? min : value > max ? max : value;
40
+ }
41
+
42
+ /**
43
+ * A subject over an object's OWN clips, on a mixer this subject mints and
44
+ * owns. `setClip` swaps which clip is showing: the chosen action plays and
45
+ * every other action is stopped. Nothing but the transport ticks this mixer,
46
+ * so the action moves only when the transport samples it — and it must NOT
47
+ * be paused: three advances a paused action by zero, so every scrub would
48
+ * show the clip's first frame.
49
+ */
50
+ function attachOwnClips(object: THREE.Object3D, transport: StageTransport): (() => void) | null {
51
+ const clips = object.animations;
52
+ if (clips.length === 0) return null;
53
+ const mixer = new THREE.AnimationMixer(object);
54
+ let current = clips[0];
55
+ if (!current) return null;
56
+
57
+ const show = (clip: THREE.AnimationClip): void => {
58
+ mixer.stopAllAction();
59
+ const action = mixer.clipAction(clip);
60
+ action.paused = false;
61
+ action.play();
62
+ current = clip;
63
+ };
64
+ show(current);
65
+
66
+ const detach = transport.attach({
67
+ id: object.uuid,
68
+ label: object.name || current.name || 'Clip',
69
+ range: () => ({ start: 0, end: current?.duration ?? 0, fps: THREE_DISPLAY_FPS }),
70
+ seek: (seconds) => {
71
+ mixer.setTime(clamp(seconds, 0, current?.duration ?? 0));
72
+ },
73
+ clips: () => clips.map((clip) => ({ id: clip.name, label: clip.name })),
74
+ setClip: (id) => {
75
+ const next = clips.find((clip) => clip.name === id);
76
+ if (next) show(next);
77
+ },
78
+ });
79
+
80
+ return () => {
81
+ detach();
82
+ mixer.stopAllAction();
83
+ mixer.uncacheRoot(object);
84
+ };
85
+ }
86
+
87
+ /**
88
+ * A subject over a mixer the world's own code made. It drives that mixer and
89
+ * never mints one; `clips` are the ones the world has played through it.
90
+ */
91
+ function attachInspected(object: THREE.Object3D, transport: StageTransport): (() => void) | null {
92
+ const inspection = liveMixerFor(object);
93
+ if (!inspection) return null;
94
+ let current = inspection.clips.values().next().value;
95
+ if (!current) return null;
96
+
97
+ const show = (clip: THREE.AnimationClip): void => {
98
+ inspection.mixer.stopAllAction();
99
+ const action = inspection.mixer.clipAction(clip);
100
+ action.paused = false;
101
+ action.play();
102
+ current = clip;
103
+ };
104
+
105
+ const detach = transport.attach({
106
+ id: object.uuid,
107
+ label: object.name || current.name || 'Animation',
108
+ range: () => ({ start: 0, end: current?.duration ?? 0, fps: THREE_DISPLAY_FPS }),
109
+ seek: (seconds) => {
110
+ inspection.mixer.setTime(clamp(seconds, 0, current?.duration ?? 0));
111
+ },
112
+ clips: () => [...inspection.clips.entries()].map(([name, clip]) => ({ id: name, label: clip.name || name })),
113
+ setClip: (id) => {
114
+ const next = inspection.clips.get(id);
115
+ if (next) show(next);
116
+ },
117
+ });
118
+
119
+ // Deliberately NOT stopping the mixer's actions on detach: this subject
120
+ // borrowed a mixer the world owns, and a borrower does not decide what the
121
+ // owner is playing when it leaves.
122
+ return detach;
123
+ }
124
+
125
+ /**
126
+ * One scan of a mounted root, and the handle that keeps it current.
127
+ *
128
+ * `refresh()` DIFFS by object uuid rather than re-attaching: a rescan of an
129
+ * unchanged tree must not detach and re-attach every subject, because that
130
+ * would reset which clip each one is showing under a person's hands. The
131
+ * caller decides when to rescan (the stage host throttles it and skips it
132
+ * while the world is driving time).
133
+ */
134
+ export interface ClipSubjectScan {
135
+ refresh(): void;
136
+ dispose(): void;
137
+ }
138
+
139
+ export function scanClipSubjects(
140
+ root: THREE.Object3D | (() => THREE.Object3D | null),
141
+ transport: StageTransport,
142
+ options: {
143
+ /** Rescan when the game registers or drops a mixer, while this answers true. */
144
+ readonly rescanOnLiveMixers?: () => boolean;
145
+ } = {},
146
+ ): ClipSubjectScan {
147
+ const attached = new Map<string, () => void>();
148
+
149
+ const refresh = (): void => {
150
+ const seen = new Set<string>();
151
+ // A getter for a stage whose rendered scene is not fixed at construction:
152
+ // a world stage draws the session store's scene once the world mounts.
153
+ const current = typeof root === 'function' ? root() : root;
154
+ current?.traverse((object) => {
155
+ if (attached.has(object.uuid)) {
156
+ seen.add(object.uuid);
157
+ return;
158
+ }
159
+ // The inspection wins when an object has both: it means a world binding
160
+ // is already driving that skeleton, and a second mixer over it would
161
+ // give one object two writers.
162
+ const detach = attachInspected(object, transport) ?? attachOwnClips(object, transport);
163
+ if (!detach) return;
164
+ attached.set(object.uuid, detach);
165
+ seen.add(object.uuid);
166
+ });
167
+ for (const [uuid, detach] of [...attached]) {
168
+ if (seen.has(uuid)) continue;
169
+ detach();
170
+ attached.delete(uuid);
171
+ }
172
+ };
173
+
174
+ refresh();
175
+ const rescanWhen = options.rescanOnLiveMixers;
176
+ const stopMixers = rescanWhen
177
+ ? subscribeLiveMixers(() => {
178
+ if (rescanWhen()) refresh();
179
+ })
180
+ : () => {};
181
+
182
+ return {
183
+ refresh,
184
+ dispose: () => {
185
+ stopMixers();
186
+ for (const detach of attached.values()) detach();
187
+ attached.clear();
188
+ },
189
+ };
190
+ }