@volter/editor-threejs 0.5.134 → 0.5.136
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/editor-threejs",
|
|
3
3
|
"author": "Volter AI, Inc.",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.136",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -135,12 +135,12 @@
|
|
|
135
135
|
"serving": "./dist-node/serving.mjs"
|
|
136
136
|
},
|
|
137
137
|
"dependencies": {
|
|
138
|
-
"@volter/editor-project": "0.5.
|
|
138
|
+
"@volter/editor-project": "0.5.136",
|
|
139
139
|
"@sparkjsdev/spark": "^2.1.0",
|
|
140
140
|
"three.quarks": "^0.16.0",
|
|
141
141
|
"zod": "^4.3.6",
|
|
142
142
|
"typescript": "5.9.3",
|
|
143
|
-
"@volter/editor-sdk": "0.5.
|
|
143
|
+
"@volter/editor-sdk": "0.5.136",
|
|
144
144
|
"postprocessing": "6.38.3",
|
|
145
145
|
"quarks.core": "0.16.0",
|
|
146
146
|
"@fortawesome/free-solid-svg-icons": "^7.2.0"
|
|
@@ -174,7 +174,7 @@ export class Object3DDocumentSession {
|
|
|
174
174
|
private readonly throughOrthographic = new THREE.OrthographicCamera();
|
|
175
175
|
/** The host's mirror of the document scene onto the rendered scene — see
|
|
176
176
|
* {@link Object3DDocumentSession.setBeforeRender}. */
|
|
177
|
-
private beforeRender: (() => void | (() => void)) | null = null;
|
|
177
|
+
private beforeRender: ((interactive: boolean) => void | (() => void)) | null = null;
|
|
178
178
|
private state = INITIAL_PRESENTATION;
|
|
179
179
|
private version = 0;
|
|
180
180
|
private readonly listeners = new Set<() => void>();
|
|
@@ -1253,7 +1253,7 @@ export class Object3DDocumentSession {
|
|
|
1253
1253
|
* The host owns the step and clears it in its own teardown; the session only
|
|
1254
1254
|
* holds the reference.
|
|
1255
1255
|
*/
|
|
1256
|
-
setBeforeRender(step: (() => void | (() => void)) | null): void {
|
|
1256
|
+
setBeforeRender(step: ((interactive: boolean) => void | (() => void)) | null): void {
|
|
1257
1257
|
this.beforeRender = step;
|
|
1258
1258
|
}
|
|
1259
1259
|
|
|
@@ -1338,8 +1338,8 @@ export class Object3DDocumentSession {
|
|
|
1338
1338
|
for (const resolve of waiters) resolve(frame);
|
|
1339
1339
|
}
|
|
1340
1340
|
|
|
1341
|
-
render(renderSolid: (camera: THREE.Camera) => void): void {
|
|
1342
|
-
const finishDraw = this.beforeRender?.();
|
|
1341
|
+
render(renderSolid: (camera: THREE.Camera) => void, interactive = false): void {
|
|
1342
|
+
const finishDraw = this.beforeRender?.(interactive);
|
|
1343
1343
|
try {
|
|
1344
1344
|
this.boneSelectionHighlight?.update();
|
|
1345
1345
|
const camera = this.camera();
|
|
@@ -1391,7 +1391,7 @@ export class Object3DDocumentSession {
|
|
|
1391
1391
|
}
|
|
1392
1392
|
|
|
1393
1393
|
/** Visible authoring draw with the editor-only native selection silhouette. */
|
|
1394
|
-
renderViewport(deltaSeconds = 0): void {
|
|
1394
|
+
renderViewport(deltaSeconds = 0, interactive = false): void {
|
|
1395
1395
|
this.advanceLook(deltaSeconds);
|
|
1396
1396
|
this.ensureComposer();
|
|
1397
1397
|
this.render((camera) => {
|
|
@@ -1407,7 +1407,7 @@ export class Object3DDocumentSession {
|
|
|
1407
1407
|
composer.setMainScene(this.scene);
|
|
1408
1408
|
composer.setMainCamera(camera);
|
|
1409
1409
|
composer.render(deltaSeconds);
|
|
1410
|
-
});
|
|
1410
|
+
}, interactive);
|
|
1411
1411
|
}
|
|
1412
1412
|
|
|
1413
1413
|
resize(width: number, height: number): void {
|
|
@@ -1904,9 +1904,11 @@ export function Object3DDocumentViewport({
|
|
|
1904
1904
|
}
|
|
1905
1905
|
};
|
|
1906
1906
|
host.syncHostScene = syncHostScene;
|
|
1907
|
-
documentSession.setBeforeRender(() => {
|
|
1907
|
+
documentSession.setBeforeRender((interactive) => {
|
|
1908
1908
|
host.syncHostScene?.();
|
|
1909
|
-
|
|
1909
|
+
const mode = documentSession.presentation().mode;
|
|
1910
|
+
const navigation = interactive && ['solid', 'preview', 'clay', 'unlit', 'matcap'].includes(mode);
|
|
1911
|
+
return source.prepareDraw?.(documentSession.camera(), {interactive: navigation, height: renderer.domElement.height});
|
|
1910
1912
|
});
|
|
1911
1913
|
// This loop draws the compass and every other overlay pass over the
|
|
1912
1914
|
// document's own render, so it — and only it — can serve the chrome
|
|
@@ -2007,6 +2009,7 @@ export function Object3DDocumentViewport({
|
|
|
2007
2009
|
drawnToneMapping = renderer.toneMapping;
|
|
2008
2010
|
drawnExposure = renderer.toneMappingExposure;
|
|
2009
2011
|
};
|
|
2012
|
+
let navigationMeasurement: THREE.Quaternion | null = null;
|
|
2010
2013
|
const animate = (time: number, resumed: boolean) => {
|
|
2011
2014
|
if (disposed || !renderer || !viewport) return;
|
|
2012
2015
|
const activeRenderer = renderer;
|
|
@@ -2032,6 +2035,11 @@ export function Object3DDocumentViewport({
|
|
|
2032
2035
|
// scene panel uses, so anything riding the loop (a camera flight)
|
|
2033
2036
|
// has final say over the pose and orbit damping never fights it.
|
|
2034
2037
|
if (!chromeless) runViewportFrame(documentId, delta);
|
|
2038
|
+
if (navigationMeasurement) {
|
|
2039
|
+
const camera = documentSession.camera();
|
|
2040
|
+
camera.quaternion.copy(navigationMeasurement);
|
|
2041
|
+
camera.updateMatrixWorld(true);
|
|
2042
|
+
}
|
|
2035
2043
|
viewport.batchedRenderer.update(delta);
|
|
2036
2044
|
profiler?.endPhase('editor');
|
|
2037
2045
|
if (!resumed && !mustDraw(advanced)) {
|
|
@@ -2050,7 +2058,10 @@ export function Object3DDocumentViewport({
|
|
|
2050
2058
|
host.scene.overrideMaterial = scene.overrideMaterial;
|
|
2051
2059
|
try {
|
|
2052
2060
|
activeViewport.renderWithInfrastructure(() => {
|
|
2053
|
-
|
|
2061
|
+
// Photographs and the default frame-cost reading draw the
|
|
2062
|
+
// full resident scene. Only navigation may use source-owned
|
|
2063
|
+
// temporary geometry; the source restores it after the draw.
|
|
2064
|
+
documentSession.renderViewport(delta, navigationMeasurement !== null || (!resumed && !documentSession.needsFrame()));
|
|
2054
2065
|
activeViewport.renderViewCube(activeRenderer);
|
|
2055
2066
|
});
|
|
2056
2067
|
} finally {
|
|
@@ -2102,7 +2113,7 @@ export function Object3DDocumentViewport({
|
|
|
2102
2113
|
* resumed so it draws, run back to back and each waited out on the GPU with a one-pixel
|
|
2103
2114
|
* read, which a real frame never does; the first frame warms and is not counted.
|
|
2104
2115
|
*/
|
|
2105
|
-
const measureFrameCost = (frames: number): StageFrameCost => {
|
|
2116
|
+
const measureFrameCost = (frames: number, quality: 'full' | 'navigation' = 'full'): StageFrameCost => {
|
|
2106
2117
|
if (!renderer) throw new Error('This stage has no renderer yet.');
|
|
2107
2118
|
const activeRenderer = renderer;
|
|
2108
2119
|
const gl = activeRenderer.getContext();
|
|
@@ -2110,17 +2121,31 @@ export function Object3DDocumentViewport({
|
|
|
2110
2121
|
const times: number[] = [];
|
|
2111
2122
|
const submitTimes: number[] = [];
|
|
2112
2123
|
const completionTimes: number[] = [];
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2124
|
+
const camera = documentSession.camera();
|
|
2125
|
+
const originalTurn = camera.quaternion.clone();
|
|
2126
|
+
const axis = new THREE.Vector3(0, 1, 0);
|
|
2127
|
+
try {
|
|
2128
|
+
for (let index = 0; index <= frames; index++) {
|
|
2129
|
+
// A real changed camera exercises culling, sorting and the
|
|
2130
|
+
// source's navigation path. Never persist this diagnostic pose.
|
|
2131
|
+
if (quality === 'navigation') navigationMeasurement = originalTurn.clone().multiply(
|
|
2132
|
+
new THREE.Quaternion().setFromAxisAngle(axis, (index + 1) * 0.0005));
|
|
2133
|
+
const start = performance.now();
|
|
2134
|
+
animate(start, true);
|
|
2135
|
+
const submitted = performance.now();
|
|
2136
|
+
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
|
|
2137
|
+
const completed = performance.now();
|
|
2138
|
+
if (index > 0) {
|
|
2139
|
+
times.push(completed - start);
|
|
2140
|
+
submitTimes.push(submitted - start);
|
|
2141
|
+
completionTimes.push(completed - submitted);
|
|
2142
|
+
}
|
|
2123
2143
|
}
|
|
2144
|
+
} finally {
|
|
2145
|
+
navigationMeasurement = null;
|
|
2146
|
+
camera.quaternion.copy(originalTurn);
|
|
2147
|
+
camera.updateMatrixWorld(true);
|
|
2148
|
+
dirty = true;
|
|
2124
2149
|
}
|
|
2125
2150
|
const drawn = { calls: activeRenderer.info.render.calls, triangles: activeRenderer.info.render.triangles };
|
|
2126
2151
|
let meshes = 0;
|
|
@@ -2141,6 +2166,7 @@ export function Object3DDocumentViewport({
|
|
|
2141
2166
|
const median = (values: number[]) => round(values.sort((a, b) => a - b)[Math.floor(values.length / 2)] ?? 0);
|
|
2142
2167
|
return {
|
|
2143
2168
|
frames,
|
|
2169
|
+
quality,
|
|
2144
2170
|
medianMs: round(at(0.5)),
|
|
2145
2171
|
p95Ms: round(at(0.95)),
|
|
2146
2172
|
minMs: round(sorted[0] ?? 0),
|
|
@@ -2177,7 +2203,7 @@ export function Object3DDocumentViewport({
|
|
|
2177
2203
|
drawCamera: () => host.session?.camera() ?? viewport.camera,
|
|
2178
2204
|
orbit: viewport.orbitControls,
|
|
2179
2205
|
scene: host.scene,
|
|
2180
|
-
frameCost: (frames) => measureFrameCost(frames),
|
|
2206
|
+
frameCost: (frames, quality) => measureFrameCost(frames, quality),
|
|
2181
2207
|
},
|
|
2182
2208
|
() => null,
|
|
2183
2209
|
(kind, object) => viewport.setHelper(kind, object),
|
|
@@ -193,6 +193,9 @@ export const viewportCommands: CommandContribution['commands'] = {
|
|
|
193
193
|
const named = cmd['stage'];
|
|
194
194
|
if (named !== undefined && typeof named !== 'string')
|
|
195
195
|
return { ok: false, error: 'document-frame-cost: "stage" names a stage by its document id.' };
|
|
196
|
+
const quality = cmd['quality'] ?? 'full';
|
|
197
|
+
if (quality !== 'full' && quality !== 'navigation')
|
|
198
|
+
return {ok: false, error: 'document-frame-cost: "quality" must be "full" or "navigation".'};
|
|
196
199
|
const documentId = named ?? activeWorkspaceDocumentId();
|
|
197
200
|
const stages = viewportStages();
|
|
198
201
|
const stage = stages.find((one) => one.documentId === documentId);
|
|
@@ -206,7 +209,7 @@ export const viewportCommands: CommandContribution['commands'] = {
|
|
|
206
209
|
const measure = stage.rig().frameCost;
|
|
207
210
|
if (!measure)
|
|
208
211
|
return { ok: false, error: `document-frame-cost: the stage for ${JSON.stringify(stage.documentId)} has no frame of its own to measure.` };
|
|
209
|
-
return { ok: true, data: { stage: stage.documentId, ...measure(frames) } };
|
|
212
|
+
return { ok: true, data: { stage: stage.documentId, ...measure(frames, quality) } };
|
|
210
213
|
}, 120_000),
|
|
211
214
|
'capture-viewport': verb(async (store, cmd) => {
|
|
212
215
|
// Fresh, unthrottled on-demand capture (see EditorShellStore.captureViewportImage).
|
|
@@ -54,8 +54,10 @@ export interface ToolObject3DPreviewSource {
|
|
|
54
54
|
update?(deltaSeconds: number): void;
|
|
55
55
|
/** Prepare camera-dependent draw buffers immediately before rendering.
|
|
56
56
|
* Does not advance content time or mutate the authored document. An optional
|
|
57
|
-
* returned function restores temporary draw state, including after errors.
|
|
58
|
-
|
|
57
|
+
* returned function restores temporary draw state, including after errors.
|
|
58
|
+
* Interactive draws may use disposable navigation geometry; omitted/false
|
|
59
|
+
* always means full detail (captures and default frame-cost readings). */
|
|
60
|
+
prepareDraw?(camera: Camera, options?: {interactive: boolean; height: number}): void | (() => void);
|
|
59
61
|
/**
|
|
60
62
|
* EVERY CHANGE TO WHAT THE SOURCE DRAWS, announced: the host calls
|
|
61
63
|
* `listener` whenever the source's graph, materials, textures or poses may
|
package/src/viewport-api.ts
CHANGED
|
@@ -32,7 +32,7 @@ export interface ViewportRig {
|
|
|
32
32
|
* `requestAnimationFrame`, each waited out on the GPU, so the number is the frame's cost and
|
|
33
33
|
* not the display's refresh. Absent on a stage with no frame of its own.
|
|
34
34
|
*/
|
|
35
|
-
frameCost?(frames: number): StageFrameCost;
|
|
35
|
+
frameCost?(frames: number, quality?: 'full' | 'navigation'): StageFrameCost;
|
|
36
36
|
readonly orbit: { readonly target: THREE.Vector3; enabled: boolean; update(): void };
|
|
37
37
|
/** The editor's own scene (helpers live on its editor layer). */
|
|
38
38
|
readonly scene: THREE.Scene;
|
|
@@ -101,4 +101,3 @@ export interface ViewportDoor {
|
|
|
101
101
|
/** Fires whenever a stage mounts or unmounts; the return unsubscribes. */
|
|
102
102
|
onStages(fn: (stages: readonly ViewportStage[]) => void): () => void;
|
|
103
103
|
}
|
|
104
|
-
|