@volter/editor-threejs 0.5.211 → 0.5.212
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 +7 -2
- package/README.md +8 -13
- package/contributions/animation-mixers.service.ts +2 -4
- package/contributions/animation-timeline.utility.tsx +1 -2
- package/contributions/three.page.ts +50 -0
- package/dist-node/serving.mjs +84 -3
- package/dist-node/shared-three.mjs +145 -0
- package/package.json +296 -16
- package/serving/animation-stamp.ts +1 -1
- package/serving/index.ts +22 -5
- package/serving/model-import-conversion.ts +6 -3
- package/serving/shared-three.ts +176 -0
- package/src/adapter/constraint.ts +1 -18
- package/src/adapter/object3d-authoring-subject.ts +1 -12
- package/src/adapter/reflection-probe.ts +1 -9
- package/src/adapter/three-contract.ts +6 -60
- package/src/animation/live-mixers.ts +0 -4
- package/src/api.ts +7 -0
- package/src/asset-loaders.ts +2 -178
- package/src/capture/photograph-sampling.ts +333 -0
- package/src/capture/scene.ts +1 -1
- package/src/ecs/object-marks.ts +6 -14
- package/src/ecs/user-data.ts +3 -138
- package/src/kit/animation/three-clips-subject.ts +19 -42
- package/src/kit/asset-compare-core.ts +161 -0
- package/src/kit/asset-compare.ts +8 -27
- package/src/kit/asset-preview-command.ts +14 -19
- package/src/kit/asset-preview-framing.ts +8 -67
- package/src/kit/asset-preview.ts +109 -342
- package/src/kit/asset-workflow/model-inspection.ts +11 -154
- package/src/kit/asset-workflow/preview-resource-lifetime.ts +25 -0
- package/src/kit/authoring/component-instance-root.ts +3 -39
- package/src/kit/authoring/live-object-transform.ts +1 -30
- package/src/kit/authoring/object3d-document-session-registry.ts +7 -15
- package/src/kit/authoring/object3d-document-session.ts +471 -208
- package/src/kit/authoring/shell-viewport-policy.ts +18 -24
- package/src/kit/authoring/source-object3d-authoring-adapter.ts +6 -48
- package/src/kit/authoring/three-projection-core.ts +11 -40
- package/src/kit/authoring/viewport-pick-context.ts +5 -12
- package/src/kit/authoring/viewport-raycast.ts +15 -93
- package/src/kit/authoring/world-hidden-viewport.ts +8 -61
- package/src/kit/capture-camera-pose.ts +68 -0
- package/src/kit/collaboration-presence.ts +41 -0
- package/src/kit/components/CameraInfo.tsx +4 -6
- package/src/kit/components/InspectorObjectPreview.tsx +3 -30
- package/src/kit/components/Object3DDocumentToolbar.css +75 -0
- package/src/kit/components/Object3DDocumentToolbar.tsx +18 -84
- package/src/kit/components/Object3DDocumentViewport.tsx +5 -8
- package/src/kit/components/StageHost.css +40 -0
- package/src/kit/components/StageHost.tsx +181 -404
- package/src/kit/components/StageOverlays.tsx +1 -2
- package/src/kit/components/StatsOverlay.tsx +5 -17
- package/src/kit/components/ViewportFurniture.tsx +27 -154
- package/src/kit/components/ViewportOverlay.tsx +13 -17
- package/src/kit/components/ViewportOverlaysMenu.tsx +177 -0
- package/src/kit/components/ViewportShadingMenu.tsx +2 -42
- package/src/kit/components/ViewportViewMenu.tsx +7 -18
- package/src/kit/components/asset-viewers/EntityModelDocument.tsx +4 -13
- package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +5 -5
- package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +24 -102
- package/src/kit/components/asset-viewers/LutAssetDocument.tsx +6 -6
- package/src/kit/components/asset-viewers/Object3DPreview.tsx +10 -45
- package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +2 -2
- package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +11 -10
- package/src/kit/components/asset-viewers/shader-source.ts +144 -0
- package/src/kit/components/asset-viewers/three-asset-viewers.tsx +2 -14
- package/src/kit/components/object3d-contribution-surfaces.tsx +1 -1
- package/src/kit/components/stage-keyboard.tsx +6 -12
- package/src/kit/components/stage-overlay-set.tsx +11 -20
- package/src/kit/components/stage-presence-markers.ts +9 -32
- package/src/kit/components/stage-transform-chrome.ts +4 -4
- package/src/kit/components/stage-transform-tools.tsx +7 -14
- package/src/kit/components/standard-viewport-dressing.ts +47 -167
- package/src/kit/components/world-root-binding.ts +2 -7
- package/src/kit/constraint-helper.ts +1 -1
- package/src/kit/document-renderer-session.ts +114 -0
- package/src/kit/editor-presentation-activity.ts +58 -0
- package/src/kit/editor-shell-store.ts +31 -403
- package/src/kit/editor-viewport.ts +476 -1762
- package/src/kit/entity-lod.ts +3 -16
- package/src/kit/entity-object.ts +1 -39
- package/src/kit/hierarchy-mark-reader.ts +13 -30
- package/src/kit/instanced-presentation.ts +1 -47
- package/src/kit/live-gesture-lock.ts +35 -0
- package/src/kit/live-module-source.ts +12 -52
- package/src/kit/model-thumbnail.ts +7 -35
- package/src/kit/play-camera-flight.ts +7 -16
- package/src/kit/projection/three.ts +13 -366
- package/src/kit/retained-document-states.ts +91 -0
- package/src/kit/scene-document-viewport.ts +3 -3
- package/src/kit/scene-framing.ts +11 -152
- package/src/kit/scene-view-fog.ts +2 -47
- package/src/kit/spatial-handle-visuals.ts +19 -28
- package/src/kit/stories/three-story-model.ts +5 -33
- package/src/kit/three-canvas-render.ts +3 -4
- package/src/kit/three-hierarchy-row-media.ts +3 -11
- package/src/kit/three-inspection-media.ts +6 -6
- package/src/kit/three-integration.ts +18 -18
- package/src/kit/three-state.ts +2 -19
- package/src/kit/three-viewport/camera-fit.ts +2 -6
- package/src/kit/three-viewport/selection-brackets.ts +5 -20
- package/src/kit/three-viewport/selection-outline.ts +4 -56
- package/src/kit/three-viewport/skeleton-helper.ts +1 -32
- package/src/kit/three-viewport/source-color.ts +1 -33
- package/src/kit/three-viewport/studio-environment.ts +4 -55
- package/src/kit/three-viewport-presentation.ts +23 -0
- package/src/kit/transport-strip.tsx +147 -0
- package/src/kit/viewport-actions.ts +8 -50
- package/src/kit/viewport-authoring-policy.ts +5 -52
- package/src/kit/viewport-commands.ts +28 -57
- package/src/kit/viewport-hotkeys.ts +29 -31
- package/src/kit/viewport-status-facet.ts +4 -5
- package/src/kit/viewport-surface-status.tsx +44 -0
- package/src/loader.ts +8 -71
- package/src/object3d-contributions.ts +18 -34
- package/src/viewport/clip-planes.ts +4 -32
- package/src/viewport/content-bounds.ts +13 -142
- package/src/viewport/editor-layers.ts +4 -26
- package/src/viewport/environment.ts +1 -1
- package/src/viewport/preview-renderer.ts +7 -45
- package/src/viewport/renderer-ownership.ts +14 -62
- package/src/viewport-api.ts +14 -33
- package/src/viewport-door.ts +16 -86
- package/contributions/three-integration.service.ts +0 -13
- package/src/adapter/hierarchy-marks.ts +0 -156
- package/src/adapter/ingest/scene-capture.ts +0 -843
- package/src/adapter/ingest/structural-ids.ts +0 -139
- package/src/adapter/ingest/visible-capture-window.ts +0 -302
- package/src/adapter/renderer-config.ts +0 -115
- package/src/asset-parse-error.ts +0 -29
- package/src/host-hierarchy-objects.ts +0 -31
- package/src/kit/authoring/design-time-settle.ts +0 -343
- package/src/kit/camera-authoring.ts +0 -175
package/src/ecs/object-marks.ts
CHANGED
|
@@ -12,18 +12,14 @@ export type EditorHelperType =
|
|
|
12
12
|
| 'cameras'
|
|
13
13
|
| 'audio'
|
|
14
14
|
| 'colliders'
|
|
15
|
-
// Joint anchor
|
|
16
|
-
// viewport's per-type visibility filter can toggle them independently.
|
|
15
|
+
// Joint anchor, axis and limit gizmos have their own visibility toggle, independent of colliders.
|
|
17
16
|
| 'joints'
|
|
18
|
-
// LOD distance rings
|
|
19
|
-
// level's camera-distance threshold in world units.
|
|
17
|
+
// LOD distance rings use each level’s world-space camera-distance threshold as their radius.
|
|
20
18
|
| 'lod'
|
|
21
19
|
| 'splines'
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// "particles"-tagged children would detach the running emitter, and quarks
|
|
26
|
-
// self-disposes any system whose emitter leaves the scene.
|
|
20
|
+
// Keep live three.quarks emitters distinct from editor-owned particle-shape wireframes. The
|
|
21
|
+
// helper sweep must preserve emitters because quarks disposes a system whose emitter leaves the
|
|
22
|
+
// scene.
|
|
27
23
|
| 'particles'
|
|
28
24
|
| 'particle-shape'
|
|
29
25
|
| 'pivot'
|
|
@@ -33,7 +29,7 @@ export type EditorHelperType =
|
|
|
33
29
|
| 'trigger-volumes'
|
|
34
30
|
| 'skeletons'
|
|
35
31
|
// A package's own helper kind, shown through the editor's viewport door
|
|
36
|
-
// (the viewport door
|
|
32
|
+
// (through the viewport door): the host lists the kinds it toggles by name,
|
|
37
33
|
// any other follows the master Helpers toggle.
|
|
38
34
|
| (string & {});
|
|
39
35
|
|
|
@@ -43,8 +39,6 @@ export interface ObjectMarkSchema {
|
|
|
43
39
|
skeletonEnabled: boolean;
|
|
44
40
|
engineInternal: boolean;
|
|
45
41
|
editorHelper: boolean;
|
|
46
|
-
volterComponentRoot: string;
|
|
47
|
-
volterBuiltInternal: boolean;
|
|
48
42
|
}
|
|
49
43
|
|
|
50
44
|
export const ObjectMarkKeys = {
|
|
@@ -53,8 +47,6 @@ export const ObjectMarkKeys = {
|
|
|
53
47
|
skeletonEnabled: 'skeletonEnabled',
|
|
54
48
|
engineInternal: 'engineInternal',
|
|
55
49
|
editorHelper: 'editorHelper',
|
|
56
|
-
volterComponentRoot: 'volterComponentRoot',
|
|
57
|
-
volterBuiltInternal: 'volterBuiltInternal',
|
|
58
50
|
} as const satisfies Record<keyof ObjectMarkSchema, string>;
|
|
59
51
|
|
|
60
52
|
export function getObjectMark<K extends keyof ObjectMarkSchema>(
|
package/src/ecs/user-data.ts
CHANGED
|
@@ -1,113 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Typed registry for `Object3D.userData` keys used by the engine and editor.
|
|
3
|
-
*
|
|
4
|
-
* Three.js lets any code stash arbitrary values on `object.userData`, and over
|
|
5
|
-
* time the engine + editor accumulated ~20 stringly-typed keys that together
|
|
6
|
-
* form a *parallel object model* — meaning carried entirely in undocumented
|
|
7
|
-
* string literals. This module is the ONE documented place that lists every
|
|
8
|
-
* such key, its value type, and what it means. All engine/editor-internal
|
|
9
|
-
* `userData` access for these keys must go through {@link getUserData} /
|
|
10
|
-
* {@link setUserData} / {@link hasUserData} / {@link deleteUserData} so the key
|
|
11
|
-
* strings live in exactly one place and are type-checked at every call site.
|
|
12
|
-
* Structural readers may use the narrow `object-marks` accessors instead;
|
|
13
|
-
* this registry includes that module's canonical keys and value schema.
|
|
14
|
-
*
|
|
15
|
-
* NOTE: This covers engine/editor *internal* keys only. User game code in the
|
|
16
|
-
* example template may stash its own ad-hoc keys; those are out of scope.
|
|
17
|
-
*
|
|
18
|
-
* ## Key reference
|
|
19
|
-
*
|
|
20
|
-
* Serialization / identity (load-bearing — editor identity):
|
|
21
|
-
* - `entityId` — the entity's stable string id. Presence also marks an
|
|
22
|
-
* Object3D as a "real" entity (vs editor helper/env object).
|
|
23
|
-
*
|
|
24
|
-
* Runtime scene-query metadata (read by `scene-query.ts` and `scene-index.ts`):
|
|
25
|
-
* - `tags` — string[] tags for `queryByTag` and the live index.
|
|
26
|
-
* - `attributes` — per-object `Record<string, string|number|boolean>`
|
|
27
|
-
* of game-defined attributes (P2 `observe`). Written
|
|
28
|
-
* through `SceneIndex.setAttribute`, which emits an
|
|
29
|
-
* `attributechanged` signal; JSON-simple values only,
|
|
30
|
-
* so an attribute survives serialization unchanged.
|
|
31
|
-
*
|
|
32
|
-
* Runtime gameplay metadata:
|
|
33
|
-
* - `forward` — `[x,y,z]` model-space visual forward exported as
|
|
34
|
-
* ordinary glTF extras for asset-facing validation.
|
|
35
|
-
* - `navRole` — `'walkable' | 'obstacle'` navmesh role; collected at runtime.
|
|
36
|
-
* - `pivot` — `[x,y,z]` local-space pivot for rotate/scale-around-pivot.
|
|
37
|
-
* - `splineCurve` — resolved THREE curve for a spline-following object.
|
|
38
|
-
* - `_camera` — THREE.Camera owned by a camera entity (collected on load).
|
|
39
|
-
* - `_particleSystem` — three.quarks ParticleSystem (scene-sync registration + census).
|
|
40
|
-
* - `gaussianSplat` — native Spark splat metadata used for renderer discovery,
|
|
41
|
-
* inspector facts, bounds, and deterministic disposal.
|
|
42
|
-
*
|
|
43
|
-
* Disposal contract:
|
|
44
|
-
* - `__sharedGeometry` — `true` when a mesh's geometry is shared/cached and MUST
|
|
45
|
-
* NOT be disposed by per-object cleanup (P0.2 contract).
|
|
46
|
-
*
|
|
47
|
-
* Editor shading overrides (editor-only, see `scene-sync.ts`):
|
|
48
|
-
* - `__shadeOrig` — original material(s) stashed before a shading override.
|
|
49
|
-
* - `__shadeUnlit` — generated unlit material(s) for the unlit view mode.
|
|
50
|
-
*
|
|
51
|
-
* Unmodified-game ingestion (editor-only, see `editor/src/ingest/`):
|
|
52
|
-
* - `__ingest` — `true` on every Object3D minted by ingestion; routes
|
|
53
|
-
* edits to live mutation.
|
|
54
|
-
* - `__ingestNextId` — monotonic id counter stashed on the captured Scene
|
|
55
|
-
* root so re-reflects keep minting unique ids.
|
|
56
|
-
*
|
|
57
|
-
* Editor scene-graph tagging (editor-only):
|
|
58
|
-
* - `engineInternal` — `true` on engine-owned infrastructure in the game scene
|
|
59
|
-
* (particle BatchedRenderer, debug-draw + its subtree).
|
|
60
|
-
* The editor's play-mode hierarchy skips these so they
|
|
61
|
-
* don't show up as selectable "entities".
|
|
62
|
-
* - `authoringRoot` — explicit opt-in for a runtime descendant to become
|
|
63
|
-
* its own authoring object instead of a part of the
|
|
64
|
-
* nearest ancestor owner.
|
|
65
|
-
* - `authoringInstance` — source OID of the custom-component callsite that
|
|
66
|
-
* rendered this existing Object3D. Used to group
|
|
67
|
-
* implementation parts without adding wrappers.
|
|
68
|
-
* - `authoringLabel` — authored literal name (or component tag fallback)
|
|
69
|
-
* for that source-backed component instance.
|
|
70
|
-
* - `authoringComponent` — exact source component identity for portable-CSF
|
|
71
|
-
* association; unlike the label, never user-facing copy.
|
|
72
|
-
* - `authoringDocument` — generic reference to the project-tool document
|
|
73
|
-
* that owns this scene instance's authored asset.
|
|
74
|
-
* - `authoringHierarchyId`, `authoringHierarchyParentId`,
|
|
75
|
-
* `authoringHierarchyOrder` — source-owned semantic identity, parent identity,
|
|
76
|
-
* and sibling ordinal shared across native surfaces.
|
|
77
|
-
* - `editorHelper` — `true` for editor-only helper objects (gizmos, wireframes).
|
|
78
|
-
*
|
|
79
|
-
* Hierarchy-presentation convention (written by GAME code, read by the editor —
|
|
80
|
-
* see `../adapter/hierarchy-marks.ts`, which is the ONLY place these two are
|
|
81
|
-
* read/written from; the `volter` prefix marks them as the HOST's namespace on a
|
|
82
|
-
* node a game owns, unlike every other key above, which the engine/editor also
|
|
83
|
-
* write):
|
|
84
|
-
* - `volterComponentRoot` — display name of the component instance this subtree
|
|
85
|
-
* IS. The node renders as one collapsed, expandable
|
|
86
|
-
* row named for it.
|
|
87
|
-
* - `volterBuiltInternal` — `true` on the ROOT of a subtree runtime code
|
|
88
|
-
* CONSTRUCTED rather than authored (skeleton bones,
|
|
89
|
-
* particle renderers). Subtree-scoped: everything
|
|
90
|
-
* below a marked node is built-internal too.
|
|
91
|
-
* - `editorHelperType` — which kind of helper (lights/particles/pivot/navmesh/...).
|
|
92
|
-
* - `editorIcon` — `true` for editor billboard icon sprites.
|
|
93
|
-
* - `skeletonVisible` — per-entity editor preference for its bone overlay.
|
|
94
|
-
* - `skeletonEnabled` — resolved visibility preference on a skeleton helper.
|
|
95
|
-
* - `envObject` — `true` for environment objects (ambient light, etc.).
|
|
96
|
-
* - `reflectionProbe` — live project-owned reflection probe projected from
|
|
97
|
-
* JSX props for renderer/editor integration.
|
|
98
|
-
* - `triggerVolume` — plain `{ radius }` a GAME writes on the node that IS
|
|
99
|
-
* a trigger volume, so the editor can draw its ring
|
|
100
|
-
* (see `../adapter/trigger-volume.ts`).
|
|
101
|
-
* - `constraints` — live project-owned spatial constraints projected
|
|
102
|
-
* into the shared Inspector and viewport.
|
|
103
|
-
* - `authoringSubject` — transient identity/inspection for an ecosystem-native
|
|
104
|
-
* subject represented by an Object3D proxy.
|
|
105
|
-
* - `splineControlPoint` — index of a spline control-point drag handle.
|
|
106
|
-
* - `vcDirIdx` — view-cube face direction index (0=+X,1=-X,2=+Y,...).
|
|
107
|
-
*/
|
|
1
|
+
/** Typed Object3D.userData keys shared by the engine and editor. Internal accesses use getUserData, setUserData and hasUserData; structural consumers may use object-marks accessors. Project-defined extras remain outside this registry. */
|
|
108
2
|
|
|
109
3
|
import type * as THREE from 'three';
|
|
110
|
-
import type { ParticleSystem } from 'three.quarks';
|
|
111
4
|
import type { ConstraintMark } from '../adapter/constraint';
|
|
112
5
|
import type { Object3DAuthoringSubjectMark } from '../adapter/object3d-authoring-subject';
|
|
113
6
|
import type { ReflectionProbeMark } from '../adapter/reflection-probe';
|
|
@@ -125,27 +18,14 @@ export interface UserDataSchema extends ObjectMarkSchema {
|
|
|
125
18
|
forward: [number, number, number];
|
|
126
19
|
tags: string[];
|
|
127
20
|
attributes: Record<string, string | number | boolean>;
|
|
128
|
-
navRole: 'walkable' | 'obstacle';
|
|
129
21
|
pivot: [number, number, number];
|
|
130
|
-
splineCurve: THREE.Curve<THREE.Vector3>;
|
|
131
|
-
splineControlPoint: number;
|
|
132
22
|
_camera: THREE.Camera;
|
|
133
|
-
_particleSystem: ParticleSystem;
|
|
134
23
|
gaussianSplat: { src: string; numSplats: number };
|
|
135
|
-
__sharedGeometry: boolean;
|
|
136
|
-
__shadeOrig: THREE.Material | THREE.Material[];
|
|
137
|
-
__shadeUnlit: THREE.Material[];
|
|
138
24
|
__ingest: boolean;
|
|
139
|
-
__ingestNextId: number;
|
|
140
25
|
authoringRoot: boolean;
|
|
141
26
|
authoringInstance: string;
|
|
142
27
|
authoringLabel: string;
|
|
143
28
|
authoringComponent: string;
|
|
144
|
-
authoringDocument: {
|
|
145
|
-
readonly kind: 'project-tool';
|
|
146
|
-
readonly name: string;
|
|
147
|
-
readonly title: string;
|
|
148
|
-
};
|
|
149
29
|
/** Project-owned identity used to join authored hierarchy rows across adapter roots. */
|
|
150
30
|
authoringHierarchyId: string;
|
|
151
31
|
/** Project-owned parent identity when the authored parent can live on another surface. */
|
|
@@ -165,9 +45,8 @@ export interface UserDataSchema extends ObjectMarkSchema {
|
|
|
165
45
|
export type UserDataKey = keyof UserDataSchema;
|
|
166
46
|
|
|
167
47
|
/**
|
|
168
|
-
* Canonical
|
|
169
|
-
*
|
|
170
|
-
* literal strings are owned here or by the included structural mark registry.
|
|
48
|
+
* Canonical accessor names map to literal userData keys owned here or by the structural mark
|
|
49
|
+
* registry.
|
|
171
50
|
*/
|
|
172
51
|
export const UserDataKeys = {
|
|
173
52
|
...ObjectMarkKeys,
|
|
@@ -175,23 +54,14 @@ export const UserDataKeys = {
|
|
|
175
54
|
forward: 'forward',
|
|
176
55
|
tags: 'tags',
|
|
177
56
|
attributes: 'attributes',
|
|
178
|
-
navRole: 'navRole',
|
|
179
57
|
pivot: 'pivot',
|
|
180
|
-
splineCurve: 'splineCurve',
|
|
181
|
-
splineControlPoint: 'splineControlPoint',
|
|
182
58
|
_camera: '_camera',
|
|
183
|
-
_particleSystem: '_particleSystem',
|
|
184
59
|
gaussianSplat: 'gaussianSplat',
|
|
185
|
-
__sharedGeometry: '__sharedGeometry',
|
|
186
|
-
__shadeOrig: '__shadeOrig',
|
|
187
|
-
__shadeUnlit: '__shadeUnlit',
|
|
188
60
|
__ingest: '__ingest',
|
|
189
|
-
__ingestNextId: '__ingestNextId',
|
|
190
61
|
authoringRoot: 'authoringRoot',
|
|
191
62
|
authoringInstance: 'authoringInstance',
|
|
192
63
|
authoringLabel: 'authoringLabel',
|
|
193
64
|
authoringComponent: 'authoringComponent',
|
|
194
|
-
authoringDocument: 'authoringDocument',
|
|
195
65
|
authoringHierarchyId: 'authoringHierarchyId',
|
|
196
66
|
authoringHierarchyParentId: 'authoringHierarchyParentId',
|
|
197
67
|
authoringHierarchyOrder: 'authoringHierarchyOrder',
|
|
@@ -228,8 +98,3 @@ export function setUserData<K extends UserDataKey>(
|
|
|
228
98
|
export function hasUserData(obj: THREE.Object3D | null | undefined, key: UserDataKey): boolean {
|
|
229
99
|
return obj ? obj.userData[UserDataKeys[key]] !== undefined : false;
|
|
230
100
|
}
|
|
231
|
-
|
|
232
|
-
/** Remove a `userData` key. */
|
|
233
|
-
export function deleteUserData(obj: THREE.Object3D, key: UserDataKey): void {
|
|
234
|
-
delete obj.userData[UserDataKeys[key]];
|
|
235
|
-
}
|
|
@@ -1,33 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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.
|
|
2
|
+
* Stage-transport subjects for three.js animation. Discover object.animations and
|
|
3
|
+
* liveMixerFor(object): standalone clip arrays get a new AnimationMixer, while a mixer owned by
|
|
4
|
+
* the world is borrowed. GLTFLoader does not assign scene.animations, so live mixer discovery also
|
|
5
|
+
* reaches clips loaded through useGLTF and useAnimations. Subjects seek through
|
|
6
|
+
* AnimationMixer.setTime; the transport owns playback position.
|
|
27
7
|
*/
|
|
28
8
|
import { liveMixerFor, subscribeLiveMixers } from '@volter/editor-threejs/animation/live-mixers';
|
|
29
9
|
import * as THREE from 'three';
|
|
30
|
-
import type { StageTransport } from '@volter/sdk/
|
|
10
|
+
import type { StageTransport } from '@volter/sdk/animation/stage-transport';
|
|
31
11
|
|
|
32
12
|
/** The display rate a three clip is shown at. Three clips carry a duration in
|
|
33
13
|
* SECONDS and no rate of their own, so the transport needs one to convert a
|
|
@@ -51,8 +31,9 @@ function attachOwnClips(object: THREE.Object3D, transport: StageTransport): (()
|
|
|
51
31
|
const clips = object.animations;
|
|
52
32
|
if (clips.length === 0) return null;
|
|
53
33
|
const mixer = new THREE.AnimationMixer(object);
|
|
54
|
-
|
|
55
|
-
if (!
|
|
34
|
+
const initial = clips[0];
|
|
35
|
+
if (!initial) return null;
|
|
36
|
+
let current: THREE.AnimationClip = initial;
|
|
56
37
|
|
|
57
38
|
const show = (clip: THREE.AnimationClip): void => {
|
|
58
39
|
mixer.stopAllAction();
|
|
@@ -66,9 +47,9 @@ function attachOwnClips(object: THREE.Object3D, transport: StageTransport): (()
|
|
|
66
47
|
const detach = transport.attach({
|
|
67
48
|
id: object.uuid,
|
|
68
49
|
label: object.name || current.name || 'Clip',
|
|
69
|
-
range: () => ({ start: 0, end: current
|
|
50
|
+
range: () => ({ start: 0, end: current.duration, fps: THREE_DISPLAY_FPS }),
|
|
70
51
|
seek: (seconds) => {
|
|
71
|
-
mixer.setTime(clamp(seconds, 0, current
|
|
52
|
+
mixer.setTime(clamp(seconds, 0, current.duration));
|
|
72
53
|
},
|
|
73
54
|
clips: () => clips.map((clip) => ({ id: clip.name, label: clip.name })),
|
|
74
55
|
setClip: (id) => {
|
|
@@ -91,8 +72,9 @@ function attachOwnClips(object: THREE.Object3D, transport: StageTransport): (()
|
|
|
91
72
|
function attachInspected(object: THREE.Object3D, transport: StageTransport): (() => void) | null {
|
|
92
73
|
const inspection = liveMixerFor(object);
|
|
93
74
|
if (!inspection) return null;
|
|
94
|
-
|
|
95
|
-
if (!
|
|
75
|
+
const initial = inspection.clips.values().next().value;
|
|
76
|
+
if (!initial) return null;
|
|
77
|
+
let current: THREE.AnimationClip = initial;
|
|
96
78
|
|
|
97
79
|
const show = (clip: THREE.AnimationClip): void => {
|
|
98
80
|
inspection.mixer.stopAllAction();
|
|
@@ -105,9 +87,9 @@ function attachInspected(object: THREE.Object3D, transport: StageTransport): (()
|
|
|
105
87
|
const detach = transport.attach({
|
|
106
88
|
id: object.uuid,
|
|
107
89
|
label: object.name || current.name || 'Animation',
|
|
108
|
-
range: () => ({ start: 0, end: current
|
|
90
|
+
range: () => ({ start: 0, end: current.duration, fps: THREE_DISPLAY_FPS }),
|
|
109
91
|
seek: (seconds) => {
|
|
110
|
-
inspection.mixer.setTime(clamp(seconds, 0, current
|
|
92
|
+
inspection.mixer.setTime(clamp(seconds, 0, current.duration));
|
|
111
93
|
},
|
|
112
94
|
clips: () => [...inspection.clips.entries()].map(([name, clip]) => ({ id: name, label: clip.name || name })),
|
|
113
95
|
setClip: (id) => {
|
|
@@ -140,8 +122,8 @@ export function scanClipSubjects(
|
|
|
140
122
|
root: THREE.Object3D | (() => THREE.Object3D | null),
|
|
141
123
|
transport: StageTransport,
|
|
142
124
|
options: {
|
|
143
|
-
/** Rescan when the game registers or drops a mixer
|
|
144
|
-
readonly rescanOnLiveMixers?:
|
|
125
|
+
/** Rescan when the game registers or drops a mixer. */
|
|
126
|
+
readonly rescanOnLiveMixers?: boolean;
|
|
145
127
|
} = {},
|
|
146
128
|
): ClipSubjectScan {
|
|
147
129
|
const attached = new Map<string, () => void>();
|
|
@@ -172,12 +154,7 @@ export function scanClipSubjects(
|
|
|
172
154
|
};
|
|
173
155
|
|
|
174
156
|
refresh();
|
|
175
|
-
const
|
|
176
|
-
const stopMixers = rescanWhen
|
|
177
|
-
? subscribeLiveMixers(() => {
|
|
178
|
-
if (rescanWhen()) refresh();
|
|
179
|
-
})
|
|
180
|
-
: () => {};
|
|
157
|
+
const stopMixers = options.rescanOnLiveMixers ? subscribeLiveMixers(refresh) : () => {};
|
|
181
158
|
|
|
182
159
|
return {
|
|
183
160
|
refresh,
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export const ASSET_COMPARE_VIEWS = ['front', 'side'] as const;
|
|
4
|
+
export type AssetCompareView = (typeof ASSET_COMPARE_VIEWS)[number];
|
|
5
|
+
|
|
6
|
+
/** Both models are scaled to this height, feet at y=0, centered on x=z=0. */
|
|
7
|
+
export const COMPARE_TARGET_HEIGHT = 1;
|
|
8
|
+
/**
|
|
9
|
+
* Fixed orthographic half-height around the model’s mid-height, with a 10% margin for reproducible
|
|
10
|
+
* silhouette comparisons.
|
|
11
|
+
*/
|
|
12
|
+
export const COMPARE_FRAME_HALF_HEIGHT = 0.55;
|
|
13
|
+
export const COMPARE_FRAME_CENTER_Y = COMPARE_TARGET_HEIGHT / 2;
|
|
14
|
+
|
|
15
|
+
/** Silhouettes render as a flat unlit white override on solid black, so the
|
|
16
|
+
* mask threshold sits mid-range — antialiased edge pixels land on the side
|
|
17
|
+
* of the ramp they dominate. */
|
|
18
|
+
export const SILHOUETTE_LUMA_THRESHOLD = 128;
|
|
19
|
+
|
|
20
|
+
/** Overlay legend (RGB). The overlay is machine-generated review evidence:
|
|
21
|
+
* every pixel is exactly one of these four colors. */
|
|
22
|
+
export const OVERLAY_BACKGROUND_RGB = [24, 26, 31] as const;
|
|
23
|
+
export const OVERLAY_ASSET_ONLY_RGB = [235, 137, 52] as const; // orange — the asset under test
|
|
24
|
+
export const OVERLAY_REF_ONLY_RGB = [56, 189, 248] as const; // cyan — the reference
|
|
25
|
+
export const OVERLAY_OVERLAP_RGB = [236, 240, 238] as const; // near-white — agreement
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Binary silhouette mask from RGBA pixels of a white-on-black render: a
|
|
29
|
+
* pixel belongs to the silhouette when any color channel crosses the
|
|
30
|
+
* threshold. Returns one byte (0|1) per pixel.
|
|
31
|
+
*/
|
|
32
|
+
export function silhouetteMaskFromRgba(rgba: ArrayLike<number>, pixelCount: number): Uint8Array {
|
|
33
|
+
if (rgba.length < pixelCount * 4) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Silhouette mask expected ${pixelCount * 4} RGBA bytes but received ${rgba.length}.`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const mask = new Uint8Array(pixelCount);
|
|
39
|
+
for (let pixel = 0; pixel < pixelCount; pixel++) {
|
|
40
|
+
const offset = pixel * 4;
|
|
41
|
+
const value =
|
|
42
|
+
rgba[offset]! >= SILHOUETTE_LUMA_THRESHOLD ||
|
|
43
|
+
rgba[offset + 1]! >= SILHOUETTE_LUMA_THRESHOLD ||
|
|
44
|
+
rgba[offset + 2]! >= SILHOUETTE_LUMA_THRESHOLD;
|
|
45
|
+
mask[pixel] = value ? 1 : 0;
|
|
46
|
+
}
|
|
47
|
+
return mask;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface MaskOverlapStats {
|
|
51
|
+
intersection: number;
|
|
52
|
+
union: number;
|
|
53
|
+
aCount: number;
|
|
54
|
+
bCount: number;
|
|
55
|
+
/** intersection / union; 0 when the union is empty (guard emptiness upstream). */
|
|
56
|
+
iou: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Silhouette intersection-over-union between two same-length binary masks. */
|
|
60
|
+
export function maskIoU(a: Uint8Array, b: Uint8Array): MaskOverlapStats {
|
|
61
|
+
if (a.length !== b.length) {
|
|
62
|
+
throw new Error(`Mask IoU requires equal-size masks (${a.length} vs ${b.length}).`);
|
|
63
|
+
}
|
|
64
|
+
let intersection = 0;
|
|
65
|
+
let aCount = 0;
|
|
66
|
+
let bCount = 0;
|
|
67
|
+
for (let index = 0; index < a.length; index++) {
|
|
68
|
+
const inA = a[index] !== 0;
|
|
69
|
+
const inB = b[index] !== 0;
|
|
70
|
+
if (inA) aCount++;
|
|
71
|
+
if (inB) bCount++;
|
|
72
|
+
if (inA && inB) intersection++;
|
|
73
|
+
}
|
|
74
|
+
const union = aCount + bCount - intersection;
|
|
75
|
+
return { intersection, union, aCount, bCount, iou: union === 0 ? 0 : intersection / union };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The compare overlay: both silhouettes on one image in visibly distinct
|
|
80
|
+
* colors — asset-only orange, reference-only cyan, agreement near-white on a
|
|
81
|
+
* dark background. Returns opaque RGBA bytes ready for an ImageData.
|
|
82
|
+
*/
|
|
83
|
+
export function overlayRgba(
|
|
84
|
+
assetMask: Uint8Array,
|
|
85
|
+
refMask: Uint8Array,
|
|
86
|
+
): Uint8ClampedArray<ArrayBuffer> {
|
|
87
|
+
if (assetMask.length !== refMask.length) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Overlay requires equal-size masks (${assetMask.length} vs ${refMask.length}).`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const out = new Uint8ClampedArray(assetMask.length * 4);
|
|
93
|
+
for (let pixel = 0; pixel < assetMask.length; pixel++) {
|
|
94
|
+
const inAsset = assetMask[pixel] !== 0;
|
|
95
|
+
const inRef = refMask[pixel] !== 0;
|
|
96
|
+
const color =
|
|
97
|
+
inAsset && inRef
|
|
98
|
+
? OVERLAY_OVERLAP_RGB
|
|
99
|
+
: inAsset
|
|
100
|
+
? OVERLAY_ASSET_ONLY_RGB
|
|
101
|
+
: inRef
|
|
102
|
+
? OVERLAY_REF_ONLY_RGB
|
|
103
|
+
: OVERLAY_BACKGROUND_RGB;
|
|
104
|
+
const offset = pixel * 4;
|
|
105
|
+
out[offset] = color[0];
|
|
106
|
+
out[offset + 1] = color[1];
|
|
107
|
+
out[offset + 2] = color[2];
|
|
108
|
+
out[offset + 3] = 255;
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** A structurally valid forward vector ([x,y,z], finite, non-degenerate in
|
|
114
|
+
* the ground plane) or null — used to read GLB `extras`/`userData.forward`
|
|
115
|
+
* without trusting arbitrary content. */
|
|
116
|
+
export function parseForwardVector(value: unknown): [number, number, number] | null {
|
|
117
|
+
if (!Array.isArray(value) || value.length !== 3) return null;
|
|
118
|
+
const [x, y, z] = value as unknown[];
|
|
119
|
+
if (typeof x !== 'number' || typeof y !== 'number' || typeof z !== 'number') return null;
|
|
120
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) return null;
|
|
121
|
+
if (Math.hypot(x, z) < 1e-6) return null; // vertical/zero forward cannot define a facing
|
|
122
|
+
return [x, y, z];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The Y rotation (radians) that turns a model whose forward is `forward`
|
|
127
|
+
* to face +Z — the direction the fixed front camera looks from. Facing is a
|
|
128
|
+
* ground-plane property; the vector's y component is ignored.
|
|
129
|
+
*/
|
|
130
|
+
export function forwardYawRadians(forward: readonly [number, number, number]): number {
|
|
131
|
+
const [x, , z] = forward;
|
|
132
|
+
if (!Number.isFinite(x) || !Number.isFinite(z) || Math.hypot(x, z) < 1e-6) {
|
|
133
|
+
throw new Error(`Cannot derive a facing from forward vector [${forward.join(', ')}].`);
|
|
134
|
+
}
|
|
135
|
+
return Math.atan2(x, z);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface NormalizedPlacement {
|
|
139
|
+
scale: number;
|
|
140
|
+
/** Applied AFTER the scale: ground at y=0, footprint centered at x=z=0. */
|
|
141
|
+
position: [number, number, number];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Bounding-box height normalization (the decided compare framing): scale the
|
|
146
|
+
* model so its bounds stand `COMPARE_TARGET_HEIGHT` tall, then place it with its
|
|
147
|
+
* ground plane at y=0 and its bounds center on the x=0/z=0 axis.
|
|
148
|
+
*/
|
|
149
|
+
export function normalizedPlacement(
|
|
150
|
+
min: readonly [number, number, number],
|
|
151
|
+
max: readonly [number, number, number],
|
|
152
|
+
): NormalizedPlacement {
|
|
153
|
+
const height = max[1] - min[1];
|
|
154
|
+
if (!Number.isFinite(height) || height <= 0) {
|
|
155
|
+
throw new Error(`Compare normalization needs positive model height, got ${height}.`);
|
|
156
|
+
}
|
|
157
|
+
const scale = COMPARE_TARGET_HEIGHT / height;
|
|
158
|
+
const centerX = (min[0] + max[0]) / 2;
|
|
159
|
+
const centerZ = (min[2] + max[2]) / 2;
|
|
160
|
+
return { scale, position: [-centerX * scale, -min[1] * scale, -centerZ * scale] };
|
|
161
|
+
}
|
package/src/kit/asset-compare.ts
CHANGED
|
@@ -1,23 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Framing is DELIBERATELY bounding-box based (equal-height normalization,
|
|
9
|
-
* shared ground plane, centered footprint) rather than the humanoid shot
|
|
10
|
-
* set's Mixamo-skeleton anchors: the reference may carry any rig (UE joint
|
|
11
|
-
* names, no rig at all), and the bone-anchored path throws on missing joints
|
|
12
|
-
* by design. Both models are yaw-normalized to FACE the front camera —
|
|
13
|
-
* forward read from GLB `extras`/`userData.forward` when present (our
|
|
14
|
-
* generated rigs persist `[0,0,-1]`), else the glTF +Z convention, with an
|
|
15
|
-
* explicit reference override for models whose extras lie.
|
|
16
|
-
*
|
|
17
|
-
* Pure scoring math lives in `asset-compare-core.ts` (unit-tested in node);
|
|
18
|
-
* this module owns only loading, scene assembly, and WebGL capture. Fixed
|
|
19
|
-
* cameras + fixed resolution + flat unlit override ⇒ identical IoU numbers
|
|
20
|
-
* across runs (a stated acceptance gate).
|
|
2
|
+
* Asset Lab comparison capture for screenshot --compare: load an asset and reference GLB, normalize
|
|
3
|
+
* their height and forward direction, capture matched orthographic front and side views, and
|
|
4
|
+
* compose silhouette IoU scores and overlays. Bounding-box framing supports arbitrary rigs.
|
|
5
|
+
* asset-compare-core.ts owns the scoring math; this module owns loading, scene assembly and WebGL
|
|
6
|
+
* capture.
|
|
21
7
|
*/
|
|
22
8
|
|
|
23
9
|
import { markHostRenderer } from '@volter/editor-threejs/viewport/renderer-ownership';
|
|
@@ -32,7 +18,7 @@ import {
|
|
|
32
18
|
normalizedPlacement,
|
|
33
19
|
overlayRgba,
|
|
34
20
|
silhouetteMaskFromRgba,
|
|
35
|
-
} from '
|
|
21
|
+
} from './asset-compare-core';
|
|
36
22
|
import {
|
|
37
23
|
checkedDimension,
|
|
38
24
|
createAssetPreviewSnapshot,
|
|
@@ -45,10 +31,7 @@ import {
|
|
|
45
31
|
readModelForward,
|
|
46
32
|
} from './asset-preview';
|
|
47
33
|
|
|
48
|
-
export type { AssetCompareView } from '
|
|
49
|
-
// The forward detection moved to `asset-preview.ts` (the shot sets need it
|
|
50
|
-
// too); re-exported here so compare-mode consumers keep their import path.
|
|
51
|
-
export { readModelForward } from './asset-preview';
|
|
34
|
+
export type { AssetCompareView } from './asset-compare-core';
|
|
52
35
|
|
|
53
36
|
export interface AssetCompareImage {
|
|
54
37
|
base64: string;
|
|
@@ -85,9 +68,7 @@ const SILHOUETTE_BACKGROUND = 0x000000;
|
|
|
85
68
|
const SILHOUETTE_FOREGROUND = 0xffffff;
|
|
86
69
|
const COMPARE_CAMERA_DISTANCE = 10;
|
|
87
70
|
|
|
88
|
-
/**
|
|
89
|
-
* when the module-look lane needed the same wire-carried bytes; this keeps
|
|
90
|
-
* compare's own error wording. */
|
|
71
|
+
/** Parse the reference GLB using comparison-specific error wording. */
|
|
91
72
|
function parseRefGlb(base64: string): Promise<THREE.Object3D> {
|
|
92
73
|
return parseGlbBytesModel(base64, 'Compare reference GLB');
|
|
93
74
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* viewport's relay verbs (`viewport-commands.ts`).
|
|
6
6
|
*/
|
|
7
7
|
import type { EditorCommandMessage, EditorCommandResult } from '@volter/sdk/commands';
|
|
8
|
-
import { parseForwardVector } from '
|
|
8
|
+
import { parseForwardVector } from './asset-compare-core';
|
|
9
9
|
import { captureEntityComparePreview, captureModelComparePreview } from './asset-compare';
|
|
10
10
|
import {
|
|
11
11
|
type AssetPreviewBackground,
|
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
captureSourceReviewShotSetModelPreview,
|
|
21
21
|
parseShotSetDefinition,
|
|
22
22
|
} from './asset-preview';
|
|
23
|
-
import { activeDocumentAuthoring } from '@volter/sdk/
|
|
24
|
-
import { parseCameraChoice, parsePoseChoice } from '
|
|
23
|
+
import { activeDocumentAuthoring } from '@volter/sdk/authoring/shell-document-ops';
|
|
24
|
+
import { parseCameraChoice, parsePoseChoice } from './capture-camera-pose';
|
|
25
25
|
import type { EditorShellStore } from './editor-shell-store';
|
|
26
26
|
import { entityObject3D } from './entity-object';
|
|
27
27
|
|
|
@@ -32,9 +32,8 @@ export async function handleAssetPreviewCommand(
|
|
|
32
32
|
const assetPath = cmd['assetPath'];
|
|
33
33
|
const entityId = cmd['entityId'];
|
|
34
34
|
// The THIRD source: a GLB that travels IN the command rather than being
|
|
35
|
-
// fetched or looked up —
|
|
36
|
-
//
|
|
37
|
-
// the exported bytes. Counted rather than XOR-ed because there are now more
|
|
35
|
+
// fetched or looked up — a Node caller that built an Object3D, where there
|
|
36
|
+
// is no GPU, hands the editor the exported bytes. Counted rather than XOR-ed because there are now more
|
|
38
37
|
// than two sources and "exactly one" has to stay exactly one.
|
|
39
38
|
const glbBase64 = cmd['glbBase64'];
|
|
40
39
|
const sourceCount =
|
|
@@ -97,9 +96,8 @@ export async function handleAssetPreviewCommand(
|
|
|
97
96
|
'definition, compare, or stage "scene" — those legs stage their own cameras and poses.',
|
|
98
97
|
};
|
|
99
98
|
}
|
|
100
|
-
// A
|
|
101
|
-
//
|
|
102
|
-
// `project.<set>.previewShots` tool); validate the untrusted
|
|
99
|
+
// A caller-defined labeled shot set travels WITH the command (`live`'s
|
|
100
|
+
// `assetPreviewShots`); validate the untrusted
|
|
103
101
|
// definition at the relay boundary so a malformed contribution fails with
|
|
104
102
|
// a named reason instead of a deep three.js error.
|
|
105
103
|
let shotSetDefinition: ReturnType<typeof parseShotSetDefinition> | undefined;
|
|
@@ -114,11 +112,11 @@ export async function handleAssetPreviewCommand(
|
|
|
114
112
|
if (shots === 'source' && parsedForward === null) {
|
|
115
113
|
return {
|
|
116
114
|
ok: false,
|
|
117
|
-
error: 'capture-asset-preview
|
|
115
|
+
error: 'capture-asset-preview shots "source" requires a non-degenerate forward [x,y,z].',
|
|
118
116
|
};
|
|
119
117
|
}
|
|
120
118
|
if (compare !== undefined) {
|
|
121
|
-
//
|
|
119
|
+
// Compare mode serves the editor's `screenshot <model.glb> --compare <ref.glb>` command.
|
|
122
120
|
// Validated here at the relay boundary so a malformed payload fails with
|
|
123
121
|
// a named reason instead of a deep three.js error.
|
|
124
122
|
if (shots !== undefined || shotSetDefinition !== undefined) {
|
|
@@ -142,8 +140,8 @@ export async function handleAssetPreviewCommand(
|
|
|
142
140
|
// Wire-carried GLB bytes stand NOWHERE, so they take no scene stage and no
|
|
143
141
|
// compare (whose reference is named some other way). An explicit `shotSet`
|
|
144
142
|
// definition is a different matter and is served: a shot set stages the
|
|
145
|
-
// subject itself, which is what lets
|
|
146
|
-
//
|
|
143
|
+
// subject itself, which is what lets a caller circle a model that only
|
|
144
|
+
// ever existed as bytes. `shots: 'source'` stays
|
|
147
145
|
// out — it is the asset-path review set, keyed to a stored forward vector.
|
|
148
146
|
if (typeof glbBase64 === 'string') {
|
|
149
147
|
if (shots !== undefined || compare !== undefined) {
|
|
@@ -191,12 +189,9 @@ export async function handleAssetPreviewCommand(
|
|
|
191
189
|
};
|
|
192
190
|
}
|
|
193
191
|
}
|
|
194
|
-
// Resolve
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
// projection without publishing that object through the shell's edit-mode
|
|
198
|
-
// `objectMap`; checking the map alone made the final door reject an id every
|
|
199
|
-
// preceding door had just accepted.
|
|
192
|
+
// Resolve screenshot subjects through the active-document adapter used by status, Hierarchy,
|
|
193
|
+
// Inspector and frame-entity. An adopted scene’s projected ids need not appear in the shell’s
|
|
194
|
+
// edit-mode object map.
|
|
200
195
|
const entityObject =
|
|
201
196
|
typeof entityId === 'string'
|
|
202
197
|
? entityObject3D(activeDocumentAuthoring(store.shell), store.objectMap, entityId)
|