@volter/blender-engine 0.1.66 → 0.1.67

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.
@@ -148,6 +148,30 @@ export function graphUvChannels(material: THREE.MeshPhysicalMaterial): number[]
148
148
  return Object.values(bindings.get(material)?.channels ?? {});
149
149
  }
150
150
 
151
+ /** Attributes a physical graph draw can read, including its pending program.
152
+ * Resident geometry retains many more authored layers than a shader consumes;
153
+ * only shader inputs count toward WebGL's vertex attribute limit. */
154
+ export function graphDrawAttributes(material: THREE.Material, geometry: THREE.BufferGeometry): Set<string> {
155
+ const names = new Set(['position', 'normal', 'uv']);
156
+ if (geometry.hasAttribute('tangent')) names.add('tangent');
157
+ if (material.vertexColors) names.add('color');
158
+ if (!(material instanceof THREE.MeshPhysicalMaterial)) return names;
159
+ const channels = geometry.userData['blenderUvChannels'] as Record<string, number> | undefined;
160
+ const uv = (name: string) => {
161
+ const channel = name === '' ? 0 : (channels?.[name] ?? 9);
162
+ names.add(channel === 0 ? 'uv' : `uv${channel}`);
163
+ };
164
+ for (const texture of [material.map, material.roughnessMap, material.normalMap])
165
+ if (texture) uv((texture.userData['blenderUvName'] as string | undefined) ?? '');
166
+ for (const binding of [bindings.get(material), pendings.get(material)?.binding]) {
167
+ if (!binding) continue;
168
+ names.add('blenderOrco');
169
+ for (const name of binding.compiled.uvs) uv(name);
170
+ for (const name of binding.compiled.attributes) names.add(graphAttributeName(name));
171
+ }
172
+ return names;
173
+ }
174
+
151
175
  /** The channel each named UV layer is in on `geometry` (a layer the mesh
152
176
  * lacks reads zeros from channel 9, Blender's missing attribute). */
153
177
  function channelsFor(compiled: CompiledGraph, geometry: THREE.BufferGeometry): Record<string, number> {
@@ -1,4 +1,5 @@
1
1
  import * as THREE from 'three';
2
+ import { graphDrawAttributes } from './blender-graph-material';
2
3
 
3
4
  // Small spatial batches retain frustum rejection without duplicating geometry.
4
5
  const BATCH_SIZE = 64;
@@ -42,6 +43,8 @@ export class BlenderRuntimeInstances {
42
43
  private readonly inverse = new THREE.Matrix4();
43
44
  private readonly matrix = new THREE.Matrix4();
44
45
  private readonly box = new THREE.Box3();
46
+ private eligible = 0;
47
+ private excluded: Record<string, number> = {};
45
48
 
46
49
  constructor(private readonly root: THREE.Group) {}
47
50
 
@@ -49,21 +52,24 @@ export class BlenderRuntimeInstances {
49
52
  this.clear();
50
53
  if (!enabled) return;
51
54
  const groups = new Map<string, THREE.Mesh[]>();
55
+ const exclude = (reason: string) => { this.excluded[reason] = (this.excluded[reason] ?? 0) + 1; };
52
56
  this.inverse.copy(this.root.matrixWorld).invert();
53
57
  for (const object of objects) {
54
58
  const mesh = object as THREE.Mesh;
55
- if (!mesh.isMesh || (mesh as THREE.SkinnedMesh).isSkinnedMesh ||
59
+ if (!mesh.isMesh) continue;
60
+ if ((mesh as THREE.SkinnedMesh).isSkinnedMesh ||
56
61
  (mesh as THREE.InstancedMesh).isInstancedMesh || mesh.morphTargetInfluences ||
57
- mesh.layers.mask !== 1) continue;
58
- // WebGL2 guarantees sixteen vertex attributes; the instance matrix uses
59
- // four. Keep richly attributed meshes on the ordinary path rather than
60
- // dropping UV/colour channels or risking a refused shader.
61
- if (Object.values(mesh.geometry.attributes).reduce((count, attribute) => count + Math.ceil(attribute.itemSize / 4), 0) > 12) continue;
62
+ mesh.layers.mask !== 1) { exclude('deformationOrLayers'); continue; }
62
63
  const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
63
64
  if (materials.some(m => m.transparent || (m as THREE.MeshPhysicalMaterial).transmission > 0 ||
64
- (m as THREE.ShaderMaterial).isShaderMaterial)) continue;
65
+ (m as THREE.ShaderMaterial).isShaderMaterial)) { exclude('material'); continue; }
66
+ // Four slots for instanceMatrix, at most twelve for each actual program.
67
+ // Count active inputs, NOT every stored authored layer: retaining unused
68
+ // UVs/colours must not disable batching. No channel is deleted to fit.
69
+ if (materials.some(m => graphDrawAttributes(m, mesh.geometry).size > 12)) { exclude('attributeBudget'); continue; }
65
70
  this.matrix.multiplyMatrices(this.inverse, mesh.matrixWorld);
66
- if (!supported(this.matrix)) continue;
71
+ if (!supported(this.matrix)) { exclude('transform'); continue; }
72
+ this.eligible++;
67
73
  const key = `${mesh.geometry.id}:${Array.isArray(mesh.material) ? 'slots' : 'single'}:` +
68
74
  materials.map(m => m.uuid).join(',') + `:${mesh.renderOrder}`;
69
75
  const group = groups.get(key);
@@ -155,9 +161,12 @@ export class BlenderRuntimeInstances {
155
161
  draw.dispose(); // releases instance attributes, never shared geometry/material
156
162
  }
157
163
  this.batches.length = 0;
164
+ this.eligible = 0;
165
+ this.excluded = {};
158
166
  }
159
167
 
160
168
  inspect() {
161
- return {batches: this.batches.length, instances: this.batches.reduce((count, {draw}) => count + draw.count, 0)};
169
+ return {batches: this.batches.length, instances: this.batches.reduce((count, {draw}) => count + draw.count, 0),
170
+ eligible: this.eligible, excluded: {...this.excluded}};
162
171
  }
163
172
  }
@@ -2027,6 +2027,10 @@ export class BlenderRuntimeView {
2027
2027
  };
2028
2028
  }
2029
2029
 
2030
+ drawStatistics() {
2031
+ return this.instances.inspect();
2032
+ }
2033
+
2030
2034
  snapshot() {
2031
2035
  return this.frame;
2032
2036
  }
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.66",
5
+ "version": "0.1.67",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },