@volter/editor-threejs 0.5.68 → 0.5.69
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 +3 -3
- package/contributions/animation-mixers.service.ts +1 -1
- package/contributions/animation-timeline.utility.tsx +1 -1
- package/dist-node/serving.mjs +10 -10
- package/package.json +4 -4
- package/serving/animation-live-module.ts +6 -6
- package/serving/animation-stamp.ts +5 -5
- package/serving/index.ts +1 -1
- package/src/adapter/hierarchy-marks.ts +5 -5
- package/src/adapter/ingest/scene-capture.ts +7 -7
- package/src/adapter/ingest/structural-ids.ts +1 -1
- package/src/adapter/ingest/visible-capture-window.ts +3 -3
- package/src/asset-loaders.ts +1 -1
- package/src/ecs/object-marks.ts +4 -4
- package/src/ecs/user-data.ts +3 -3
- package/src/kit/asset-compare.ts +2 -2
- package/src/kit/asset-preview-command.ts +2 -2
- package/src/kit/asset-preview.ts +1 -1
- package/src/kit/authoring/component-instance-root.ts +2 -2
- package/src/kit/authoring/object3d-document-session.ts +1 -1
- package/src/kit/authoring/three-projection-core.ts +1 -1
- package/src/kit/authoring/viewport-pick-context.ts +1 -1
- package/src/kit/authoring/viewport-raycast.ts +1 -1
- package/src/kit/components/CameraInfo.tsx +1 -1
- package/src/kit/components/InspectorObjectPreview.tsx +1 -1
- package/src/kit/components/Object3DDocumentToolbar.tsx +10 -10
- package/src/kit/components/StageHost.tsx +14 -14
- package/src/kit/components/StatsOverlay.tsx +4 -4
- package/src/kit/components/ViewportFurniture.tsx +22 -22
- package/src/kit/components/ViewportOverlay.tsx +4 -4
- package/src/kit/components/ViewportShadingMenu.tsx +9 -9
- package/src/kit/components/ViewportViewMenu.tsx +1 -1
- package/src/kit/components/stage-presence-markers.ts +2 -2
- package/src/kit/components/standard-viewport-dressing.ts +18 -18
- package/src/kit/editor-shell-store.ts +1 -1
- package/src/kit/editor-viewport.ts +17 -17
- package/src/kit/live-module-source.ts +1 -1
- package/src/kit/projection/three.ts +3 -3
- package/src/kit/three-viewport/selection-outline.ts +9 -9
- package/src/kit/three-viewport/skeleton-helper.ts +1 -1
- package/src/kit/three-viewport/studio-environment.ts +1 -1
- package/src/kit/viewport-hotkeys.ts +2 -2
- package/src/viewport/content-bounds.ts +1 -1
package/NOTICE
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Copyright 2026 Volter AI, Inc.
|
|
2
2
|
|
|
3
3
|
Modules under src/viewport and src/capture were imported from the
|
|
4
|
-
AGPL-3.0-only
|
|
4
|
+
AGPL-3.0-only threejs package of volter-ai/volter-engine; LICENSE contains that license.
|
|
5
5
|
serving/model-import-conversion.ts was imported from the AGPL-3.0-only
|
|
6
|
-
|
|
6
|
+
editor package's server in volter-ai/volter-engine and retains that license.
|
|
7
7
|
|
|
8
8
|
Modules under src/adapter, src/animation, src/ecs and src/render, together
|
|
9
9
|
with src/asset-loaders.ts, src/asset-parse-error.ts and src/loader.ts, were
|
|
10
|
-
imported from the Apache-2.0 @
|
|
10
|
+
imported from the Apache-2.0 @volter/threejs-runtime package. They retain that
|
|
11
11
|
license; LICENSE-APACHE contains its text.
|
|
12
12
|
|
|
13
13
|
Source revisions and original file hashes are recorded in the repository's
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The animation stamp's status facet (`@volter/editor-sdk/services`, a `workspace.service`
|
|
3
|
-
* contribution): `
|
|
3
|
+
* contribution): the editor's `status` command reports the mixers the running world's own code made, with the
|
|
4
4
|
* clips it has played through each and the objects each animates, so "the timeline found
|
|
5
5
|
* nothing" is answerable from the session.
|
|
6
6
|
*/
|
|
@@ -32,7 +32,7 @@ export default function AnimationTimeline() {
|
|
|
32
32
|
data-testid="animation-timeline-empty"
|
|
33
33
|
data-document={activeId ?? ''}
|
|
34
34
|
data-transport={handle ? 'yes' : 'no'}
|
|
35
|
-
style={{ padding: 'var(--
|
|
35
|
+
style={{ padding: 'var(--volter-space-2) var(--volter-space-3)' }}
|
|
36
36
|
>
|
|
37
37
|
{handle
|
|
38
38
|
? 'Nothing in the active document plays an animation.'
|
package/dist-node/serving.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { relative, sep } from "node:path";
|
|
|
3
3
|
import ts from "typescript";
|
|
4
4
|
|
|
5
5
|
// serving/animation-live-module.ts
|
|
6
|
-
var ANIMATION_LIVE_MODULE_ID = "virtual:
|
|
6
|
+
var ANIMATION_LIVE_MODULE_ID = "virtual:volter-three-animation-live";
|
|
7
7
|
var animationLiveModuleSource = `
|
|
8
8
|
const live = (globalThis[Symbol.for('volter.three.animation.live')] ??= {
|
|
9
9
|
mixers: new Map(),
|
|
@@ -16,10 +16,10 @@ function notify() {
|
|
|
16
16
|
try { listener(); } catch (error) { console.error(error); }
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
export function
|
|
19
|
+
export function __volterMixer(mixer, key) {
|
|
20
20
|
if (!mixer || typeof mixer.clipAction !== 'function') return mixer;
|
|
21
|
-
if (mixer.
|
|
22
|
-
Object.defineProperty(mixer, '
|
|
21
|
+
if (mixer.__volterMixerKey) return mixer;
|
|
22
|
+
Object.defineProperty(mixer, '__volterMixerKey', { value: key });
|
|
23
23
|
const entry = { key, mixer, clips: new Map(), roots: new Set() };
|
|
24
24
|
live.mixers.set(mixer, entry);
|
|
25
25
|
const clipAction = mixer.clipAction;
|
|
@@ -47,9 +47,9 @@ export function __vgaiMixer(mixer, key) {
|
|
|
47
47
|
return mixer;
|
|
48
48
|
}
|
|
49
49
|
/** drei's \`useAnimations\` result: its mixer, and the clips it was handed. */
|
|
50
|
-
export function
|
|
50
|
+
export function __volterAnimations(result, key) {
|
|
51
51
|
if (!result || !result.mixer) return result;
|
|
52
|
-
|
|
52
|
+
__volterMixer(result.mixer, key);
|
|
53
53
|
const entry = live.mixers.get(result.mixer);
|
|
54
54
|
if (entry && Array.isArray(result.clips)) {
|
|
55
55
|
let changed = false;
|
|
@@ -81,8 +81,8 @@ function stampAnimation(code, file, relativeFile) {
|
|
|
81
81
|
const source = ts.createSourceFile(file, code, ts.ScriptTarget.Latest, true, kind);
|
|
82
82
|
const sites = [];
|
|
83
83
|
const visit = (node) => {
|
|
84
|
-
if (isMixerConstruction(node)) sites.push({ start: node.getStart(source), end: node.getEnd(), wrapper: "
|
|
85
|
-
else if (isUseAnimations(node)) sites.push({ start: node.getStart(source), end: node.getEnd(), wrapper: "
|
|
84
|
+
if (isMixerConstruction(node)) sites.push({ start: node.getStart(source), end: node.getEnd(), wrapper: "__volterMixer" });
|
|
85
|
+
else if (isUseAnimations(node)) sites.push({ start: node.getStart(source), end: node.getEnd(), wrapper: "__volterAnimations" });
|
|
86
86
|
ts.forEachChild(node, visit);
|
|
87
87
|
};
|
|
88
88
|
visit(source);
|
|
@@ -93,7 +93,7 @@ function stampAnimation(code, file, relativeFile) {
|
|
|
93
93
|
const key = `${relativeFile}:${line + 1}:${character + 1}`;
|
|
94
94
|
out = `${out.slice(0, site.start)}${site.wrapper}(${out.slice(site.start, site.end)}, ${JSON.stringify(key)})${out.slice(site.end)}`;
|
|
95
95
|
}
|
|
96
|
-
return `import {
|
|
96
|
+
return `import { __volterMixer, __volterAnimations } from ${JSON.stringify(ANIMATION_LIVE_MODULE_ID)};
|
|
97
97
|
${out}`;
|
|
98
98
|
}
|
|
99
99
|
function animationStampPlugin(services) {
|
|
@@ -104,7 +104,7 @@ function animationStampPlugin(services) {
|
|
|
104
104
|
return roots.some((root) => file.startsWith(root + sep) || file.startsWith(root + "/"));
|
|
105
105
|
};
|
|
106
106
|
return {
|
|
107
|
-
name: "
|
|
107
|
+
name: "volter-three-animation",
|
|
108
108
|
enforce: "pre",
|
|
109
109
|
resolveId(id) {
|
|
110
110
|
return id === ANIMATION_LIVE_MODULE_ID ? VIRTUAL_ID : null;
|
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.69",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
"prepack": "npm run build",
|
|
127
127
|
"typecheck": "tsc -p tsconfig.json"
|
|
128
128
|
},
|
|
129
|
-
"
|
|
129
|
+
"volter": {
|
|
130
130
|
"contributions": [
|
|
131
131
|
"./contributions/three-integration.service.ts",
|
|
132
132
|
"./contributions/animation-mixers.service.ts",
|
|
@@ -135,12 +135,12 @@
|
|
|
135
135
|
"serving": "./dist-node/serving.mjs"
|
|
136
136
|
},
|
|
137
137
|
"dependencies": {
|
|
138
|
-
"@volter/editor-project": "0.5.
|
|
138
|
+
"@volter/editor-project": "0.5.69",
|
|
139
139
|
"@sparkjsdev/spark": "^2.1.0",
|
|
140
140
|
"three.quarks": "^0.16.0",
|
|
141
141
|
"zod": "^4.3.6",
|
|
142
142
|
"typescript": "5.9.3",
|
|
143
|
-
"@volter/editor-sdk": "0.5.
|
|
143
|
+
"@volter/editor-sdk": "0.5.69",
|
|
144
144
|
"postprocessing": "6.38.3",
|
|
145
145
|
"quarks.core": "0.16.0",
|
|
146
146
|
"@fortawesome/free-solid-svg-icons": "^7.2.0"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* the editor's bundle reads it (`src/animation/live-mixers.ts`).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
export const ANIMATION_LIVE_MODULE_ID = 'virtual:
|
|
12
|
+
export const ANIMATION_LIVE_MODULE_ID = 'virtual:volter-three-animation-live';
|
|
13
13
|
|
|
14
14
|
export const animationLiveModuleSource = `
|
|
15
15
|
const live = (globalThis[Symbol.for('volter.three.animation.live')] ??= {
|
|
@@ -23,10 +23,10 @@ function notify() {
|
|
|
23
23
|
try { listener(); } catch (error) { console.error(error); }
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
export function
|
|
26
|
+
export function __volterMixer(mixer, key) {
|
|
27
27
|
if (!mixer || typeof mixer.clipAction !== 'function') return mixer;
|
|
28
|
-
if (mixer.
|
|
29
|
-
Object.defineProperty(mixer, '
|
|
28
|
+
if (mixer.__volterMixerKey) return mixer;
|
|
29
|
+
Object.defineProperty(mixer, '__volterMixerKey', { value: key });
|
|
30
30
|
const entry = { key, mixer, clips: new Map(), roots: new Set() };
|
|
31
31
|
live.mixers.set(mixer, entry);
|
|
32
32
|
const clipAction = mixer.clipAction;
|
|
@@ -54,9 +54,9 @@ export function __vgaiMixer(mixer, key) {
|
|
|
54
54
|
return mixer;
|
|
55
55
|
}
|
|
56
56
|
/** drei's \`useAnimations\` result: its mixer, and the clips it was handed. */
|
|
57
|
-
export function
|
|
57
|
+
export function __volterAnimations(result, key) {
|
|
58
58
|
if (!result || !result.mixer) return result;
|
|
59
|
-
|
|
59
|
+
__volterMixer(result.mixer, key);
|
|
60
60
|
const entry = live.mixers.get(result.mixer);
|
|
61
61
|
if (entry && Array.isArray(result.clips)) {
|
|
62
62
|
let changed = false;
|
|
@@ -40,10 +40,10 @@ export function stampAnimation(code: string, file: string, relativeFile: string)
|
|
|
40
40
|
if (!code.includes('AnimationMixer') && !code.includes('useAnimations')) return null;
|
|
41
41
|
const kind = file.endsWith('.tsx') ? ts.ScriptKind.TSX : file.endsWith('.jsx') ? ts.ScriptKind.JSX : ts.ScriptKind.TS;
|
|
42
42
|
const source = ts.createSourceFile(file, code, ts.ScriptTarget.Latest, true, kind);
|
|
43
|
-
const sites: { start: number; end: number; wrapper: '
|
|
43
|
+
const sites: { start: number; end: number; wrapper: '__volterMixer' | '__volterAnimations' }[] = [];
|
|
44
44
|
const visit = (node: ts.Node): void => {
|
|
45
|
-
if (isMixerConstruction(node)) sites.push({ start: node.getStart(source), end: node.getEnd(), wrapper: '
|
|
46
|
-
else if (isUseAnimations(node)) sites.push({ start: node.getStart(source), end: node.getEnd(), wrapper: '
|
|
45
|
+
if (isMixerConstruction(node)) sites.push({ start: node.getStart(source), end: node.getEnd(), wrapper: '__volterMixer' });
|
|
46
|
+
else if (isUseAnimations(node)) sites.push({ start: node.getStart(source), end: node.getEnd(), wrapper: '__volterAnimations' });
|
|
47
47
|
ts.forEachChild(node, visit);
|
|
48
48
|
};
|
|
49
49
|
visit(source);
|
|
@@ -54,7 +54,7 @@ export function stampAnimation(code: string, file: string, relativeFile: string)
|
|
|
54
54
|
const key = `${relativeFile}:${line + 1}:${character + 1}`;
|
|
55
55
|
out = `${out.slice(0, site.start)}${site.wrapper}(${out.slice(site.start, site.end)}, ${JSON.stringify(key)})${out.slice(site.end)}`;
|
|
56
56
|
}
|
|
57
|
-
return `import {
|
|
57
|
+
return `import { __volterMixer, __volterAnimations } from ${JSON.stringify(ANIMATION_LIVE_MODULE_ID)};\n${out}`;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export function animationStampPlugin(services: AnimationServingServices): Plugin {
|
|
@@ -65,7 +65,7 @@ export function animationStampPlugin(services: AnimationServingServices): Plugin
|
|
|
65
65
|
return roots.some((root) => file.startsWith(root + sep) || file.startsWith(root + '/'));
|
|
66
66
|
};
|
|
67
67
|
return {
|
|
68
|
-
name: '
|
|
68
|
+
name: 'volter-three-animation',
|
|
69
69
|
enforce: 'pre',
|
|
70
70
|
resolveId(id) {
|
|
71
71
|
return id === ANIMATION_LIVE_MODULE_ID ? VIRTUAL_ID : null;
|
package/serving/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@volter/editor-threejs`'s server half (`package.json#
|
|
2
|
+
* `@volter/editor-threejs`'s server half (`package.json#volter.serving`): the animation stamp that
|
|
3
3
|
* lets the editor drive the mixers a project's own code makes, and the conversion of source model
|
|
4
4
|
* formats to runtime GLB for the asset library's imports.
|
|
5
5
|
*/
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
* ## What is NOT built-internal
|
|
103
103
|
*
|
|
104
104
|
* A node your game means as content — even one a class rather than JSX
|
|
105
|
-
* constructs — is content. `
|
|
105
|
+
* constructs — is content. `volterBuiltInternal` is not "the editor cannot write
|
|
106
106
|
* this"; that question is `TransformProvider.editability`'s, answered per
|
|
107
107
|
* channel with a sentence. This mark answers "is this implementation", and
|
|
108
108
|
* getting it wrong hides real content.
|
|
@@ -124,12 +124,12 @@ import { getObjectMark, setObjectMark } from '../ecs/object-marks';
|
|
|
124
124
|
* the instance is already named by `node.name`, and the editor prints both.
|
|
125
125
|
*/
|
|
126
126
|
export function markComponentRoot(node: THREE.Object3D, name: string): void {
|
|
127
|
-
setObjectMark(node, '
|
|
127
|
+
setObjectMark(node, 'volterComponentRoot', name);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
/** The component name `node` is the root of, or `undefined` for an ordinary node. */
|
|
131
131
|
export function componentRootName(node: THREE.Object3D | null | undefined): string | undefined {
|
|
132
|
-
const name = getObjectMark(node, '
|
|
132
|
+
const name = getObjectMark(node, 'volterComponentRoot');
|
|
133
133
|
return typeof name === 'string' && name ? name : undefined;
|
|
134
134
|
}
|
|
135
135
|
|
|
@@ -145,12 +145,12 @@ export function isComponentRoot(node: THREE.Object3D | null | undefined): boolea
|
|
|
145
145
|
* the seam and not a sweep.
|
|
146
146
|
*/
|
|
147
147
|
export function markBuiltInternal(node: THREE.Object3D): void {
|
|
148
|
-
setObjectMark(node, '
|
|
148
|
+
setObjectMark(node, 'volterBuiltInternal', true);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
/** Whether `node` itself carries the implementation mark. Ancestry is the
|
|
152
152
|
* CALLER's walk — see this module's header for why the mark is subtree-scoped
|
|
153
153
|
* but not restamped onto descendants. */
|
|
154
154
|
export function isBuiltInternal(node: THREE.Object3D | null | undefined): boolean {
|
|
155
|
-
return getObjectMark(node, '
|
|
155
|
+
return getObjectMark(node, 'volterBuiltInternal') === true;
|
|
156
156
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* that captures `(scene, camera, renderer)` on the first real frame.
|
|
18
18
|
*
|
|
19
19
|
* This is the hardened, typed form of the `ingest-study` spike
|
|
20
|
-
* (`docs/ingest-study-spike/
|
|
20
|
+
* (`docs/ingest-study-spike/volter-ingest-adapter.js`), proven against an
|
|
21
21
|
* unmodified `three.js/examples/games_fps` game.
|
|
22
22
|
*
|
|
23
23
|
* DOM-backed addon renderers use ordinary prototype methods instead. The host
|
|
@@ -112,12 +112,12 @@ export interface SceneCaptureOptions {
|
|
|
112
112
|
* Identity, not shape, is the discriminator: a host renderer and a game
|
|
113
113
|
* renderer are the same class, both built after install, both drawing real
|
|
114
114
|
* scenes. The host is the only party that knows which is which, so it says
|
|
115
|
-
* so (`@
|
|
115
|
+
* so (`@volter/threejs/viewport/renderer-ownership`).
|
|
116
116
|
*/
|
|
117
117
|
isHostRenderer?: (renderer: unknown) => boolean;
|
|
118
118
|
/**
|
|
119
119
|
* The game's OWN declared world, read from its contract
|
|
120
|
-
* (`window.
|
|
120
|
+
* (`window.volterGame` — the host passes a reader, never a cached value, because
|
|
121
121
|
* the contract is declared by the game's modules and may not exist yet when
|
|
122
122
|
* the trap installs).
|
|
123
123
|
*
|
|
@@ -136,7 +136,7 @@ export interface SceneCaptureOptions {
|
|
|
136
136
|
* forever, and the real world that renders one frame later reaches no reader
|
|
137
137
|
* at all. This is that reader. It never changes which world is adopted — it
|
|
138
138
|
* makes the ambiguity a recorded fact (`packages/editor/src/world-adoption.ts`
|
|
139
|
-
* publishes it to `
|
|
139
|
+
* publishes it to the editor's `status` command).
|
|
140
140
|
*
|
|
141
141
|
* Post-processing games legitimately render several (scene, camera) pairs per
|
|
142
142
|
* frame, so alternates are INFORMATION, never an error. Host renders are
|
|
@@ -411,7 +411,7 @@ export function installSceneCapture(
|
|
|
411
411
|
|
|
412
412
|
// Stash the real render fn per-instance under a unique symbol so multiple
|
|
413
413
|
// renderers (editor's + game's) never collide.
|
|
414
|
-
const REAL = Symbol('
|
|
414
|
+
const REAL = Symbol('volter.realRender');
|
|
415
415
|
|
|
416
416
|
let captured: CapturedRuntime | null = null;
|
|
417
417
|
let drawCount = 0;
|
|
@@ -439,7 +439,7 @@ export function installSceneCapture(
|
|
|
439
439
|
// prototype getter/setter. The constructor's `this.setAnimationLoop = realFn` hits
|
|
440
440
|
// our SETTER (the instance has no own property yet) and we stash realFn per-instance;
|
|
441
441
|
// the GETTER returns a wrapper that records each non-null callback before forwarding.
|
|
442
|
-
const REAL_SAL = Symbol('
|
|
442
|
+
const REAL_SAL = Symbol('volter.realSetAnimationLoop');
|
|
443
443
|
const loopCallbacks = new WeakMap<object, (time: number) => void>();
|
|
444
444
|
const loopedRenderers = new Set<object>();
|
|
445
445
|
/** Visibility clock of the in-flight `waitForCapture`, if any. */
|
|
@@ -788,7 +788,7 @@ export function installSceneCapture(
|
|
|
788
788
|
waiters.push(wrapped);
|
|
789
789
|
waitingVisibility = opts.visibility ?? documentVisibilityClock();
|
|
790
790
|
opts.onWait?.(captureWindow);
|
|
791
|
-
// A waiter that starts already-hidden (the normal `
|
|
791
|
+
// A waiter that starts already-hidden (the normal the editor's `play` command path
|
|
792
792
|
// against a backgrounded tab) must not wait for a human to foreground
|
|
793
793
|
// it. Pump any loop the game has already registered.
|
|
794
794
|
queueMicrotask(pumpHiddenLoops);
|
|
@@ -28,7 +28,7 @@ export const CAMERA_ID = 'ingest:camera';
|
|
|
28
28
|
/**
|
|
29
29
|
* The editor parks its OWN objects — grid, its two lights, the particle
|
|
30
30
|
* BatchedRenderer, every TransformControls/gizmo helper — on layer 31
|
|
31
|
-
* (`@
|
|
31
|
+
* (`@volter/threejs/viewport/editor-layers`'s `EDITOR_LAYER`) so the game camera
|
|
32
32
|
* never sees them. On an ingest root those objects are added to the GAME'S OWN
|
|
33
33
|
* scene, which is the same tree this walk indexes.
|
|
34
34
|
*/
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* render while its tab is hidden, because the browser parks `requestAnimation
|
|
9
9
|
* Frame` for a backgrounded document. The two facts together make one
|
|
10
10
|
* deterministic failure: a tab that opens in the BACKGROUND (the normal human
|
|
11
|
-
* path — `
|
|
11
|
+
* path — the editor's `edit` command auto-opens a tab that routinely lands behind the current
|
|
12
12
|
* window) burns its whole capture window unable to draw, the deadline fires,
|
|
13
13
|
* the mount dies terminally, and foregrounding the tab later changes nothing.
|
|
14
14
|
* Agents, whose tabs happen to be visible, never saw it.
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* budget is not a failure — a human returning to the tab is what spends it —
|
|
27
27
|
* and a cap would be exactly the boot-time clock this module exists to
|
|
28
28
|
* remove. The parked wait is reported instead of being silent: see
|
|
29
|
-
* `CaptureWaitObserver` in `scene-capture.ts` and `
|
|
29
|
+
* `CaptureWaitObserver` in `scene-capture.ts` and the editor's `status` command's
|
|
30
30
|
* `ingestCaptureWait`.
|
|
31
31
|
*
|
|
32
32
|
* The state machine is a pure function of (banked segments, now, hidden) so
|
|
@@ -223,7 +223,7 @@ export interface CaptureWaitOptions {
|
|
|
223
223
|
*
|
|
224
224
|
* A wait parked on a hidden tab is otherwise indistinguishable from a hung
|
|
225
225
|
* mount: nothing renders, nothing fails, and every door reports silence.
|
|
226
|
-
* This is the seam the editor publishes to `
|
|
226
|
+
* This is the seam the editor publishes to the editor's `status` command so the answer is
|
|
227
227
|
* "waiting for the first visible frame — the tab is hidden", not a countdown
|
|
228
228
|
* that is not running.
|
|
229
229
|
*/
|
package/src/asset-loaders.ts
CHANGED
|
@@ -128,7 +128,7 @@ export async function loadSplat(url: string, providedBytes?: Uint8Array): Promis
|
|
|
128
128
|
const size = bounds.getSize(new THREE.Vector3()).multiplyScalar(1.5);
|
|
129
129
|
const center = bounds.getCenter(new THREE.Vector3());
|
|
130
130
|
const proxy = new THREE.Mesh(new THREE.BoxGeometry(size.x, size.y, size.z));
|
|
131
|
-
proxy.name = '
|
|
131
|
+
proxy.name = '__volter_splat_bounds';
|
|
132
132
|
proxy.position.copy(center);
|
|
133
133
|
proxy.visible = false;
|
|
134
134
|
proxy.raycast = () => {};
|
package/src/ecs/object-marks.ts
CHANGED
|
@@ -43,8 +43,8 @@ export interface ObjectMarkSchema {
|
|
|
43
43
|
skeletonEnabled: boolean;
|
|
44
44
|
engineInternal: boolean;
|
|
45
45
|
editorHelper: boolean;
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
volterComponentRoot: string;
|
|
47
|
+
volterBuiltInternal: boolean;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export const ObjectMarkKeys = {
|
|
@@ -53,8 +53,8 @@ export const ObjectMarkKeys = {
|
|
|
53
53
|
skeletonEnabled: 'skeletonEnabled',
|
|
54
54
|
engineInternal: 'engineInternal',
|
|
55
55
|
editorHelper: 'editorHelper',
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
volterComponentRoot: 'volterComponentRoot',
|
|
57
|
+
volterBuiltInternal: 'volterBuiltInternal',
|
|
58
58
|
} as const satisfies Record<keyof ObjectMarkSchema, string>;
|
|
59
59
|
|
|
60
60
|
export function getObjectMark<K extends keyof ObjectMarkSchema>(
|
package/src/ecs/user-data.ts
CHANGED
|
@@ -78,13 +78,13 @@
|
|
|
78
78
|
*
|
|
79
79
|
* Hierarchy-presentation convention (written by GAME code, read by the editor —
|
|
80
80
|
* see `../adapter/hierarchy-marks.ts`, which is the ONLY place these two are
|
|
81
|
-
* read/written from; the `
|
|
81
|
+
* read/written from; the `volter` prefix marks them as the HOST's namespace on a
|
|
82
82
|
* node a game owns, unlike every other key above, which the engine/editor also
|
|
83
83
|
* write):
|
|
84
|
-
* - `
|
|
84
|
+
* - `volterComponentRoot` — display name of the component instance this subtree
|
|
85
85
|
* IS. The node renders as one collapsed, expandable
|
|
86
86
|
* row named for it.
|
|
87
|
-
* - `
|
|
87
|
+
* - `volterBuiltInternal` — `true` on the ROOT of a subtree runtime code
|
|
88
88
|
* CONSTRUCTED rather than authored (skeleton bones,
|
|
89
89
|
* particle renderers). Subtree-scoped: everything
|
|
90
90
|
* below a marked node is built-internal too.
|
package/src/kit/asset-compare.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* B8.4 — the Asset Lab COMPARE surface (`
|
|
3
|
-
* --compare <ref.glb>`): renders the project asset AND a caller-supplied reference GLB
|
|
2
|
+
* B8.4 — the Asset Lab COMPARE surface (the editor's `screenshot <model.glb>
|
|
3
|
+
* --compare <ref.glb>` command): renders the project asset AND a caller-supplied reference GLB
|
|
4
4
|
* with matched orthographic front + side framing, then scores their
|
|
5
5
|
* silhouettes (IoU) and composes review overlays, so an agent can
|
|
6
6
|
* numerically converge a procedural character toward a reference.
|
|
@@ -118,7 +118,7 @@ export async function handleAssetPreviewCommand(
|
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
120
|
if (compare !== undefined) {
|
|
121
|
-
// B8.4 — the compare mode (`
|
|
121
|
+
// B8.4 — the compare mode (the editor's `screenshot <model.glb> --compare <ref.glb>` command).
|
|
122
122
|
// Validated here at the relay boundary so a malformed payload fails with
|
|
123
123
|
// a named reason instead of a deep three.js error.
|
|
124
124
|
if (shots !== undefined || shotSetDefinition !== undefined) {
|
|
@@ -142,7 +142,7 @@ export async function handleAssetPreviewCommand(
|
|
|
142
142
|
// Wire-carried GLB bytes stand NOWHERE, so they take no scene stage and no
|
|
143
143
|
// compare (whose reference is named some other way). An explicit `shotSet`
|
|
144
144
|
// definition is a different matter and is served: a shot set stages the
|
|
145
|
-
// subject itself, which is what lets `
|
|
145
|
+
// subject itself, which is what lets the editor's `screenshot <module> --orbit <n>` command
|
|
146
146
|
// circle a model that only ever existed as bytes. `shots: 'source'` stays
|
|
147
147
|
// out — it is the asset-path review set, keyed to a stored forward vector.
|
|
148
148
|
if (typeof glbBase64 === 'string') {
|
package/src/kit/asset-preview.ts
CHANGED
|
@@ -2587,7 +2587,7 @@ export async function captureGlbBytesAssetPreview(
|
|
|
2587
2587
|
|
|
2588
2588
|
/**
|
|
2589
2589
|
* The same bytes, photographed as a LABELED SHOT SET instead of the four
|
|
2590
|
-
* views — what `
|
|
2590
|
+
* views — what the editor's `screenshot <module> --orbit <n>` command renders. A shot set
|
|
2591
2591
|
* stages the subject itself, exactly the way the asset-path lane's
|
|
2592
2592
|
* {@link captureShotSetModelPreview} does, so bytes are no less valid a
|
|
2593
2593
|
* subject here than a project GLB is.
|
|
@@ -90,8 +90,8 @@ function instanceCallsiteOid(object: THREE.Object3D | null | undefined): string
|
|
|
90
90
|
* Object3D (Drei cameras are the canonical example). It is a callsite address,
|
|
91
91
|
* not the object's definition-side `userData.oid`. */
|
|
92
92
|
function forwardedCallsiteOid(object: THREE.Object3D | null | undefined): string | undefined {
|
|
93
|
-
const forwarded = (object as (THREE.Object3D & {
|
|
94
|
-
?.
|
|
93
|
+
const forwarded = (object as (THREE.Object3D & { __volterOid?: unknown }) | null | undefined)
|
|
94
|
+
?.__volterOid;
|
|
95
95
|
return typeof forwarded === 'string' && forwarded ? forwarded : undefined;
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -1920,7 +1920,7 @@ class SelectionOrigins extends THREE.Points<THREE.BufferGeometry, THREE.PointsMa
|
|
|
1920
1920
|
toneMapped: false,
|
|
1921
1921
|
}),
|
|
1922
1922
|
);
|
|
1923
|
-
this.name = '
|
|
1923
|
+
this.name = 'volter:selection-origins';
|
|
1924
1924
|
this.userData['editorHelper'] = true;
|
|
1925
1925
|
this.renderOrder = 1000;
|
|
1926
1926
|
this.frustumCulled = false;
|
|
@@ -138,7 +138,7 @@ export function isTransparentWrapper(object: THREE.Object3D): boolean {
|
|
|
138
138
|
object.userData['oid'] === undefined &&
|
|
139
139
|
getUserData(object, 'authoringInstance') === undefined &&
|
|
140
140
|
getUserData(object, 'authoringRoot') !== true &&
|
|
141
|
-
getUserData(object, '
|
|
141
|
+
getUserData(object, 'volterComponentRoot') === undefined
|
|
142
142
|
);
|
|
143
143
|
}
|
|
144
144
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Viewport pick context (B4, D12) — the camera/canvas seam that
|
|
3
|
-
* `
|
|
3
|
+
* `VolterSceneAuthoringAdapter.pickable.pick` needs but cannot own itself: both
|
|
4
4
|
* live on `EditorViewport`, not on the adapter (the adapter only ever held a
|
|
5
5
|
* `store` — see its own doc comment). A tiny module-level slot, same shape as
|
|
6
6
|
* `./active-systems.ts`: `EditorViewport`'s constructor sets it once its
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `editor-viewport.ts`'s former `_raycastEntity` so EVERY threejs-backed
|
|
5
5
|
* authoring adapter whose live objects sit in `store.objectMap` reuses the
|
|
6
6
|
* exact same logic:
|
|
7
|
-
* - first-party `
|
|
7
|
+
* - first-party `VolterSceneAuthoringAdapter` (edit mode's focused world),
|
|
8
8
|
* - `ThreeAuthoringAdapter` (any live three tree — bare ingest
|
|
9
9
|
* mode AND the three child of a multi-world PLAY composite).
|
|
10
10
|
*
|
|
@@ -40,7 +40,7 @@ export function CameraInfo() {
|
|
|
40
40
|
<EditorSurface
|
|
41
41
|
variant="overlay"
|
|
42
42
|
border
|
|
43
|
-
className="
|
|
43
|
+
className="volter-camera-info volter-chrome-island volter-glass-island"
|
|
44
44
|
data-island-scale="compact"
|
|
45
45
|
>
|
|
46
46
|
<Inline gap={8} align="center" style={{ whiteSpace: 'nowrap' }}>
|
|
@@ -39,7 +39,7 @@ export function InspectorObjectPreview({
|
|
|
39
39
|
width: '100%',
|
|
40
40
|
height: 220,
|
|
41
41
|
minHeight: 220,
|
|
42
|
-
borderBottom: '1px solid var(--
|
|
42
|
+
borderBottom: '1px solid var(--volter-structural-divider)',
|
|
43
43
|
overflow: 'hidden',
|
|
44
44
|
position: 'relative',
|
|
45
45
|
}
|
|
@@ -203,7 +203,7 @@ export function Object3DDocumentToolbar({
|
|
|
203
203
|
if (!assetPath) return;
|
|
204
204
|
setError(null);
|
|
205
205
|
try {
|
|
206
|
-
// The shell owns where a framing is RECORDED (`.
|
|
206
|
+
// The shell owns where a framing is RECORDED (`.volter/thumbnails.json`,
|
|
207
207
|
// written through the project-file route); this surface only knows the
|
|
208
208
|
// asset and the pose. See `object3d-document-write-policy.ts`.
|
|
209
209
|
await object3DDocumentWritePolicy().saveThumbnailFraming(
|
|
@@ -230,11 +230,11 @@ export function Object3DDocumentToolbar({
|
|
|
230
230
|
|
|
231
231
|
const frameLabel = session.selection().length > 0 ? 'Frame selection' : 'Frame all';
|
|
232
232
|
return (
|
|
233
|
-
<div className="
|
|
233
|
+
<div className="volter-model-document-header">
|
|
234
234
|
<EditorToolbar
|
|
235
235
|
compact
|
|
236
236
|
label="Viewport view controls"
|
|
237
|
-
className="
|
|
237
|
+
className="volter-model-viewport-toolbar volter-model-viewport-toolbar--view"
|
|
238
238
|
>
|
|
239
239
|
{/* THE BUTTON SAYS WHAT IT DOES. `session.frame()` frames the
|
|
240
240
|
selection when there is one and the whole subject when there is
|
|
@@ -248,7 +248,7 @@ export function Object3DDocumentToolbar({
|
|
|
248
248
|
<EditorIcon icon={faExpand} size="md" />
|
|
249
249
|
</IconButton>
|
|
250
250
|
</Tooltip>
|
|
251
|
-
<div className="
|
|
251
|
+
<div className="volter-viewport-popover-anchor">
|
|
252
252
|
<Tooltip text="Camera orientation and projection">
|
|
253
253
|
<Button
|
|
254
254
|
ref={viewRef}
|
|
@@ -264,7 +264,7 @@ export function Object3DDocumentToolbar({
|
|
|
264
264
|
aria-haspopup="menu"
|
|
265
265
|
aria-expanded={viewOpen}
|
|
266
266
|
onClick={() => setViewOpen((open) => !open)}
|
|
267
|
-
className="
|
|
267
|
+
className="volter-model-toolbar-labelled-control"
|
|
268
268
|
>
|
|
269
269
|
<EditorIcon icon={faCube} size="sm" />
|
|
270
270
|
<span>
|
|
@@ -306,7 +306,7 @@ export function Object3DDocumentToolbar({
|
|
|
306
306
|
setViewOpen(false);
|
|
307
307
|
}}
|
|
308
308
|
>
|
|
309
|
-
<span className="
|
|
309
|
+
<span className="volter-menu-check">
|
|
310
310
|
{presentation.projection === projection && (
|
|
311
311
|
<EditorIcon icon={faCheck} size="xs" />
|
|
312
312
|
)}
|
|
@@ -338,7 +338,7 @@ export function Object3DDocumentToolbar({
|
|
|
338
338
|
<EditorToolbar
|
|
339
339
|
compact
|
|
340
340
|
label="Viewport preview controls"
|
|
341
|
-
className="
|
|
341
|
+
className="volter-model-viewport-toolbar volter-model-viewport-toolbar--preview"
|
|
342
342
|
>
|
|
343
343
|
{/* THE DISPLAY CONTROLS SIT AT THE TRAILING EDGE, which is where
|
|
344
344
|
Blender's are: its 3D View header packs Visibility, Gizmo,
|
|
@@ -463,7 +463,7 @@ export function Object3DDocumentToolbar({
|
|
|
463
463
|
}))),
|
|
464
464
|
]}
|
|
465
465
|
/>
|
|
466
|
-
<div className="
|
|
466
|
+
<div className="volter-viewport-popover-anchor">
|
|
467
467
|
<Tooltip text="Asset preview actions">
|
|
468
468
|
<IconButton
|
|
469
469
|
ref={moreRef}
|
|
@@ -527,7 +527,7 @@ export function Object3DDocumentToolbar({
|
|
|
527
527
|
</div>
|
|
528
528
|
</EditorToolbar>
|
|
529
529
|
{capture ? (
|
|
530
|
-
<EditorPopover className="
|
|
530
|
+
<EditorPopover className="volter-model-capture-preview">
|
|
531
531
|
<div>
|
|
532
532
|
<Text as="strong" variant="label">
|
|
533
533
|
Deterministic views
|
|
@@ -540,7 +540,7 @@ export function Object3DDocumentToolbar({
|
|
|
540
540
|
</EditorPopover>
|
|
541
541
|
) : null}
|
|
542
542
|
{error ? (
|
|
543
|
-
<EditorBanner className="
|
|
543
|
+
<EditorBanner className="volter-model-toolbar-error" tone="error">
|
|
544
544
|
{error}
|
|
545
545
|
</EditorBanner>
|
|
546
546
|
) : null}
|