@volter/blender-engine 0.1.73 → 0.1.74
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.
|
@@ -33,8 +33,9 @@ export class BlenderMotionGeometry {
|
|
|
33
33
|
private drawnMeshes = 0;
|
|
34
34
|
private fullTriangles = 0;
|
|
35
35
|
private motionTriangles = 0;
|
|
36
|
+
private lastNavigation = {meshes: 0, fullTriangles: 0, motionTriangles: 0, tooClose: 0};
|
|
36
37
|
|
|
37
|
-
constructor(private readonly
|
|
38
|
+
constructor(private readonly invalidate: () => void) {}
|
|
38
39
|
|
|
39
40
|
setObjects(objects: Iterable<THREE.Object3D>): void {
|
|
40
41
|
const groups = new Map<THREE.BufferGeometry, THREE.Mesh[]>();
|
|
@@ -45,7 +46,9 @@ export class BlenderMotionGeometry {
|
|
|
45
46
|
const g = mesh.geometry;
|
|
46
47
|
const position = g.getAttribute('position');
|
|
47
48
|
if (!g.index || g.index.count < 6144 || !position || position.count > 200_000 ||
|
|
48
|
-
Object.values(g.attributes).some(a => !(a instanceof THREE.BufferAttribute)
|
|
49
|
+
Object.values(g.attributes).some(a => !(a instanceof THREE.BufferAttribute) ||
|
|
50
|
+
(a as THREE.BufferAttribute & {isFloat16BufferAttribute?: boolean}).isFloat16BufferAttribute ||
|
|
51
|
+
(a as THREE.InstancedBufferAttribute).isInstancedBufferAttribute) ||
|
|
49
52
|
Object.keys(g.morphAttributes).length || g.drawRange.start !== 0 || g.drawRange.count < g.index.count) continue;
|
|
50
53
|
// Only a whole, single material surface. Material boundaries are never
|
|
51
54
|
// crossed by the simplifier, nor are authored group ranges shortened.
|
|
@@ -166,10 +169,10 @@ export class BlenderMotionGeometry {
|
|
|
166
169
|
this.timer = setTimeout(() => { this.timer = null; this.next(); }, 0);
|
|
167
170
|
}
|
|
168
171
|
|
|
169
|
-
prepare(camera: THREE.Camera, interactive: boolean, height: number):
|
|
172
|
+
prepare(camera: THREE.Camera, interactive: boolean, height: number): boolean {
|
|
170
173
|
this.finish();
|
|
171
174
|
this.drawnMeshes = this.fullTriangles = this.motionTriangles = 0;
|
|
172
|
-
if (!interactive || !(height > 0)) return;
|
|
175
|
+
if (!interactive || !(height > 0)) return false;
|
|
173
176
|
const now = performance.now();
|
|
174
177
|
const moved = this.cameraKnown && (!camera.matrixWorld.equals(this.cameraWorld) ||
|
|
175
178
|
!camera.projectionMatrix.equals(this.cameraProjection));
|
|
@@ -182,44 +185,37 @@ export class BlenderMotionGeometry {
|
|
|
182
185
|
if (this.quietTimer) clearTimeout(this.quietTimer);
|
|
183
186
|
this.quietTimer = setTimeout(() => { this.quietTimer = null; this.invalidate(); }, QUIET_MS + 1);
|
|
184
187
|
}
|
|
185
|
-
if (now - this.movedAt > QUIET_MS) return;
|
|
188
|
+
if (now - this.movedAt > QUIET_MS) return false;
|
|
186
189
|
this.projection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
187
190
|
this.frustum.setFromProjectionMatrix(this.projection);
|
|
188
|
-
|
|
191
|
+
let tooClose = 0;
|
|
189
192
|
for (const [source, entry] of this.cache) {
|
|
190
|
-
let fits = true;
|
|
191
193
|
for (const mesh of this.groups.get(source) ?? []) {
|
|
192
194
|
if (!instanceObjectShown(mesh) || (mesh.frustumCulled && !this.frustum.intersectsObject(mesh))) continue;
|
|
193
|
-
|
|
195
|
+
// Frobenius norm bounds the linear transform even under shear; the
|
|
196
|
+
// longest axis alone can understate the projected simplifier error.
|
|
197
|
+
const e = mesh.matrixWorld.elements;
|
|
198
|
+
const scale = Math.hypot(e[0]!, e[1]!, e[2]!, e[4]!, e[5]!, e[6]!, e[8]!, e[9]!, e[10]!);
|
|
194
199
|
const sphere = source.boundingSphere!;
|
|
195
200
|
this.centre.copy(sphere.center).applyMatrix4(mesh.matrixWorld).applyMatrix4(camera.matrixWorldInverse);
|
|
196
201
|
const depth = (camera as THREE.PerspectiveCamera).isPerspectiveCamera
|
|
197
202
|
? -this.centre.z - sphere.radius * scale : 1;
|
|
198
203
|
const pixels = entry.error * scale * Math.abs(camera.projectionMatrix.elements[5]!) * height / (2 * depth);
|
|
199
|
-
if (!(depth > 0) || pixels > 1) {
|
|
204
|
+
if (!(depth > 0) || pixels > 1) { tooClose++; continue; }
|
|
205
|
+
// Decide per placement: one near-camera blade must not veto thousands
|
|
206
|
+
// of distant placements of its shared geometry. The following draw
|
|
207
|
+
// planner sees these geometry identities, so transparency still batches
|
|
208
|
+
// only consecutive compatible surfaces in the original camera order.
|
|
209
|
+
this.swapped.push({mesh, geometry: source});
|
|
210
|
+
mesh.geometry = entry.geometry;
|
|
211
|
+
this.drawnMeshes++;
|
|
212
|
+
this.fullTriangles += source.index!.count / 3;
|
|
213
|
+
this.motionTriangles += entry.geometry.index!.count / 3;
|
|
200
214
|
}
|
|
201
|
-
if (fits) allowed.set(source, entry.geometry);
|
|
202
215
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
this.
|
|
206
|
-
const mesh = object as THREE.Mesh;
|
|
207
|
-
if (!mesh.isMesh || (mesh as THREE.SkinnedMesh).isSkinnedMesh || mesh.morphTargetInfluences ||
|
|
208
|
-
((mesh as THREE.InstancedMesh).isInstancedMesh && mesh.userData['engineInternal'] !== true)) return;
|
|
209
|
-
if (Array.isArray(mesh.material) && (mesh.material.length !== 1 || mesh.geometry.groups.length !== 1 ||
|
|
210
|
-
mesh.geometry.groups[0]!.start !== 0 || mesh.geometry.groups[0]!.count !== mesh.geometry.index?.count ||
|
|
211
|
-
mesh.geometry.groups[0]!.materialIndex !== 0)) return;
|
|
212
|
-
const copy = allowed.get(mesh.geometry);
|
|
213
|
-
if (!copy) return;
|
|
214
|
-
this.swapped.push({mesh, geometry: mesh.geometry});
|
|
215
|
-
if (mesh.visible && mesh.layers.isEnabled(0)) {
|
|
216
|
-
const count = (mesh as THREE.InstancedMesh).isInstancedMesh ? (mesh as THREE.InstancedMesh).count : 1;
|
|
217
|
-
this.drawnMeshes += count;
|
|
218
|
-
this.fullTriangles += mesh.geometry.index!.count / 3 * count;
|
|
219
|
-
this.motionTriangles += copy.index!.count / 3 * count;
|
|
220
|
-
}
|
|
221
|
-
mesh.geometry = copy;
|
|
222
|
-
});
|
|
216
|
+
this.lastNavigation = {meshes: this.drawnMeshes, fullTriangles: this.fullTriangles,
|
|
217
|
+
motionTriangles: this.motionTriangles, tooClose};
|
|
218
|
+
return this.swapped.length > 0;
|
|
223
219
|
}
|
|
224
220
|
|
|
225
221
|
private quietTimer: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -229,7 +225,7 @@ export class BlenderMotionGeometry {
|
|
|
229
225
|
}
|
|
230
226
|
inspect() {
|
|
231
227
|
return {copies: this.cache.size, bytes: this.bytes, queued: this.queue.length, pending: this.pending !== null,
|
|
232
|
-
unavailable: this.unavailable,
|
|
228
|
+
unavailable: this.unavailable, lastNavigation: {...this.lastNavigation}};
|
|
233
229
|
}
|
|
234
230
|
private releaseCopies(): void {
|
|
235
231
|
this.finish();
|
|
@@ -645,7 +645,7 @@ export class BlenderRuntimeView {
|
|
|
645
645
|
readonly root = new THREE.Group();
|
|
646
646
|
private readonly instances = new BlenderRuntimeInstances(this.root);
|
|
647
647
|
private readonly transparentInstances = new BlenderTransparentInstances(this.root);
|
|
648
|
-
private readonly motionGeometry = new BlenderMotionGeometry(
|
|
648
|
+
private readonly motionGeometry = new BlenderMotionGeometry(presenterChanged);
|
|
649
649
|
private drawBatching = true;
|
|
650
650
|
private readonly objects = new Map<string, THREE.Object3D>();
|
|
651
651
|
private readonly meshes = new Map<
|
|
@@ -1249,8 +1249,14 @@ export class BlenderRuntimeView {
|
|
|
1249
1249
|
try {
|
|
1250
1250
|
this.instances.prepareDraw(camera);
|
|
1251
1251
|
this.root.updateMatrixWorld(true);
|
|
1252
|
+
const motion = this.motionGeometry.prepare(camera, !this.rendered && (this.mode ?? this.frame?.mode) === 'OBJECT' &&
|
|
1253
|
+
options?.interactive === true, options?.height ?? 0);
|
|
1254
|
+
// A changed navigation geometry is an ordinary-draw fallback for an
|
|
1255
|
+
// opaque batch; its full-detail members remain instanced. Transparent
|
|
1256
|
+
// runs are planned AFTER per-placement geometry selection, preserving
|
|
1257
|
+
// their sorting barriers and compatible geometry grouping.
|
|
1258
|
+
if (motion) this.instances.sync();
|
|
1252
1259
|
this.transparentInstances.prepare(camera);
|
|
1253
|
-
this.motionGeometry.prepare(camera, !this.rendered && options?.interactive === true, options?.height ?? 0);
|
|
1254
1260
|
} catch (error) {
|
|
1255
1261
|
this.motionGeometry.finish();
|
|
1256
1262
|
this.instances.finishDraw();
|