@volter/editor-threejs 0.5.66 → 0.5.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/contributions/animation-timeline.utility.tsx +44 -0
  2. package/contributions/three-integration.service.ts +13 -0
  3. package/package.json +96 -6
  4. package/src/adapter/renderer-config.ts +3 -4
  5. package/src/adapter/three-contract.ts +72 -0
  6. package/src/ecs/object-marks.ts +1 -1
  7. package/src/ecs/user-data.ts +0 -9
  8. package/src/host-hierarchy-objects.ts +31 -0
  9. package/src/kit/animation/three-clips-subject.ts +190 -0
  10. package/src/kit/asset-compare.ts +294 -0
  11. package/src/kit/asset-preview-command.ts +265 -0
  12. package/src/kit/asset-preview-framing.ts +357 -0
  13. package/src/kit/asset-preview.ts +2802 -0
  14. package/src/kit/asset-workflow/model-inspection.ts +830 -0
  15. package/src/kit/authoring/component-instance-root.ts +171 -0
  16. package/src/kit/authoring/design-time-settle.ts +343 -0
  17. package/src/kit/authoring/live-object-transform.ts +62 -0
  18. package/src/kit/authoring/object3d-document-session-registry.ts +154 -0
  19. package/src/kit/authoring/object3d-document-session.ts +1965 -0
  20. package/src/kit/authoring/object3d-gesture-controller.ts +113 -0
  21. package/src/kit/authoring/quarks-particle-systems.ts +19 -0
  22. package/src/kit/authoring/shell-viewport-policy.ts +48 -0
  23. package/src/kit/authoring/source-object3d-authoring-adapter.ts +526 -0
  24. package/src/kit/authoring/three-projection-core.ts +226 -0
  25. package/src/kit/authoring/viewport-pick-context.ts +39 -0
  26. package/src/kit/authoring/viewport-raycast.ts +240 -0
  27. package/src/kit/authoring/world-hidden-viewport.ts +95 -0
  28. package/src/kit/camera-authoring.ts +175 -0
  29. package/src/kit/components/CameraInfo.tsx +56 -0
  30. package/src/kit/components/InspectorObjectPreview.tsx +57 -0
  31. package/src/kit/components/Object3DDocumentToolbar.tsx +549 -0
  32. package/src/kit/components/Object3DDocumentViewport.tsx +58 -0
  33. package/src/kit/components/StageHost.tsx +2547 -0
  34. package/src/kit/components/StageOverlays.tsx +21 -0
  35. package/src/kit/components/StatsOverlay.tsx +78 -0
  36. package/src/kit/components/ToolObject3DPreview.tsx +39 -0
  37. package/src/kit/components/ViewportFurniture.tsx +655 -0
  38. package/src/kit/components/ViewportOverlay.tsx +215 -0
  39. package/src/kit/components/ViewportShadingMenu.tsx +340 -0
  40. package/src/kit/components/ViewportViewMenu.tsx +155 -0
  41. package/src/kit/components/asset-viewers/EntityModelDocument.tsx +121 -0
  42. package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
  43. package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +395 -0
  44. package/src/kit/components/asset-viewers/LutAssetDocument.tsx +444 -0
  45. package/src/kit/components/asset-viewers/ModelAssetDocument.tsx +105 -0
  46. package/src/kit/components/asset-viewers/Object3DPreview.tsx +356 -0
  47. package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +527 -0
  48. package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
  49. package/src/kit/components/asset-viewers/three-asset-viewers.tsx +132 -0
  50. package/src/kit/components/object3d-contribution-surfaces.tsx +33 -0
  51. package/src/kit/components/stage-keyboard.tsx +40 -0
  52. package/src/kit/components/stage-overlay-set.tsx +105 -0
  53. package/src/kit/components/stage-presence-markers.ts +482 -0
  54. package/src/kit/components/stage-transform-chrome.ts +30 -0
  55. package/src/kit/components/stage-transform-tools.tsx +73 -0
  56. package/src/kit/components/stage-view-name.ts +30 -0
  57. package/src/kit/components/standard-viewport-dressing.ts +1042 -0
  58. package/src/kit/components/world-root-binding.ts +64 -0
  59. package/src/kit/constraint-helper.ts +338 -0
  60. package/src/kit/editor-shell-store.ts +814 -0
  61. package/src/kit/editor-viewport.ts +6621 -0
  62. package/src/kit/entity-lod.ts +31 -0
  63. package/src/kit/entity-object.ts +92 -0
  64. package/src/kit/hierarchy-mark-reader.ts +74 -0
  65. package/src/kit/instanced-presentation.ts +164 -0
  66. package/src/kit/live-module-source.ts +230 -0
  67. package/src/kit/model-thumbnail.ts +539 -0
  68. package/src/kit/play-camera-flight.ts +300 -0
  69. package/src/kit/projection/three.ts +898 -0
  70. package/src/kit/reflection-probe-helper.ts +142 -0
  71. package/src/kit/scene-document-viewport.ts +51 -0
  72. package/src/kit/scene-framing.ts +315 -0
  73. package/src/kit/scene-view-fog.ts +89 -0
  74. package/src/kit/spatial-handle-visuals.ts +332 -0
  75. package/src/kit/stories/three-story-model.ts +66 -0
  76. package/src/kit/three-canvas-render.ts +44 -0
  77. package/src/kit/three-hierarchy-row-media.ts +26 -0
  78. package/src/kit/three-inspection-media.ts +73 -0
  79. package/src/kit/three-integration.ts +86 -0
  80. package/src/kit/three-state.ts +33 -0
  81. package/src/kit/three-viewport/bone-selection-highlight.ts +119 -0
  82. package/src/kit/three-viewport/camera-fit.ts +41 -0
  83. package/src/kit/three-viewport/interactive-renderer.ts +132 -0
  84. package/src/kit/three-viewport/selection-brackets.ts +355 -0
  85. package/src/kit/three-viewport/selection-outline.ts +333 -0
  86. package/src/kit/three-viewport/skeleton-helper.ts +61 -0
  87. package/src/kit/three-viewport/source-color.ts +197 -0
  88. package/src/kit/three-viewport/studio-environment.ts +96 -0
  89. package/src/kit/trigger-volume-helper.ts +116 -0
  90. package/src/kit/viewport-actions.ts +128 -0
  91. package/src/kit/viewport-authoring-policy.ts +154 -0
  92. package/src/kit/viewport-commands.ts +318 -0
  93. package/src/kit/viewport-hotkeys.ts +119 -0
  94. package/src/kit/viewport-shading-boundary.ts +12 -0
  95. package/src/kit/viewport-status-facet.ts +53 -0
  96. package/src/object3d-contributions.ts +494 -0
  97. package/src/render/viewport-shading.ts +6 -2
  98. package/src/viewport-api.ts +92 -0
  99. package/src/viewport-door.ts +237 -0
@@ -0,0 +1,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
+ }