@volter/blender-engine 0.1.76 → 0.1.77

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.
@@ -306,10 +306,18 @@ export class BlenderMotionGeometry {
306
306
  for (const [source, entry] of this.cache) {
307
307
  for (const mesh of this.groups.get(source) ?? []) {
308
308
  if (!instanceObjectShown(mesh) || (mesh.frustumCulled && !this.frustum.intersectsObject(mesh))) continue;
309
- // Frobenius norm bounds the linear transform even under shear; the
310
- // longest axis alone can understate the projected simplifier error.
309
+ // Bound the largest singular value by the maximum absolute row sum
310
+ // of AᵀA. Unlike Frobenius this is tight for orthogonal TRS columns,
311
+ // while still bounding shear. A uniform rotation must not inflate
312
+ // both error and radius by sqrt(3) and veto nearby placements.
311
313
  const e = mesh.matrixWorld.elements;
312
- const scale = Math.hypot(e[0]!, e[1]!, e[2]!, e[4]!, e[5]!, e[6]!, e[8]!, e[9]!, e[10]!);
314
+ const x2 = e[0]! ** 2 + e[1]! ** 2 + e[2]! ** 2;
315
+ const y2 = e[4]! ** 2 + e[5]! ** 2 + e[6]! ** 2;
316
+ const z2 = e[8]! ** 2 + e[9]! ** 2 + e[10]! ** 2;
317
+ const xy = Math.abs(e[0]! * e[4]! + e[1]! * e[5]! + e[2]! * e[6]!);
318
+ const xz = Math.abs(e[0]! * e[8]! + e[1]! * e[9]! + e[2]! * e[10]!);
319
+ const yz = Math.abs(e[4]! * e[8]! + e[5]! * e[9]! + e[6]! * e[10]!);
320
+ const scale = Math.sqrt(Math.max(x2 + xy + xz, y2 + xy + yz, z2 + xz + yz));
313
321
  const sphere = source.boundingSphere!;
314
322
  this.centre.copy(sphere.center).applyMatrix4(mesh.matrixWorld).applyMatrix4(camera.matrixWorldInverse);
315
323
  const depth = (camera as THREE.PerspectiveCamera).isPerspectiveCamera
@@ -1243,12 +1243,19 @@ export class BlenderRuntimeView {
1243
1243
  presenterChanged();
1244
1244
  }
1245
1245
 
1246
+ private drawPreparation: {
1247
+ last: {opaqueMs: number; motionMs: number; transparentMs: number} | null;
1248
+ navigation: {opaqueMs: number; motionMs: number; transparentMs: number} | null;
1249
+ } = {last: null, navigation: null};
1250
+
1246
1251
  /** Before the renderer uploads attributes/builds its queues, for this area's
1247
1252
  * actual camera. Transparent instance runs are camera-order dependent. */
1248
1253
  prepareDraw(camera: THREE.Camera, options?: {interactive: boolean; height: number}): () => void {
1249
1254
  try {
1255
+ const start = performance.now();
1250
1256
  this.instances.prepareDraw(camera);
1251
1257
  this.root.updateMatrixWorld(true);
1258
+ const opaqueDone = performance.now();
1252
1259
  const motion = this.motionGeometry.prepare(camera, !this.rendered && (this.mode ?? this.frame?.mode) === 'OBJECT' &&
1253
1260
  options?.interactive === true, options?.height ?? 0);
1254
1261
  // A changed navigation geometry is an ordinary-draw fallback for an
@@ -1256,7 +1263,12 @@ export class BlenderRuntimeView {
1256
1263
  // runs are planned AFTER per-placement geometry selection, preserving
1257
1264
  // their sorting barriers and compatible geometry grouping.
1258
1265
  if (motion) this.instances.sync();
1266
+ const motionDone = performance.now();
1259
1267
  this.transparentInstances.prepare(camera);
1268
+ const timing = {opaqueMs: opaqueDone - start, motionMs: motionDone - opaqueDone,
1269
+ transparentMs: performance.now() - motionDone};
1270
+ this.drawPreparation.last = timing;
1271
+ if (motion) this.drawPreparation.navigation = timing;
1260
1272
  } catch (error) {
1261
1273
  this.motionGeometry.finish();
1262
1274
  this.instances.finishDraw();
@@ -2075,7 +2087,7 @@ export class BlenderRuntimeView {
2075
2087
  }
2076
2088
 
2077
2089
  drawStatistics() {
2078
- return {enabled: this.drawBatching, opaque: this.instances.inspect(), transparent: this.transparentInstances.inspect(), motion: this.motionGeometry.inspect()};
2090
+ return {enabled: this.drawBatching, preparation: {...this.drawPreparation}, opaque: this.instances.inspect(), transparent: this.transparentInstances.inspect(), motion: this.motionGeometry.inspect()};
2079
2091
  }
2080
2092
 
2081
2093
  snapshot() {
@@ -29,6 +29,7 @@ export class BlenderTransparentInstances {
29
29
  private readonly depth = new THREE.Vector4();
30
30
  private batches = 0;
31
31
  private instances = 0;
32
+ private excluded: Record<string, number> = {};
32
33
  private readonly planState: (number | string)[] = [];
33
34
  private readonly planned: Draw[] = [];
34
35
  private stateIndex = 0;
@@ -65,6 +66,7 @@ export class BlenderTransparentInstances {
65
66
  draw.members.length = 0;
66
67
  }
67
68
  this.batches = this.instances = 0;
69
+ this.excluded = {};
68
70
  this.frustum.setFromProjectionMatrix(this.projection);
69
71
  this.inverse.copy(this.root.matrixWorld).invert();
70
72
  const entries: Entry[] = [];
@@ -118,6 +120,14 @@ export class BlenderTransparentInstances {
118
120
  key = groupKey;
119
121
  }
120
122
  }
123
+ if (key === null) {
124
+ const reason = !candidate ? 'multipleMaterials'
125
+ : (candidate as THREE.ShaderMaterial).isShaderMaterial ? 'customShader'
126
+ : (candidate as THREE.MeshPhysicalMaterial).transmission > 0 ? 'transmission'
127
+ : candidate.side === THREE.DoubleSide && !candidate.forceSinglePass ? 'twoPass'
128
+ : 'geometryLayersOrTransform';
129
+ this.excluded[reason] = (this.excluded[reason] ?? 0) + 1;
130
+ }
121
131
  entries.push({mesh, material, key, groupOrder: order, z, centre});
122
132
  }
123
133
  entries.sort((a, b) => a.groupOrder - b.groupOrder || a.mesh.renderOrder - b.mesh.renderOrder || b.z - a.z || a.mesh.id - b.mesh.id);
@@ -189,7 +199,7 @@ export class BlenderTransparentInstances {
189
199
  }
190
200
  }
191
201
 
192
- inspect() { return {batches: this.batches, instances: this.instances}; }
202
+ inspect() { return {batches: this.batches, instances: this.instances, excluded: {...this.excluded}}; }
193
203
 
194
204
  private observe(value: number | string): void {
195
205
  if (this.planState[this.stateIndex] !== value) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@volter/blender-engine",
3
3
  "author": "Volter AI, Inc.",
4
4
  "license": "GPL-3.0-or-later",
5
- "version": "0.1.76",
5
+ "version": "0.1.77",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },