@woosh/meep-engine 2.76.4 → 2.77.0

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.
Files changed (26) hide show
  1. package/build/meep.cjs +197 -616
  2. package/build/meep.min.js +1 -1
  3. package/build/meep.module.js +197 -616
  4. package/editor/view/ecs/components/TerrainController.js +9 -16
  5. package/package.json +1 -1
  6. package/src/core/collection/heap/Uint32Heap.js +10 -1
  7. package/src/core/graph/Edge.js +20 -0
  8. package/src/core/graph/Graph.js +1 -0
  9. package/src/core/graph/SquareMatrix.js +4 -2
  10. package/src/core/graph/WeightedEdge.js +5 -9
  11. package/src/core/graph/coloring/validateGraphColoring.js +1 -1
  12. package/src/core/graph/eigen/matrix_eigenvalues_in_place.js +21 -0
  13. package/src/core/graph/eigen/{eigen.spec.js → matrix_eigenvalues_in_place.spec.js} +2 -2
  14. package/src/core/graph/eigen/matrix_householder_in_place.js +92 -0
  15. package/src/core/graph/eigen/{eigen.js → matrix_qr_in_place.js} +2 -113
  16. package/src/core/graph/v2/Graph.js +16 -9
  17. package/src/core/graph/v2/NodeContainer.js +120 -22
  18. package/src/engine/ecs/storage/binary/BinarySerializationRegistry.js +8 -6
  19. package/src/engine/graphics/particles/node-based/codegen/modules/FunctionModuleRegistry.js +1 -1
  20. package/src/engine/navigation/grid/find_path_on_grid_astar.js +25 -22
  21. package/src/engine/navigation/grid/find_path_on_grid_astar.spec.js +2 -2
  22. package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +17 -33
  23. package/src/core/graph/GraphUtils.js +0 -284
  24. package/src/engine/ecs/terrain/ecs/splat/SplatMapMaterialPatch.js +0 -464
  25. package/src/engine/ecs/terrain/ecs/splat/SplatMapOptimizer.js +0 -622
  26. package/src/engine/ecs/terrain/ecs/splat/SplatMapOptimizerDebugger.js +0 -383
@@ -1,622 +0,0 @@
1
- import Graph from "../../../../../core/graph/Graph.js";
2
- import { QuadTreeNode } from "../../../../../core/geom/2d/quad-tree/QuadTreeNode.js";
3
- import { BitSet } from "../../../../../core/binary/BitSet.js";
4
- import { SplatMapMaterialPatch } from "./SplatMapMaterialPatch.js";
5
- import { colorizeGraphGreedyWeight } from "../../../../../core/graph/coloring/colorizeGraphGreedyWeight.js";
6
- import { validateGraphColoring } from "../../../../../core/graph/coloring/validateGraphColoring.js";
7
- import ConcurrentExecutor from "../../../../../core/process/executor/ConcurrentExecutor.js";
8
- import Task from "../../../../../core/process/task/Task.js";
9
- import { TaskSignal } from "../../../../../core/process/task/TaskSignal.js";
10
- import { actionTask } from "../../../../../core/process/task/util/actionTask.js";
11
- import { countTask } from "../../../../../core/process/task/util/countTask.js";
12
-
13
- /**
14
- * We convert splat mapping into a graph of connecting material patches and solve the overlaps as a "Graph Coloring" problem
15
- */
16
- export class SplatMapOptimizer {
17
- constructor() {
18
- /**
19
- *
20
- * @type {Graph<SplatMapMaterialPatch>}
21
- */
22
- this.graph = new Graph();
23
-
24
- /**
25
- * Contains patches to speed up lookup
26
- * @type {QuadTreeNode<SplatMapMaterialPatch>}
27
- */
28
- this.quadTree = new QuadTreeNode();
29
-
30
- /**
31
- *
32
- * @type {Uint8Array}
33
- */
34
- this.ranking = null;
35
-
36
- /**
37
- *
38
- * @type {SplatMapping}
39
- */
40
- this.mapping = null;
41
-
42
- /**
43
- *
44
- * @type {BitSet}
45
- */
46
- this.marks = new BitSet();
47
-
48
- }
49
-
50
- /**
51
- *
52
- * @param {SplatMapping} mapping
53
- * @return {Promise}
54
- */
55
- static optimizeSynchronous(mapping) {
56
-
57
- const optimizer = new SplatMapOptimizer();
58
-
59
- optimizer.mapping = mapping;
60
-
61
- const tasks = optimizer.optimize();
62
-
63
- const executor = new ConcurrentExecutor(0, Number.POSITIVE_INFINITY);
64
-
65
- executor.runMany(tasks);
66
-
67
- return Task.promiseAll(tasks);
68
- }
69
-
70
- /**
71
- * @returns {Task[]}
72
- */
73
- optimize() {
74
-
75
- const result = [];
76
-
77
- const tInitialize = this.initialize();
78
-
79
- Array.prototype.push.apply(result, tInitialize);
80
-
81
- // optimizer.removePatchesSmallerThan(100);
82
-
83
- const self = this;
84
-
85
- const tMergePatches = new Task({
86
- name: 'Merge redundant patches',
87
- cycleFunction() {
88
- const removedPatchCount = self.mergeRedundantPatches();
89
-
90
- if (removedPatchCount === 0) {
91
- return TaskSignal.EndSuccess;
92
- }
93
-
94
- return TaskSignal.Continue;
95
- }
96
- });
97
-
98
- tMergePatches.addDependencies(tInitialize);
99
-
100
- result.push(tMergePatches);
101
-
102
- const tSolve = actionTask(() => this.solve());
103
-
104
- tSolve.addDependency(tMergePatches);
105
- tSolve.addDependencies(tInitialize);
106
-
107
- result.push(tSolve);
108
-
109
- return result;
110
- }
111
-
112
-
113
- /**
114
- *
115
- * @param {SplatMapMaterialPatch} patch
116
- */
117
- removePatch(patch) {
118
-
119
- this.graph.removeNode(patch);
120
- patch.quad.disconnect();
121
-
122
- }
123
-
124
- removeNoisePatches(threshold = 0.05) {
125
-
126
- const nodes = this.graph.nodes;
127
- const n = nodes.length;
128
-
129
- const garbage = [];
130
-
131
- const depth = this.mapping.depth;
132
- const weightData = this.mapping.weightData;
133
-
134
- for (let i = 0; i < n; i++) {
135
- const node = nodes[i];
136
-
137
- const contribution = node.computeMaxWeightContribution(weightData, depth);
138
-
139
- if (contribution < threshold) {
140
- garbage.push(node);
141
- }
142
- }
143
-
144
- for (let i = 0; i < garbage.length; i++) {
145
- this.removePatch(garbage[i]);
146
- }
147
-
148
- }
149
-
150
- /**
151
- *
152
- * @param {number} area
153
- * @param {number} contributionThreshold
154
- */
155
- removePatchesSmallerThan(area, contributionThreshold = 0.9) {
156
-
157
- const nodes = this.graph.nodes;
158
- const n = nodes.length;
159
-
160
- const garbage = [];
161
-
162
- const depth = this.mapping.depth;
163
- const weightData = this.mapping.weightData;
164
-
165
- for (let i = 0; i < n; i++) {
166
-
167
- const node = nodes[i];
168
-
169
- if (node.area >= area) {
170
- continue;
171
- }
172
-
173
- const contribution = node.computeMaxWeightContribution(weightData, depth);
174
-
175
- if (contribution < contributionThreshold) {
176
- garbage.push(node);
177
- }
178
- }
179
-
180
- for (let i = 0; i < garbage.length; i++) {
181
- this.removePatch(garbage[i]);
182
- }
183
- }
184
-
185
-
186
- /**
187
- * NOTE: uses flooding algorithm to build a patch
188
- * @param {number} materialIndex
189
- * @param {number} start_x
190
- * @param {number} start_y
191
- * @returns {SplatMapMaterialPatch}
192
- */
193
- buildPatch(materialIndex, start_x, start_y) {
194
-
195
- const mapping = this.mapping;
196
-
197
- /**
198
- *
199
- * @type {Uint8Array}
200
- */
201
- const ranking = this.ranking;
202
-
203
- const width = mapping.size.x;
204
- const height = mapping.size.y;
205
-
206
- const layerSize = width * height;
207
-
208
- const targetLayerAddress = layerSize * materialIndex;
209
-
210
- const patch = new SplatMapMaterialPatch(width, height);
211
-
212
- patch.materialIndex = materialIndex;
213
- patch.mask.reset();
214
- patch.aabb.setNegativelyInfiniteBounds();
215
-
216
- const weightData = mapping.weightData;
217
-
218
- const open = [start_y * width + start_x];
219
-
220
- const closed = new BitSet();
221
-
222
- const marks = this.marks;
223
-
224
- main: while (open.length > 0) {
225
- const index = open.pop();
226
- closed.set(index, true);
227
-
228
- const address = index + targetLayerAddress;
229
-
230
- //sample ranking of the patch texel
231
- const rank = ranking[address];
232
-
233
- if (rank > 3) {
234
- //will not fit in the material map, ignore
235
- continue;
236
- }
237
-
238
- const weight = weightData[address];
239
-
240
-
241
- marks.set(address, true);
242
-
243
- const x = index % width;
244
- const y = (index / width) | 0;
245
-
246
- patch.aabb._expandToFit(x, y, x, y);
247
- patch.mask.set(index, true);
248
- patch.area++;
249
-
250
- if (weight === 0) {
251
- //tile has weight of 0, don't bother about its neighbours
252
- continue main;
253
- }
254
-
255
- //build neighbours
256
- if (y > 0) {
257
- const n2 = index - width;
258
-
259
- if (!closed.get(n2)) {
260
- open.push(n2);
261
- }
262
- }
263
-
264
- if (y < height - 1) {
265
- const n3 = index + width;
266
-
267
- if (!closed.get(n3)) {
268
- open.push(n3);
269
- }
270
- }
271
-
272
- if (x > 0) {
273
- const n0 = index - 1;
274
-
275
- if (!closed.get(n0)) {
276
- open.push(n0);
277
- }
278
- }
279
-
280
- if (x < width - 1) {
281
- const n1 = index + 1;
282
-
283
- if (!closed.get(n1)) {
284
- open.push(n1);
285
- }
286
- }
287
-
288
- continue main;
289
-
290
- }
291
-
292
- return patch;
293
- }
294
-
295
- mergeRedundantPatches() {
296
- const graph = this.graph;
297
- const nodes = graph.nodes;
298
-
299
- /**
300
- *
301
- * @type {SplatMapMaterialPatch[][]}
302
- */
303
- const chains = [];
304
-
305
- for (let i = 0; i < nodes.length; i++) {
306
- const patch = nodes[i];
307
-
308
- const chain = [patch];
309
-
310
- graph.traverseSuccessors(patch, n => {
311
- if (n === patch) {
312
- //skip edges to self
313
- return;
314
- }
315
-
316
- if (n.materialIndex === patch.materialIndex) {
317
-
318
- for (let j = 0; j < chains.length; j++) {
319
- const chain = chains[j];
320
-
321
- if (chain.indexOf(n) !== -1) {
322
- //already a part of a chain, skip
323
- return;
324
- }
325
-
326
- }
327
-
328
- chain.push(n);
329
- }
330
- });
331
-
332
- if (chain.length > 1) {
333
- chains.push(chain);
334
- }
335
- }
336
-
337
- //merge chains
338
- let chainCount = chains.length;
339
- for (let i = 0; i < chainCount; i++) {
340
- const chain_0 = chains[i];
341
-
342
- let chain_0_length = chain_0.length;
343
-
344
- for (let j = 0; j < chain_0_length; j++) {
345
- const link_0 = chain_0[j];
346
-
347
- for (let k = i + 1; k < chainCount; k++) {
348
- const chain_1 = chains[k];
349
-
350
- const link_1_index = chain_1.indexOf(link_0);
351
-
352
- if (link_1_index === -1) {
353
- continue;
354
- }
355
-
356
- //found a match, lets merge the chains
357
- for (let m = 0; m < chain_1.length; m++) {
358
- const link_1 = chain_1[m];
359
-
360
- if (chain_0.indexOf(link_1) === -1) {
361
- chain_0.push(link_1);
362
- chain_0_length++;
363
- }
364
- }
365
-
366
- //remove the chain
367
- chains.splice(k, 1);
368
-
369
- //update iterators
370
- k--;
371
- chainCount--;
372
- }
373
- }
374
- }
375
-
376
- /**
377
- *
378
- * @type {SplatMapMaterialPatch[]}
379
- */
380
- const garbage = [];
381
-
382
- //execute merge
383
- for (let i = 0; i < chainCount; i++) {
384
- const chain = chains[i];
385
-
386
- const target = chain[0];
387
-
388
- for (let j = 1; j < chain.length; j++) {
389
-
390
- const source = chain[j];
391
-
392
- if (garbage.indexOf(target) !== -1) {
393
- //desired merge target has already been marked as garbage, skip merge
394
- continue;
395
- }
396
-
397
- target.add(source);
398
-
399
- //take over connections of the source
400
- const neighbours = graph.getNeighbours(source);
401
-
402
- const neighboursCount = neighbours.length;
403
-
404
- for (let j = 0; j < neighboursCount; j++) {
405
- const neighbour = neighbours[j];
406
-
407
- if (neighbour !== target || !graph.edgeExists(target, neighbour)) {
408
- graph.createEdge(target, neighbour);
409
- }
410
- }
411
-
412
- garbage.push(source);
413
-
414
- }
415
- }
416
-
417
- //cleanup
418
- for (let i = 0; i < garbage.length; i++) {
419
- const patch = garbage[i];
420
- this.removePatch(patch);
421
- }
422
-
423
- return garbage.length;
424
- }
425
-
426
- /**
427
- *
428
- * @param {SplatMapMaterialPatch} patch
429
- */
430
- insertPatch(patch) {
431
- /**
432
- *
433
- * @type {SplatMapMaterialPatch[]}
434
- */
435
- const connectedPatches = [];
436
-
437
- this.graph.addNode(patch);
438
-
439
- const bb = patch.aabb;
440
-
441
- /**
442
- *
443
- * @param {QuadTreeDatum<SplatMapMaterialPatch>} container
444
- * @param {number} x
445
- * @param {number} y
446
- */
447
- function visitOverlap(container, x, y) {
448
- const otherPatch = container.data;
449
-
450
- const overlaps = otherPatch.test(x, y);
451
-
452
- if (overlaps && connectedPatches.indexOf(otherPatch) === -1) {
453
- connectedPatches.push(otherPatch);
454
- }
455
- }
456
-
457
- //traverse the patch to find overlaps with other patches
458
- for (let y = bb.y0; y <= bb.y1; y++) {
459
-
460
- for (let x = bb.x0; x <= bb.x1; x++) {
461
-
462
- if (!patch.test(x, y)) {
463
- continue;
464
- }
465
-
466
- this.quadTree.traversePointIntersections(x, y, visitOverlap);
467
- }
468
-
469
- }
470
-
471
- if (patch.quad !== null) {
472
- patch.quad.disconnect();
473
- }
474
-
475
- const quadTreeDatum = this.quadTree.add(patch, bb.x0, bb.y0, bb.x1, bb.y1);
476
- patch.quad = quadTreeDatum;
477
-
478
- //insert edges to connecting patches
479
- for (let i = 0; i < connectedPatches.length; i++) {
480
- const other = connectedPatches[i];
481
-
482
- this.graph.createEdge(patch, other);
483
- }
484
-
485
- }
486
-
487
- /**
488
- *
489
- * @return {Task[]}
490
- */
491
- initialize() {
492
- this.graph.clear();
493
-
494
- //build patches
495
-
496
- const mapping = this.mapping;
497
-
498
- const width = mapping.size.x;
499
- const height = mapping.size.y;
500
- const depth = mapping.depth;
501
-
502
- const weightData = mapping.weightData;
503
-
504
- this.ranking = new Uint8Array(width * height * depth);
505
-
506
- //build ranking map
507
- const tComputeRankings = mapping.computeWeightRankingMap(this.ranking);
508
-
509
- const marks = this.marks;
510
- marks.reset();
511
-
512
- const layerSize = width * height;
513
-
514
- const tBuildPatches = countTask(0, layerSize * depth, address => {
515
-
516
- if (marks.get(address)) {
517
- //already processed, skip
518
- return;
519
- }
520
-
521
- if (weightData[address] === 0) {
522
- //tile has 0 weight, don't make a patch for it
523
- return;
524
- }
525
-
526
- const d = (address / layerSize) | 0;
527
- const layerIndex = address % layerSize;
528
-
529
-
530
- const y = (layerIndex / width) | 0;
531
- const x = layerIndex % width;
532
-
533
- const patch = this.buildPatch(d, x, y);
534
-
535
- if (patch.area === 0) {
536
- return;
537
- }
538
-
539
- this.insertPatch(patch);
540
-
541
- });
542
-
543
- tBuildPatches.addDependency(tComputeRankings);
544
-
545
- return [tComputeRankings, tBuildPatches];
546
- }
547
-
548
- solve() {
549
- const nodes = this.graph.nodes;
550
-
551
- let nodeCount = nodes.length;
552
-
553
- const weightData = this.mapping.weightData;
554
- const materialSampler = this.mapping.materialSampler;
555
-
556
- for (let i = 0; i < nodeCount; i++) {
557
- const patch = nodes[i];
558
-
559
- patch.readWeights(weightData);
560
- }
561
-
562
- nodeCount = nodes.length;
563
-
564
- const colors = colorizeGraphGreedyWeight(this.graph);
565
-
566
- if (!validateGraphColoring(colors, this.graph)) {
567
- console.error('Created invalid graph coloring');
568
- }
569
-
570
- //split colored sets into those that fit the channel set and those that don't
571
- const colorMappableNodes = [];
572
- const colorFloatingNodes = [];
573
-
574
- for (let i = 0; i < nodeCount; i++) {
575
- const c = colors[i];
576
-
577
- if (c >= materialSampler.itemSize) {
578
- colorFloatingNodes.push(i);
579
- } else {
580
- colorMappableNodes.push(i);
581
- }
582
- }
583
-
584
-
585
- //purge materials
586
- const materialData = materialSampler.data;
587
- materialData.fill(255);
588
-
589
- const occupancy = new BitSet();
590
-
591
- for (let i = 0; i < colorMappableNodes.length; i++) {
592
- const nodeIndex = colorMappableNodes[i];
593
-
594
- const patch = nodes[nodeIndex];
595
-
596
- const color = colors[nodeIndex];
597
-
598
- patch.write(color, materialSampler, occupancy);
599
-
600
- }
601
-
602
- for (let i = 0; i < colorFloatingNodes.length; i++) {
603
- const nodeIndex = colorFloatingNodes[i];
604
-
605
- const patch = nodes[nodeIndex];
606
-
607
- patch.writeByOccupancy(materialSampler, occupancy);
608
- }
609
-
610
- this.mapping.materialTexture.needsUpdate = true;
611
- this.mapping.weightTexture.needsUpdate = true;
612
-
613
- // const d = new SplatMapOptimizerDebugger();
614
- //
615
- // const v = d.build(this.graph);
616
- //
617
- // v.link();
618
- // window.document.body.appendChild(v.el);
619
-
620
- return colors;
621
- }
622
- }