@volter/blender-engine 0.1.77 → 0.1.78
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.
|
@@ -347,8 +347,11 @@ ${compiled.uvs.map(n => `varying vec2 ${uvVarying(n)};`).join('\n')}`;
|
|
|
347
347
|
`vBlenderObjectPosition = transformed;
|
|
348
348
|
vBlenderObjectNormal = objectNormal;
|
|
349
349
|
mat4 blenderWorldMatrix = modelMatrix;
|
|
350
|
+
#ifdef USE_BATCHING
|
|
351
|
+
blenderWorldMatrix = blenderWorldMatrix * batchingMatrix;
|
|
352
|
+
#endif
|
|
350
353
|
#ifdef USE_INSTANCING
|
|
351
|
-
blenderWorldMatrix =
|
|
354
|
+
blenderWorldMatrix = blenderWorldMatrix * instanceMatrix;
|
|
352
355
|
#endif
|
|
353
356
|
vBlenderWorldPosition = (blenderWorldMatrix * vec4(transformed, 1.0)).xyz;
|
|
354
357
|
vBlenderWorldNormal = normalize(transpose(inverse(mat3(blenderWorldMatrix))) * objectNormal);
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
export const ORDERED_BATCH_CAPACITY = 64;
|
|
4
|
+
const FAMILY_BYTES = 4 * 1024 * 1024;
|
|
5
|
+
const CACHE_BYTES = 32 * 1024 * 1024;
|
|
6
|
+
const MAX_DRAWS = 1024;
|
|
7
|
+
type Shape = {geometry: THREE.BufferGeometry; stamp: string};
|
|
8
|
+
type Family = {material: THREE.Material; shapes: Map<number, Shape>; template: THREE.BatchedMesh | null;
|
|
9
|
+
ids: Map<number, number>; draws: THREE.BatchedMesh[]; dirty: boolean; bytes: number; refused: boolean};
|
|
10
|
+
|
|
11
|
+
/** Immutable packed attributes are shared between ordered draw runs. Only the
|
|
12
|
+
* matrix/indirection textures and command lists belong to an individual run.
|
|
13
|
+
* All interaction and authored geometry remain on the canonical meshes. */
|
|
14
|
+
export class BlenderOrderedBatches {
|
|
15
|
+
private readonly families = new Map<string, Family>();
|
|
16
|
+
private readonly registrations = new Map<string, string | null>();
|
|
17
|
+
private bytes = 0;
|
|
18
|
+
private drawCount = 0;
|
|
19
|
+
private created = 0;
|
|
20
|
+
pending = false;
|
|
21
|
+
|
|
22
|
+
begin(): void { this.created = 0; this.pending = false; this.registrations.clear(); }
|
|
23
|
+
|
|
24
|
+
register(geometry: THREE.BufferGeometry, material: THREE.Material, order: number): string | null {
|
|
25
|
+
const registration = `${geometry.id}:${material.uuid}:${order}`;
|
|
26
|
+
if (this.registrations.has(registration)) return this.registrations.get(registration)!;
|
|
27
|
+
this.registrations.set(registration, null);
|
|
28
|
+
const position = geometry.getAttribute('position');
|
|
29
|
+
const count = geometry.index?.count ?? position?.count ?? 0;
|
|
30
|
+
if (!position || geometry.drawRange.start !== 0 || geometry.drawRange.count < count ||
|
|
31
|
+
Object.keys(geometry.morphAttributes).length) return null;
|
|
32
|
+
const layout: unknown[] = [!!geometry.index, geometry.userData['blenderUvChannels'] ?? null];
|
|
33
|
+
const versions: unknown[] = [geometry.index?.id, geometry.index?.version];
|
|
34
|
+
let bytes = (geometry.index?.count ?? 0) * 4;
|
|
35
|
+
for (const name of Object.keys(geometry.attributes).sort()) {
|
|
36
|
+
const attribute = geometry.getAttribute(name);
|
|
37
|
+
if (!(attribute instanceof THREE.BufferAttribute) ||
|
|
38
|
+
(attribute as THREE.InstancedBufferAttribute).isInstancedBufferAttribute ||
|
|
39
|
+
attribute instanceof THREE.Float16BufferAttribute ||
|
|
40
|
+
attribute.count !== position.count) return null;
|
|
41
|
+
layout.push([name, attribute.array.constructor.name, attribute.itemSize, attribute.normalized, attribute.gpuType]);
|
|
42
|
+
versions.push(name, attribute.id, attribute.version);
|
|
43
|
+
bytes += attribute.array.byteLength;
|
|
44
|
+
}
|
|
45
|
+
if (bytes > FAMILY_BYTES) return null;
|
|
46
|
+
const key = `multi:${material.uuid}:${order}:${JSON.stringify(layout)}`;
|
|
47
|
+
let family = this.families.get(key);
|
|
48
|
+
if (!family) {
|
|
49
|
+
family = {material, shapes: new Map(), template: null, ids: new Map(), draws: [], dirty: true, bytes: 0, refused: false};
|
|
50
|
+
this.families.set(key, family);
|
|
51
|
+
}
|
|
52
|
+
const stamp = JSON.stringify(versions);
|
|
53
|
+
const held = family.shapes.get(geometry.id);
|
|
54
|
+
if (!held || held.stamp !== stamp) {
|
|
55
|
+
family.shapes.set(geometry.id, {geometry, stamp});
|
|
56
|
+
family.dirty = true;
|
|
57
|
+
}
|
|
58
|
+
this.registrations.set(registration, key);
|
|
59
|
+
return key;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
pack(): void {
|
|
63
|
+
for (const family of this.families.values()) {
|
|
64
|
+
if (!family.dirty) continue;
|
|
65
|
+
this.release(family);
|
|
66
|
+
family.dirty = false;
|
|
67
|
+
let vertices = 0, indices = 0, bytes = 0;
|
|
68
|
+
for (const {geometry} of family.shapes.values()) {
|
|
69
|
+
vertices += geometry.getAttribute('position').count;
|
|
70
|
+
indices += geometry.index?.count ?? 0;
|
|
71
|
+
for (const attribute of Object.values(geometry.attributes)) bytes += attribute.array.byteLength;
|
|
72
|
+
}
|
|
73
|
+
bytes += indices * (vertices > 65535 ? 4 : 2);
|
|
74
|
+
family.refused = bytes > FAMILY_BYTES || this.bytes + bytes > CACHE_BYTES;
|
|
75
|
+
if (family.refused) continue;
|
|
76
|
+
const template = new THREE.BatchedMesh(ORDERED_BATCH_CAPACITY, vertices, indices, family.material);
|
|
77
|
+
template.sortObjects = false;
|
|
78
|
+
template.perObjectFrustumCulled = false;
|
|
79
|
+
template.frustumCulled = false;
|
|
80
|
+
template.matrixAutoUpdate = false;
|
|
81
|
+
template.boundingSphere = new THREE.Sphere();
|
|
82
|
+
template.name = 'Blender ordered multi-draw';
|
|
83
|
+
template.userData['engineInternal'] = true;
|
|
84
|
+
for (const {geometry} of family.shapes.values()) family.ids.set(geometry.id, template.addGeometry(geometry));
|
|
85
|
+
// Named UV selection is a geometry-level material input. The family key
|
|
86
|
+
// requires identical maps; every per-vertex graph attribute is copied.
|
|
87
|
+
const first = family.shapes.values().next().value!.geometry;
|
|
88
|
+
template.geometry.userData['blenderUvChannels'] = first.userData['blenderUvChannels'];
|
|
89
|
+
for (const name of Object.keys(first.attributes))
|
|
90
|
+
(template.geometry.getAttribute(name) as THREE.BufferAttribute).gpuType = (first.getAttribute(name) as THREE.BufferAttribute).gpuType;
|
|
91
|
+
family.template = template;
|
|
92
|
+
family.bytes = bytes;
|
|
93
|
+
this.bytes += bytes;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
available(key: string): boolean { return !!this.families.get(key)?.template; }
|
|
98
|
+
|
|
99
|
+
get(key: string, index: number): THREE.BatchedMesh | null {
|
|
100
|
+
const family = this.families.get(key)!;
|
|
101
|
+
const template = family.template;
|
|
102
|
+
if (!template) return null;
|
|
103
|
+
let draw = family.draws[index];
|
|
104
|
+
if (!draw) {
|
|
105
|
+
if (this.drawCount >= MAX_DRAWS) return null;
|
|
106
|
+
// copy() uses public Three APIs and briefly copies the packed arrays.
|
|
107
|
+
// Bound that transient work per frame; ordinary meshes fill every gap.
|
|
108
|
+
if (this.created >= 4) { this.pending = true; return null; }
|
|
109
|
+
draw = new THREE.BatchedMesh(1, 0, 0, family.material);
|
|
110
|
+
draw.dispose();
|
|
111
|
+
draw.copy(template);
|
|
112
|
+
for (const name of Object.keys(template.geometry.attributes))
|
|
113
|
+
draw.geometry.setAttribute(name, template.geometry.getAttribute(name));
|
|
114
|
+
draw.geometry.setIndex(template.geometry.index);
|
|
115
|
+
const firstId = family.ids.values().next().value!;
|
|
116
|
+
for (let i = 0; i < ORDERED_BATCH_CAPACITY; i++) {
|
|
117
|
+
draw.addInstance(firstId);
|
|
118
|
+
draw.setVisibleAt(i, false);
|
|
119
|
+
}
|
|
120
|
+
family.draws.push(draw);
|
|
121
|
+
this.created++;
|
|
122
|
+
this.drawCount++;
|
|
123
|
+
}
|
|
124
|
+
return draw;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
geometryId(key: string, geometry: THREE.BufferGeometry): number { return this.families.get(key)!.ids.get(geometry.id)!; }
|
|
128
|
+
|
|
129
|
+
inspect() { return {bytes: this.bytes, draws: this.drawCount, pending: this.pending,
|
|
130
|
+
refusedFamilies: [...this.families.values()].filter(f => f.refused).length}; }
|
|
131
|
+
|
|
132
|
+
private release(family: Family): void {
|
|
133
|
+
for (const draw of family.draws) {
|
|
134
|
+
draw.removeFromParent();
|
|
135
|
+
// BatchedMesh.dispose owns its geometry. Detach shared buffer attributes
|
|
136
|
+
// first, so retiring one run cannot delete another run's GPU buffers.
|
|
137
|
+
for (const name of Object.keys(draw.geometry.attributes)) draw.geometry.deleteAttribute(name);
|
|
138
|
+
draw.geometry.setIndex(null);
|
|
139
|
+
draw.dispose();
|
|
140
|
+
this.drawCount--;
|
|
141
|
+
}
|
|
142
|
+
family.draws.length = 0;
|
|
143
|
+
family.template?.dispose();
|
|
144
|
+
family.template = null;
|
|
145
|
+
family.ids.clear();
|
|
146
|
+
this.bytes -= family.bytes;
|
|
147
|
+
family.bytes = 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
clear(): void {
|
|
151
|
+
for (const family of this.families.values()) this.release(family);
|
|
152
|
+
this.families.clear();
|
|
153
|
+
this.registrations.clear();
|
|
154
|
+
this.pending = false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -1250,7 +1250,7 @@ export class BlenderRuntimeView {
|
|
|
1250
1250
|
|
|
1251
1251
|
/** Before the renderer uploads attributes/builds its queues, for this area's
|
|
1252
1252
|
* actual camera. Transparent instance runs are camera-order dependent. */
|
|
1253
|
-
prepareDraw(camera: THREE.Camera, options?: {interactive: boolean; height: number}): () => void {
|
|
1253
|
+
prepareDraw(camera: THREE.Camera, options?: {interactive: boolean; height: number; multiDraw?: boolean}): () => void {
|
|
1254
1254
|
try {
|
|
1255
1255
|
const start = performance.now();
|
|
1256
1256
|
this.instances.prepareDraw(camera);
|
|
@@ -1264,7 +1264,7 @@ export class BlenderRuntimeView {
|
|
|
1264
1264
|
// their sorting barriers and compatible geometry grouping.
|
|
1265
1265
|
if (motion) this.instances.sync();
|
|
1266
1266
|
const motionDone = performance.now();
|
|
1267
|
-
this.transparentInstances.prepare(camera);
|
|
1267
|
+
this.transparentInstances.prepare(camera, options?.multiDraw === true);
|
|
1268
1268
|
const timing = {opaqueMs: opaqueDone - start, motionMs: motionDone - opaqueDone,
|
|
1269
1269
|
transparentMs: performance.now() - motionDone};
|
|
1270
1270
|
this.drawPreparation.last = timing;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
|
+
import {BlenderOrderedBatches, ORDERED_BATCH_CAPACITY} from './blender-ordered-batches';
|
|
3
|
+
import {presenterChanged} from './blender-presenter-change';
|
|
2
4
|
import {graphDrawAttributes} from './blender-graph-material';
|
|
3
5
|
import {instanceMatrixSupported, instanceObjectShown} from './blender-runtime-instances';
|
|
4
6
|
|
|
5
|
-
const CAPACITY =
|
|
6
|
-
type Entry = {mesh: THREE.Mesh; material: THREE.Material | null; key: string | null; groupOrder: number; z: number; centre: THREE.Vector3};
|
|
7
|
-
type Draw = {mesh: THREE.InstancedMesh; members: THREE.Mesh[]};
|
|
7
|
+
const CAPACITY = ORDERED_BATCH_CAPACITY;
|
|
8
|
+
type Entry = {mesh: THREE.Mesh; material: THREE.Material | null; key: string | null; batchKey: string | null; groupOrder: number; z: number; centre: THREE.Vector3};
|
|
9
|
+
type Draw = {mesh: THREE.InstancedMesh | THREE.BatchedMesh; members: THREE.Mesh[]};
|
|
8
10
|
|
|
9
11
|
function groupOrder(mesh: THREE.Object3D): number {
|
|
10
12
|
for (let parent = mesh.parent; parent; parent = parent.parent)
|
|
@@ -19,6 +21,9 @@ function groupOrder(mesh: THREE.Object3D): number {
|
|
|
19
21
|
* This does not turn blended foliage into cutouts or change its opacity. */
|
|
20
22
|
export class BlenderTransparentInstances {
|
|
21
23
|
private objects: THREE.Mesh[] = [];
|
|
24
|
+
private readonly ordered = new BlenderOrderedBatches();
|
|
25
|
+
private readonly mixedDraws = new Map<THREE.BatchedMesh, Draw>();
|
|
26
|
+
private multiDraw = false;
|
|
22
27
|
private readonly pools = new Map<string, Draw[]>();
|
|
23
28
|
private readonly hidden: THREE.Mesh[] = [];
|
|
24
29
|
private readonly inverse = new THREE.Matrix4();
|
|
@@ -42,12 +47,15 @@ export class BlenderTransparentInstances {
|
|
|
42
47
|
this.objects = [...objects].filter((object): object is THREE.Mesh => (object as THREE.Mesh).isMesh === true);
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
prepare(camera: THREE.Camera): void {
|
|
50
|
+
prepare(camera: THREE.Camera, multiDraw = false): void {
|
|
46
51
|
this.finishDraw();
|
|
47
52
|
if (!this.objects.length) return;
|
|
48
53
|
camera.updateMatrixWorld();
|
|
49
54
|
this.projection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
50
|
-
|
|
55
|
+
const capabilityChanged = this.multiDraw !== multiDraw;
|
|
56
|
+
this.multiDraw = multiDraw;
|
|
57
|
+
const inputsChanged = this.planChanged(camera);
|
|
58
|
+
if (!inputsChanged && !capabilityChanged && !this.ordered.pending) {
|
|
51
59
|
// The draw plan is a disposable presentation, not evaluated scene data.
|
|
52
60
|
// Reuse only after exact comparison of every input to culling/ordering;
|
|
53
61
|
// a camera move, drag, visibility or material change rebuilds it below.
|
|
@@ -61,6 +69,8 @@ export class BlenderTransparentInstances {
|
|
|
61
69
|
return;
|
|
62
70
|
}
|
|
63
71
|
this.planned.length = 0;
|
|
72
|
+
for (const draw of this.mixedDraws.values()) draw.members.length = 0;
|
|
73
|
+
this.ordered.begin();
|
|
64
74
|
for (const pool of this.pools.values()) for (const draw of pool) {
|
|
65
75
|
draw.mesh.visible = false;
|
|
66
76
|
draw.members.length = 0;
|
|
@@ -128,8 +138,14 @@ export class BlenderTransparentInstances {
|
|
|
128
138
|
: 'geometryLayersOrTransform';
|
|
129
139
|
this.excluded[reason] = (this.excluded[reason] ?? 0) + 1;
|
|
130
140
|
}
|
|
131
|
-
|
|
141
|
+
const batchKey = multiDraw && key && material ? this.ordered.register(mesh.geometry, material, mesh.renderOrder) : null;
|
|
142
|
+
entries.push({mesh, material, key, batchKey, groupOrder: order, z, centre});
|
|
132
143
|
}
|
|
144
|
+
this.ordered.pack();
|
|
145
|
+
// A family may exceed the bounded cache. Retain its original instancing
|
|
146
|
+
// key rather than merging incompatible geometries on the fallback path.
|
|
147
|
+
for (const entry of entries) if (entry.batchKey && this.ordered.available(entry.batchKey)) entry.key = entry.batchKey;
|
|
148
|
+
for (const [mesh] of this.mixedDraws) if (!mesh.parent) this.mixedDraws.delete(mesh);
|
|
133
149
|
entries.sort((a, b) => a.groupOrder - b.groupOrder || a.mesh.renderOrder - b.mesh.renderOrder || b.z - a.z || a.mesh.id - b.mesh.id);
|
|
134
150
|
const used = new Map<string, number>();
|
|
135
151
|
for (let begin = 0; begin < entries.length;) {
|
|
@@ -153,9 +169,26 @@ export class BlenderTransparentInstances {
|
|
|
153
169
|
const head = entries[offset]!;
|
|
154
170
|
const index = used.get(first.key) ?? 0;
|
|
155
171
|
used.set(first.key, index + 1);
|
|
172
|
+
const mixed = first.key.startsWith('multi:');
|
|
173
|
+
let draw: Draw | undefined;
|
|
174
|
+
if (mixed) {
|
|
175
|
+
const mesh = this.ordered.get(first.key, index);
|
|
176
|
+
if (!mesh) continue;
|
|
177
|
+
draw = this.mixedDraws.get(mesh);
|
|
178
|
+
if (!draw) {
|
|
179
|
+
draw = {mesh, members: []};
|
|
180
|
+
this.mixedDraws.set(mesh, draw);
|
|
181
|
+
const held = draw;
|
|
182
|
+
mesh.raycast = (raycaster, hits) => {
|
|
183
|
+
if (mesh.visible) for (const member of held.members)
|
|
184
|
+
if (instanceObjectShown(member)) member.raycast(raycaster, hits);
|
|
185
|
+
};
|
|
186
|
+
this.root.add(mesh);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
156
189
|
let pool = this.pools.get(first.key);
|
|
157
190
|
if (!pool) { pool = []; this.pools.set(first.key, pool); }
|
|
158
|
-
|
|
191
|
+
if (!mixed) draw = pool[index];
|
|
159
192
|
if (!draw) {
|
|
160
193
|
const mesh = new THREE.InstancedMesh(first.mesh.geometry, first.material!, CAPACITY);
|
|
161
194
|
mesh.name = 'Blender ordered transparency';
|
|
@@ -184,12 +217,20 @@ export class BlenderTransparentInstances {
|
|
|
184
217
|
const member = entries[offset + i]!.mesh;
|
|
185
218
|
this.matrix.multiplyMatrices(this.inverse, member.matrixWorld);
|
|
186
219
|
draw.mesh.setMatrixAt(i, this.matrix);
|
|
220
|
+
if (draw.mesh instanceof THREE.BatchedMesh) {
|
|
221
|
+
draw.mesh.setGeometryIdAt(i, this.ordered.geometryId(first.key, member.geometry));
|
|
222
|
+
draw.mesh.setVisibleAt(i, true);
|
|
223
|
+
}
|
|
187
224
|
draw.members.push(member);
|
|
188
225
|
member.layers.disable(0);
|
|
189
226
|
this.hidden.push(member);
|
|
190
227
|
}
|
|
191
|
-
draw.mesh.
|
|
192
|
-
|
|
228
|
+
if (draw.mesh instanceof THREE.BatchedMesh) {
|
|
229
|
+
for (let i = length; i < CAPACITY; i++) draw.mesh.setVisibleAt(i, false);
|
|
230
|
+
} else {
|
|
231
|
+
draw.mesh.count = length;
|
|
232
|
+
draw.mesh.instanceMatrix.needsUpdate = true;
|
|
233
|
+
}
|
|
193
234
|
this.planned.push(draw);
|
|
194
235
|
this.batches++;
|
|
195
236
|
this.instances += length;
|
|
@@ -197,9 +238,12 @@ export class BlenderTransparentInstances {
|
|
|
197
238
|
}
|
|
198
239
|
begin = end;
|
|
199
240
|
}
|
|
241
|
+
// A quiet viewport must also finish bounded pool construction. Notify only
|
|
242
|
+
// after this draw's temporary layer/geometry swaps have been restored.
|
|
243
|
+
if (this.ordered.pending) queueMicrotask(presenterChanged);
|
|
200
244
|
}
|
|
201
245
|
|
|
202
|
-
inspect() { return {batches: this.batches, instances: this.instances, excluded: {...this.excluded}}; }
|
|
246
|
+
inspect() { return {batches: this.batches, instances: this.instances, excluded: {...this.excluded}, multiDraw: this.ordered.inspect()}; }
|
|
203
247
|
|
|
204
248
|
private observe(value: number | string): void {
|
|
205
249
|
if (this.planState[this.stateIndex] !== value) {
|
|
@@ -217,6 +261,7 @@ export class BlenderTransparentInstances {
|
|
|
217
261
|
this.observe(camera.layers.mask);
|
|
218
262
|
for (const n of this.projection.elements) this.observe(n);
|
|
219
263
|
for (const n of this.root.matrixWorld.elements) this.observe(n);
|
|
264
|
+
const observedGeometry = new Set<THREE.BufferGeometry>();
|
|
220
265
|
for (const mesh of this.objects) {
|
|
221
266
|
this.observe(mesh.parent?.id ?? -1);
|
|
222
267
|
this.observe(instanceObjectShown(mesh) ? 1 : 0);
|
|
@@ -228,6 +273,19 @@ export class BlenderTransparentInstances {
|
|
|
228
273
|
for (const n of mesh.matrixWorld.elements) this.observe(n);
|
|
229
274
|
const geometry = mesh.geometry;
|
|
230
275
|
this.observe(geometry.id);
|
|
276
|
+
if (!observedGeometry.has(geometry)) {
|
|
277
|
+
observedGeometry.add(geometry);
|
|
278
|
+
this.observe(geometry.index?.id ?? -1);
|
|
279
|
+
this.observe(geometry.index?.version ?? -1);
|
|
280
|
+
this.observe(JSON.stringify(geometry.userData['blenderUvChannels'] ?? null));
|
|
281
|
+
for (const name of Object.keys(geometry.attributes).sort()) {
|
|
282
|
+
const attribute = geometry.getAttribute(name);
|
|
283
|
+
this.observe(name);
|
|
284
|
+
if (attribute instanceof THREE.BufferAttribute) {
|
|
285
|
+
this.observe(attribute.id); this.observe(attribute.version);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
231
289
|
this.observe(geometry.drawRange.start);
|
|
232
290
|
this.observe(geometry.drawRange.count);
|
|
233
291
|
this.observe(geometry.index?.count ?? geometry.getAttribute('position').count);
|
|
@@ -259,6 +317,7 @@ export class BlenderTransparentInstances {
|
|
|
259
317
|
for (const mesh of this.hidden) mesh.layers.enable(0);
|
|
260
318
|
this.hidden.length = 0;
|
|
261
319
|
for (const pool of this.pools.values()) for (const {mesh} of pool) mesh.visible = false;
|
|
320
|
+
for (const {mesh} of this.mixedDraws.values()) mesh.visible = false;
|
|
262
321
|
}
|
|
263
322
|
|
|
264
323
|
clear(): void {
|
|
@@ -268,6 +327,8 @@ export class BlenderTransparentInstances {
|
|
|
268
327
|
mesh.dispose();
|
|
269
328
|
}
|
|
270
329
|
this.pools.clear();
|
|
330
|
+
this.ordered.clear();
|
|
331
|
+
this.mixedDraws.clear();
|
|
271
332
|
this.planned.length = 0;
|
|
272
333
|
this.planState.length = 0;
|
|
273
334
|
this.objects = [];
|