@zephyr3d/scene 0.9.24 → 0.9.25

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.
@@ -31,30 +31,40 @@ import '../../shaders/atmosphere.js';
31
31
  import '../../render/rendergraph/scene_render_context.js';
32
32
  import { ClipmapGrassMaterial } from './grassmaterial.js';
33
33
 
34
- const MAX_INSTANCES_PER_NODE = 2048;
35
34
  const INSTANCE_BYTES = 4 * 4;
35
+ /** Number of placement cells along each axis of a grass tile */ const TILE_CELLS = 64;
36
+ /** Default number of placement cells per density map texel along each axis */ const DEFAULT_CELLS_PER_TEXEL = 2;
37
+ const MAX_CELLS_PER_TEXEL = 8;
36
38
  /**
37
- * Grass glade instance manager class
39
+ * Deterministic 2D integer hash, returns a value in [0, 1).
40
+ * Grass placement derives everything (jitter, survival threshold, rotation)
41
+ * from this hash so that blade positions are stable across edits and reloads.
42
+ */ function hashCell(x, z, seed) {
43
+ let h = Math.imul(x, 0x27d4eb2d) ^ Math.imul(z, 0x165667b1) ^ Math.imul(seed, 0x9e3779b9) | 0;
44
+ h = Math.imul(h ^ h >>> 15, 0x85ebca6b);
45
+ h = Math.imul(h ^ h >>> 13, 0xc2b2ae35);
46
+ h ^= h >>> 16;
47
+ return (h >>> 0) / 4294967296;
48
+ }
49
+ /**
50
+ * Grass blade instance buffer for a single tile
38
51
  * @internal
39
52
  */ class GrassInstances extends Disposable {
40
- _instances;
53
+ _numInstances;
41
54
  _baseVertexBuffer;
42
55
  _indexBuffer;
43
56
  _instanceBuffer;
44
57
  _primitive;
45
58
  constructor(baseVertexBuffer, indexBuffer){
46
59
  super();
47
- this._instances = [];
60
+ this._numInstances = 0;
48
61
  this._baseVertexBuffer = new DRef(baseVertexBuffer);
49
62
  this._indexBuffer = new DRef(indexBuffer);
50
63
  this._instanceBuffer = new DRef();
51
64
  this._primitive = new DRef();
52
65
  }
53
66
  get numInstances() {
54
- return this._instances.length;
55
- }
56
- get instanceBuffer() {
57
- return this._instanceBuffer.get();
67
+ return this._numInstances;
58
68
  }
59
69
  setBaseVertexBuffer(baseVertexBuffer) {
60
70
  if (baseVertexBuffer !== this._baseVertexBuffer.get()) {
@@ -62,8 +72,25 @@ const INSTANCE_BYTES = 4 * 4;
62
72
  this._primitive.dispose();
63
73
  }
64
74
  }
75
+ /**
76
+ * Sets the packed instance data (x, y, sin, cos per instance)
77
+ */ setData(data, count) {
78
+ this._numInstances = count;
79
+ if (count === 0) {
80
+ return;
81
+ }
82
+ const device = getDevice();
83
+ const bytesRequired = count * INSTANCE_BYTES;
84
+ let buffer = this._instanceBuffer.get();
85
+ if (!buffer || buffer.byteLength < bytesRequired) {
86
+ buffer = device.createVertexBuffer('tex1_f32x4', new Uint8Array(nextPowerOf2(bytesRequired)));
87
+ this._instanceBuffer.set(buffer);
88
+ this._primitive.dispose();
89
+ }
90
+ buffer.bufferSubData(0, data.subarray(0, count * 4));
91
+ }
65
92
  draw() {
66
- if (this._instances.length > 0) {
93
+ if (this._numInstances > 0) {
67
94
  if (!this._primitive.get()) {
68
95
  const primitive = new Primitive();
69
96
  primitive.setVertexBuffer(this._baseVertexBuffer.get());
@@ -74,58 +101,8 @@ const INSTANCE_BYTES = 4 * 4;
74
101
  primitive.indexCount = this._indexBuffer.get().length;
75
102
  this._primitive.set(primitive);
76
103
  }
77
- this._primitive.get().drawInstanced(this._instances.length);
78
- }
79
- }
80
- updateBuffers() {
81
- const device = getDevice();
82
- if (this._instances.length === 0) {
83
- this._instanceBuffer.set(null);
84
- return;
85
- }
86
- if (this._instances.length > 0 && !this._instanceBuffer.get()) {
87
- this._instanceBuffer.set(device.createVertexBuffer('tex1_f32x4', new Uint8Array(this._instances.length * INSTANCE_BYTES)));
88
- }
89
- let buffer = this._instanceBuffer.get();
90
- const currentBytes = buffer.byteLength;
91
- const bytesRequired = this._instances.length * INSTANCE_BYTES;
92
- if (currentBytes < bytesRequired) {
93
- buffer = device.createVertexBuffer('tex1_f32x4', new Uint8Array(nextPowerOf2(bytesRequired)));
94
- this._instanceBuffer.set(buffer);
95
- this._primitive.dispose();
96
- }
97
- const data = new Float32Array(4 * this._instances.length);
98
- for(let i = 0; i < this._instances.length; i++){
99
- data[i * 4 + 0] = this._instances[i].x;
100
- data[i * 4 + 1] = this._instances[i].y;
101
- data[i * 4 + 2] = Math.sin(this._instances[i].angle);
102
- data[i * 4 + 3] = Math.cos(this._instances[i].angle);
103
- }
104
- buffer.bufferSubData(0, data);
105
- }
106
- removeInstances(minX, minZ, maxX, maxZ, num) {
107
- if (num <= 0) {
108
- return 0;
109
- }
110
- let removed = 0;
111
- for(let i = this._instances.length - 1; i >= 0; i--){
112
- const instance = this._instances[i];
113
- if (instance.x >= minX && instance.x <= maxX && instance.y >= minZ && instance.y <= maxZ) {
114
- this._instances.splice(i, 1);
115
- removed++;
116
- if (removed === num) {
117
- break;
118
- }
119
- }
120
- }
121
- if (removed > 0) {
122
- this.updateBuffers();
104
+ this._primitive.get().drawInstanced(this._numInstances);
123
105
  }
124
- return removed;
125
- }
126
- addInstances(instances) {
127
- this._instances.push(...instances);
128
- this.updateBuffers();
129
106
  }
130
107
  onDispose() {
131
108
  super.onDispose();
@@ -137,34 +114,53 @@ const INSTANCE_BYTES = 4 * 4;
137
114
  }
138
115
  /**
139
116
  * Grass layer class
117
+ *
140
118
  * @public
141
119
  */ class GrassLayer extends Disposable {
142
120
  static _indexBuffer = new DRef();
121
+ static _cullAABB = new AABB();
122
+ static _visibleTiles = [];
123
+ static _instanceData = new Float32Array(TILE_CELLS * TILE_CELLS * 4);
143
124
  _material;
144
- _quadtree;
125
+ _seed;
145
126
  _bladeWidth;
146
127
  _bladeHeight;
147
128
  _baseVertexBuffer;
129
+ _densityWidth;
130
+ _densityHeight;
131
+ _cellsPerTexel;
132
+ _densityMap;
133
+ _tiles;
134
+ _tilesX;
135
+ _tilesZ;
136
+ _numBlades;
148
137
  /**
149
138
  * Creates an instance of GrassLayer
150
139
  * @param terrain - Clipmap terrain object
140
+ * @param seed - Seed for deterministic blade placement, usually the layer index
151
141
  * @param bladeWidth - Grass blade width
152
142
  * @param bladeHeight - Grass blade height
153
143
  * @param albedoMap - Albedo texture for the blade
154
- */ constructor(terrain, bladeWidth, bladeHeight, albedoMap){
144
+ */ constructor(terrain, seed, bladeWidth, bladeHeight, albedoMap){
155
145
  super();
156
146
  this._material = new DRef(new ClipmapGrassMaterial(terrain));
157
147
  this._material.get().albedoTexture = albedoMap ?? null;
158
148
  if (albedoMap) {
159
149
  this._material.get().setTextureSize(albedoMap.width, albedoMap.height);
160
150
  }
151
+ this._seed = seed;
161
152
  this._bladeWidth = bladeWidth;
162
153
  this._bladeHeight = bladeHeight;
163
154
  this._baseVertexBuffer = new DRef(this.createBaseVertexBuffer(this._bladeWidth, this._bladeHeight));
164
- this._quadtree = new DRef(new GrassQuadtreeNode(this._baseVertexBuffer.get(), GrassLayer._getIndexBuffer()));
165
- }
166
- /** @internal */ get quadtree() {
167
- return this._quadtree.get();
155
+ this._densityWidth = Math.max(1, terrain.sizeX);
156
+ this._densityHeight = Math.max(1, terrain.sizeZ);
157
+ this._cellsPerTexel = DEFAULT_CELLS_PER_TEXEL;
158
+ this._densityMap = new Uint8Array(this._densityWidth * this._densityHeight);
159
+ this._tiles = new Map();
160
+ this._tilesX = 0;
161
+ this._tilesZ = 0;
162
+ this._numBlades = 0;
163
+ this.updateTileGrid();
168
164
  }
169
165
  /** @internal */ updateMaterial() {
170
166
  this._material.get().uniformChanged();
@@ -184,21 +180,93 @@ const INSTANCE_BYTES = 4 * 4;
184
180
  */ getAlbedoMap() {
185
181
  return this._material.get().albedoTexture;
186
182
  }
183
+ /** How many grass blades are currently generated in this layer */ get numBlades() {
184
+ return this._numBlades;
185
+ }
186
+ /**
187
+ * Density map of this layer, one byte per texel (0 = no grass, 255 = full density).
188
+ * Texel (x, z) covers the normalized terrain region [x/w..(x+1)/w, z/h..(z+1)/h].
189
+ * After modifying the data, call {@link GrassLayer.updateDensityRegion} to
190
+ * regenerate the affected blade instances.
191
+ */ get densityMap() {
192
+ return this._densityMap;
193
+ }
194
+ /** Width of the density map in texels */ get densityMapWidth() {
195
+ return this._densityWidth;
196
+ }
197
+ /** Height of the density map in texels */ get densityMapHeight() {
198
+ return this._densityHeight;
199
+ }
200
+ /**
201
+ * Number of placement cells per density map texel along each axis.
202
+ * The maximum blade count per texel is the square of this value.
203
+ */ get cellsPerTexel() {
204
+ return this._cellsPerTexel;
205
+ }
206
+ set cellsPerTexel(val) {
207
+ val = Math.max(1, Math.min(MAX_CELLS_PER_TEXEL, val | 0));
208
+ if (val !== this._cellsPerTexel) {
209
+ this._cellsPerTexel = val;
210
+ this.rebuild();
211
+ }
212
+ }
213
+ /**
214
+ * Replaces the density map and regenerates all blade instances
215
+ * @param width - Density map width in texels
216
+ * @param height - Density map height in texels
217
+ * @param cellsPerTexel - Placement cells per texel along each axis
218
+ * @param data - Density data, one byte per texel
219
+ */ setDensityData(width, height, cellsPerTexel, data) {
220
+ if (width < 1 || height < 1 || width * height !== data.length) {
221
+ console.error('Invalid grass density data');
222
+ return;
223
+ }
224
+ this._densityWidth = width;
225
+ this._densityHeight = height;
226
+ this._cellsPerTexel = Math.max(1, Math.min(MAX_CELLS_PER_TEXEL, cellsPerTexel | 0));
227
+ this._densityMap = data;
228
+ this.rebuild();
229
+ }
187
230
  /**
188
- * Add grass blades to this layer in a region
189
- * @param instances - Grass blade instances to add
190
- */ addInstances(instances) {
191
- return this._quadtree.get().addInstances(instances);
231
+ * Regenerates blade instances for all tiles overlapping a density map region
232
+ * @param minTexelX - Minimum x texel of the region (inclusive)
233
+ * @param minTexelZ - Minimum z texel of the region (inclusive)
234
+ * @param maxTexelX - Maximum x texel of the region (exclusive)
235
+ * @param maxTexelZ - Maximum z texel of the region (exclusive)
236
+ */ updateDensityRegion(minTexelX, minTexelZ, maxTexelX, maxTexelZ) {
237
+ const k = this._cellsPerTexel;
238
+ // expand by one texel to cover the bilinear sampling footprint
239
+ const cx0 = Math.max(0, (minTexelX - 1) * k);
240
+ const cz0 = Math.max(0, (minTexelZ - 1) * k);
241
+ const cx1 = Math.min(this._densityWidth * k, (maxTexelX + 1) * k);
242
+ const cz1 = Math.min(this._densityHeight * k, (maxTexelZ + 1) * k);
243
+ if (cx1 <= cx0 || cz1 <= cz0) {
244
+ return;
245
+ }
246
+ const tx0 = Math.floor(cx0 / TILE_CELLS);
247
+ const tz0 = Math.floor(cz0 / TILE_CELLS);
248
+ const tx1 = Math.min(this._tilesX, Math.ceil(cx1 / TILE_CELLS));
249
+ const tz1 = Math.min(this._tilesZ, Math.ceil(cz1 / TILE_CELLS));
250
+ for(let tz = tz0; tz < tz1; tz++){
251
+ for(let tx = tx0; tx < tx1; tx++){
252
+ this.generateTile(tx, tz);
253
+ }
254
+ }
192
255
  }
193
256
  /**
194
- * Remove grass blades to this layer in a region
195
- * @param minX - Minimum x position of the region
196
- * @param minZ - Minimum z position of the region
197
- * @param maxX - Maximum x position of the region
198
- * @param maxZ - Maximum z position of the region
199
- * @param numInstances - How many grass blades to add
200
- */ removeInstances(minX, minZ, maxX, maxZ, numInstances) {
201
- this._quadtree.get().removeInstances(minX, minZ, maxX, maxZ, numInstances);
257
+ * Regenerates blade instances for the entire layer
258
+ */ rebuild() {
259
+ for (const tile of this._tiles.values()){
260
+ tile.dispose();
261
+ }
262
+ this._tiles.clear();
263
+ this._numBlades = 0;
264
+ this.updateTileGrid();
265
+ for(let tz = 0; tz < this._tilesZ; tz++){
266
+ for(let tx = 0; tx < this._tilesX; tx++){
267
+ this.generateTile(tx, tz);
268
+ }
269
+ }
202
270
  }
203
271
  /** Grass blade width in this layer */ get bladeWidth() {
204
272
  return this._bladeWidth;
@@ -221,7 +289,92 @@ const INSTANCE_BYTES = 4 * 4;
221
289
  this._bladeWidth = width;
222
290
  this._bladeHeight = height;
223
291
  this._baseVertexBuffer.set(this.createBaseVertexBuffer(this._bladeWidth, this._bladeHeight));
224
- this._quadtree.get().setBaseVertexBuffer(this._baseVertexBuffer.get());
292
+ for (const tile of this._tiles.values()){
293
+ tile.setBaseVertexBuffer(this._baseVertexBuffer.get());
294
+ }
295
+ }
296
+ }
297
+ /** @internal */ updateTileGrid() {
298
+ this._tilesX = Math.ceil(this._densityWidth * this._cellsPerTexel / TILE_CELLS);
299
+ this._tilesZ = Math.ceil(this._densityHeight * this._cellsPerTexel / TILE_CELLS);
300
+ }
301
+ /** @internal */ sampleDensity(u, v) {
302
+ const w = this._densityWidth;
303
+ const h = this._densityHeight;
304
+ const x = u * w - 0.5;
305
+ const z = v * h - 0.5;
306
+ const ix = Math.floor(x);
307
+ const iz = Math.floor(z);
308
+ const fx = x - ix;
309
+ const fz = z - iz;
310
+ const x0 = Math.min(Math.max(ix, 0), w - 1);
311
+ const x1 = Math.min(Math.max(ix + 1, 0), w - 1);
312
+ const z0 = Math.min(Math.max(iz, 0), h - 1);
313
+ const z1 = Math.min(Math.max(iz + 1, 0), h - 1);
314
+ const d = this._densityMap;
315
+ const a = d[z0 * w + x0] + (d[z0 * w + x1] - d[z0 * w + x0]) * fx;
316
+ const b = d[z1 * w + x0] + (d[z1 * w + x1] - d[z1 * w + x0]) * fx;
317
+ return (a + (b - a) * fz) * (1 / 255);
318
+ }
319
+ /** @internal */ regionIsEmpty(cx0, cz0, cx1, cz1) {
320
+ const k = this._cellsPerTexel;
321
+ const x0 = Math.max(0, Math.floor(cx0 / k) - 1);
322
+ const z0 = Math.max(0, Math.floor(cz0 / k) - 1);
323
+ const x1 = Math.min(this._densityWidth, Math.ceil(cx1 / k) + 1);
324
+ const z1 = Math.min(this._densityHeight, Math.ceil(cz1 / k) + 1);
325
+ for(let z = z0; z < z1; z++){
326
+ const rowBase = z * this._densityWidth;
327
+ for(let x = x0; x < x1; x++){
328
+ if (this._densityMap[rowBase + x] !== 0) {
329
+ return false;
330
+ }
331
+ }
332
+ }
333
+ return true;
334
+ }
335
+ /** @internal */ generateTile(tx, tz) {
336
+ const k = this._cellsPerTexel;
337
+ const cw = this._densityWidth * k;
338
+ const ch = this._densityHeight * k;
339
+ const cx0 = tx * TILE_CELLS;
340
+ const cz0 = tz * TILE_CELLS;
341
+ const cx1 = Math.min(cx0 + TILE_CELLS, cw);
342
+ const cz1 = Math.min(cz0 + TILE_CELLS, ch);
343
+ const key = tz * this._tilesX + tx;
344
+ let tile = this._tiles.get(key);
345
+ // fast path: nothing to generate if there is no tile yet and no density
346
+ if (!tile && this.regionIsEmpty(cx0, cz0, cx1, cz1)) {
347
+ return;
348
+ }
349
+ const data = GrassLayer._instanceData;
350
+ const seed = this._seed * 4;
351
+ let count = 0;
352
+ for(let cz = cz0; cz < cz1; cz++){
353
+ for(let cx = cx0; cx < cx1; cx++){
354
+ const u = (cx + hashCell(cx, cz, seed)) / cw;
355
+ const v = (cz + hashCell(cx, cz, seed + 1)) / ch;
356
+ if (this.sampleDensity(u, v) > hashCell(cx, cz, seed + 2)) {
357
+ const angle = hashCell(cx, cz, seed + 3) * Math.PI * 2;
358
+ data[count * 4 + 0] = u;
359
+ data[count * 4 + 1] = v;
360
+ data[count * 4 + 2] = Math.sin(angle);
361
+ data[count * 4 + 3] = Math.cos(angle);
362
+ count++;
363
+ }
364
+ }
365
+ }
366
+ this._numBlades += count - (tile?.numInstances ?? 0);
367
+ if (count === 0) {
368
+ if (tile) {
369
+ tile.dispose();
370
+ this._tiles.delete(key);
371
+ }
372
+ } else {
373
+ if (!tile) {
374
+ tile = new GrassInstances(this._baseVertexBuffer.get(), GrassLayer._getIndexBuffer());
375
+ this._tiles.set(key, tile);
376
+ }
377
+ tile.setData(data, count);
225
378
  }
226
379
  }
227
380
  /** @internal */ static _getIndexBuffer() {
@@ -323,16 +476,49 @@ const INSTANCE_BYTES = 4 * 4;
323
476
  ], vertices);
324
477
  }
325
478
  /** @internal */ draw(ctx, region, minY, maxY) {
479
+ const visible = GrassLayer._visibleTiles;
480
+ visible.length = 0;
481
+ const camera = ctx.camera;
482
+ const cullAABB = GrassLayer._cullAABB;
483
+ const cellsW = this._densityWidth * this._cellsPerTexel;
484
+ const cellsH = this._densityHeight * this._cellsPerTexel;
485
+ const rx = region.x;
486
+ const rz = region.y;
487
+ const rw = region.z - region.x;
488
+ const rh = region.w - region.y;
489
+ for (const [key, tile] of this._tiles){
490
+ const tx = key % this._tilesX;
491
+ const tz = (key - tx) / this._tilesX;
492
+ const u0 = tx * TILE_CELLS / cellsW;
493
+ const v0 = tz * TILE_CELLS / cellsH;
494
+ const u1 = Math.min(1, (tx + 1) * TILE_CELLS / cellsW);
495
+ const v1 = Math.min(1, (tz + 1) * TILE_CELLS / cellsH);
496
+ cullAABB.minPoint.setXYZ(rx + u0 * rw, minY, rz + v0 * rh);
497
+ cullAABB.maxPoint.setXYZ(rx + u1 * rw, maxY, rz + v1 * rh);
498
+ const clipState = camera.clipMask ? cullAABB.getClipStateWithFrustumMask(camera.frustum, camera.clipMask) : cullAABB.getClipStateWithFrustum(camera.frustum);
499
+ if (clipState !== ClipState.NOT_CLIPPED) {
500
+ visible.push(tile);
501
+ }
502
+ }
503
+ if (visible.length === 0) {
504
+ return;
505
+ }
326
506
  this._material.get().apply(ctx);
327
507
  for(let pass = 0; pass < this._material.get().numPasses; pass++){
328
508
  this._material.get().bind(ctx.device, pass);
329
- this._quadtree.get().draw(ctx.camera, region, minY, maxY, false);
509
+ for (const tile of visible){
510
+ tile.draw();
511
+ }
330
512
  }
513
+ visible.length = 0;
331
514
  }
332
515
  /** @internal */ onDispose() {
333
516
  super.onDispose();
334
517
  this._material.dispose();
335
- this._quadtree.dispose();
518
+ for (const tile of this._tiles.values()){
519
+ tile.dispose();
520
+ }
521
+ this._tiles.clear();
336
522
  this._baseVertexBuffer.dispose();
337
523
  }
338
524
  }
@@ -341,7 +527,6 @@ const INSTANCE_BYTES = 4 * 4;
341
527
  * @public
342
528
  */ class GrassRenderer extends Disposable {
343
529
  _terrain;
344
- _numGrassBlades;
345
530
  _layers;
346
531
  /**
347
532
  * Creates an instance of GrassRenderer
@@ -349,7 +534,6 @@ const INSTANCE_BYTES = 4 * 4;
349
534
  */ constructor(terrain){
350
535
  super();
351
536
  this._terrain = new DWeakRef(terrain);
352
- this._numGrassBlades = 0;
353
537
  this._layers = [];
354
538
  }
355
539
  /** @internal */ updateMaterial() {
@@ -358,7 +542,7 @@ const INSTANCE_BYTES = 4 * 4;
358
542
  }
359
543
  }
360
544
  /** How many grass blades */ get numGrassBlades() {
361
- return this._numGrassBlades;
545
+ return this._layers.reduce((sum, layer)=>sum + layer.numBlades, 0);
362
546
  }
363
547
  /** How many grass layers */ get numLayers() {
364
548
  return this._layers.length;
@@ -377,7 +561,7 @@ const INSTANCE_BYTES = 4 * 4;
377
561
  * @param albedoMap - Albedo texture of grass blades in this layer
378
562
  * @returns Index of the added grass layer
379
563
  */ addLayer(bladeWidth, bladeHeight, albedoMap) {
380
- const layer = new GrassLayer(this._terrain.get(), bladeWidth, bladeHeight, albedoMap);
564
+ const layer = new GrassLayer(this._terrain.get(), this._layers.length, bladeWidth, bladeHeight, albedoMap);
381
565
  this._layers.push(layer);
382
566
  return this._layers.length - 1;
383
567
  }
@@ -427,34 +611,6 @@ const INSTANCE_BYTES = 4 * 4;
427
611
  console.error(`Invalid grass layer: ${layer}`);
428
612
  }
429
613
  }
430
- /**
431
- * Add grass instances to the grass layer at given index
432
- * @param layer - Index of the grass layer
433
- * @param instances - Grass blade instances to add
434
- */ addInstances(layer, instances) {
435
- const grassLayer = this._layers[layer];
436
- if (!grassLayer) {
437
- console.error(`Invalid grass layer: ${layer}`);
438
- } else {
439
- this._numGrassBlades += grassLayer.addInstances(instances);
440
- }
441
- }
442
- /**
443
- * Remove grass instances from the grass layer at given index within a region
444
- * @param layer - Index of the grass layer
445
- * @param minX - Minimum x of the region
446
- * @param minZ - Minimum z of the region
447
- * @param maxX - Maximum x of the region
448
- * @param maxZ - Maximum z of the region
449
- * @param num - How many grass blades to remove
450
- */ removeInstances(layer, minX, minZ, maxX, maxZ, num) {
451
- const grassLayer = this._layers[layer];
452
- if (!grassLayer) {
453
- console.error(`Invalid grass layer: ${layer}`);
454
- } else {
455
- grassLayer.removeInstances(minX, minZ, maxX, maxZ, num);
456
- }
457
- }
458
614
  /** @internal */ draw(ctx) {
459
615
  const bv = this._terrain.get().getWorldBoundingVolume().toAABB();
460
616
  const minY = bv.minPoint.y;
@@ -471,141 +627,6 @@ const INSTANCE_BYTES = 4 * 4;
471
627
  }
472
628
  }
473
629
  }
474
- /** @internal */ class GrassQuadtreeNode extends Disposable {
475
- static _cullAABB = new AABB();
476
- _grassInstances;
477
- _children;
478
- _baseVertexBuffer;
479
- _indexBuffer;
480
- _minX;
481
- _minZ;
482
- _maxX;
483
- _maxZ;
484
- constructor(baseVertexBuffer, indexBuffer){
485
- super();
486
- this._baseVertexBuffer = new DRef(baseVertexBuffer);
487
- this._indexBuffer = new DRef(indexBuffer);
488
- this._grassInstances = new DRef(new GrassInstances(baseVertexBuffer, indexBuffer));
489
- this._children = null;
490
- this._minX = 0;
491
- this._minZ = 0;
492
- this._maxX = 1;
493
- this._maxZ = 1;
494
- }
495
- get grassInstances() {
496
- return this._grassInstances.get();
497
- }
498
- get children() {
499
- return this._children;
500
- }
501
- draw(camera, region, minY, maxY, skipClipTest) {
502
- if (!skipClipTest) {
503
- const cullAABB = GrassQuadtreeNode._cullAABB;
504
- const x = region.x;
505
- const z = region.y;
506
- const dx = region.z - x;
507
- const dz = region.w - z;
508
- cullAABB.minPoint.setXYZ(x + this._minX * dx, minY, z + this._minZ * dz);
509
- cullAABB.maxPoint.setXYZ(x + this._maxX * dx, maxY, z + this._maxZ * dz);
510
- const clipState = camera.clipMask ? cullAABB.getClipStateWithFrustumMask(camera.frustum, camera.clipMask) : cullAABB.getClipStateWithFrustum(camera.frustum);
511
- if (clipState === ClipState.NOT_CLIPPED) {
512
- return;
513
- }
514
- skipClipTest = clipState === ClipState.A_INSIDE_B;
515
- }
516
- if (this._grassInstances.get().numInstances > 0) {
517
- this._grassInstances.get().draw();
518
- }
519
- if (this._children) {
520
- for (const child of this._children){
521
- child.draw(camera, region, minY, maxY, skipClipTest);
522
- }
523
- }
524
- }
525
- setBaseVertexBuffer(baseVertexBuffer) {
526
- if (baseVertexBuffer !== this._baseVertexBuffer.get()) {
527
- this._baseVertexBuffer.set(baseVertexBuffer);
528
- this._grassInstances.get().setBaseVertexBuffer(baseVertexBuffer);
529
- if (this._children) {
530
- for (const child of this._children){
531
- child.setBaseVertexBuffer(baseVertexBuffer);
532
- }
533
- }
534
- }
535
- }
536
- removeInstances(minX, minZ, maxX, maxZ, numInstances) {
537
- let n = Math.min(this._grassInstances.get().numInstances, numInstances);
538
- if (n <= 0) {
539
- return 0;
540
- }
541
- let removed = 0;
542
- if (this._children) {
543
- for (const child of this._children){
544
- if (child._minX < maxX && child._minZ < maxZ && child._maxX > minX && child._maxZ > minZ) {
545
- removed += child.removeInstances(minX, minZ, maxX, maxZ, n);
546
- n -= removed;
547
- }
548
- }
549
- }
550
- if (n > 0) {
551
- removed += this._grassInstances.get().removeInstances(minX, minZ, maxX, maxZ, n);
552
- }
553
- return removed;
554
- }
555
- addInstances(instances) {
556
- if (instances.length === 0) {
557
- return 0;
558
- }
559
- let n = Math.min(instances.length, MAX_INSTANCES_PER_NODE - this._grassInstances.get().numInstances);
560
- this._grassInstances.get().addInstances(instances.slice(0, n));
561
- if (n < instances.length) {
562
- if (!this._children) {
563
- this._children = [
564
- new GrassQuadtreeNode(this._baseVertexBuffer.get(), this._indexBuffer.get()),
565
- new GrassQuadtreeNode(this._baseVertexBuffer.get(), this._indexBuffer.get()),
566
- new GrassQuadtreeNode(this._baseVertexBuffer.get(), this._indexBuffer.get()),
567
- new GrassQuadtreeNode(this._baseVertexBuffer.get(), this._indexBuffer.get())
568
- ];
569
- this._children[0]._minX = this._minX;
570
- this._children[0]._minZ = this._minZ;
571
- this._children[0]._maxX = (this._minX + this._maxX) * 0.5;
572
- this._children[0]._maxZ = (this._minZ + this._maxZ) * 0.5;
573
- this._children[1]._minX = this._children[0]._maxX;
574
- this._children[1]._minZ = this._minZ;
575
- this._children[1]._maxX = this._maxX;
576
- this._children[1]._maxZ = this._children[0]._maxZ;
577
- this._children[2]._minX = this._minX;
578
- this._children[2]._minZ = this._children[0]._maxZ;
579
- this._children[2]._maxX = this._children[0]._maxX;
580
- this._children[2]._maxZ = this._maxZ;
581
- this._children[3]._minX = this._children[0]._maxX;
582
- this._children[3]._minZ = this._children[0]._maxZ;
583
- this._children[3]._maxX = this._maxX;
584
- this._children[3]._maxZ = this._maxZ;
585
- }
586
- instances = instances.slice(n);
587
- for (const child of this._children){
588
- if (instances.length > 0) {
589
- const childInstances = instances.filter((val)=>val.x >= child._minX && val.y >= child._minZ && val.x < child._maxX && val.y < child._maxZ);
590
- n += child.addInstances(childInstances);
591
- instances = instances.filter((val)=>!(val.x >= child._minX && val.y >= child._minZ && val.x < child._maxX && val.y < child._maxZ));
592
- }
593
- }
594
- }
595
- return n;
596
- }
597
- onDispose() {
598
- super.onDispose();
599
- this._baseVertexBuffer.dispose();
600
- this._indexBuffer.dispose();
601
- this._grassInstances.dispose();
602
- if (this._children) {
603
- for (const child of this._children){
604
- child.dispose();
605
- }
606
- }
607
- }
608
- }
609
630
 
610
- export { GrassInstances, GrassLayer, GrassQuadtreeNode, GrassRenderer };
631
+ export { GrassInstances, GrassLayer, GrassRenderer };
611
632
  //# sourceMappingURL=grass.js.map