@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.
- package/contributions/animation-timeline.utility.tsx +44 -0
- package/contributions/three-integration.service.ts +13 -0
- package/package.json +96 -6
- package/src/adapter/renderer-config.ts +3 -4
- package/src/adapter/three-contract.ts +72 -0
- package/src/ecs/object-marks.ts +1 -1
- package/src/ecs/user-data.ts +0 -9
- 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-api.ts +92 -0
- 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.
|
|
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/
|
|
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.
|
|
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.
|
|
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.
|
|
8
|
-
*
|
|
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
|
|
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
|
+
}
|
package/src/ecs/object-marks.ts
CHANGED
|
@@ -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
|
-
// (
|
|
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
|
|
package/src/ecs/user-data.ts
CHANGED
|
@@ -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
|
+
}
|