@volter/editor-threejs 0.5.65 → 0.5.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +2 -0
- package/contributions/animation-mixers.service.ts +20 -0
- package/contributions/animation-timeline.utility.tsx +44 -0
- package/contributions/three-integration.service.ts +13 -0
- package/dist-node/serving.mjs +405 -0
- package/package.json +113 -5
- package/serving/animation-live-module.ts +70 -0
- package/serving/animation-stamp.ts +88 -0
- package/serving/index.ts +14 -0
- package/serving/model-import-conversion.ts +344 -0
- package/src/adapter/ingest/scene-capture.ts +1 -23
- package/src/adapter/renderer-config.ts +3 -4
- package/src/adapter/three-contract.ts +72 -0
- package/src/animation/live-mixers.ts +55 -0
- package/src/ecs/object-marks.ts +1 -1
- package/src/ecs/user-data.ts +0 -16
- 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/content-bounds.ts +38 -4
- package/src/viewport/environment.ts +16 -0
- package/src/viewport-api.ts +92 -0
- package/src/viewport-door.ts +237 -0
- package/src/animation/animation-clock.ts +0 -479
- package/src/animation/runtime-inspection.ts +0 -45
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { faCamera, faCaretDown, faEllipsisVertical } from '@fortawesome/free-solid-svg-icons';
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
EditorIcon,
|
|
5
|
+
EditorPopover,
|
|
6
|
+
IconButton,
|
|
7
|
+
Inline,
|
|
8
|
+
Stack,
|
|
9
|
+
Text,
|
|
10
|
+
Tooltip,
|
|
11
|
+
} from '@volter/editor-sdk/widgets';
|
|
12
|
+
import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
13
|
+
import type { ShellStore } from '@volter/editor-sdk/kit/shell-store';
|
|
14
|
+
import {
|
|
15
|
+
setThreeViewportProjection,
|
|
16
|
+
subscribeThreeViewportPresentation,
|
|
17
|
+
threeViewportPresentation,
|
|
18
|
+
} from '@volter/editor-sdk/kit/three-viewport-presentation';
|
|
19
|
+
import {
|
|
20
|
+
object3DDocumentSession,
|
|
21
|
+
object3DDocumentSessionsVersion,
|
|
22
|
+
subscribeObject3DDocumentSessions,
|
|
23
|
+
} from '../authoring/object3d-document-session-registry';
|
|
24
|
+
|
|
25
|
+
const NO_SESSION_SUBSCRIBE = () => () => {};
|
|
26
|
+
const ZERO = () => 0;
|
|
27
|
+
|
|
28
|
+
/** The standard Scene-view camera menu: projection and axis views in one
|
|
29
|
+
* place. These are editor-camera choices only. */
|
|
30
|
+
/** The stage's projection + axis-view presets. `shell` is the STAGE's own
|
|
31
|
+
* store's shell, so a preset drives the stage the control is mounted over
|
|
32
|
+
* (ARCHITECTURE-CORE §One stage unit 4).
|
|
33
|
+
*
|
|
34
|
+
* A DOCUMENT STAGE IS PAINTED BY ITS SESSION (`Object3DDocumentSession.camera()`), and the
|
|
35
|
+
* editor-wide projection (`setThreeViewportProjection`) reaches only the world root's stage
|
|
36
|
+
* (`world-root-stage.ts` is its one subscriber): over a document the Perspective and
|
|
37
|
+
* Orthographic buttons changed nothing, the defect `ViewportFurniture`'s projection toggle
|
|
38
|
+
* records. With `documentId` naming a stage that has a session, the menu drives that session,
|
|
39
|
+
* as the document toolbar's view menu does; the world root keeps the editor-wide value.
|
|
40
|
+
*
|
|
41
|
+
* `label` draws the trigger as the view's NAME (the look's `stage.chrome.viewName`: Godot's
|
|
42
|
+
* "⋮ Perspective", Unreal's "Perspective" pill) instead of the camera icon; `kebab` puts
|
|
43
|
+
* Godot's three dots before it. */
|
|
44
|
+
export function ViewportViewMenu({
|
|
45
|
+
shell,
|
|
46
|
+
documentId,
|
|
47
|
+
label,
|
|
48
|
+
kebab = false,
|
|
49
|
+
}: {
|
|
50
|
+
readonly shell: Pick<ShellStore, 'setViewPreset'>;
|
|
51
|
+
readonly documentId?: string;
|
|
52
|
+
readonly label?: string;
|
|
53
|
+
readonly kebab?: boolean;
|
|
54
|
+
}) {
|
|
55
|
+
const state = useSyncExternalStore(
|
|
56
|
+
subscribeThreeViewportPresentation,
|
|
57
|
+
threeViewportPresentation,
|
|
58
|
+
threeViewportPresentation,
|
|
59
|
+
);
|
|
60
|
+
useSyncExternalStore(subscribeObject3DDocumentSessions, object3DDocumentSessionsVersion);
|
|
61
|
+
const session = documentId ? object3DDocumentSession(documentId) : null;
|
|
62
|
+
useSyncExternalStore(session?.subscribe ?? NO_SESSION_SUBSCRIBE, session?.getSnapshot ?? ZERO, session?.getSnapshot ?? ZERO);
|
|
63
|
+
const projection = session ? session.projection() : state.projection;
|
|
64
|
+
const setProjection = (next: 'perspective' | 'orthographic'): void => {
|
|
65
|
+
if (session) session.setProjection(next);
|
|
66
|
+
else setThreeViewportProjection(next);
|
|
67
|
+
};
|
|
68
|
+
const [open, setOpen] = useState(false);
|
|
69
|
+
const anchor = useRef<HTMLDivElement>(null);
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!open) return;
|
|
72
|
+
const close = (event: MouseEvent) => {
|
|
73
|
+
if (!anchor.current?.contains(event.target as Node)) setOpen(false);
|
|
74
|
+
};
|
|
75
|
+
document.addEventListener('mousedown', close);
|
|
76
|
+
return () => document.removeEventListener('mousedown', close);
|
|
77
|
+
}, [open]);
|
|
78
|
+
|
|
79
|
+
const preset = (view: 'top' | 'front' | 'right'): void => {
|
|
80
|
+
if (session) {
|
|
81
|
+
session.setViewPreset(view, 'view');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
setThreeViewportProjection('orthographic');
|
|
85
|
+
shell.setViewPreset(view);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div ref={anchor} className="vgai-viewport-popover-anchor">
|
|
90
|
+
{label === undefined ? (
|
|
91
|
+
<Tooltip text={projection === 'perspective' ? 'Perspective' : 'Orthographic'}>
|
|
92
|
+
<IconButton
|
|
93
|
+
aria-label="Viewport views"
|
|
94
|
+
aria-expanded={open}
|
|
95
|
+
size="comfortable"
|
|
96
|
+
onClick={() => setOpen((value) => !value)}
|
|
97
|
+
>
|
|
98
|
+
<EditorIcon icon={open ? faCaretDown : faCamera} size="md" />
|
|
99
|
+
</IconButton>
|
|
100
|
+
</Tooltip>
|
|
101
|
+
) : (
|
|
102
|
+
<Button
|
|
103
|
+
size="comfortable"
|
|
104
|
+
variant="ghost"
|
|
105
|
+
aria-label="Viewport views"
|
|
106
|
+
aria-expanded={open}
|
|
107
|
+
data-testid="viewport-view-name"
|
|
108
|
+
onClick={() => setOpen((value) => !value)}
|
|
109
|
+
>
|
|
110
|
+
{kebab ? <EditorIcon icon={faEllipsisVertical} size="sm" /> : null}
|
|
111
|
+
{label}
|
|
112
|
+
</Button>
|
|
113
|
+
)}
|
|
114
|
+
{open ? (
|
|
115
|
+
<EditorPopover style={{ minWidth: 230 }}>
|
|
116
|
+
<Stack gap={3}>
|
|
117
|
+
<Text variant="caption" tone="muted">
|
|
118
|
+
Projection
|
|
119
|
+
</Text>
|
|
120
|
+
<Inline gap={1}>
|
|
121
|
+
<Button
|
|
122
|
+
size="compact"
|
|
123
|
+
variant={projection === 'perspective' ? 'primary' : 'ghost'}
|
|
124
|
+
onClick={() => setProjection('perspective')}
|
|
125
|
+
>
|
|
126
|
+
Perspective
|
|
127
|
+
</Button>
|
|
128
|
+
<Button
|
|
129
|
+
size="compact"
|
|
130
|
+
variant={projection === 'orthographic' ? 'primary' : 'ghost'}
|
|
131
|
+
onClick={() => setProjection('orthographic')}
|
|
132
|
+
>
|
|
133
|
+
Orthographic
|
|
134
|
+
</Button>
|
|
135
|
+
</Inline>
|
|
136
|
+
<Text variant="caption" tone="muted">
|
|
137
|
+
Axis View
|
|
138
|
+
</Text>
|
|
139
|
+
<Inline gap={1}>
|
|
140
|
+
<Button size="compact" variant="ghost" onClick={() => preset('top')}>
|
|
141
|
+
Top
|
|
142
|
+
</Button>
|
|
143
|
+
<Button size="compact" variant="ghost" onClick={() => preset('front')}>
|
|
144
|
+
Front
|
|
145
|
+
</Button>
|
|
146
|
+
<Button size="compact" variant="ghost" onClick={() => preset('right')}>
|
|
147
|
+
Right
|
|
148
|
+
</Button>
|
|
149
|
+
</Inline>
|
|
150
|
+
</Stack>
|
|
151
|
+
</EditorPopover>
|
|
152
|
+
) : null}
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { threeStateOf } from '../../three-state';
|
|
2
|
+
import { themeVars } from '@volter/editor-sdk/widgets';
|
|
3
|
+
import { liveMixerFor } from '@volter/editor-threejs/animation/live-mixers';
|
|
4
|
+
import { useCallback, useSyncExternalStore } from 'react';
|
|
5
|
+
import type * as THREE from 'three';
|
|
6
|
+
import {
|
|
7
|
+
assetPreviewSubject,
|
|
8
|
+
createAssetPreviewSnapshot,
|
|
9
|
+
disposeAssetPreviewSnapshot,
|
|
10
|
+
} from '../../asset-preview';
|
|
11
|
+
import { useEditorStore } from '@volter/editor-sdk/kit/editor-runtime';
|
|
12
|
+
import { Object3DDocumentViewport } from '../StageHost';
|
|
13
|
+
|
|
14
|
+
function entityClips(source: THREE.Object3D): THREE.AnimationClip[] {
|
|
15
|
+
const discovered = new Set<THREE.AnimationClip>();
|
|
16
|
+
source.traverse((object) => {
|
|
17
|
+
const live = liveMixerFor(object);
|
|
18
|
+
if (live) for (const clip of live.clips.values()) discovered.add(clip);
|
|
19
|
+
for (const clip of object.animations) discovered.add(clip);
|
|
20
|
+
});
|
|
21
|
+
return [...discovered];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Isolated native projection of a live `Object3D`; the source graph is never
|
|
26
|
+
* reparented (`createAssetPreviewSnapshot` copies it).
|
|
27
|
+
*
|
|
28
|
+
* The object comes in already resolved, so this component has no way to fail
|
|
29
|
+
* for a thing that is on screen — it is the honest shared body of both the
|
|
30
|
+
* Asset Lab's entity document ({@link EntityModelDocument}, which resolves the
|
|
31
|
+
* scene entity and owns the "no longer available" case) and the inspector's
|
|
32
|
+
* preview section (`components/InspectorObjectPreview.tsx`), whose subject the
|
|
33
|
+
* composer has already proved previewable.
|
|
34
|
+
*/
|
|
35
|
+
export function Object3DSnapshotDocument({
|
|
36
|
+
documentId,
|
|
37
|
+
source,
|
|
38
|
+
sourcePath,
|
|
39
|
+
displayName,
|
|
40
|
+
active,
|
|
41
|
+
chromeless = false,
|
|
42
|
+
revision = 0,
|
|
43
|
+
modelSource,
|
|
44
|
+
}: {
|
|
45
|
+
readonly documentId: string;
|
|
46
|
+
readonly source: THREE.Object3D;
|
|
47
|
+
/** Human-readable provenance line for the viewport's own chrome. */
|
|
48
|
+
readonly sourcePath?: string;
|
|
49
|
+
readonly displayName: string;
|
|
50
|
+
readonly active: boolean;
|
|
51
|
+
readonly chromeless?: boolean;
|
|
52
|
+
/** Bump to rebuild the snapshot when the source graph mutated in place. */
|
|
53
|
+
readonly revision?: number;
|
|
54
|
+
readonly modelSource?: { readonly kind: 'entity'; readonly entityId: string };
|
|
55
|
+
}) {
|
|
56
|
+
const build = useCallback(() => {
|
|
57
|
+
// The mesh under the click, or the skeleton root that owns its bones.
|
|
58
|
+
const root = createAssetPreviewSnapshot(assetPreviewSubject(source));
|
|
59
|
+
const animations = entityClips(source);
|
|
60
|
+
root.animations = [...animations];
|
|
61
|
+
return {
|
|
62
|
+
root,
|
|
63
|
+
animations,
|
|
64
|
+
dispose() {
|
|
65
|
+
disposeAssetPreviewSnapshot(root);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}, [source, revision]);
|
|
69
|
+
return (
|
|
70
|
+
<Object3DDocumentViewport
|
|
71
|
+
documentId={documentId}
|
|
72
|
+
sourcePath={sourcePath ?? displayName}
|
|
73
|
+
displayName={displayName}
|
|
74
|
+
build={build}
|
|
75
|
+
active={active}
|
|
76
|
+
chromeless={chromeless}
|
|
77
|
+
// The chromeless host is the inspector's preview, which remounts on
|
|
78
|
+
// every selection — it draws through the shared renderer instead of
|
|
79
|
+
// building one per click (`inspector-preview-renderer.ts`). A full
|
|
80
|
+
// document lives as long as its panel and keeps its own.
|
|
81
|
+
rendererLane={chromeless ? 'inspector-preview' : 'own'}
|
|
82
|
+
assetType="entity"
|
|
83
|
+
{...(modelSource ? { modelSource } : {})}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The Asset Lab document for one live scene entity. */
|
|
89
|
+
export function EntityModelDocument({
|
|
90
|
+
documentId,
|
|
91
|
+
entityId,
|
|
92
|
+
displayName,
|
|
93
|
+
active,
|
|
94
|
+
}: {
|
|
95
|
+
readonly documentId: string;
|
|
96
|
+
readonly entityId: string;
|
|
97
|
+
readonly displayName: string;
|
|
98
|
+
readonly active: boolean;
|
|
99
|
+
}) {
|
|
100
|
+
const store = threeStateOf(useEditorStore());
|
|
101
|
+
const storeVersion = useSyncExternalStore(store.shell.subscribe, store.shell.getSnapshot);
|
|
102
|
+
const source = store.objectMap.get(entityId);
|
|
103
|
+
|
|
104
|
+
if (!source)
|
|
105
|
+
return (
|
|
106
|
+
<div role="alert" style={{ padding: 20, color: themeVars.semantic.danger }}>
|
|
107
|
+
Scene entity {entityId} is no longer available.
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
return (
|
|
111
|
+
<Object3DSnapshotDocument
|
|
112
|
+
documentId={documentId}
|
|
113
|
+
source={source}
|
|
114
|
+
sourcePath={`Entity ${entityId}`}
|
|
115
|
+
displayName={displayName}
|
|
116
|
+
active={active}
|
|
117
|
+
revision={storeVersion}
|
|
118
|
+
modelSource={{ kind: 'entity', entityId }}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
}
|