@woosh/meep-engine 2.43.1 → 2.43.4

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 (138) hide show
  1. package/core/binary/BinaryBuffer.js +13 -1
  2. package/core/binary/BitSet.js +2 -2
  3. package/core/bvh2/aabb3/aabb3_array_combine.js +2 -2
  4. package/core/collection/RingBuffer.js +4 -2
  5. package/core/collection/RingBuffer.spec.js +59 -0
  6. package/core/collection/array/ArrayIteratorRandom.js +1 -1
  7. package/core/collection/{ArrayUtils.spec.js → array/arrayPickBestElement.spec.js} +1 -1
  8. package/core/collection/array/arrayPickBestElements.js +51 -0
  9. package/core/collection/array/arrayPickMinElement.js +43 -0
  10. package/core/collection/array/arrayQuickSort.js +1 -1
  11. package/core/collection/array/arraySetSortingDiff.js +1 -1
  12. package/core/collection/array/arraySwapElements.js +12 -0
  13. package/core/collection/array/array_range_equal_strict.js +22 -0
  14. package/core/collection/array/groupArrayBy.js +42 -0
  15. package/core/collection/array/isArrayEqual.js +50 -0
  16. package/core/collection/array/randomMultipleFromArray.js +34 -0
  17. package/core/collection/array/randomizeArrayElementOrder.js +23 -0
  18. package/core/color/sRGB_to_linear.js +9 -4
  19. package/core/geom/2d/convex-hull/convex_hull_monotone_2d.js +1 -1
  20. package/core/geom/3d/aabb/aabb3_build_frustum.js +1 -1
  21. package/core/geom/3d/aabb/compute_aabb_from_points.js +1 -1
  22. package/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +3 -1
  23. package/core/geom/3d/morton/v3_morton_encode_transformed.spec.js +20 -0
  24. package/core/geom/3d/plane/orient3d_fast.js +11 -10
  25. package/core/geom/3d/plane/orient3d_robust.js +41 -0
  26. package/core/geom/3d/plane/plane_computeConvex3PlaneIntersection.js +0 -23
  27. package/core/geom/3d/plane/plane_three_compute_convex3_plane_intersection.js +24 -0
  28. package/core/geom/3d/shape/UnionShape3D.js +1 -1
  29. package/core/geom/3d/sphere/harmonics/README.md +15 -0
  30. package/core/geom/3d/sphere/harmonics/sh3_add.js +21 -0
  31. package/core/geom/3d/sphere/harmonics/sh3_dering_optimize_positive.js +618 -0
  32. package/core/geom/3d/sphere/harmonics/sh3_sample_by_direction.js +49 -0
  33. package/core/geom/3d/sphere/harmonics/sh3_sample_irradiance_by_direction.js +53 -0
  34. package/core/geom/3d/tetrahedra/README.md +10 -1
  35. package/core/geom/3d/tetrahedra/TetrahedralMesh.js +650 -0
  36. package/core/geom/3d/tetrahedra/TetrahedralMesh.spec.js +233 -0
  37. package/core/geom/3d/tetrahedra/build_tetrahedral_mesh_buffer_geometry.js +75 -0
  38. package/core/geom/3d/tetrahedra/compute_bounding_simplex_3d.js +2 -2
  39. package/core/geom/3d/tetrahedra/compute_bounding_simplex_3d.spec.js +4 -4
  40. package/core/geom/3d/tetrahedra/delaunay/Cavity.js +49 -7
  41. package/core/geom/3d/tetrahedra/delaunay/compute_delaunay_tetrahedral_mesh.js +51 -17
  42. package/core/geom/3d/tetrahedra/delaunay/debug_validate_mesh.js +19 -0
  43. package/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +191 -0
  44. package/core/geom/3d/tetrahedra/delaunay/push_boundary_with_validation.js +27 -0
  45. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity.js +59 -43
  46. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_sub_determinant.js +77 -0
  47. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_sub_determinant.spec.js +30 -0
  48. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_walk_towards_containing_tetrahedron.js +58 -0
  49. package/core/geom/3d/tetrahedra/delaunay/validate_cavity_boundary.js +60 -0
  50. package/core/geom/3d/tetrahedra/{point_in_tetrahedron_circumsphere.js → in_sphere_fast.js} +11 -13
  51. package/core/geom/3d/tetrahedra/in_sphere_robust.js +53 -0
  52. package/core/geom/3d/tetrahedra/prototypeTetrahedraBuilder.js +44 -35
  53. package/core/geom/3d/tetrahedra/tetrahedron_compute_signed_volume.js +83 -0
  54. package/core/geom/3d/tetrahedra/tetrahedron_compute_signed_volume.spec.js +24 -0
  55. package/core/geom/3d/tetrahedra/tetrahedron_contains_point.spec.js +66 -0
  56. package/core/geom/3d/tetrahedra/validate_tetrahedral_mesh.js +166 -0
  57. package/core/geom/3d/util/make_justified_point_grid.js +31 -0
  58. package/core/geom/Bezier.js +0 -27
  59. package/core/geom/Plane.js +0 -4
  60. package/core/geom/packing/miniball/Subspan.js +2 -2
  61. package/core/geom/v3_lerp.js +6 -1
  62. package/core/math/isqrt.js +28 -0
  63. package/core/math/isqrt.spec.js +9 -0
  64. package/core/math/max.spec.js +25 -0
  65. package/core/math/min2.spec.js +25 -0
  66. package/core/model/node-graph/node/NodeInstance.js +3 -3
  67. package/core/primitives/strings/prefixTree/PrefixTreeLeaf.js +1 -1
  68. package/core/process/delay.js +5 -0
  69. package/core/process/task/util/randomCountTask.js +1 -1
  70. package/editor/Editor.js +3 -0
  71. package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +195 -11
  72. package/editor/ecs/component/editors/ecs/ParameterTrackSetEditor.js +16 -0
  73. package/editor/ecs/component/editors/ecs/ParticleEmitterLayerEditor.js +4 -0
  74. package/editor/ecs/component/editors/primitive/ArrayEditor.js +1 -1
  75. package/editor/tools/v2/BlenderCameraOrientationGizmo.js +6 -0
  76. package/editor/view/ecs/components/common/AutoCanvasView.js +13 -25
  77. package/engine/EngineHarness.js +11 -5
  78. package/engine/asset/AssetManager.d.ts +5 -1
  79. package/engine/asset/AssetManager.js +50 -15
  80. package/engine/asset/AssetManager.spec.js +17 -11
  81. package/engine/asset/AssetRequest.js +57 -0
  82. package/engine/asset/loaders/ArrayBufferLoader.js +22 -0
  83. package/engine/asset/loaders/AssetLoader.js +1 -1
  84. package/engine/ecs/System.js +1 -1
  85. package/engine/ecs/dynamic_actions/DynamicActorSystem.js +1 -1
  86. package/engine/ecs/terrain/ecs/TerrainSystem.js +7 -1
  87. package/engine/ecs/transform/copy_three_transform.js +15 -0
  88. package/engine/graphics/FrameRunner.js +5 -9
  89. package/engine/graphics/ecs/animation/animator/AnimationClipDefinition.js +1 -1
  90. package/engine/graphics/ecs/animation/animator/graph/definition/AnimationGraphDefinition.js +1 -1
  91. package/engine/graphics/ecs/camera/Camera.js +1 -10
  92. package/engine/graphics/ecs/camera/CameraSystem.js +8 -8
  93. package/engine/graphics/ecs/camera/ProjectionType.js +9 -0
  94. package/engine/graphics/ecs/camera/build_three_camera_object.js +3 -3
  95. package/engine/graphics/ecs/decal/v2/prototypeDecalSystem.js +59 -4
  96. package/engine/graphics/ecs/light/Light.js +6 -1
  97. package/engine/graphics/ecs/light/LightSystem.d.ts +1 -1
  98. package/engine/graphics/ecs/mesh-v2/three_object_to_entity_composition.js +2 -17
  99. package/engine/graphics/geometry/VertexDataSpec.js +1 -1
  100. package/engine/graphics/geometry/instancing/InstancedMeshGroup.js +2 -2
  101. package/engine/graphics/impostors/octahedral/prototypeBaker.js +3 -3
  102. package/engine/graphics/micron/plugin/GLTFAssetTransformer.js +1 -1
  103. package/engine/graphics/micron/plugin/MicronRenderPlugin.js +3 -1
  104. package/engine/graphics/particles/node-based/codegen/modules/FunctionSignature.js +1 -1
  105. package/engine/graphics/render/forward_plus/LightManager.js +1 -1
  106. package/engine/graphics/render/forward_plus/LightManager.spec.js +4 -0
  107. package/engine/graphics/render/forward_plus/computeFrustumCorners.js +4 -2
  108. package/engine/graphics/render/forward_plus/prototype/prototypeLightManager.js +2 -2
  109. package/engine/graphics/render/layers/RenderLayerUtils.js +2 -2
  110. package/engine/graphics/sh3/LightProbeVolume.js +595 -0
  111. package/engine/graphics/sh3/SH3VisualisationMaterial.js +79 -0
  112. package/engine/graphics/sh3/prototypeSH3Probe.js +427 -0
  113. package/engine/graphics/sh3/visualise_probe.js +40 -0
  114. package/engine/graphics/shaders/DenoiseShader.js +1 -1
  115. package/engine/graphics/texture/atlas/AtlasPatch.js +11 -3
  116. package/engine/graphics/texture/atlas/CachingTextureAtlas.js +2 -2
  117. package/engine/graphics/texture/atlas/TextureAtlas.js +22 -4
  118. package/engine/graphics/texture/atlas/TextureAtlas.spec.js +22 -0
  119. package/engine/graphics/texture/sampler/Sampler2D.js +0 -64
  120. package/engine/graphics/texture/sampler/Sampler2D.spec.js +2 -1
  121. package/engine/graphics/texture/sampler/sampler2d_combine.js +67 -0
  122. package/engine/intelligence/behavior/ecs/BehaviorSystem.spec.js +0 -3
  123. package/engine/intelligence/blackboard/AbstractBlackboard.d.ts +1 -1
  124. package/engine/network/PriorityFetch.js +192 -0
  125. package/engine/simulation/DormandPrince.js +1 -1
  126. package/engine/ui/DraggableAspect.js +0 -1
  127. package/generation/grid/generation/road/GridTaskGenerateRoads.js +1 -1
  128. package/package.json +2 -1
  129. package/samples/terrain/from_image_2.js +127 -82
  130. package/view/elements/CanvasView.js +7 -1
  131. package/view/elements/image/HTMLElementCacheKey.js +1 -1
  132. package/view/util/DomSizeObserver.js +3 -5
  133. package/core/collection/ArrayUtils.js +0 -263
  134. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_walk_toward_cavity.js +0 -48
  135. package/core/geom/3d/tetrahedra/hxt/a.js +0 -524
  136. package/core/geom/3d/tetrahedra/hxt/hxt.js +0 -140
  137. package/core/geom/3d/tetrahedra/hxt/hxt.wasm +0 -0
  138. package/core/geom/3d/tetrahedra/tetrahedra_collection.js +0 -383
@@ -0,0 +1,191 @@
1
+ import { assert } from "../../../../assert.js";
2
+ import { array_copy } from "../../../../collection/array/copyArray.js";
3
+ import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
4
+ import { validate_neighbour } from "../validate_tetrahedral_mesh.js";
5
+
6
+ /**
7
+ *
8
+ * @param {TetrahedralMesh} mesh
9
+ * @param {ArrayLike<number>|number[]|Float32Array} points
10
+ * @param {BitSet} visited_flags
11
+ * @param {Cavity} cavity
12
+ * @param {number} cur_tet
13
+ * @returns {number} new current tet
14
+ */
15
+ export function fill_in_a_cavity(mesh, points, visited_flags, cavity, cur_tet) {
16
+
17
+ let clength = cavity.__deleted_size; // cavity size
18
+ const blength = cavity.__boundary_size; // boundary size
19
+
20
+ assert.equal(blength & 1, 0, 'there is always a pair number of triangle on the boundary. N points, 2N triangle, 3N edges');
21
+
22
+ /*
23
+ Re-use tets that would be deleted, if there are not enough dead tets - allocate new ones as necessary
24
+ */
25
+ const deleted_tets = cavity.__deleted;
26
+ if (blength > clength) {
27
+
28
+ for (let i = clength; i < blength; i++) {
29
+ // allocate tets for the cavity filling, remember them inside the "deleted" set for now
30
+ deleted_tets[i] = mesh.allocate();
31
+ }
32
+
33
+ clength = blength;
34
+ }
35
+
36
+ const boundary = cavity.__boundary;
37
+ const start = clength - blength;
38
+
39
+ for (let i = 0; i < blength; i++) {
40
+ // go over the faces in the cavity boundary and matching tet that will be filling the space
41
+
42
+ const current_tet_index = deleted_tets[i + start];
43
+
44
+ const i5 = i * 5;
45
+
46
+ // write filling tet's corners
47
+ mesh.setVertexIndex(current_tet_index, 0, boundary[i5]); // "ball"/cavity center point is written here
48
+ mesh.setVertexIndex(current_tet_index, 1, boundary[i5 + 1]);
49
+ mesh.setVertexIndex(current_tet_index, 2, boundary[i5 + 2]);
50
+ mesh.setVertexIndex(current_tet_index, 3, boundary[i5 + 3]);
51
+
52
+ const encoded_boundary_tet = boundary[i5 + 4];
53
+
54
+ mesh.setNeighbour(current_tet_index, 0, encoded_boundary_tet); // connect opposing neighbour
55
+
56
+ const encoded_current_tet = current_tet_index << 2;
57
+
58
+ if (encoded_boundary_tet !== INVALID_NEIGHBOUR) {
59
+ // update neighbour
60
+ mesh.setNeighbour(encoded_boundary_tet >> 2, encoded_boundary_tet & 3, encoded_current_tet);
61
+ }
62
+
63
+ // boundary[i5 + 4] = encoded_current_tet;
64
+
65
+ visited_flags.set(current_tet_index, false); // clear flag
66
+ }
67
+
68
+ computeAdjacencies(mesh, cavity, start, blength);
69
+
70
+
71
+ for (let i = 0; i < start; i++) {
72
+ // cleanup any orphaned tets that we didn't re-use
73
+ mesh.delete(deleted_tets[i]);
74
+ }
75
+
76
+ // debug_validate_mesh(mesh, points);
77
+
78
+ cavity.__boundary_size = 0;
79
+ cavity.__deleted_size = 0;
80
+
81
+ /*
82
+ Pick a tet from the created ball, it will generally be a good guess for next point
83
+ */
84
+ return deleted_tets[start];
85
+
86
+ }
87
+
88
+ /**
89
+ * All tets within the ball must be connected to one another, we test that here
90
+ */
91
+ function validate_patched_ball_adjacencies(mesh, ball_tets, ball_tets_offset, ball_tets_count, consumer) {
92
+ let valid = true;
93
+
94
+ let ball_index, tet;
95
+
96
+ for (let i = 0; i < ball_tets_count; i++) {
97
+ ball_index = ball_tets_offset + i;
98
+ tet = ball_tets[ball_index];
99
+
100
+ // except for 0th neighbour, all others must be valid as they represent connection within the ball itself
101
+ for (let j = 1; j < 4; j++) {
102
+
103
+ if (!validate_neighbour(mesh, tet, j, consumer)) {
104
+ valid = false;
105
+ }
106
+ }
107
+ }
108
+
109
+ return valid;
110
+ }
111
+
112
+ /**
113
+ * filling back the cavity
114
+ * N+2 point on the surface of the cavity
115
+ * 2N triangle on the surface of the cavity, x3 (4*0.5+1) data = 6N+9 uint64_t
116
+ * => enough place for the 3N edge x2 data = 6N uint64_t
117
+ * @param {TetrahedralMesh} mesh
118
+ * @param {Cavity} cavity
119
+ * @param {number} start
120
+ * @param {number} blength
121
+ */
122
+ function computeAdjacencies(mesh, cavity, start, blength) {
123
+ const boundary = cavity.__boundary;
124
+
125
+ let tlength = 0;
126
+
127
+ const Tmp = boundary; // we know there is enough space...
128
+
129
+ for (let i = 0; i < blength; i++) {
130
+
131
+ const cur_tet = cavity.__deleted[start + i];
132
+
133
+ // pointer to the position of Node[0] in the Tmp array
134
+
135
+ for (let j = 0; j < 3; j++) {
136
+
137
+ // define the edge by the minimum vertex and the other
138
+ const index_0 = (j + 1) % 3 + 1;
139
+ const index_1 = (j + 2) % 3 + 1;
140
+
141
+ const n0 = mesh.getVertexIndex(cur_tet, index_0);
142
+ const n1 = mesh.getVertexIndex(cur_tet, index_1);
143
+
144
+ const current_tet_neighbour_id = j + 1;
145
+ const encoded_current_tet = (cur_tet << 2) | (current_tet_neighbour_id & 3);
146
+
147
+ // linear searching/pushing into Tmp
148
+ let k = 0;
149
+ for (; k < tlength; k++) {
150
+ // this is the only nested loop... the one that cost it all
151
+
152
+ if (Tmp[k * 3] === n0 && Tmp[k * 3 + 1] === n1)
153
+ // found a match, stop here
154
+ break;
155
+ }
156
+
157
+ if (k === tlength) {
158
+ // we did not find it
159
+ const offset = tlength * 3;
160
+
161
+ // flip the edge order and store it. Neighbouring tet will have edge order reversed
162
+ Tmp[offset] = n1;
163
+ Tmp[offset + 1] = n0;
164
+
165
+ Tmp[offset + 2] = encoded_current_tet;
166
+
167
+ tlength++;
168
+ } else {
169
+ // we found the neighbour !
170
+ const encoded_neighbour = Tmp[k * 3 + 2];
171
+
172
+ mesh.setNeighbour(cur_tet, current_tet_neighbour_id, encoded_neighbour);
173
+ mesh.setNeighbour(encoded_neighbour >> 2, encoded_neighbour & 3, encoded_current_tet);
174
+
175
+ // reduce search space, there can only be one neighbour pair, after it's found we can remove the entry from lookup table
176
+ tlength--;
177
+
178
+ if (k < tlength) {
179
+ // put the last entry in the one we just discovered
180
+ array_copy(Tmp, tlength * 3, Tmp, k * 3, 3);
181
+ }
182
+ }
183
+ }
184
+ }
185
+
186
+ assert.equal(tlength, 0, 'Failed to compute adjacencies');
187
+
188
+ // if (!validate_patched_ball_adjacencies(mesh, cavity.__deleted, start, blength, console.error)) {
189
+ // debugger;
190
+ // }
191
+ }
@@ -0,0 +1,27 @@
1
+ import { tetrahedron_compute_signed_volume } from "../tetrahedron_compute_signed_volume.js";
2
+
3
+ /**
4
+ * Debug method that validates cavity tet before pushing it
5
+ * @param {Cavity} cavity
6
+ * @param {number} a
7
+ * @param {number} b
8
+ * @param {number} c
9
+ * @param {number} d
10
+ * @param {number} neighbour
11
+ * @param {number[]} points
12
+ * @param {function(problem:string):*} consumer
13
+ */
14
+ export function push_boundary_with_validation(points, cavity, a, b, c, d, neighbour, consumer = console.warn) {
15
+ let valid = true;
16
+ const vol = tetrahedron_compute_signed_volume(points, a, b, c, d);
17
+
18
+ if (vol < 0) {
19
+ consumer(`Pushing a negative volume(=${vol}) boundary tet ${[a, b, c, d].join(', ')}`);
20
+
21
+ valid = false;
22
+ }
23
+
24
+ cavity.push_boundary(a, b, c, d, neighbour);
25
+
26
+ return valid;
27
+ }
@@ -1,73 +1,89 @@
1
1
  import { assert } from "../../../../assert.js";
2
- import { point_in_tetrahedron_circumsphere } from "../point_in_tetrahedron_circumsphere.js";
3
- import { INVALID_NEIGHBOUR } from "../tetrahedra_collection.js";
2
+ import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
3
+ import { in_sphere_robust } from "../in_sphere_robust.js";
4
4
 
5
5
  /**
6
6
  *
7
7
  * @param {TetrahedralMesh} mesh
8
8
  * @param {Cavity} cavity
9
9
  * @param {number[]|Float32Array} points
10
+ * @param {BitSet} visited_flags
10
11
  * @param {number} containing_tetra tetrahedron that contains point
11
12
  * @param {number} point_index point that forms the cavity
12
13
  */
13
- export function tetrahedral_mesh_compute_cavity(mesh, cavity, points, containing_tetra, point_index) {
14
+ export function tetrahedral_mesh_compute_cavity(
15
+ mesh,
16
+ points,
17
+ visited_flags,
18
+ cavity,
19
+ containing_tetra,
20
+ point_index
21
+ ) {
14
22
  assert.isNonNegativeInteger(containing_tetra, 'containing_tetra');
15
23
 
16
- cavity.reset();
24
+ // add the tet to cavity
25
+ cavity.push_deleted(containing_tetra);
26
+ visited_flags.set(containing_tetra, true);
17
27
 
18
- /**
19
- *
20
- * @type {number[]}
21
- */
22
- const open_set = [
23
- containing_tetra
24
- ];
28
+ for (let start = cavity.__deleted_size - 1; start < cavity.__deleted_size; start++) {
29
+ const cur_tet = cavity.__deleted[start];
25
30
 
26
- /**
27
- *
28
- * @type {number[]}
29
- */
30
- const closed_set = [];
31
+ for (let i = 0; i < 4; i++) {
32
+ const encoded_neighbour = mesh.getNeighbour(cur_tet, i);
31
33
 
32
- // 2. find the Delaunay "cavity", a set of tetrahedrons who's circumsphere overlarlaps the point
33
- // perform breadth-first expansion on the containing tetra to identify the cavity
34
- while (open_set.length > 0) {
34
+ const k0 = (1 << i) & 3;
35
+ const k1 = (i + 2) % 3;
36
+ const k2 = (~((i + 1) >> 1) & 2) + 1;
35
37
 
36
- const tetra = open_set.pop();
37
- cavity.push(tetra);
38
- closed_set.push(tetra);
38
+ if (encoded_neighbour === INVALID_NEIGHBOUR) {
39
+ // no neighbour, include this side into boundary
39
40
 
40
- // get neighbours
41
- for (let i = 0; i < 4; i++) {
41
+ cavity.push_boundary(
42
+ point_index,
43
+ mesh.getVertexIndex(cur_tet, k0),
44
+ mesh.getVertexIndex(cur_tet, k1),
45
+ mesh.getVertexIndex(cur_tet, k2),
46
+ encoded_neighbour,
47
+ );
42
48
 
43
- const neighbour_encoded = mesh.getNeighbour(tetra, i);
44
- const neighbour_tetra_index = neighbour_encoded >>> 2;
49
+ continue;
50
+ }
45
51
 
46
- if (
47
- (neighbour_encoded === INVALID_NEIGHBOUR) // no neighbour
48
- || closed_set.includes(neighbour_tetra_index) // already visited
49
- || open_set.includes(neighbour_tetra_index) // already in open set
50
- ) {
52
+ const neighbour_index = encoded_neighbour >> 2;
51
53
 
54
+ if (visited_flags.get(neighbour_index)) {
55
+ // already visited
52
56
  continue;
53
57
  }
54
58
 
55
- const a = mesh.getCornerIndex(neighbour_tetra_index, 0);
56
- const b = mesh.getCornerIndex(neighbour_tetra_index, 1);
57
- const c = mesh.getCornerIndex(neighbour_tetra_index, 2);
58
- const d = mesh.getCornerIndex(neighbour_tetra_index, 3);
59
+ if (in_sphere_robust(
60
+ points,
61
+ mesh.getVertexIndex(neighbour_index, 0),
62
+ mesh.getVertexIndex(neighbour_index, 1),
63
+ mesh.getVertexIndex(neighbour_index, 2),
64
+ mesh.getVertexIndex(neighbour_index, 3),
65
+ point_index
66
+ ) < 0) {
67
+ // outside the cavity, meaning it defines boundary
59
68
 
60
- // check neighbour
61
- const is_in_sphere = point_in_tetrahedron_circumsphere(points, a, b, c, d, containing_tetra);
69
+ cavity.push_boundary(
70
+ point_index,
71
+ mesh.getVertexIndex(cur_tet, k0),
72
+ mesh.getVertexIndex(cur_tet, k1),
73
+ mesh.getVertexIndex(cur_tet, k2),
74
+ encoded_neighbour,
75
+ );
62
76
 
63
- if (is_in_sphere) {
64
- open_set.push(neighbour_tetra_index);
65
77
  } else {
66
- // move directly to closed set
67
- closed_set.push(neighbour_tetra_index);
68
- }
69
-
78
+ // assert.greaterThanOrEqual(visited_flags.length, neighbour_index * 4 + 3, 'subdet array is too small');
70
79
 
80
+ //part of the cavity, remove
81
+ cavity.push_deleted(neighbour_index);
82
+ visited_flags.set(neighbour_index, true);
83
+ }
71
84
  }
85
+
72
86
  }
87
+
88
+ // debug_validate_cavity_boundary(mesh, cavity);
73
89
  }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Compute sub-determinants for a given tetrahedron
3
+ * NOTE: this implementation doesn't use GHOST vertices, all tets/vertices are assumed to be real and valid
4
+ * @see https://git.immc.ucl.ac.be/hextreme/hxt_seqdel/-/blob/master/src/hxt_tetrahedra.c#L754
5
+ * @param {number[]|Float32Array} result
6
+ * @param {number} result_offset
7
+ * @param {TetrahedralMesh} mesh
8
+ * @param {number[]|Float32Array} points
9
+ * @param {number} tetrahedron_index
10
+ */
11
+ export function tetrahedral_mesh_compute_sub_determinant(result, result_offset, mesh, points, tetrahedron_index) {
12
+ /*
13
+ NOTE: the code is re-ordered slightly and inlined for speed, re-ordering is mostly to reduce register pressure
14
+ */
15
+ const a_index = mesh.getVertexIndex(tetrahedron_index, 0);
16
+ const b_index = mesh.getVertexIndex(tetrahedron_index, 1);
17
+ const c_index = mesh.getVertexIndex(tetrahedron_index, 2);
18
+ const d_index = mesh.getVertexIndex(tetrahedron_index, 3);
19
+
20
+ // read out coordinates
21
+ const a3 = a_index * 3;
22
+ const b3 = b_index * 3;
23
+ const c3 = c_index * 3;
24
+ const d3 = d_index * 3;
25
+
26
+ const a_x = points[a3];
27
+ const a_y = points[a3 + 1];
28
+ const a_z = points[a3 + 2];
29
+
30
+ const b_x = points[b3];
31
+ const b_y = points[b3 + 1];
32
+ const b_z = points[b3 + 2];
33
+
34
+ // construct AB, AC, AD
35
+ const ab_x = b_x - a_x;
36
+ const ab_y = b_y - a_y;
37
+ const ab_z = b_z - a_z;
38
+
39
+ const c_x = points[c3];
40
+ const c_y = points[c3 + 1];
41
+ const c_z = points[c3 + 2];
42
+
43
+ const ac_x = c_x - a_x;
44
+ const ac_y = c_y - a_y;
45
+ const ac_z = c_z - a_z;
46
+
47
+ const d_x = points[d3];
48
+ const d_y = points[d3 + 1];
49
+ const d_z = points[d3 + 2];
50
+
51
+ const ad_x = d_x - a_x;
52
+ const ad_y = d_y - a_y;
53
+ const ad_z = d_z - a_z;
54
+
55
+ const ab_w = ab_x * ab_x + ab_y * ab_y + ab_z * ab_z;
56
+ const ac_w = ac_x * ac_x + ac_y * ac_y + ac_z * ac_z;
57
+ const ad_w = ad_x * ad_x + ad_y * ad_y + ad_z * ad_z;
58
+
59
+ const cd12 = ac_z * ad_y - ac_y * ad_z;
60
+ const db12 = ad_z * ab_y - ad_y * ab_z;
61
+ const bc12 = ab_z * ac_y - ab_y * ac_z;
62
+
63
+ const cd30 = ac_x * ad_w - ac_w * ad_x;
64
+ const db30 = ad_x * ab_w - ad_w * ab_x;
65
+ const bc30 = ab_x * ac_w - ab_w * ac_x;
66
+
67
+ // each subdet is a simple triple product
68
+ const subdet_0 = ab_w * cd12 + ac_w * db12 + ad_w * bc12;
69
+ const subdet_1 = ab_z * cd30 + ac_z * db30 + ad_z * bc30;
70
+ const subdet_2 = ab_y * cd30 + ac_y * db30 + ad_y * bc30;
71
+ const subdet_3 = ab_x * cd12 + ac_x * db12 + ad_x * bc12;
72
+
73
+ result[result_offset] = subdet_0;
74
+ result[result_offset + 1] = subdet_1;
75
+ result[result_offset + 2] = subdet_2;
76
+ result[result_offset + 3] = subdet_3;
77
+ }
@@ -0,0 +1,30 @@
1
+ import { TetrahedralMesh } from "../TetrahedralMesh.js";
2
+ import { tetrahedral_mesh_compute_sub_determinant } from "./tetrahedral_mesh_compute_sub_determinant.js";
3
+
4
+ test("sanity test", () => {
5
+ const mesh = new TetrahedralMesh(1);
6
+
7
+ const tet = mesh.allocate();
8
+
9
+ mesh.setVertexIndex(tet, 0, 0);
10
+ mesh.setVertexIndex(tet, 1, 1);
11
+ mesh.setVertexIndex(tet, 2, 2);
12
+ mesh.setVertexIndex(tet, 3, 3);
13
+
14
+ const result = [];
15
+
16
+ const points = [
17
+ 1, 2, 3,
18
+ 4, 5, 6,
19
+ 7, 8, 9,
20
+ 10, 11, 12
21
+ ];
22
+
23
+ tetrahedral_mesh_compute_sub_determinant(result, 0, mesh, points, tet);
24
+
25
+ for (let i = 0; i < 4; i++) {
26
+ expect(typeof result[i]).toBe("number");
27
+ expect(result[i]).not.toBeNaN();
28
+ }
29
+
30
+ });
@@ -0,0 +1,58 @@
1
+ import { assert } from "../../../../assert.js";
2
+ import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
3
+ import { orient3d_robust } from "../../plane/orient3d_robust.js";
4
+
5
+ /**
6
+ * Walk from a given tetrahedron in the mesh towards tetrahedron that contains input point
7
+ * This is an essential method for incrementally finding the next tetrahedron for cavity computation
8
+ *
9
+ * @see https://git.immc.ucl.ac.be/hextreme/hxt_seqdel/-/blob/master/src/hxt_tetrahedra.c#L330
10
+ * @see p3 "One machine, one minute, three billion tetrahedra" by Célestin Marot, Jeanne Pellerin, Jean-François Remacle
11
+ * @param {TetrahedralMesh} mesh
12
+ * @param {number[]|Float32Array} points
13
+ * @param {number} hint_tetrahedron we will start here and walk towards our target
14
+ * @param {number} search_vertex_index
15
+ * @returns {number}
16
+ */
17
+ export function tetrahedral_mesh_walk_towards_containing_tetrahedron(mesh, points, hint_tetrahedron, search_vertex_index) {
18
+
19
+ let entering_face = 4;
20
+
21
+ let cur_tet = hint_tetrahedron;
22
+
23
+ while (true) {
24
+
25
+ let i;
26
+ for (i = 0; i < 4; i++) {
27
+
28
+ // we walk whenever the volume is positive
29
+ const a_i = (i + 1) & 3;
30
+ const b_i = (i & 2) ^ 3;
31
+ const c_i = (i + 3) & 2;
32
+
33
+ const a_index = mesh.getVertexIndex(cur_tet, a_i);
34
+ const b_index = mesh.getVertexIndex(cur_tet, b_i);
35
+ const c_index = mesh.getVertexIndex(cur_tet, c_i);
36
+
37
+ if (i !== entering_face && orient3d_robust(points, a_index, b_index, c_index, search_vertex_index) < 0.0) {
38
+
39
+ // point is outside the tet on the neighbour's side, move in that direction
40
+ const neighbour = mesh.getNeighbour(cur_tet, i);
41
+
42
+ assert.notEqual(neighbour, INVALID_NEIGHBOUR, 'walked outside of the mesh');
43
+
44
+ cur_tet = neighbour >>> 2;
45
+ entering_face = neighbour & 3;
46
+
47
+ break;
48
+
49
+ }
50
+ }
51
+
52
+ if (i === 4) {
53
+ // point is inside the tet
54
+ return cur_tet;
55
+ }
56
+ }
57
+
58
+ }
@@ -0,0 +1,60 @@
1
+ import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
2
+ import { array_range_equal_strict } from "../../../../collection/array/array_range_equal_strict.js";
3
+
4
+ export function debug_validate_cavity_boundary(mesh, cavity) {
5
+ let valid = true;
6
+ const problems = [];
7
+
8
+ if (!validate_cavity_boundary(cavity, problem => problems.push(problem))) {
9
+ valid = false;
10
+ }
11
+
12
+ if (!valid) {
13
+ debugger;
14
+ }
15
+
16
+ return valid;
17
+ }
18
+
19
+ /**
20
+ *
21
+ * @param {Cavity} cavity
22
+ * @param {function(problem:string):*} consumer
23
+ * @returns {boolean}
24
+ */
25
+ export function validate_cavity_boundary(cavity, consumer) {
26
+ let valid = true;
27
+
28
+ const boundary = cavity.__boundary;
29
+ const boundary_size = cavity.__boundary_size;
30
+
31
+ for (let i = 0; i < boundary_size; i++) {
32
+ const encoded_tet_0 = boundary[i * 5 + 4];
33
+
34
+ if (encoded_tet_0 === INVALID_NEIGHBOUR) {
35
+ // this is "no neighbour" marker, there are allowed to be many of these
36
+ continue;
37
+ }
38
+
39
+ for (let j = i + 1; j < boundary_size; j++) {
40
+
41
+ const encoded_tet_1 = boundary[j * 5 + 4];
42
+
43
+ if (encoded_tet_0 === encoded_tet_1) {
44
+ valid = false;
45
+
46
+ let message = `Duplicate neighbourhood ${encoded_tet_0} (tet=${(encoded_tet_0 >> 2)}, vert=${(encoded_tet_0 & 3)}), boundary elements [${i}, ${j}]`;
47
+
48
+ if (!array_range_equal_strict(boundary, i * 5, boundary, j * 5, 4)) {
49
+ // boundary definitions are divergent
50
+ message += `, boundary definitions are divergent - ${i}:[${boundary.slice(i * 5, i * 5 + 4).join(', ')}], ${j}:[${boundary.slice(j * 5, j * 5 + 4).join(', ')}]`
51
+ }
52
+
53
+ consumer(message);
54
+ }
55
+
56
+ }
57
+ }
58
+
59
+ return valid;
60
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Approximate 3D insphere test. Nonrobust.
2
+ * Approximate 3D insphere test. Non-robust.
3
3
  *
4
4
  * Return a positive value if the point pe lies inside the
5
5
  * sphere passing through pa, pb, pc, and pd; a negative value
@@ -19,7 +19,7 @@
19
19
  * @param {number} e reference point index that we are testing against the tetrahedron
20
20
  * @returns {number}
21
21
  */
22
- export function point_in_tetrahedron_circumsphere(
22
+ export function in_sphere_fast(
23
23
  points,
24
24
  a, b, c, d, e
25
25
  ) {
@@ -34,20 +34,20 @@ export function point_in_tetrahedron_circumsphere(
34
34
  const ey = points[e3 + 1];
35
35
  const ez = points[e3 + 2];
36
36
 
37
-
38
37
  const aex = points[a3] - ex;
39
- const bex = points[b3] - ex;
40
- const cex = points[c3] - ex;
41
- const dex = points[d3] - ex;
42
-
43
38
  const aey = points[a3 + 1] - ey;
44
- const bey = points[b3 + 1] - ey;
45
- const cey = points[c3 + 1] - ey;
46
- const dey = points[d3 + 1] - ey;
47
-
48
39
  const aez = points[a3 + 2] - ez;
40
+
41
+ const bex = points[b3] - ex;
42
+ const bey = points[b3 + 1] - ey;
49
43
  const bez = points[b3 + 2] - ez;
44
+
45
+ const cex = points[c3] - ex;
46
+ const cey = points[c3 + 1] - ey;
50
47
  const cez = points[c3 + 2] - ez;
48
+
49
+ const dex = points[d3] - ex;
50
+ const dey = points[d3 + 1] - ey;
51
51
  const dez = points[d3 + 2] - ez;
52
52
 
53
53
  const aexbey = aex * bey;
@@ -86,7 +86,5 @@ export function point_in_tetrahedron_circumsphere(
86
86
 
87
87
  const det = (dlift * abc - clift * dab) + (blift * cda - alift * bcd);
88
88
 
89
- // TODO HXT uses 2 more methods to perform exact calculation (avoiding rounding errors), we could include that if necessary
90
-
91
89
  return det;
92
90
  }
@@ -0,0 +1,53 @@
1
+ import { insphere } from "robust-predicates";
2
+
3
+ /**
4
+ * Approximate 3D insphere test. Robust
5
+ *
6
+ *
7
+ * @param {number[]} points
8
+ * @param {number} a tetrahedral point index
9
+ * @param {number} b tetrahedral point index
10
+ * @param {number} c tetrahedral point index
11
+ * @param {number} d tetrahedral point index
12
+ * @param {number} e reference point index that we are testing against the tetrahedron
13
+ * @returns {number}
14
+ */
15
+ export function in_sphere_robust(
16
+ points,
17
+ a, b, c, d, e
18
+ ) {
19
+
20
+ const a3 = a * 3;
21
+ const b3 = b * 3;
22
+ const c3 = c * 3;
23
+ const d3 = d * 3;
24
+ const e3 = e * 3;
25
+
26
+ const ax = points[a3];
27
+ const ay = points[a3 + 1];
28
+ const az = points[a3 + 2];
29
+
30
+ const bx = points[b3];
31
+ const by = points[b3 + 1];
32
+ const bz = points[b3 + 2];
33
+
34
+ const cx = points[c3];
35
+ const cy = points[c3 + 1];
36
+ const cz = points[c3 + 2];
37
+
38
+ const dx = points[d3];
39
+ const dy = points[d3 + 1];
40
+ const dz = points[d3 + 2];
41
+
42
+ const ex = points[e3];
43
+ const ey = points[e3 + 1];
44
+ const ez = points[e3 + 2];
45
+
46
+ return -insphere(
47
+ ax, ay, az,
48
+ bx, by, bz,
49
+ cx, cy, cz,
50
+ dx, dy, dz,
51
+ ex, ey, ez
52
+ );
53
+ }