@volter/blender-engine 0.1.79 → 0.1.80

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.
@@ -38,6 +38,9 @@ export class BlenderMotionGeometry {
38
38
  private readonly projection = new THREE.Matrix4();
39
39
  private readonly frustum = new THREE.Frustum();
40
40
  private readonly centre = new THREE.Vector3();
41
+ private readonly boxCentre = new THREE.Vector3();
42
+ private readonly halfExtent = new THREE.Vector3();
43
+ private readonly viewWorld = new THREE.Matrix4();
41
44
  private drawnMeshes = 0;
42
45
  private fullTriangles = 0;
43
46
  private motionTriangles = 0;
@@ -304,13 +307,19 @@ export class BlenderMotionGeometry {
304
307
  this.frustum.setFromProjectionMatrix(this.projection);
305
308
  let tooClose = 0;
306
309
  for (const [source, entry] of this.cache) {
310
+ // The copy carries the ORIGINAL bounds derived during its yielding
311
+ // build. Do not scan vertices or use the smaller simplified bounds.
312
+ const bounds = entry.geometry.boundingBox!;
313
+ bounds.getSize(this.halfExtent).multiplyScalar(0.5);
307
314
  for (const mesh of this.groups.get(source) ?? []) {
308
315
  if (!instanceObjectShown(mesh) || (mesh.frustumCulled && !this.frustum.intersectsObject(mesh))) continue;
309
316
  // Bound the largest singular value by the maximum absolute row sum
310
317
  // of AᵀA. Unlike Frobenius this is tight for orthogonal TRS columns,
311
318
  // while still bounding shear. A uniform rotation must not inflate
312
319
  // both error and radius by sqrt(3) and veto nearby placements.
313
- const e = mesh.matrixWorld.elements;
320
+ // Bound in view space, also accounting for an explicitly scaled camera.
321
+ this.viewWorld.multiplyMatrices(camera.matrixWorldInverse, mesh.matrixWorld);
322
+ const e = this.viewWorld.elements;
314
323
  const x2 = e[0]! ** 2 + e[1]! ** 2 + e[2]! ** 2;
315
324
  const y2 = e[4]! ** 2 + e[5]! ** 2 + e[6]! ** 2;
316
325
  const z2 = e[8]! ** 2 + e[9]! ** 2 + e[10]! ** 2;
@@ -319,9 +328,20 @@ export class BlenderMotionGeometry {
319
328
  const yz = Math.abs(e[4]! * e[8]! + e[5]! * e[9]! + e[6]! * e[10]!);
320
329
  const scale = Math.sqrt(Math.max(x2 + xy + xz, y2 + xy + yz, z2 + xz + yz));
321
330
  const sphere = source.boundingSphere!;
322
- this.centre.copy(sphere.center).applyMatrix4(mesh.matrixWorld).applyMatrix4(camera.matrixWorldInverse);
323
- const depth = (camera as THREE.PerspectiveCamera).isPerspectiveCamera
324
- ? -this.centre.z - sphere.radius * scale : 1;
331
+ this.centre.copy(sphere.center).applyMatrix4(this.viewWorld);
332
+ let depth = 1;
333
+ if ((camera as THREE.PerspectiveCamera).isPerspectiveCamera) {
334
+ const sphereDepth = -this.centre.z - sphere.radius * scale;
335
+ bounds.getCenter(this.boxCentre).applyMatrix4(this.viewWorld);
336
+ const v = this.viewWorld.elements;
337
+ const radiusZ = Math.abs(v[2]!) * this.halfExtent.x +
338
+ Math.abs(v[6]!) * this.halfExtent.y + Math.abs(v[10]!) * this.halfExtent.z;
339
+ // Both enclosures contain every original vertex. Their nearest
340
+ // depths are lower bounds, so the larger remains a lower bound.
341
+ // A tall shape's height need not inflate its radius toward a level
342
+ // camera. This tightens the bound without relaxing the pixel gate.
343
+ depth = Math.max(sphereDepth, -this.boxCentre.z - radiusZ);
344
+ }
325
345
  const pixels = entry.error * scale * Math.abs(camera.projectionMatrix.elements[5]!) * height / (2 * depth);
326
346
  if (!(depth > 0) || pixels > 1) { tooClose++; continue; }
327
347
  // Decide per placement: one near-camera blade must not veto thousands
@@ -263,6 +263,11 @@ export class BlenderTransparentInstances {
263
263
  for (const n of this.root.matrixWorld.elements) this.observe(n);
264
264
  const observedGeometry = new Set<THREE.BufferGeometry>();
265
265
  for (const mesh of this.objects) {
266
+ // Check eligibility every frame, including opaque -> transparent
267
+ // transitions, but opaque geometry/attribute versions cannot affect a
268
+ // transparent draw plan. Large solid-only scenes need no such census.
269
+ const currentMaterials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
270
+ if (!currentMaterials.some(material => material.visible && material.transparent)) continue;
266
271
  this.observe(mesh.parent?.id ?? -1);
267
272
  this.observe(instanceObjectShown(mesh) ? 1 : 0);
268
273
  this.observe(mesh.layers.mask);
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.79",
5
+ "version": "0.1.80",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },