@woosh/meep-engine 2.43.3 → 2.43.5

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 (52) hide show
  1. package/core/binary/BinaryBuffer.js +13 -1
  2. package/core/binary/BitSet.js +2 -2
  3. package/core/collection/array/array_range_equal_strict.js +22 -0
  4. package/core/collection/map/AsyncMapWrapper.js +13 -1
  5. package/core/collection/map/CachedAsyncMap.js +9 -2
  6. package/core/collection/map/CachedAsyncMap.spec.js +47 -0
  7. package/core/color/sRGB_to_linear.js +9 -4
  8. package/core/geom/3d/plane/orient3d_fast.js +3 -0
  9. package/core/geom/3d/plane/orient3d_robust.js +41 -0
  10. package/core/geom/3d/sphere/harmonics/README.md +15 -0
  11. package/core/geom/3d/sphere/harmonics/sh3_add.js +21 -0
  12. package/core/geom/3d/sphere/harmonics/sh3_dering_optimize_positive.js +618 -0
  13. package/core/geom/3d/sphere/harmonics/sh3_sample_by_direction.js +49 -0
  14. package/core/geom/3d/sphere/harmonics/sh3_sample_irradiance_by_direction.js +53 -0
  15. package/core/geom/3d/tetrahedra/TetrahedralMesh.js +251 -68
  16. package/core/geom/3d/tetrahedra/TetrahedralMesh.spec.js +80 -3
  17. package/core/geom/3d/tetrahedra/build_tetrahedral_mesh_buffer_geometry.js +75 -0
  18. package/core/geom/3d/tetrahedra/delaunay/Cavity.js +5 -1
  19. package/core/geom/3d/tetrahedra/delaunay/compute_delaunay_tetrahedral_mesh.js +30 -31
  20. package/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +54 -18
  21. package/core/geom/3d/tetrahedra/delaunay/push_boundary_with_validation.js +27 -0
  22. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity.js +89 -0
  23. package/core/geom/3d/tetrahedra/delaunay/{tetrahedral_mesh_walk_toward_cavity.js → tetrahedral_mesh_walk_towards_containing_tetrahedron.js} +15 -12
  24. package/core/geom/3d/tetrahedra/delaunay/validate_cavity_boundary.js +60 -0
  25. package/core/geom/3d/tetrahedra/{point_in_tetrahedron_circumsphere.js → in_sphere_fast.js} +2 -4
  26. package/core/geom/3d/tetrahedra/in_sphere_robust.js +53 -0
  27. package/core/geom/3d/tetrahedra/prototypeTetrahedraBuilder.js +44 -35
  28. package/core/geom/3d/tetrahedra/validate_tetrahedral_mesh.js +85 -38
  29. package/core/geom/3d/util/make_justified_point_grid.js +31 -0
  30. package/core/process/delay.js +5 -0
  31. package/editor/Editor.js +3 -0
  32. package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +195 -11
  33. package/editor/ecs/component/editors/ecs/ParameterTrackSetEditor.js +16 -0
  34. package/editor/ecs/component/editors/ecs/ParticleEmitterLayerEditor.js +4 -0
  35. package/engine/EngineHarness.js +11 -5
  36. package/engine/ecs/terrain/ecs/TerrainSystem.js +7 -1
  37. package/engine/ecs/transform/copy_three_transform.js +15 -0
  38. package/engine/graphics/ecs/light/Light.js +6 -1
  39. package/engine/graphics/ecs/light/LightSystem.d.ts +1 -1
  40. package/engine/graphics/ecs/mesh-v2/three_object_to_entity_composition.js +2 -17
  41. package/engine/graphics/geometry/instancing/InstancedMeshGroup.js +2 -2
  42. package/engine/graphics/micron/plugin/shaded_geometry/MicronShadedGeometryRenderAdapter.js +9 -1
  43. package/engine/graphics/sh3/LightProbeVolume.js +595 -0
  44. package/engine/graphics/sh3/SH3VisualisationMaterial.js +79 -0
  45. package/engine/graphics/sh3/prototypeSH3Probe.js +427 -0
  46. package/engine/graphics/sh3/visualise_probe.js +40 -0
  47. package/engine/graphics/texture/atlas/TextureAtlas.js +15 -3
  48. package/engine/intelligence/blackboard/AbstractBlackboard.d.ts +1 -1
  49. package/package.json +2 -1
  50. package/samples/terrain/from_image_2.js +127 -82
  51. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity2.js +0 -224
  52. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_insert_point.js +0 -98
@@ -0,0 +1,75 @@
1
+ import { BufferGeometry, Float32BufferAttribute, Uint32BufferAttribute } from "three";
2
+ import { BitSet } from "../../../binary/BitSet.js";
3
+ import { assert } from "../../../assert.js";
4
+
5
+ /**
6
+ *
7
+ * @param {TetrahedralMesh} tetrahedra
8
+ * @param {Float32Array|number[]} points
9
+ * @return {BufferGeometry}
10
+ */
11
+ export function build_tetrahedral_mesh_buffer_geometry(tetrahedra, points) {
12
+ const lines = [];
13
+
14
+ const occupancy = new BitSet();
15
+
16
+ const point_count = points.length / 3;
17
+
18
+ assert.isNonNegativeInteger(point_count, 'point_count');
19
+
20
+ /**
21
+ *
22
+ * @param {number} a
23
+ * @param {number} b
24
+ */
25
+ function ensure_line(a, b) {
26
+ assert.isNonNegativeInteger(a, 'a');
27
+ assert.isNonNegativeInteger(b, 'b');
28
+
29
+ assert.lessThan(a, point_count, 'index A is out of bounds');
30
+ assert.lessThan(b, point_count, 'index B is out of bounds');
31
+
32
+ let v0, v1;
33
+
34
+ if (a < b) {
35
+ v0 = a;
36
+ v1 = b;
37
+ } else {
38
+ v0 = b;
39
+ v1 = a;
40
+ }
41
+
42
+ const index = v0 * point_count + v1;
43
+
44
+ if (occupancy.getAndSet(index)) {
45
+ // already recorded
46
+ return;
47
+ }
48
+
49
+ // no line record yet, create it
50
+ lines.push(v0, v1);
51
+ }
52
+
53
+ tetrahedra.forEach((tet, mesh) => {
54
+ assert.ok(mesh.exists(tet), `Visited un-allocated tet ${tet}`);
55
+
56
+ const a = mesh.getVertexIndex(tet, 0);
57
+ const b = mesh.getVertexIndex(tet, 1);
58
+ const c = mesh.getVertexIndex(tet, 2);
59
+ const d = mesh.getVertexIndex(tet, 3);
60
+
61
+ ensure_line(a, b);
62
+ ensure_line(a, c);
63
+ ensure_line(a, d);
64
+
65
+ ensure_line(b, c);
66
+ ensure_line(c, d);
67
+ ensure_line(b, d);
68
+ })
69
+
70
+ const geometry = new BufferGeometry();
71
+
72
+ geometry.setIndex(new Uint32BufferAttribute(lines, 1, false));
73
+ geometry.setAttribute('position', new Float32BufferAttribute(points, 3, false));
74
+ return geometry;
75
+ }
@@ -1,3 +1,5 @@
1
+ import { assert } from "../../../../assert.js";
2
+
1
3
  export class Cavity {
2
4
  constructor() {
3
5
  /**
@@ -13,7 +15,7 @@ export class Cavity {
13
15
  this.__deleted_size = 0;
14
16
 
15
17
  /**
16
- * 5-tuples
18
+ * flat array of 5-tuples
17
19
  * @type {number[]}
18
20
  * @private
19
21
  */
@@ -59,6 +61,8 @@ export class Cavity {
59
61
  * @param {number} i
60
62
  */
61
63
  push_deleted(i) {
64
+ assert.equal(this.includes(i), false, `ball element ${i} already exists`);
65
+
62
66
  this.__deleted[this.__deleted_size++] = i;
63
67
  }
64
68
 
@@ -2,22 +2,34 @@
2
2
 
3
3
  import { compute_bounding_simplex_3d } from "../compute_bounding_simplex_3d.js";
4
4
  import { TetrahedralMesh } from "../TetrahedralMesh.js";
5
- import { tetrahedral_mesh_walk_toward_cavity } from "./tetrahedral_mesh_walk_toward_cavity.js";
6
- import { tetrahedral_mesh_compute_sub_determinant } from "./tetrahedral_mesh_compute_sub_determinant.js";
5
+ import {
6
+ tetrahedral_mesh_walk_towards_containing_tetrahedron
7
+ } from "./tetrahedral_mesh_walk_towards_containing_tetrahedron.js";
7
8
  import { Cavity } from "./Cavity.js";
8
- import { tetrahedral_mesh_compute_cavity2 } from "./tetrahedral_mesh_compute_cavity2.js";
9
+ import { tetrahedral_mesh_compute_cavity } from "./tetrahedral_mesh_compute_cavity.js";
9
10
  import { fill_in_a_cavity } from "./fill_in_a_cavity.js";
10
- import { debug_validate_mesh } from "./debug_validate_mesh.js";
11
+ import { BitSet } from "../../../../binary/BitSet.js";
12
+ import { assert } from "../../../../assert.js";
11
13
 
12
14
 
13
15
  /**
14
16
  * @see https://en.wikipedia.org/wiki/Bowyer%E2%80%93Watson_algorithm
15
17
  * @see [1] "One machine, one minute, three billion tetrahedra" by Célestin Marot et al
18
+ * @param {TetrahedralMesh} mesh
16
19
  * @param {number[]} input serialized set of 3d points (x,y,z)
17
20
  * @param {number} n number of points in the input
18
- * @returns {TetrahedralMesh}
21
+ * @returns {boolean}
19
22
  */
20
- export function compute_delaunay_tetrahedral_mesh(input, n) {
23
+ export function compute_delaunay_tetrahedral_mesh(mesh, input, n) {
24
+ assert.notNull(mesh, 'mesh');
25
+ assert.defined(mesh, 'mesh');
26
+
27
+ assert.isNonNegativeInteger(n, 'n');
28
+
29
+ if (n < 4) {
30
+ // no mesh can be created, too few points
31
+ return false;
32
+ }
21
33
 
22
34
  /**
23
35
  * According to [1] number of tetrahedra is around 6 times greater than number of input vertices, we use that fact to pre-allocate memory
@@ -27,21 +39,17 @@ export function compute_delaunay_tetrahedral_mesh(input, n) {
27
39
  *
28
40
  * The +7 is for the initial "super" tetrahedron and 6 tetras for intermediate triangulation
29
41
  */
30
- const tetrahedron_count_upper_bound = Math.ceil((n * n - 3 * n - 2) / 2) + 7;
31
42
 
32
- const mesh = new TetrahedralMesh(tetrahedron_count_upper_bound);
43
+ // start with a fairly low capacity for best-case scenario, rely on internal resizing after that
33
44
 
34
- if (n < 4) {
35
- // no mesh can be created, too few points
36
- return mesh;
37
- }
45
+ mesh.growCapacity(n + 1);
38
46
 
39
47
  const CAVITY = new Cavity();
40
48
 
41
49
  // TODO apply spatial sort to point, to get better cache locality
42
50
 
43
- // compute bounds over the input
44
- const sub_determinants = new Float32Array(tetrahedron_count_upper_bound * 4);
51
+ const build_flags = BitSet.fixedSize(n);
52
+
45
53
  const points = new Float32Array((n + 4) * 3);
46
54
 
47
55
  points.set(input);
@@ -49,7 +57,7 @@ export function compute_delaunay_tetrahedral_mesh(input, n) {
49
57
  /*
50
58
  create bounding volume tetrahedron over input set
51
59
  */
52
- compute_bounding_simplex_3d(points, n * 3, points, n, 1);
60
+ compute_bounding_simplex_3d(points, n * 3, points, n, 10);
53
61
 
54
62
  const super_tet = mesh.allocate();
55
63
 
@@ -58,36 +66,27 @@ export function compute_delaunay_tetrahedral_mesh(input, n) {
58
66
  mesh.setVertexIndex(super_tet, 2, n + 2);
59
67
  mesh.setVertexIndex(super_tet, 3, n + 3);
60
68
 
61
- // make circular references on neighbour (algorithm doesn't work otherwise)
62
- // mesh.setNeighbour(super_tet, 0, super_tet << 2 | 0);
63
- // mesh.setNeighbour(super_tet, 1, super_tet << 2 | 1);
64
- // mesh.setNeighbour(super_tet, 2, super_tet << 2 | 2);
65
- // mesh.setNeighbour(super_tet, 3, super_tet << 2 | 3);
66
-
67
- tetrahedral_mesh_compute_sub_determinant(sub_determinants, 0, mesh, points, 0);
68
-
69
- debug_validate_mesh(mesh, points);
69
+ // debug_validate_mesh(mesh, points);
70
70
 
71
71
  // add all points to the mesh
72
72
  let current_tet = 0;
73
73
 
74
74
  for (let i = 0; i < n; i++) {
75
75
 
76
- current_tet = tetrahedral_mesh_walk_toward_cavity(mesh, points, current_tet, i);
76
+ current_tet = tetrahedral_mesh_walk_towards_containing_tetrahedron(mesh, points, current_tet, i);
77
77
 
78
- tetrahedral_mesh_compute_cavity2(mesh, points, sub_determinants, CAVITY, current_tet, i);
79
- // tetrahedral_mesh_compute_cavity(mesh, CAVITY, points, current_tet, i);
78
+ tetrahedral_mesh_compute_cavity(mesh, points, build_flags, CAVITY, current_tet, i);
80
79
 
81
- current_tet = fill_in_a_cavity(mesh, points, sub_determinants, CAVITY, current_tet);
80
+ current_tet = fill_in_a_cavity(mesh, points, build_flags, CAVITY, current_tet);
82
81
 
83
82
  }
84
83
 
85
84
  // remove tetras formed by with the super bounding volume
86
- mesh.removeTetrasConnectedToPoints(points, n, n + 3);
85
+ mesh.removeTetrasConnectedToPoints(n, n + 3);
87
86
 
88
87
 
89
- debug_validate_mesh(mesh, points);
88
+ // debug_validate_mesh(mesh, points);
90
89
 
91
- return mesh;
90
+ return true;
92
91
  }
93
92
 
@@ -1,19 +1,18 @@
1
1
  import { assert } from "../../../../assert.js";
2
- import { tetrahedral_mesh_compute_sub_determinant } from "./tetrahedral_mesh_compute_sub_determinant.js";
3
2
  import { array_copy } from "../../../../collection/array/copyArray.js";
4
3
  import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
5
- import { debug_validate_mesh } from "./debug_validate_mesh.js";
4
+ import { validate_neighbour } from "../validate_tetrahedral_mesh.js";
6
5
 
7
6
  /**
8
7
  *
9
8
  * @param {TetrahedralMesh} mesh
10
9
  * @param {ArrayLike<number>|number[]|Float32Array} points
11
- * @param {ArrayLike<number>|number[]|Float32Array} subdets
10
+ * @param {BitSet} visited_flags
12
11
  * @param {Cavity} cavity
13
12
  * @param {number} cur_tet
14
13
  * @returns {number} new current tet
15
14
  */
16
- export function fill_in_a_cavity(mesh, points, subdets, cavity, cur_tet) {
15
+ export function fill_in_a_cavity(mesh, points, visited_flags, cavity, cur_tet) {
17
16
 
18
17
  let clength = cavity.__deleted_size; // cavity size
19
18
  const blength = cavity.__boundary_size; // boundary size
@@ -23,11 +22,12 @@ export function fill_in_a_cavity(mesh, points, subdets, cavity, cur_tet) {
23
22
  /*
24
23
  Re-use tets that would be deleted, if there are not enough dead tets - allocate new ones as necessary
25
24
  */
25
+ const deleted_tets = cavity.__deleted;
26
26
  if (blength > clength) {
27
27
 
28
28
  for (let i = clength; i < blength; i++) {
29
29
  // allocate tets for the cavity filling, remember them inside the "deleted" set for now
30
- cavity.__deleted[i] = mesh.allocate();
30
+ deleted_tets[i] = mesh.allocate();
31
31
  }
32
32
 
33
33
  clength = blength;
@@ -39,7 +39,7 @@ export function fill_in_a_cavity(mesh, points, subdets, cavity, cur_tet) {
39
39
  for (let i = 0; i < blength; i++) {
40
40
  // go over the faces in the cavity boundary and matching tet that will be filling the space
41
41
 
42
- const current_tet_index = cavity.__deleted[i + start];
42
+ const current_tet_index = deleted_tets[i + start];
43
43
 
44
44
  const i5 = i * 5;
45
45
 
@@ -62,21 +62,51 @@ export function fill_in_a_cavity(mesh, points, subdets, cavity, cur_tet) {
62
62
 
63
63
  // boundary[i5 + 4] = encoded_current_tet;
64
64
 
65
- // compute SubDet
66
- tetrahedral_mesh_compute_sub_determinant(subdets, current_tet_index * 4, mesh, points, current_tet_index);
65
+ visited_flags.set(current_tet_index, false); // clear flag
67
66
  }
68
67
 
69
68
  computeAdjacencies(mesh, cavity, start, blength);
70
- // computeAdjacenciesSlow(mesh, cavity, start, blength);
71
69
 
72
- debug_validate_mesh(mesh, points);
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);
73
77
 
74
78
  cavity.__boundary_size = 0;
75
- cavity.__deleted_size = start;
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;
76
95
 
96
+ for (let i = 0; i < ball_tets_count; i++) {
97
+ ball_index = ball_tets_offset + i;
98
+ tet = ball_tets[ball_index];
77
99
 
78
- return cavity.__deleted[start];
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++) {
79
102
 
103
+ if (!validate_neighbour(mesh, tet, j, consumer)) {
104
+ valid = false;
105
+ }
106
+ }
107
+ }
108
+
109
+ return valid;
80
110
  }
81
111
 
82
112
  /**
@@ -95,7 +125,6 @@ function computeAdjacencies(mesh, cavity, start, blength) {
95
125
  let tlength = 0;
96
126
 
97
127
  const Tmp = boundary; // we know there is enough space...
98
- const index = [2, 3, 1, 2];
99
128
 
100
129
  for (let i = 0; i < blength; i++) {
101
130
 
@@ -104,9 +133,13 @@ function computeAdjacencies(mesh, cavity, start, blength) {
104
133
  // pointer to the position of Node[0] in the Tmp array
105
134
 
106
135
  for (let j = 0; j < 3; j++) {
136
+
107
137
  // define the edge by the minimum vertex and the other
108
- const n0 = mesh.getVertexIndex(cur_tet, index[j]);
109
- const n1 = mesh.getVertexIndex(cur_tet, index[j + 1]);
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);
110
143
 
111
144
  const current_tet_neighbour_id = j + 1;
112
145
  const encoded_current_tet = (cur_tet << 2) | (current_tet_neighbour_id & 3);
@@ -134,10 +167,10 @@ function computeAdjacencies(mesh, cavity, start, blength) {
134
167
  tlength++;
135
168
  } else {
136
169
  // we found the neighbour !
137
- const encoded_pairValue = Tmp[k * 3 + 2];
170
+ const encoded_neighbour = Tmp[k * 3 + 2];
138
171
 
139
- mesh.setNeighbour(cur_tet, current_tet_neighbour_id, encoded_pairValue);
140
- mesh.setNeighbour(encoded_pairValue >> 2, encoded_pairValue & 3, encoded_current_tet);
172
+ mesh.setNeighbour(cur_tet, current_tet_neighbour_id, encoded_neighbour);
173
+ mesh.setNeighbour(encoded_neighbour >> 2, encoded_neighbour & 3, encoded_current_tet);
141
174
 
142
175
  // reduce search space, there can only be one neighbour pair, after it's found we can remove the entry from lookup table
143
176
  tlength--;
@@ -152,4 +185,7 @@ function computeAdjacencies(mesh, cavity, start, blength) {
152
185
 
153
186
  assert.equal(tlength, 0, 'Failed to compute adjacencies');
154
187
 
188
+ // if (!validate_patched_ball_adjacencies(mesh, cavity.__deleted, start, blength, console.error)) {
189
+ // debugger;
190
+ // }
155
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
+ }
@@ -0,0 +1,89 @@
1
+ import { assert } from "../../../../assert.js";
2
+ import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
3
+ import { in_sphere_robust } from "../in_sphere_robust.js";
4
+
5
+ /**
6
+ *
7
+ * @param {TetrahedralMesh} mesh
8
+ * @param {Cavity} cavity
9
+ * @param {number[]|Float32Array} points
10
+ * @param {BitSet} visited_flags
11
+ * @param {number} containing_tetra tetrahedron that contains point
12
+ * @param {number} point_index point that forms the cavity
13
+ */
14
+ export function tetrahedral_mesh_compute_cavity(
15
+ mesh,
16
+ points,
17
+ visited_flags,
18
+ cavity,
19
+ containing_tetra,
20
+ point_index
21
+ ) {
22
+ assert.isNonNegativeInteger(containing_tetra, 'containing_tetra');
23
+
24
+ // add the tet to cavity
25
+ cavity.push_deleted(containing_tetra);
26
+ visited_flags.set(containing_tetra, true);
27
+
28
+ for (let start = cavity.__deleted_size - 1; start < cavity.__deleted_size; start++) {
29
+ const cur_tet = cavity.__deleted[start];
30
+
31
+ for (let i = 0; i < 4; i++) {
32
+ const encoded_neighbour = mesh.getNeighbour(cur_tet, i);
33
+
34
+ const k0 = (1 << i) & 3;
35
+ const k1 = (i + 2) % 3;
36
+ const k2 = (~((i + 1) >> 1) & 2) + 1;
37
+
38
+ if (encoded_neighbour === INVALID_NEIGHBOUR) {
39
+ // no neighbour, include this side into boundary
40
+
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
+ );
48
+
49
+ continue;
50
+ }
51
+
52
+ const neighbour_index = encoded_neighbour >> 2;
53
+
54
+ if (visited_flags.get(neighbour_index)) {
55
+ // already visited
56
+ continue;
57
+ }
58
+
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
68
+
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
+ );
76
+
77
+ } else {
78
+ // assert.greaterThanOrEqual(visited_flags.length, neighbour_index * 4 + 3, 'subdet array is too small');
79
+
80
+ //part of the cavity, remove
81
+ cavity.push_deleted(neighbour_index);
82
+ visited_flags.set(neighbour_index, true);
83
+ }
84
+ }
85
+
86
+ }
87
+
88
+ // debug_validate_cavity_boundary(mesh, cavity);
89
+ }
@@ -1,6 +1,6 @@
1
- import { orient3d_fast } from "../../plane/orient3d_fast.js";
2
1
  import { assert } from "../../../../assert.js";
3
2
  import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
3
+ import { orient3d_robust } from "../../plane/orient3d_robust.js";
4
4
 
5
5
  /**
6
6
  * Walk from a given tetrahedron in the mesh towards tetrahedron that contains input point
@@ -10,15 +10,15 @@ import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
10
10
  * @see p3 "One machine, one minute, three billion tetrahedra" by Célestin Marot, Jeanne Pellerin, Jean-François Remacle
11
11
  * @param {TetrahedralMesh} mesh
12
12
  * @param {number[]|Float32Array} points
13
- * @param {number} current_tet
14
- * @param {number} current_vertex
13
+ * @param {number} hint_tetrahedron we will start here and walk towards our target
14
+ * @param {number} search_vertex_index
15
15
  * @returns {number}
16
16
  */
17
- export function tetrahedral_mesh_walk_toward_cavity(mesh, points, current_tet, current_vertex) {
17
+ export function tetrahedral_mesh_walk_towards_containing_tetrahedron(mesh, points, hint_tetrahedron, search_vertex_index) {
18
18
 
19
19
  let entering_face = 4;
20
20
 
21
- let curTet = current_tet;
21
+ let cur_tet = hint_tetrahedron;
22
22
 
23
23
  while (true) {
24
24
 
@@ -30,25 +30,28 @@ export function tetrahedral_mesh_walk_toward_cavity(mesh, points, current_tet, c
30
30
  const b_i = (i & 2) ^ 3;
31
31
  const c_i = (i + 3) & 2;
32
32
 
33
- const a_index = mesh.getVertexIndex(curTet, a_i);
34
- const b_index = mesh.getVertexIndex(curTet, b_i);
35
- const c_index = mesh.getVertexIndex(curTet, c_i);
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) {
36
38
 
37
- if (i !== entering_face && orient3d_fast(points, a_index, b_index, c_index, current_vertex) < 0.0) {
38
39
  // point is outside the tet on the neighbour's side, move in that direction
39
- const neighbour = mesh.getNeighbour(curTet, i);
40
+ const neighbour = mesh.getNeighbour(cur_tet, i);
40
41
 
41
42
  assert.notEqual(neighbour, INVALID_NEIGHBOUR, 'walked outside of the mesh');
42
43
 
43
- curTet = neighbour >>> 2;
44
+ cur_tet = neighbour >>> 2;
44
45
  entering_face = neighbour & 3;
46
+
45
47
  break;
48
+
46
49
  }
47
50
  }
48
51
 
49
52
  if (i === 4) {
50
53
  // point is inside the tet
51
- return curTet;
54
+ return cur_tet;
52
55
  }
53
56
  }
54
57
 
@@ -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
  ) {
@@ -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
+ }