@volter/editor-threejs 0.5.211 → 0.5.213
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 +6 -11
- package/src/kit/components/StageHost.css +40 -0
- package/src/kit/components/StageHost.tsx +192 -439
- 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 +6 -17
- package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +9 -11
- package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +31 -133
- package/src/kit/components/asset-viewers/LutAssetDocument.tsx +10 -12
- package/src/kit/components/asset-viewers/ModelAssetDocument.tsx +4 -8
- package/src/kit/components/asset-viewers/Object3DPreview.tsx +13 -64
- package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +6 -10
- package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +48 -65
- 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 +5 -3
- 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 +486 -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/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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ToolObject3DDocumentAuthoringFactory } from '../../../object3d-contributions';
|
|
2
|
-
import {
|
|
2
|
+
import { StateSurface } from '@volter/sdk/widgets';
|
|
3
|
+
import { Progress } from '@volter/sdk/progress';
|
|
3
4
|
import type {
|
|
4
5
|
AuthoringAdapter,
|
|
5
6
|
InspectorProvider,
|
|
@@ -16,7 +17,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
|
16
17
|
import type * as THREE from 'three';
|
|
17
18
|
import { type ParticleSystem, QuarksLoader } from 'three.quarks';
|
|
18
19
|
import { quarksParticleSystems } from '../../authoring/quarks-particle-systems';
|
|
19
|
-
import {
|
|
20
|
+
import { projectPathOfServedAsset } from '@volter/sdk/project/output-roots';
|
|
20
21
|
import { Object3DDocumentViewport } from '../StageHost';
|
|
21
22
|
import { threeObject } from '../../../adapter/three-contract';
|
|
22
23
|
|
|
@@ -445,7 +446,7 @@ export async function loadNativeQuarksJsonSource(
|
|
|
445
446
|
return null;
|
|
446
447
|
}
|
|
447
448
|
return isNativeQuarksJson(json)
|
|
448
|
-
? { text, json, projectPath:
|
|
449
|
+
? { text, json, projectPath: projectPathOfServedAsset(assetPath) }
|
|
449
450
|
: null;
|
|
450
451
|
}
|
|
451
452
|
|
|
@@ -503,14 +504,9 @@ export function QuarksAssetDocument({
|
|
|
503
504
|
|
|
504
505
|
if (error)
|
|
505
506
|
return (
|
|
506
|
-
<
|
|
507
|
-
{error}
|
|
508
|
-
</div>
|
|
509
|
-
);
|
|
510
|
-
if (!artifact)
|
|
511
|
-
return (
|
|
512
|
-
<div style={{ padding: 20, color: themeVars.content.muted }}>Loading {displayName}…</div>
|
|
507
|
+
<StateSurface tone="error" title={`Could not load ${displayName}`} detail={error} />
|
|
513
508
|
);
|
|
509
|
+
if (!artifact) return <Progress documentId={documentId} title={`Loading ${displayName}`} />;
|
|
514
510
|
return (
|
|
515
511
|
<Object3DDocumentViewport
|
|
516
512
|
documentId={documentId}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { editorHost } from '@volter/sdk/host';
|
|
1
2
|
import { faCode, faSliders, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
|
|
2
|
-
import { Checkbox, ColorInput, NumberInput, Select, themeVars } from '@volter/sdk/widgets';
|
|
3
|
+
import { Checkbox, ColorInput, EditorBanner, NumberInput, Select, spaceVar, StateSurface, Text, themeVars } from '@volter/sdk/widgets';
|
|
4
|
+
import { Progress } from '@volter/sdk/progress';
|
|
3
5
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
6
|
import * as THREE from 'three';
|
|
5
|
-
import { readProjectTextFile } from '@volter/sdk/
|
|
6
|
-
import type { InspectionSection } from '@volter/sdk/
|
|
7
|
-
import { CONTRIBUTED_SECTION_ORDER, PROPERTIES_SECTION_ORDER } from '@volter/sdk/
|
|
8
|
-
import { AssetEditorShell } from '@volter/sdk/
|
|
9
|
-
import { subscribeProjectAsset } from '@volter/sdk/kit/components/asset-editor-persistence';
|
|
7
|
+
import { readProjectTextFile } from '@volter/sdk/api/project-source';
|
|
8
|
+
import type { InspectionSection } from '@volter/sdk/inspection-model';
|
|
9
|
+
import { CONTRIBUTED_SECTION_ORDER, PROPERTIES_SECTION_ORDER } from '@volter/sdk/inspection-model';
|
|
10
|
+
import { AssetEditorShell } from '@volter/sdk/components/AssetEditorShell';
|
|
10
11
|
import {
|
|
11
12
|
FALLBACK_FRAGMENT_SHADER,
|
|
12
13
|
FALLBACK_VERTEX_SHADER,
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
type ShaderUniformDescriptor,
|
|
19
20
|
shaderCompanionPath,
|
|
20
21
|
shaderStageFromPath,
|
|
21
|
-
} from '
|
|
22
|
+
} from './shader-source';
|
|
22
23
|
|
|
23
24
|
type PreviewGeometry = 'sphere' | 'box' | 'plane';
|
|
24
25
|
type UniformPreviewValue = number | boolean | string | number[];
|
|
@@ -267,7 +268,7 @@ function ShaderPreview({
|
|
|
267
268
|
const observer = new ResizeObserver(resize);
|
|
268
269
|
observer.observe(host);
|
|
269
270
|
|
|
270
|
-
const
|
|
271
|
+
const timer = new THREE.Timer();
|
|
271
272
|
try {
|
|
272
273
|
renderer.compile(scene, camera);
|
|
273
274
|
renderer.render(scene, camera);
|
|
@@ -294,7 +295,8 @@ function ShaderPreview({
|
|
|
294
295
|
|
|
295
296
|
const render = () => {
|
|
296
297
|
if (disposed) return;
|
|
297
|
-
|
|
298
|
+
timer.update();
|
|
299
|
+
const elapsed = timer.getElapsed();
|
|
298
300
|
for (const descriptor of descriptors) {
|
|
299
301
|
const uniform = uniforms[descriptor.name];
|
|
300
302
|
if (!uniform) continue;
|
|
@@ -448,6 +450,14 @@ function UniformControlRow({
|
|
|
448
450
|
);
|
|
449
451
|
}
|
|
450
452
|
|
|
453
|
+
/** One diagnostic as a line: where (the file, its line and column) and what. */
|
|
454
|
+
function diagnosticLine(diagnostic: ShaderDiagnostic, files: ShaderFiles): string {
|
|
455
|
+
const file = diagnostic.stage === 'program' ? null : files[diagnostic.stage];
|
|
456
|
+
const where = file?.available ? basename(file.path) : diagnostic.stage;
|
|
457
|
+
const at = diagnostic.line ? `:${diagnostic.line}${diagnostic.column ? `:${diagnostic.column}` : ''}` : '';
|
|
458
|
+
return `${where}${at} · ${diagnostic.message}`;
|
|
459
|
+
}
|
|
460
|
+
|
|
451
461
|
function DiagnosticList({
|
|
452
462
|
diagnostics,
|
|
453
463
|
files,
|
|
@@ -456,37 +466,21 @@ function DiagnosticList({
|
|
|
456
466
|
files: ShaderFiles;
|
|
457
467
|
}) {
|
|
458
468
|
if (diagnostics.length === 0) {
|
|
459
|
-
return
|
|
460
|
-
<div style={{ color: themeVars.semantic.success, fontSize: 11, padding: 4 }}>
|
|
461
|
-
Shader program compiled successfully.
|
|
462
|
-
</div>
|
|
463
|
-
);
|
|
469
|
+
return <StateSurface inline tone="success" title="Shader program compiled successfully." />;
|
|
464
470
|
}
|
|
465
471
|
return (
|
|
466
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap:
|
|
467
|
-
{diagnostics.map((diagnostic, index) =>
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
lineHeight: 1.45,
|
|
479
|
-
}}
|
|
480
|
-
>
|
|
481
|
-
{file?.available ? basename(file.path) : diagnostic.stage}
|
|
482
|
-
{diagnostic.line
|
|
483
|
-
? `:${diagnostic.line}${diagnostic.column ? `:${diagnostic.column}` : ''}`
|
|
484
|
-
: ''}
|
|
485
|
-
{' · '}
|
|
486
|
-
{diagnostic.message}
|
|
487
|
-
</div>
|
|
488
|
-
);
|
|
489
|
-
})}
|
|
472
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: spaceVar[2] }}>
|
|
473
|
+
{diagnostics.map((diagnostic, index) => (
|
|
474
|
+
<Text
|
|
475
|
+
key={`${diagnostic.stage}:${diagnostic.line ?? 0}:${diagnostic.message}:${index}`}
|
|
476
|
+
as="div"
|
|
477
|
+
variant="code"
|
|
478
|
+
selectable
|
|
479
|
+
tone={diagnostic.severity === 'warning' ? 'warning' : 'danger'}
|
|
480
|
+
>
|
|
481
|
+
{diagnosticLine(diagnostic, files)}
|
|
482
|
+
</Text>
|
|
483
|
+
))}
|
|
490
484
|
</div>
|
|
491
485
|
);
|
|
492
486
|
}
|
|
@@ -535,8 +529,8 @@ export function ShaderAssetDocument({
|
|
|
535
529
|
const reload = () => setFileRevision((revision) => revision + 1);
|
|
536
530
|
const companionPath = shaderCompanionPath(assetPath);
|
|
537
531
|
const unsubscribers = [
|
|
538
|
-
|
|
539
|
-
...(companionPath ? [
|
|
532
|
+
editorHost().files.subscribeAsset(assetPath, reload),
|
|
533
|
+
...(companionPath ? [editorHost().files.subscribeAsset(companionPath, reload)] : []),
|
|
540
534
|
];
|
|
541
535
|
return () => {
|
|
542
536
|
for (const unsubscribe of unsubscribers) unsubscribe();
|
|
@@ -588,17 +582,18 @@ export function ShaderAssetDocument({
|
|
|
588
582
|
order: PROPERTIES_SECTION_ORDER,
|
|
589
583
|
body: {
|
|
590
584
|
kind: 'custom',
|
|
591
|
-
|
|
585
|
+
// (shown once the files are read: the document passes its sections only then)
|
|
586
|
+
render: () => files && (
|
|
592
587
|
<div
|
|
593
588
|
style={{ display: 'flex', flexDirection: 'column', gap: 5, padding: 4, fontSize: 10 }}
|
|
594
589
|
>
|
|
595
590
|
<span>
|
|
596
|
-
Vertex · {
|
|
597
|
-
{
|
|
591
|
+
Vertex · {basename(files.vertex.path)}
|
|
592
|
+
{!files.vertex.available ? ' (preview fallback)' : ''}
|
|
598
593
|
</span>
|
|
599
594
|
<span>
|
|
600
|
-
Fragment · {
|
|
601
|
-
{
|
|
595
|
+
Fragment · {basename(files.fragment.path)}
|
|
596
|
+
{!files.fragment.available ? ' (preview fallback)' : ''}
|
|
602
597
|
</span>
|
|
603
598
|
<span style={{ color: themeVars.content.muted }}>
|
|
604
599
|
Ordinary sibling GLSL files; runtime composition stays in TS/TSX.
|
|
@@ -657,9 +652,7 @@ export function ShaderAssetDocument({
|
|
|
657
652
|
title={basename(assetPath)}
|
|
658
653
|
status="Shader unavailable"
|
|
659
654
|
>
|
|
660
|
-
<
|
|
661
|
-
{loadError}
|
|
662
|
-
</div>
|
|
655
|
+
<StateSurface tone="error" title={`Could not load ${basename(assetPath)}`} detail={loadError} />
|
|
663
656
|
</AssetEditorShell>
|
|
664
657
|
);
|
|
665
658
|
}
|
|
@@ -670,9 +663,8 @@ export function ShaderAssetDocument({
|
|
|
670
663
|
active={active}
|
|
671
664
|
type="shader"
|
|
672
665
|
title={basename(assetPath)}
|
|
673
|
-
status="Loading GLSL"
|
|
674
666
|
>
|
|
675
|
-
<
|
|
667
|
+
<Progress documentId={documentId} title={`Loading ${basename(assetPath)}`} />
|
|
676
668
|
</AssetEditorShell>
|
|
677
669
|
);
|
|
678
670
|
}
|
|
@@ -720,22 +712,13 @@ export function ShaderAssetDocument({
|
|
|
720
712
|
</Select>
|
|
721
713
|
</div>
|
|
722
714
|
{diagnostics.length > 0 && (
|
|
723
|
-
<
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
right: 12,
|
|
728
|
-
bottom: 12,
|
|
729
|
-
maxHeight: 120,
|
|
730
|
-
overflow: 'auto',
|
|
731
|
-
padding: 8,
|
|
732
|
-
border: `1px solid ${themeVars.semantic.danger}`,
|
|
733
|
-
borderRadius: 6,
|
|
734
|
-
background: themeVars.surface.overlay,
|
|
735
|
-
}}
|
|
715
|
+
<EditorBanner
|
|
716
|
+
tone={diagnostics.some((diagnostic) => diagnostic.severity !== 'warning') ? 'error' : 'warning'}
|
|
717
|
+
style={{ position: 'absolute', left: 0, right: 0, bottom: 0 }}
|
|
718
|
+
detail={diagnostics.map((diagnostic) => diagnosticLine(diagnostic, files)).join('\n')}
|
|
736
719
|
>
|
|
737
|
-
|
|
738
|
-
</
|
|
720
|
+
{`${diagnostics.length} issue${diagnostics.length === 1 ? '' : 's'}`}
|
|
721
|
+
</EditorBanner>
|
|
739
722
|
)}
|
|
740
723
|
</div>
|
|
741
724
|
</AssetEditorShell>
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/** Native GLSL document helpers. GLSL remains the artifact; these functions
|
|
2
|
+
* only describe it for Asset Lab and never serialize a Volter shader shape. */
|
|
3
|
+
|
|
4
|
+
export type ShaderStage = 'vertex' | 'fragment';
|
|
5
|
+
|
|
6
|
+
export interface ShaderUniformDescriptor {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly type: string;
|
|
9
|
+
readonly arraySize?: number | undefined;
|
|
10
|
+
readonly stages: readonly ShaderStage[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ShaderDiagnostic {
|
|
14
|
+
readonly stage: ShaderStage | 'program';
|
|
15
|
+
readonly severity: 'error' | 'warning';
|
|
16
|
+
readonly message: string;
|
|
17
|
+
readonly line?: number | undefined;
|
|
18
|
+
readonly column?: number | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const VERTEX_SUFFIXES = ['.vert', '.vert.glsl'] as const;
|
|
22
|
+
const FRAGMENT_SUFFIXES = ['.frag', '.frag.glsl'] as const;
|
|
23
|
+
|
|
24
|
+
function cleanPath(path: string): string {
|
|
25
|
+
return path.split(/[?#]/, 1)[0]!.toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function shaderStageFromPath(path: string, source = ''): ShaderStage {
|
|
29
|
+
const clean = cleanPath(path);
|
|
30
|
+
if (VERTEX_SUFFIXES.some((suffix) => clean.endsWith(suffix))) return 'vertex';
|
|
31
|
+
if (FRAGMENT_SUFFIXES.some((suffix) => clean.endsWith(suffix))) return 'fragment';
|
|
32
|
+
return /\bgl_Position\b/.test(source) ? 'vertex' : 'fragment';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Conventional sibling only. A standalone `.glsl` file stays standalone. */
|
|
36
|
+
export function shaderCompanionPath(path: string): string | null {
|
|
37
|
+
const clean = path.split(/[?#]/, 1)[0]!;
|
|
38
|
+
if (/\.vert\.glsl$/i.test(clean)) return clean.replace(/\.vert\.glsl$/i, '.frag.glsl');
|
|
39
|
+
if (/\.frag\.glsl$/i.test(clean)) return clean.replace(/\.frag\.glsl$/i, '.vert.glsl');
|
|
40
|
+
if (/\.vert$/i.test(clean)) return clean.replace(/\.vert$/i, '.frag');
|
|
41
|
+
if (/\.frag$/i.test(clean)) return clean.replace(/\.frag$/i, '.vert');
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function withoutComments(source: string): string {
|
|
46
|
+
// Preserve newlines so reflection never perturbs source-line diagnostics.
|
|
47
|
+
return source
|
|
48
|
+
.replace(/\/\*[\s\S]*?\*\//g, (comment) => comment.replace(/[^\n]/g, ' '))
|
|
49
|
+
.replace(/\/\/[^\n]*/g, '');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function declarations(source: string, stage: ShaderStage): ShaderUniformDescriptor[] {
|
|
53
|
+
const result: ShaderUniformDescriptor[] = [];
|
|
54
|
+
const pattern =
|
|
55
|
+
/\buniform\s+(?:(?:lowp|mediump|highp)\s+)?([A-Za-z_]\w*)\s+([A-Za-z_]\w*)\s*(?:\[\s*(\d+)\s*\])?\s*;/g;
|
|
56
|
+
for (const match of withoutComments(source).matchAll(pattern)) {
|
|
57
|
+
const type = match[1];
|
|
58
|
+
const name = match[2];
|
|
59
|
+
if (!type || !name) continue;
|
|
60
|
+
const arraySize = match[3] ? Number.parseInt(match[3], 10) : undefined;
|
|
61
|
+
result.push({ name, type, ...(arraySize ? { arraySize } : {}), stages: [stage] });
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function reflectShaderUniforms(
|
|
67
|
+
vertexSource: string,
|
|
68
|
+
fragmentSource: string,
|
|
69
|
+
): ShaderUniformDescriptor[] {
|
|
70
|
+
const reflected = new Map<string, ShaderUniformDescriptor>();
|
|
71
|
+
for (const uniform of [
|
|
72
|
+
...declarations(vertexSource, 'vertex'),
|
|
73
|
+
...declarations(fragmentSource, 'fragment'),
|
|
74
|
+
]) {
|
|
75
|
+
const current = reflected.get(uniform.name);
|
|
76
|
+
if (!current) {
|
|
77
|
+
reflected.set(uniform.name, uniform);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
reflected.set(uniform.name, {
|
|
81
|
+
...current,
|
|
82
|
+
stages: [...new Set([...current.stages, ...uniform.stages])],
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return [...reflected.values()];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function parseShaderDiagnostics(
|
|
89
|
+
log: string,
|
|
90
|
+
stage: ShaderDiagnostic['stage'],
|
|
91
|
+
): ShaderDiagnostic[] {
|
|
92
|
+
const diagnostics = log
|
|
93
|
+
.split('\n')
|
|
94
|
+
.map((line) => parseDiagnosticLine(line.trim(), stage))
|
|
95
|
+
.filter((diagnostic): diagnostic is ShaderDiagnostic => diagnostic !== null);
|
|
96
|
+
if (diagnostics.length === 0 && log.trim()) {
|
|
97
|
+
diagnostics.push({ stage, severity: 'error', message: log.trim() });
|
|
98
|
+
}
|
|
99
|
+
return diagnostics;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function parseDiagnosticLine(
|
|
103
|
+
text: string,
|
|
104
|
+
stage: ShaderDiagnostic['stage'],
|
|
105
|
+
): ShaderDiagnostic | null {
|
|
106
|
+
if (!text) return null;
|
|
107
|
+
const colon = text.match(/^(ERROR|WARNING):\s*\d+:(\d+)(?::|\((\d+)\):)\s*(.*)$/i);
|
|
108
|
+
if (colon) {
|
|
109
|
+
return {
|
|
110
|
+
stage,
|
|
111
|
+
severity: colon[1]!.toLowerCase() === 'warning' ? 'warning' : 'error',
|
|
112
|
+
line: Number.parseInt(colon[2]!, 10),
|
|
113
|
+
...(colon[3] ? { column: Number.parseInt(colon[3], 10) } : {}),
|
|
114
|
+
message: colon[4] || text,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const paren = text.match(/^\d+:(\d+)\((\d+)\):\s*(?:(error|warning)\s*:?)?\s*(.*)$/i);
|
|
118
|
+
if (!paren) return null;
|
|
119
|
+
return {
|
|
120
|
+
stage,
|
|
121
|
+
severity: paren[3]?.toLowerCase() === 'warning' ? 'warning' : 'error',
|
|
122
|
+
line: Number.parseInt(paren[1]!, 10),
|
|
123
|
+
column: Number.parseInt(paren[2]!, 10),
|
|
124
|
+
message: paren[4] || text,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Reset driver-reported lines after Three's injected ShaderMaterial prefix. */
|
|
129
|
+
export function previewShaderSource(source: string): string {
|
|
130
|
+
return `#line 1\n${source}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const FALLBACK_VERTEX_SHADER = `varying vec2 vUv;
|
|
134
|
+
|
|
135
|
+
void main() {
|
|
136
|
+
vUv = uv;
|
|
137
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
138
|
+
}`;
|
|
139
|
+
|
|
140
|
+
export const FALLBACK_FRAGMENT_SHADER = `varying vec2 vUv;
|
|
141
|
+
|
|
142
|
+
void main() {
|
|
143
|
+
gl_FragColor = vec4(vUv, 0.55, 1.0);
|
|
144
|
+
}`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THE THREE.JS ASSET LAB VIEWERS, registered by route
|
|
3
|
-
* (`@volter/sdk/
|
|
3
|
+
* (`@volter/sdk/asset-viewers`): a glTF model, an environment map, a
|
|
4
4
|
* LUT, a shader, a live modeling module and a live entity's model. Each loads on
|
|
5
5
|
* first use, so a document that never opens one never pays for three.js.
|
|
6
6
|
*/
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type JsonContentViewer,
|
|
11
11
|
registerAssetViewer,
|
|
12
12
|
registerJsonContentViewer,
|
|
13
|
-
} from '@volter/sdk/
|
|
13
|
+
} from '@volter/sdk/asset-viewers';
|
|
14
14
|
import { type ComponentType, lazy } from 'react';
|
|
15
15
|
|
|
16
16
|
const viewers: Readonly<Record<AssetViewerRoute, ComponentType<AssetViewerProps>>> = {
|
|
@@ -64,18 +64,6 @@ const viewers: Readonly<Record<AssetViewerRoute, ComponentType<AssetViewerProps>
|
|
|
64
64
|
),
|
|
65
65
|
};
|
|
66
66
|
}),
|
|
67
|
-
'model-preview': lazy(async () => {
|
|
68
|
-
const { Object3DPreview } = await import('./Object3DPreview');
|
|
69
|
-
return {
|
|
70
|
-
default: (props: AssetViewerProps) => (
|
|
71
|
-
<Object3DPreview
|
|
72
|
-
assetPath={props.assetPath}
|
|
73
|
-
{...(props.materialPath ? { materialPath: props.materialPath } : {})}
|
|
74
|
-
displayName={props.displayName}
|
|
75
|
-
/>
|
|
76
|
-
),
|
|
77
|
-
};
|
|
78
|
-
}),
|
|
79
67
|
// A project script that BUILDS an Object3D opens as the live modeling
|
|
80
68
|
// document; anything else keeps the text preview it is handed as `fallback`.
|
|
81
69
|
module: lazy(async () => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { announceDocumentStage } from '@volter/sdk/
|
|
1
|
+
import { announceDocumentStage } from '@volter/sdk/document-viewports';
|
|
2
|
+
import { Progress } from '@volter/sdk/progress';
|
|
2
3
|
import { lazy, Suspense, useEffect } from 'react';
|
|
3
4
|
import type { ToolObject3DAuthoringProps, ToolObject3DPreviewProps } from '../../object3d-contributions';
|
|
4
5
|
|
|
@@ -12,7 +13,8 @@ const Authoring = lazy(async () => ({ default: (await import('./StageHost')).Too
|
|
|
12
13
|
|
|
13
14
|
export function Object3DPreviewSurface(props: ToolObject3DPreviewProps) {
|
|
14
15
|
return (
|
|
15
|
-
|
|
16
|
+
// (a preview card reports no wait: it is a card in a panel or a catalog, neither a document nor a view)
|
|
17
|
+
<Suspense fallback={null}>
|
|
16
18
|
<Preview {...props} />
|
|
17
19
|
</Suspense>
|
|
18
20
|
);
|
|
@@ -26,7 +28,7 @@ export function Object3DAuthoringSurface(props: ToolObject3DAuthoringProps) {
|
|
|
26
28
|
// `workspace.document` contributions do not, and which is which belongs to the PROJECT.
|
|
27
29
|
useEffect(() => announceDocumentStage(props.documentId), [props.documentId]);
|
|
28
30
|
return (
|
|
29
|
-
<Suspense fallback={<
|
|
31
|
+
<Suspense fallback={<Progress documentId={props.documentId} title="Loading 3D authoring surface" />}>
|
|
30
32
|
<Authoring {...props} />
|
|
31
33
|
</Suspense>
|
|
32
34
|
);
|
|
@@ -3,18 +3,12 @@
|
|
|
3
3
|
* Frame, pivot, snap and the vertex-snap hold, bound for the stage whose
|
|
4
4
|
* document is ACTIVE.
|
|
5
5
|
*
|
|
6
|
-
* They belong to the STAGE,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* alone — and the stage's nine (`transform.combined/translate/rotate/scale`,
|
|
13
|
-
* `viewport.toggleSnap/frameSelection/cyclePivot/snapToFloor`, the four view
|
|
14
|
-
* presets) were absent, so G/R/S on the Model stage answered nothing at all.
|
|
15
|
-
* The overlay set is an OVERLAY condition (a bounded host must not pay for
|
|
16
|
-
* the collaboration client it reaches); which keys a stage answers is not,
|
|
17
|
-
* and the two were one line by accident.
|
|
6
|
+
* They belong to the STAGE, not to `stage-overlay-set.tsx`, which a stage
|
|
7
|
+
* only reaches when it has the shell above it (`StageHost.tsx`'s
|
|
8
|
+
* `shellStore && !chromeless`): a document contributed by a PACKAGE mounts
|
|
9
|
+
* outside `EditorProvider` and still answers G/R/S. The overlay set is an
|
|
10
|
+
* OVERLAY condition (a bounded host must not pay for the collaboration client
|
|
11
|
+
* it reaches); which keys a stage answers is not.
|
|
18
12
|
*
|
|
19
13
|
* The ONE registry still means ONE holder (`hotkeys.ts`'s scope table, and
|
|
20
14
|
* `key-actions.ts`'s action table under the frame): the active
|
|
@@ -2,26 +2,26 @@
|
|
|
2
2
|
* hints and statistics. Package documents supply their own chrome. */
|
|
3
3
|
|
|
4
4
|
import { useSyncExternalStore } from 'react';
|
|
5
|
-
import { activeAuthoringVersion, subscribeActiveAuthoring } from '@volter/sdk/
|
|
5
|
+
import { activeAuthoringVersion, subscribeActiveAuthoring } from '@volter/sdk/authoring/active-adapter';
|
|
6
6
|
import {
|
|
7
7
|
object3DDocumentSessionsVersion,
|
|
8
8
|
subscribeObject3DDocumentSessions,
|
|
9
9
|
} from '../authoring/object3d-document-session-registry';
|
|
10
10
|
import type { EditorShellStore } from '../editor-shell-store';
|
|
11
11
|
import type { EditorViewport } from '../editor-viewport';
|
|
12
|
-
import { documentStageContext, type StageChrome,
|
|
12
|
+
import { documentStageContext, type StageChrome, stageSelectionToolsApply } from '@volter/sdk/stage-context';
|
|
13
13
|
import {
|
|
14
14
|
subscribeWorkspaceDocuments,
|
|
15
15
|
workspaceDocumentRegistryVersion,
|
|
16
|
-
} from '@volter/sdk/
|
|
16
|
+
} from '@volter/sdk/workspace-document-registry';
|
|
17
17
|
import {
|
|
18
18
|
subscribeViewportPresentation,
|
|
19
19
|
viewPresentation,
|
|
20
20
|
viewportPresentationVersion,
|
|
21
|
-
} from '@volter/sdk/
|
|
21
|
+
} from '@volter/sdk/viewport-presentation';
|
|
22
22
|
import { CameraInfo } from './CameraInfo';
|
|
23
23
|
import { StatsOverlay } from './StatsOverlay';
|
|
24
|
-
import { TransientHintOverlay } from '@volter/sdk/
|
|
24
|
+
import { TransientHintOverlay } from '@volter/sdk/components/TransientHint';
|
|
25
25
|
import { ViewportOverlay } from './ViewportOverlay';
|
|
26
26
|
|
|
27
27
|
/** What the HOST hands over about the stage itself — its own store, the
|
|
@@ -52,8 +52,7 @@ export function StageOverlaySet({
|
|
|
52
52
|
active,
|
|
53
53
|
chrome,
|
|
54
54
|
}: StageOverlaySetProps) {
|
|
55
|
-
/**
|
|
56
|
-
* building, which is the same store every overlay read before unit 4. */
|
|
55
|
+
/** Use the stage's store once built, and the shell store during bootstrap. */
|
|
57
56
|
const stageStore = stage?.store ?? store;
|
|
58
57
|
useSyncExternalStore(store.shell.subscribe, store.shell.getShellSnapshot ?? store.shell.getSnapshot);
|
|
59
58
|
// The stage's own state (its grid, helpers, stats, tool mode) lives on ITS
|
|
@@ -64,42 +63,34 @@ export function StageOverlaySet({
|
|
|
64
63
|
useSyncExternalStore(subscribeWorkspaceDocuments, workspaceDocumentRegistryVersion);
|
|
65
64
|
// THIS stage's context — not the focused one. A capability is present where
|
|
66
65
|
// its condition holds, and the condition is asked of the stage that would
|
|
67
|
-
// draw it
|
|
66
|
+
// draw it.
|
|
68
67
|
const ctx = documentStageContext(store.shell, documentId, chrome);
|
|
69
68
|
/** Whether the selection tools draw here — and that is the WHOLE condition:
|
|
70
69
|
* they read THIS stage's own store (`stage.store`, the one the shared
|
|
71
70
|
* panels reach through `focusedStageStore()`), so a prefab's grid button
|
|
72
71
|
* toggles the prefab's grid and its readout reports the camera its reader
|
|
73
72
|
* is looking through. */
|
|
74
|
-
const showsSelectionTools =
|
|
73
|
+
const showsSelectionTools = stageSelectionToolsApply(ctx);
|
|
75
74
|
// The camera readout is the editor's own; a view whose target draws none leaves it off
|
|
76
75
|
// (`overlays.cameraReadout`).
|
|
77
76
|
useSyncExternalStore(subscribeViewportPresentation, viewportPresentationVersion, viewportPresentationVersion);
|
|
78
77
|
const cameraReadout = viewPresentation(documentId).overlays.cameraReadout;
|
|
79
78
|
return (
|
|
80
79
|
<>
|
|
81
|
-
{/*
|
|
82
|
-
reached only by a stage that has the shell above it, and a
|
|
83
|
-
package-contributed document (the Model document) has none — so it
|
|
84
|
-
answered no stage key at all. It is `StageHost.tsx`'s now, beside
|
|
85
|
-
the stage handle, under the same activation condition
|
|
86
|
-
(`stage-keyboard.tsx` carries the measurement). */}
|
|
80
|
+
{/* StageHost mounts the stage keyboard beside its handle under the stage's activation condition. */}
|
|
87
81
|
{showsSelectionTools ? (
|
|
88
82
|
<>
|
|
89
83
|
<ViewportOverlay store={stageStore} documentId={documentId} />
|
|
90
84
|
{cameraReadout ? <CameraInfo /> : null}
|
|
91
85
|
</>
|
|
92
86
|
) : null}
|
|
93
|
-
{ctx.surface === '
|
|
87
|
+
{ctx.surface === 'stage' && stageStore.showStats && <StatsOverlay />}
|
|
94
88
|
{/* H2 — the ONE transient hint channel. Lives here because a refused
|
|
95
89
|
transform is a viewport gesture; the overlay is absent from the DOM
|
|
96
90
|
entirely until something has a reason to say. ONE channel means one
|
|
97
91
|
mount — the ACTIVE stage's — so a hint never draws twice. */}
|
|
98
92
|
{active ? <TransientHintOverlay /> : null}
|
|
99
|
-
{/*
|
|
100
|
-
Compact connection state lives in the status bar
|
|
101
|
-
(`status-contributions.tsx`); the detailed surface is the `network`
|
|
102
|
-
workspace utility (`NetworkInspectorPanel`), revealed from there. */}
|
|
93
|
+
{/* Connection state belongs to the status bar; the network utility provides detail. */}
|
|
103
94
|
</>
|
|
104
95
|
);
|
|
105
96
|
}
|
|
@@ -1,36 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* THE OTHER PARTICIPANTS, ON A STAGE — the spatial half of collaboration
|
|
3
|
-
* presence: what THIS stage publishes about the reader's own camera and
|
|
4
|
-
* gestures, and the camera frusta, selection boxes and pointer rays it paints
|
|
5
|
-
* for everyone else (WORK.md §Presence and the substrate, presence unit 4).
|
|
6
|
-
*
|
|
7
|
-
* It was the scene panel's, so only the world root had it. Under
|
|
8
|
-
* ARCHITECTURE-CORE §One stage it is a CAPABILITY with a condition — a stage
|
|
9
|
-
* painting a three surface — so every 3D document mounts it and a prefab
|
|
10
|
-
* story shows the same markers the Scene does.
|
|
11
|
-
*
|
|
12
|
-
* WHICH STAGE A PARTICIPANT IS ON. Presence is ONE record per participant
|
|
13
|
-
* (`collaboration-presence.ts`), and its `document` field is written by the
|
|
14
|
-
* shell for the ACTIVE workspace document (`DefaultEditorLayout.tsx`). So a
|
|
15
|
-
* stage publishes its camera only while its own document is the active one —
|
|
16
|
-
* otherwise several stages would overwrite each other's pose in the single
|
|
17
|
-
* record — and paints a participant only on the stage whose id matches that
|
|
18
|
-
* participant's `presence.document`. Measured against the spec's "publish it
|
|
19
|
-
* from the host with the stage's documentId": the presence record has ONE
|
|
20
|
-
* `document` field and the shell already owns it, so the stage owns the
|
|
21
|
-
* CONDITION on writing, not a second writer of that field.
|
|
22
|
-
*/
|
|
1
|
+
/** Publish this stage’s camera only while its document is active, and draw each participant on the stage named by presence.document. The shell owns the document field. */
|
|
23
2
|
|
|
24
|
-
import
|
|
3
|
+
import { editorHost } from '@volter/sdk/host';
|
|
4
|
+
import type { CollaborationParticipant } from '@volter/sdk/collaboration-types';
|
|
25
5
|
import { EDITOR_THEME_CLASS, graphiteDarkEditorTheme } from '@volter/sdk/widgets';
|
|
26
6
|
import { contentWorldBounds } from '@volter/editor-threejs/viewport/content-bounds';
|
|
27
7
|
import * as THREE from 'three';
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import { EDITOR_PARTICIPANT_ID } from '@volter/sdk/kit/editor-presence';
|
|
8
|
+
import { reportCollaborationPresence } from '../collaboration-presence';
|
|
9
|
+
import { COLLABORATION_PARTICIPANT_ID as EDITOR_PARTICIPANT_ID } from '@volter/sdk/editor-session-attribution';
|
|
31
10
|
import type { EditorShellStore } from '../editor-shell-store';
|
|
32
11
|
import type { EditorViewport } from '../editor-viewport';
|
|
33
|
-
import { activeWorkspaceDocumentId } from '@volter/sdk/
|
|
12
|
+
import { activeWorkspaceDocumentId } from '@volter/sdk/workspace-document-registry';
|
|
34
13
|
|
|
35
14
|
interface RemoteCameraMarker {
|
|
36
15
|
camera: THREE.PerspectiveCamera;
|
|
@@ -58,9 +37,7 @@ interface RemoteRayMarker {
|
|
|
58
37
|
/**
|
|
59
38
|
* Remote-camera labels paint into a DETACHED canvas, and a 2D context cannot
|
|
60
39
|
* resolve `var(--volter-…)` — so the theme's value has to be read off the live
|
|
61
|
-
* theme root before it reaches `fillStyle`.
|
|
62
|
-
* environments), fall back to the default theme's own value rather than a raw
|
|
63
|
-
* literal, so the label never depends on a color the theme contract doesn't own.
|
|
40
|
+
* theme root before it reaches `fillStyle`. Use the default theme's value during bootstrap.
|
|
64
41
|
*/
|
|
65
42
|
function contentOnAccentColor(): string {
|
|
66
43
|
const root = document.querySelector(`.${EDITOR_THEME_CLASS}`);
|
|
@@ -324,7 +301,7 @@ export function bindStagePresenceMarkers(options: StagePresenceOptions): StagePr
|
|
|
324
301
|
const remoteRayMarkers = new Map<string, RemoteRayMarker>();
|
|
325
302
|
let remotePresenceSequence = -1;
|
|
326
303
|
const syncRemoteSpatialMarkers = (): void => {
|
|
327
|
-
const snapshot =
|
|
304
|
+
const snapshot = editorHost().collaboration.snapshot();
|
|
328
305
|
if (!snapshot || snapshot.sequence === remotePresenceSequence) return;
|
|
329
306
|
remotePresenceSequence = snapshot.sequence;
|
|
330
307
|
/** THE PAINT CONDITION: a participant is on the stage its own presence
|
|
@@ -409,7 +386,7 @@ export function bindStagePresenceMarkers(options: StagePresenceOptions): StagePr
|
|
|
409
386
|
if (!publishes()) return;
|
|
410
387
|
const participantId = (event as CustomEvent<{ participantId?: unknown }>).detail?.participantId;
|
|
411
388
|
if (typeof participantId !== 'string') return;
|
|
412
|
-
const camera =
|
|
389
|
+
const camera = editorHost().collaboration.snapshot()?.participants.find(
|
|
413
390
|
(participant) => participant.participantId === participantId,
|
|
414
391
|
)?.presence.camera;
|
|
415
392
|
if (!camera) return;
|