@woosh/meep-engine 2.43.17 → 2.43.19

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 (68) hide show
  1. package/core/assert.js +3 -1
  2. package/core/bvh2/aabb3/aabb3_intersects_ray.js +14 -9
  3. package/core/bvh2/aabb3/aabb3_intersects_ray_branchless.js +52 -0
  4. package/core/bvh2/bvh3/ExplicitBinaryBoundingVolumeHierarchy.d.ts +2 -0
  5. package/core/bvh2/bvh3/ExplicitBinaryBoundingVolumeHierarchy.js +162 -10
  6. package/core/bvh2/bvh3/ebvh_build_for_geometry_incremental.js +34 -0
  7. package/core/bvh2/bvh3/ebvh_build_for_geometry_morton.js +175 -0
  8. package/core/bvh2/bvh3/ebvh_sort_for_traversal_depth_first.js +122 -0
  9. package/core/bvh2/bvh3/{bvh_collect_user_data.js → query/bvh_collect_user_data.js} +1 -1
  10. package/core/bvh2/bvh3/{bvh_query_leaves_generic.js → query/bvh_query_leaves_generic.js} +1 -1
  11. package/core/bvh2/bvh3/query/bvh_query_leaves_ray.js +97 -0
  12. package/core/bvh2/bvh3/{bvh_query_user_data_generic.js → query/bvh_query_user_data_generic.js} +1 -1
  13. package/core/bvh2/bvh3/{bvh_query_user_data_nearest_to_point.js → query/bvh_query_user_data_nearest_to_point.js} +3 -3
  14. package/core/bvh2/bvh3/{bvh_query_user_data_nearest_to_point.spec.js → query/bvh_query_user_data_nearest_to_point.spec.js} +1 -1
  15. package/core/bvh2/bvh3/{bvh_query_user_data_overlaps_frustum.js → query/bvh_query_user_data_overlaps_frustum.js} +2 -2
  16. package/core/bvh2/bvh3/query/compute_tight_near_far_clipping_planes.js +1 -1
  17. package/core/collection/array/arrayQuickSort.js +1 -1
  18. package/core/collection/array/typed/typed_array_copy.js +2 -2
  19. package/core/geom/3d/aabb/compute_aabb_from_points.js +4 -3
  20. package/core/geom/3d/compute_triangle_normal.js +76 -0
  21. package/core/geom/3d/topology/samples/sampleFloodFill.js +1 -1
  22. package/core/geom/3d/topology/simplify/compute_face_normal_change_dot_product.js +1 -1
  23. package/core/geom/3d/topology/simplify/quadratic/Quadratic3.js +1 -1
  24. package/core/geom/3d/topology/struct/TopoTriangle.js +1 -57
  25. package/core/geom/3d/topology/tm_face_normal.js +1 -1
  26. package/core/geom/3d/topology/tm_vertex_compute_normal.js +1 -1
  27. package/core/geom/3d/triangle/computeTriangleRayIntersection.js +195 -27
  28. package/core/geom/Vector3.js +12 -12
  29. package/core/math/physics/brdf/D_GGX.js +13 -0
  30. package/editor/tools/v2/prototypeTransformControls.js +14 -2
  31. package/engine/ecs/parent/EntityNode.js +80 -7
  32. package/engine/ecs/parent/EntityNodeFlags.js +8 -0
  33. package/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +2 -2
  34. package/engine/graphics/ecs/mesh-v2/aggregate/SGMesh.js +9 -1
  35. package/engine/graphics/ecs/mesh-v2/render/ShadedGeometryRendererContext.js +1 -1
  36. package/engine/graphics/geometry/AttributeSpec.js +18 -3
  37. package/engine/graphics/geometry/VertexDataSpec.js +53 -3
  38. package/engine/graphics/micron/format/VirtualGeometry.js +7 -0
  39. package/engine/graphics/micron/render/VirtualGeometryBuilder.js +1 -1
  40. package/engine/graphics/micron/render/refinement/get_geometry_patch_cut.js +5 -2
  41. package/engine/graphics/particles/particular/engine/parameter/sample/RGBA_LUT_HEATMAP_IR.js +11 -0
  42. package/engine/graphics/particles/particular/engine/utils/volume/prototypeParticleVolume.js +2 -9
  43. package/engine/graphics/render/forward_plus/model/DirectionalLight.js +40 -0
  44. package/engine/graphics/sh3/README.md +1 -0
  45. package/engine/graphics/sh3/path_tracer/GeometryBVHBatched.js +265 -0
  46. package/engine/graphics/sh3/path_tracer/PathTracedMesh.js +85 -0
  47. package/engine/graphics/sh3/path_tracer/PathTracer.js +534 -0
  48. package/engine/graphics/sh3/path_tracer/apply_texture_clamping_to_coordinate.js +22 -0
  49. package/engine/graphics/sh3/path_tracer/compute_triangle_group_aabb3.js +36 -0
  50. package/engine/graphics/sh3/path_tracer/getBiasedNormalSample.js +55 -0
  51. package/engine/graphics/sh3/path_tracer/make_one_vector3.js +7 -0
  52. package/engine/graphics/sh3/path_tracer/make_sky_hosek.js +44 -0
  53. package/engine/graphics/sh3/path_tracer/make_sky_rtiw.js +17 -0
  54. package/engine/graphics/sh3/path_tracer/make_zero_vector3.js +7 -0
  55. package/engine/graphics/sh3/path_tracer/prototypePathTracer.js +631 -0
  56. package/engine/graphics/sh3/path_tracer/random_in_hemisphere.js +39 -0
  57. package/engine/graphics/sh3/path_tracer/ray_hit_apply_transform.js +42 -0
  58. package/engine/graphics/sh3/path_tracer/ray_reflect.js +27 -0
  59. package/engine/graphics/sh3/path_tracer/sample_triangle_attribute.js +35 -0
  60. package/engine/graphics/sh3/path_tracer/vec3_uint8_to_float.js +12 -0
  61. package/engine/graphics/sh3/sky/hosek/README.md +4 -0
  62. package/engine/graphics/sh3/sky/hosek/prototype_hosek.js +71 -0
  63. package/engine/graphics/sh3/sky/hosek/sky_hosek_compute_irradiance_by_direction.js +4171 -0
  64. package/engine/graphics/texture/sampler/convertTexture2Sampler2D.js +2 -0
  65. package/package.json +1 -1
  66. package/view/elements/progress/SmoothProgressBar.js +1 -1
  67. package/view/task/TaskProgressView.js +6 -8
  68. package/core/bvh2/bvh3/bvh_query_leaves_ray.js +0 -95
package/core/assert.js CHANGED
@@ -380,7 +380,9 @@ assert.notNaN = function (value, name = "value") {
380
380
  * @param {string} name
381
381
  */
382
382
  assert.isFiniteNumber = function (value, name = "value") {
383
- assert.ok(Number.isFinite(value), `${name} must be a finite number, instead was ${value}`);
383
+ if (!Number.isFinite(value)) {
384
+ throw new Error(`${name} must be a finite number, instead was ${value}`);
385
+ }
384
386
  };
385
387
 
386
388
  export {
@@ -1,5 +1,6 @@
1
1
  import { fabsf } from "../../math/fabsf.js";
2
2
 
3
+
3
4
  /**
4
5
  * NOTES:
5
6
  * https://web.archive.org/web/20090803054252/http://tog.acm.org/resources/GraphicsGems/gems/RayBox.c
@@ -20,41 +21,46 @@ import { fabsf } from "../../math/fabsf.js";
20
21
  * @param {number} dirZ
21
22
  * @returns {boolean}
22
23
  */
23
- function aabb3_intersects_ray(x0, y0, z0, x1, y1, z1, oX, oY, oZ, dirX, dirY, dirZ) {
24
+ export function aabb3_intersects_ray(
25
+ x0, y0, z0,
26
+ x1, y1, z1,
27
+ oX, oY, oZ,
28
+ dirX, dirY, dirZ
29
+ ) {
24
30
 
25
31
  // Z Projection
26
- const boxExtentsX = (x1 - x0) / 2;
32
+ const boxExtentsX = (x1 - x0) * 0.5;
27
33
 
28
34
  const centerX = x0 + boxExtentsX;
29
35
 
30
36
  const diffX = oX - centerX;
31
37
 
32
38
 
33
- if (fabsf(diffX) > boxExtentsX && diffX * dirX >= 0.0) {
39
+ if (diffX * dirX >= 0.0 && fabsf(diffX) > boxExtentsX) {
34
40
  return false;
35
41
  }
36
42
 
37
43
  // Y projection
38
- const boxExtentsY = (y1 - y0) / 2;
44
+ const boxExtentsY = (y1 - y0) * 0.5;
39
45
 
40
46
  const centerY = y0 + boxExtentsY;
41
47
 
42
48
  const diffY = oY - centerY;
43
49
 
44
50
 
45
- if (fabsf(diffY) > boxExtentsY && diffY * dirY >= 0.0) {
51
+ if (diffY * dirY >= 0.0 && fabsf(diffY) > boxExtentsY) {
46
52
  return false;
47
53
  }
48
54
 
49
55
  // Z projection
50
- const boxExtentsZ = (z1 - z0) / 2;
56
+ const boxExtentsZ = (z1 - z0) * 0.5;
51
57
 
52
58
  const centerZ = z0 + boxExtentsZ;
53
59
 
54
60
  const diffZ = oZ - centerZ;
55
61
 
56
62
 
57
- if (fabsf(diffZ) > boxExtentsZ && diffZ * dirZ >= 0.0) {
63
+ if (diffZ * dirZ >= 0.0 && fabsf(diffZ) > boxExtentsZ) {
58
64
  return false;
59
65
  }
60
66
 
@@ -64,9 +70,9 @@ function aabb3_intersects_ray(x0, y0, z0, x1, y1, z1, oX, oY, oZ, dirX, dirY, di
64
70
  //b = fabsf(Dir.y);
65
71
  //if(fabsf(Diff.y)>BoxExtents.y + b) return false;
66
72
 
67
- const a = fabsf(dirX);
68
73
  const b = fabsf(dirY);
69
74
  const c = fabsf(dirZ);
75
+ const a = fabsf(dirX);
70
76
 
71
77
  const f0 = dirY * diffZ - dirZ * diffY;
72
78
 
@@ -89,4 +95,3 @@ function aabb3_intersects_ray(x0, y0, z0, x1, y1, z1, oX, oY, oZ, dirX, dirY, di
89
95
  return true;
90
96
  }
91
97
 
92
- export { aabb3_intersects_ray };
@@ -0,0 +1,52 @@
1
+ import { min2 } from "../../math/min2.js";
2
+ import { max2 } from "../../math/max2.js";
3
+
4
+ /**
5
+ * SLOW, don't use in production
6
+ * It is branchless on systems where min/max instructions exist.
7
+ * Unfortunately in JS that's not the case, so this is just a curiosity
8
+ * @see https://tavianator.com/2011/ray_box.html
9
+ * @param {number} x0
10
+ * @param {number} y0
11
+ * @param {number} z0
12
+ * @param {number} x1
13
+ * @param {number} y1
14
+ * @param {number} z1
15
+ * @param {number} oX
16
+ * @param {number} oY
17
+ * @param {number} oZ
18
+ * @param {number} dirX
19
+ * @param {number} dirY
20
+ * @param {number} dirZ
21
+ * @returns {boolean}
22
+ */
23
+ export function aabb3_intersects_ray_branchless(
24
+ x0, y0, z0,
25
+ x1, y1, z1,
26
+ oX, oY, oZ,
27
+ dirX, dirY, dirZ
28
+ ) {
29
+ const inv_dir_x = 1 / dirX;
30
+ const inv_dir_y = 1 / dirY;
31
+ const inv_dir_z = 1 / dirZ;
32
+
33
+ const tx1 = (x0 - oX) * inv_dir_x;
34
+ const tx2 = (x1 - oX) * inv_dir_x;
35
+
36
+ let tmin = min2(tx1, tx2);
37
+ let tmax = max2(tx1, tx2);
38
+
39
+ const ty1 = (y0 - oY) * inv_dir_y;
40
+ const ty2 = (y1 - oY) * inv_dir_y;
41
+
42
+ tmin = max2(tmin, min2(ty1, ty2));
43
+ tmax = min2(tmax, max2(ty1, ty2));
44
+
45
+ const tz1 = (z0 - oZ) * inv_dir_z;
46
+ const tz2 = (z1 - oZ) * inv_dir_z;
47
+
48
+ tmin = max2(tmin, min2(tz1, tz2));
49
+ tmax = min2(tmax, max2(tz1, tz2));
50
+
51
+ return tmax >= tmin;
52
+ }
@@ -20,4 +20,6 @@ export class ExplicitBinaryBoundingVolumeHierarchy {
20
20
  node_get_user_data(node: number): number
21
21
 
22
22
  release_all(): void
23
+
24
+ trim(): void
23
25
  }
@@ -2,11 +2,13 @@ import { aabb3_compute_surface_area } from "../aabb3/aabb3_compute_surface_area.
2
2
  import { min2 } from "../../math/min2.js";
3
3
  import { max2 } from "../../math/max2.js";
4
4
  import { assert } from "../../assert.js";
5
+ import { typed_array_copy } from "../../collection/array/typed/typed_array_copy.js";
6
+ import { array_copy } from "../../collection/array/copyArray.js";
5
7
 
6
- const COLUMN_PARENT = 6;
7
- const COLUMN_CHILD_1 = 7;
8
- const COLUMN_CHILD_2 = 8;
9
- const COLUMN_HEIGHT = 9;
8
+ export const COLUMN_PARENT = 6;
9
+ export const COLUMN_CHILD_1 = 7;
10
+ export const COLUMN_CHILD_2 = 8;
11
+ export const COLUMN_HEIGHT = 9;
10
12
 
11
13
  /**
12
14
  * A non-leaf node have both CHILD_1 and CHILD_2 set, when CHILD_1 is not set - it's a leaf node
@@ -40,7 +42,7 @@ const CAPACITY_GROW_MIN_STEP = 64;
40
42
  * @readonly
41
43
  * @type {number}
42
44
  */
43
- const ELEMENT_WORD_COUNT = 10;
45
+ export const ELEMENT_WORD_COUNT = 10;
44
46
 
45
47
  /**
46
48
  * How many nodes can be stored in the newly constructed tree before allocation needs to take place
@@ -134,6 +136,16 @@ export class ExplicitBinaryBoundingVolumeHierarchy {
134
136
  this.__capacity + CAPACITY_GROW_MIN_STEP
135
137
  ));
136
138
 
139
+ this.__set_capacity(new_capacity);
140
+ }
141
+
142
+ /**
143
+ *
144
+ * @param {number} new_capacity
145
+ * @private
146
+ */
147
+ __set_capacity(new_capacity) {
148
+
137
149
  const old_data_uint32 = this.__data_uint32;
138
150
 
139
151
  const new_data_buffer = new ArrayBuffer(new_capacity * ELEMENT_WORD_COUNT * 4);
@@ -144,11 +156,20 @@ export class ExplicitBinaryBoundingVolumeHierarchy {
144
156
  this.__data_uint32 = new Uint32Array(new_data_buffer);
145
157
 
146
158
  // copy old data into new buffer
147
- this.__data_uint32.set(old_data_uint32);
159
+ typed_array_copy(old_data_uint32, this.__data_uint32);
148
160
 
149
161
  this.__capacity = new_capacity;
150
162
  }
151
163
 
164
+ /**
165
+ * Trim allocated memory region to only contain allocated nodes
166
+ */
167
+ trim() {
168
+ if (this.__capacity > this.__size) {
169
+ this.__set_capacity(this.__size);
170
+ }
171
+ }
172
+
152
173
  /**
153
174
  *
154
175
  * @returns {number}
@@ -260,6 +281,15 @@ export class ExplicitBinaryBoundingVolumeHierarchy {
260
281
  return this.__data_uint32[ELEMENT_WORD_COUNT * id + COLUMN_CHILD_1];
261
282
  }
262
283
 
284
+ /**
285
+ *
286
+ * @param {number} node
287
+ * @param {number} child1
288
+ */
289
+ node_set_child1(node, child1) {
290
+ this.__data_uint32[ELEMENT_WORD_COUNT * node + COLUMN_CHILD_1] = child1;
291
+ }
292
+
263
293
  /**
264
294
  *
265
295
  * @param {number} id
@@ -270,6 +300,54 @@ export class ExplicitBinaryBoundingVolumeHierarchy {
270
300
  return this.__data_uint32[ELEMENT_WORD_COUNT * id + COLUMN_CHILD_2];
271
301
  }
272
302
 
303
+ /**
304
+ *
305
+ * @param {number} node
306
+ * @param {number} child2
307
+ */
308
+ node_set_child2(node, child2) {
309
+ this.__data_uint32[ELEMENT_WORD_COUNT * node + COLUMN_CHILD_2] = child2;
310
+ }
311
+
312
+ /**
313
+ *
314
+ * @param {number} id
315
+ * @returns {number}
316
+ */
317
+ node_get_parent(id) {
318
+ assert.isNonNegativeInteger(id, 'id');
319
+ return this.__data_uint32[ELEMENT_WORD_COUNT * id + COLUMN_PARENT];
320
+ }
321
+
322
+ /**
323
+ *
324
+ * @param {number} node
325
+ * @param {number} parent
326
+ */
327
+ node_set_parent(node, parent) {
328
+ this.__data_uint32[ELEMENT_WORD_COUNT * node + COLUMN_PARENT] = parent;
329
+ }
330
+
331
+
332
+ /**
333
+ *
334
+ * @param {number} id
335
+ * @returns {number}
336
+ */
337
+ node_get_height(id) {
338
+ assert.isNonNegativeInteger(id, 'id');
339
+ return this.__data_uint32[ELEMENT_WORD_COUNT * id + COLUMN_HEIGHT];
340
+ }
341
+
342
+ /**
343
+ *
344
+ * @param {number} id
345
+ * @param {number} height
346
+ */
347
+ node_set_height(id, height) {
348
+ assert.isNonNegativeInteger(id, 'id');
349
+ this.__data_uint32[ELEMENT_WORD_COUNT * id + COLUMN_HEIGHT] = height;
350
+ }
273
351
 
274
352
  /**
275
353
  *
@@ -946,10 +1024,84 @@ export class ExplicitBinaryBoundingVolumeHierarchy {
946
1024
  }
947
1025
 
948
1026
  /**
949
- * Sort live nodes in the traversal order.
950
- * This makes most queries have linear access, resulting in near-optimal cache usage
1027
+ * Update parent and child links of a given node to point to a new location, useful for re-locating nodes
1028
+ * @param {number} node node to update
1029
+ * @param {number} destination Where updated links should point to
1030
+ * @private
951
1031
  */
952
- sort_for_traversal_depth_first() {
953
- throw new Error('Not Implemented');
1032
+ __move_node_links(node, destination) {
1033
+
1034
+ const uint32 = this.__data_uint32;
1035
+
1036
+ const source_address = node * ELEMENT_WORD_COUNT;
1037
+
1038
+ // update children of a
1039
+ const child1 = uint32[source_address + COLUMN_CHILD_1];
1040
+ const child2 = uint32[source_address + COLUMN_CHILD_2];
1041
+
1042
+ if (child1 !== NULL_NODE) {
1043
+
1044
+ uint32[child1 * ELEMENT_WORD_COUNT + COLUMN_PARENT] = destination;
1045
+ uint32[child2 * ELEMENT_WORD_COUNT + COLUMN_PARENT] = destination;
1046
+
1047
+ }
1048
+
1049
+ // update parent of a
1050
+ const parent = uint32[source_address + COLUMN_PARENT];
1051
+
1052
+ if (parent !== NULL_NODE) {
1053
+ const parent_child1 = uint32[parent * ELEMENT_WORD_COUNT + COLUMN_CHILD_1];
1054
+
1055
+ if (parent_child1 === node) {
1056
+ uint32[parent * ELEMENT_WORD_COUNT + COLUMN_CHILD_1] = destination;
1057
+ } else {
1058
+ uint32[parent * ELEMENT_WORD_COUNT + COLUMN_CHILD_2] = destination;
1059
+ }
1060
+ }
1061
+ }
1062
+
1063
+ /**
1064
+ * Swap two nodes in memory
1065
+ * @param {number} a
1066
+ * @param {number} b
1067
+ * @returns {boolean}
1068
+ */
1069
+ swap_nodes(a, b) {
1070
+ // console.log(`swap ${a} - ${b}`)
1071
+
1072
+ const uint32 = this.__data_uint32;
1073
+
1074
+ const address_a = a * ELEMENT_WORD_COUNT;
1075
+ const address_b = b * ELEMENT_WORD_COUNT;
1076
+
1077
+
1078
+ if (uint32[address_a + COLUMN_PARENT] === b) {
1079
+ // attempting to swap direct parent/child, this is unsupported
1080
+ return false;
1081
+ }
1082
+ if (uint32[address_b + COLUMN_PARENT] === a) {
1083
+ // attempting to swap direct parent/child, this is unsupported
1084
+ return false;
1085
+ }
1086
+
1087
+ this.__move_node_links(a, b);
1088
+ this.__move_node_links(b, a);
1089
+
1090
+ // copy A to temp buffer
1091
+ array_copy(uint32, address_a, this.__free, this.__free_pointer, ELEMENT_WORD_COUNT);
1092
+
1093
+ // write data
1094
+ array_copy(uint32, address_b, uint32, address_a, ELEMENT_WORD_COUNT);
1095
+ array_copy(this.__free, this.__free_pointer, uint32, address_b, ELEMENT_WORD_COUNT);
1096
+
1097
+ // update root as necessary
1098
+ if (this.__root === a) {
1099
+ this.__root = b;
1100
+ } else if (this.__root === b) {
1101
+ this.__root = a;
1102
+ }
1103
+
1104
+ return true;
954
1105
  }
955
1106
  }
1107
+
@@ -0,0 +1,34 @@
1
+ import { min2 } from "../../math/min2.js";
2
+ import { compute_triangle_group_aabb3 } from "../../../engine/graphics/sh3/path_tracer/compute_triangle_group_aabb3.js";
3
+
4
+ /**
5
+ * Build the BVH by relying on BVH's own incremental insertion mechanism
6
+ * @param {ExplicitBinaryBoundingVolumeHierarchy} bvh
7
+ * @param {number[]|Float32Array} index_array
8
+ * @param {number[]|Float32Array} positions_array
9
+ * @param {number} [batch_size] can batch triangles in groups of up-to this many triangles per BVH leaf
10
+ */
11
+ export function ebvh_build_for_geometry_incremental(bvh, index_array, positions_array, batch_size = 1) {
12
+ bvh.release_all();
13
+ const triangle_count = index_array.length / 3;
14
+
15
+ const leaf_node_count = Math.ceil(triangle_count / batch_size);
16
+
17
+ const leaf_aabb = [];
18
+
19
+ for (let i = 0; i < leaf_node_count; i++) {
20
+
21
+ const index_start = i * batch_size;
22
+ const index_end = min2(triangle_count, index_start + batch_size);
23
+
24
+ const group_size = index_end - index_start;
25
+
26
+ compute_triangle_group_aabb3(leaf_aabb, 0, index_array, positions_array, index_start * 3, group_size, 3);
27
+
28
+ const node_index = bvh.allocate_node();
29
+ bvh.node_set_aabb(node_index, leaf_aabb);
30
+ bvh.node_set_user_data(node_index, i);
31
+
32
+ bvh.insert_leaf(node_index);
33
+ }
34
+ }
@@ -0,0 +1,175 @@
1
+ import { AABB3 } from "../aabb3/AABB3.js";
2
+ import { aabb3_from_v3_array } from "../aabb3/aabb3_from_v3_array.js";
3
+ import morton from "../../geom/3d/morton/Morton.js";
4
+ import { arrayQuickSort } from "../../collection/array/arrayQuickSort.js";
5
+ import { compute_triangle_group_aabb3 } from "../../../engine/graphics/sh3/path_tracer/compute_triangle_group_aabb3.js";
6
+ import { NULL_NODE } from "./ExplicitBinaryBoundingVolumeHierarchy.js";
7
+ import { max2 } from "../../math/max2.js";
8
+
9
+ /**
10
+ * Build the BVH bottom-up using spatial hash sorting
11
+ * Resulting BVH is produced very quickly, has perfect balance and is of decent quality
12
+ * @param {ExplicitBinaryBoundingVolumeHierarchy} bvh
13
+ * @param {number[]|Float32Array} index_array
14
+ * @param {number[]|Float32Array} position_array
15
+ */
16
+ export function ebvh_build_for_geometry_morton(bvh, index_array, position_array) {
17
+ // clear out existing BVH
18
+ bvh.release_all();
19
+
20
+ const aabb3 = new AABB3();
21
+
22
+ // get bounds for the entire geometry
23
+ aabb3_from_v3_array(aabb3, position_array, position_array.length);
24
+
25
+ const aabb_x0 = aabb3.x0;
26
+ const aabb_y0 = aabb3.y0;
27
+ const aabb_z0 = aabb3.z0;
28
+
29
+ const aabb_size_x = aabb3.getExtentsX();
30
+ const aabb_size_y = aabb3.getExtentsY();
31
+ const aabb_size_z = aabb3.getExtentsZ();
32
+
33
+ const morton_scale_x = aabb_size_x === 0 ? 0 : 1023 / aabb_size_x;
34
+ const morton_scale_y = aabb_size_y === 0 ? 0 : 1023 / aabb_size_y;
35
+ const morton_scale_z = aabb_size_z === 0 ? 0 : 1023 / aabb_size_z;
36
+
37
+ // allocate nodes
38
+ const tri_count = index_array.length / 3;
39
+ const node_leaf_count = tri_count;
40
+ const node_bin_count = Math.ceil(node_leaf_count / 2) * 2 - 1;
41
+
42
+ const node_total_count = node_leaf_count + node_bin_count;
43
+
44
+ const nodes = new Uint32Array(node_total_count);
45
+
46
+ // skip allocation calls, allocate exactly as many nodes as we need
47
+ bvh.__set_capacity(node_total_count);
48
+ bvh.__size = node_total_count;
49
+
50
+ for (let i = 0; i < node_total_count; i++) {
51
+ // store nodes in reverse order so that top-level nodes end up on top
52
+ nodes[i] = (node_total_count - 1) - i;
53
+ }
54
+
55
+ // for (let i = node_total_count - 1; i >= 0; i--) {
56
+ // // store nodes in reverse order so that top-level nodes end up on top
57
+ // nodes[i] = bvh.allocate_node();
58
+ // }
59
+
60
+ const morton_codes = new Uint32Array(tri_count);
61
+ const sorted_triangle_order = new Uint32Array(tri_count);
62
+
63
+ // compute morton codes
64
+ for (let i = 0; i < tri_count; i++) {
65
+
66
+ sorted_triangle_order[i] = i;
67
+
68
+ const i3 = i * 3;
69
+
70
+ const a = index_array[i3];
71
+ const b = index_array[i3 + 1];
72
+ const c = index_array[i3 + 2];
73
+
74
+ const a_address = a * 3;
75
+ const ax = position_array[a_address];
76
+ const ay = position_array[a_address + 1];
77
+ const az = position_array[a_address + 2];
78
+
79
+ const b_address = b * 3;
80
+ const bx = position_array[b_address];
81
+ const by = position_array[b_address + 1];
82
+ const bz = position_array[b_address + 2];
83
+
84
+ const c_address = c * 3;
85
+ const cx = position_array[c_address];
86
+ const cy = position_array[c_address + 1];
87
+ const cz = position_array[c_address + 2];
88
+
89
+ const center_x = (ax + bx + cx) * 0.333333333;
90
+ const center_y = (ay + by + cy) * 0.333333333;
91
+ const center_z = (az + bz + cz) * 0.333333333;
92
+
93
+
94
+ // normalize to bounds
95
+ const ncx = (center_x - aabb_x0) * morton_scale_x;
96
+ const ncy = (center_y - aabb_y0) * morton_scale_y;
97
+ const ncz = (center_z - aabb_z0) * morton_scale_z;
98
+
99
+ const morton_code = morton(
100
+ Math.round(ncx),
101
+ Math.round(ncy),
102
+ Math.round(ncz)
103
+ );
104
+
105
+ morton_codes[i] = morton_code;
106
+ }
107
+
108
+ // sort leaves by morton codes
109
+ arrayQuickSort(sorted_triangle_order, (triangle_index) => morton_codes[triangle_index], null, 0, tri_count - 1);
110
+
111
+ let used_index = 0;
112
+ const unprocessed_nodes = new Uint32Array(tri_count);
113
+ // assign leaves
114
+ const aabb_array = new Float32Array(6);
115
+ for (let i = 0; i < tri_count; i++) {
116
+ const node = nodes[used_index++];
117
+
118
+ const triangle_index = sorted_triangle_order[i];
119
+
120
+ bvh.node_set_child1(node, NULL_NODE);
121
+ bvh.node_set_user_data(node, triangle_index);
122
+
123
+ bvh.node_set_height(node, 0);
124
+
125
+ compute_triangle_group_aabb3(aabb_array, 0, index_array, position_array, triangle_index * 3, 1, 3);
126
+
127
+ bvh.node_set_aabb(node, aabb_array);
128
+
129
+ unprocessed_nodes[i] = node;
130
+ }
131
+
132
+ // Assemble hierarchy
133
+ let unprocessed_node_count = tri_count;
134
+
135
+
136
+ while (used_index < node_total_count) {
137
+
138
+ let added_nodes = 0;
139
+ let cursor = 0;
140
+
141
+ while (cursor + 1 < unprocessed_node_count) {
142
+ const child_1 = unprocessed_nodes[cursor++];
143
+ const child_2 = unprocessed_nodes[cursor++];
144
+
145
+ const parent = nodes[used_index++];
146
+
147
+ bvh.node_set_combined_aabb(parent, child_1, child_2);
148
+
149
+ bvh.node_set_parent(child_1, parent);
150
+ bvh.node_set_parent(child_2, parent);
151
+
152
+ bvh.node_set_child1(parent, child_1);
153
+ bvh.node_set_child2(parent, child_2);
154
+
155
+ bvh.node_set_height(parent,
156
+ 1 + max2(
157
+ bvh.node_get_height(child_1),
158
+ bvh.node_get_height(child_2)
159
+ )
160
+ );
161
+
162
+ unprocessed_nodes[added_nodes++] = parent;
163
+ }
164
+
165
+ while (cursor < unprocessed_node_count) {
166
+ // dangling nodes, push them onto the next level
167
+ unprocessed_nodes[added_nodes++] = unprocessed_nodes[cursor++];
168
+ }
169
+
170
+ unprocessed_node_count = added_nodes;
171
+ }
172
+
173
+ // assign root
174
+ bvh.__root = nodes[nodes.length - 1];
175
+ }
@@ -0,0 +1,122 @@
1
+ import {
2
+ COLUMN_CHILD_1,
3
+ COLUMN_HEIGHT,
4
+ COLUMN_PARENT,
5
+ ELEMENT_WORD_COUNT,
6
+ NULL_NODE
7
+ } from "./ExplicitBinaryBoundingVolumeHierarchy.js";
8
+
9
+ /**
10
+ * Sort live nodes in the traversal order.
11
+ * This makes most queries have linear access, resulting in near-optimal cache usage
12
+ * NOTE: assumes that there are no free nodes
13
+ * @param {ExplicitBinaryBoundingVolumeHierarchy} bvh
14
+ */
15
+ export function ebvh_sort_for_traversal_depth_first(bvh) {
16
+ const stack = [];
17
+
18
+ stack[0] = 0;
19
+ stack[1] = bvh.__size - 1;
20
+
21
+ let stack_pointer = 2;
22
+ let i, j;
23
+
24
+ const uint32 = bvh.__data_uint32;
25
+ const float32 = bvh.__data_float32;
26
+
27
+ while (stack_pointer > 0) {
28
+ stack_pointer -= 2;
29
+
30
+ const right = stack[stack_pointer + 1];
31
+ const left = stack[stack_pointer];
32
+
33
+ i = left;
34
+ j = right;
35
+
36
+ const pivotIndex = (left + right) >> 1;
37
+
38
+ /* partition */
39
+ while (i <= j) {
40
+
41
+ while (traversal_sort_compare_nodes(uint32, float32, i, pivotIndex) > 0) {
42
+ i++;
43
+ }
44
+
45
+ while (traversal_sort_compare_nodes(uint32, float32, j, pivotIndex) < 0) {
46
+ j--;
47
+ }
48
+
49
+ if (i <= j) {
50
+
51
+ if (i !== j) {
52
+ //do swap
53
+ bvh.swap_nodes(i, j);
54
+ }
55
+
56
+ i++;
57
+ j--;
58
+ }
59
+ }
60
+
61
+ /* recursion */
62
+ if (left < j) {
63
+ stack[stack_pointer++] = left;
64
+ stack[stack_pointer++] = j;
65
+ }
66
+ if (i < right) {
67
+ stack[stack_pointer++] = i;
68
+ stack[stack_pointer++] = right;
69
+ }
70
+ }
71
+ }
72
+
73
+ /**
74
+ *
75
+ * @param {Uint32Array} uint32
76
+ * @param {number} node_index
77
+ * @return {number}
78
+ */
79
+ function compute_traversal_index_upper_bound(uint32, node_index) {
80
+ let n = node_index;
81
+
82
+ let parent = uint32[node_index * ELEMENT_WORD_COUNT + COLUMN_PARENT];
83
+
84
+ let result = 0;
85
+ let x = 0;
86
+
87
+ while (parent !== NULL_NODE) {
88
+
89
+ if (uint32[parent * ELEMENT_WORD_COUNT + COLUMN_CHILD_1] === n) {
90
+ result += Math.pow(2, x);
91
+ }
92
+
93
+ x++;
94
+
95
+ n = parent;
96
+ parent = uint32[n * ELEMENT_WORD_COUNT + COLUMN_PARENT];
97
+ }
98
+
99
+ return result;
100
+ }
101
+
102
+ /**
103
+ *
104
+ * @param {Uint32Array} uint32
105
+ * @param {Float32Array} float32
106
+ * @param {number} a
107
+ * @param {number} b
108
+ * @returns {number}
109
+ */
110
+ function traversal_sort_compare_nodes(uint32, float32, a, b) {
111
+ const a_height = uint32[a * ELEMENT_WORD_COUNT + COLUMN_HEIGHT];
112
+ const b_height = uint32[b * ELEMENT_WORD_COUNT + COLUMN_HEIGHT];
113
+
114
+ const height_delta = a_height - b_height;
115
+
116
+ if (height_delta !== 0) {
117
+ return height_delta;
118
+ }
119
+
120
+ return compute_traversal_index_upper_bound(uint32, a) - compute_traversal_index_upper_bound(uint32, b);
121
+
122
+ }