@woosh/meep-engine 2.43.1 → 2.43.3

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 (105) hide show
  1. package/core/bvh2/aabb3/aabb3_array_combine.js +2 -2
  2. package/core/collection/RingBuffer.js +4 -2
  3. package/core/collection/RingBuffer.spec.js +59 -0
  4. package/core/collection/array/ArrayIteratorRandom.js +1 -1
  5. package/core/collection/{ArrayUtils.spec.js → array/arrayPickBestElement.spec.js} +1 -1
  6. package/core/collection/array/arrayPickBestElements.js +51 -0
  7. package/core/collection/array/arrayPickMinElement.js +43 -0
  8. package/core/collection/array/arrayQuickSort.js +1 -1
  9. package/core/collection/array/arraySetSortingDiff.js +1 -1
  10. package/core/collection/array/arraySwapElements.js +12 -0
  11. package/core/collection/array/groupArrayBy.js +42 -0
  12. package/core/collection/array/isArrayEqual.js +50 -0
  13. package/core/collection/array/randomMultipleFromArray.js +34 -0
  14. package/core/collection/array/randomizeArrayElementOrder.js +23 -0
  15. package/core/geom/2d/convex-hull/convex_hull_monotone_2d.js +1 -1
  16. package/core/geom/3d/aabb/aabb3_build_frustum.js +1 -1
  17. package/core/geom/3d/aabb/compute_aabb_from_points.js +1 -1
  18. package/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +3 -1
  19. package/core/geom/3d/morton/v3_morton_encode_transformed.spec.js +20 -0
  20. package/core/geom/3d/plane/orient3d_fast.js +8 -10
  21. package/core/geom/3d/plane/plane_computeConvex3PlaneIntersection.js +0 -23
  22. package/core/geom/3d/plane/plane_three_compute_convex3_plane_intersection.js +24 -0
  23. package/core/geom/3d/shape/UnionShape3D.js +1 -1
  24. package/core/geom/3d/tetrahedra/README.md +10 -1
  25. package/core/geom/3d/tetrahedra/{tetrahedra_collection.js → TetrahedralMesh.js} +236 -152
  26. package/core/geom/3d/tetrahedra/TetrahedralMesh.spec.js +156 -0
  27. package/core/geom/3d/tetrahedra/compute_bounding_simplex_3d.js +2 -2
  28. package/core/geom/3d/tetrahedra/compute_bounding_simplex_3d.spec.js +4 -4
  29. package/core/geom/3d/tetrahedra/delaunay/Cavity.js +45 -7
  30. package/core/geom/3d/tetrahedra/delaunay/compute_delaunay_tetrahedral_mesh.js +44 -9
  31. package/core/geom/3d/tetrahedra/delaunay/debug_validate_mesh.js +19 -0
  32. package/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +155 -0
  33. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity2.js +224 -0
  34. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_sub_determinant.js +77 -0
  35. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_sub_determinant.spec.js +30 -0
  36. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_insert_point.js +98 -0
  37. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_walk_toward_cavity.js +13 -6
  38. package/core/geom/3d/tetrahedra/point_in_tetrahedron_circumsphere.js +9 -9
  39. package/core/geom/3d/tetrahedra/prototypeTetrahedraBuilder.js +1 -1
  40. package/core/geom/3d/tetrahedra/tetrahedron_compute_signed_volume.js +83 -0
  41. package/core/geom/3d/tetrahedra/tetrahedron_compute_signed_volume.spec.js +24 -0
  42. package/core/geom/3d/tetrahedra/tetrahedron_contains_point.spec.js +66 -0
  43. package/core/geom/3d/tetrahedra/validate_tetrahedral_mesh.js +119 -0
  44. package/core/geom/Bezier.js +0 -27
  45. package/core/geom/Plane.js +0 -4
  46. package/core/geom/packing/miniball/Subspan.js +2 -2
  47. package/core/geom/v3_lerp.js +6 -1
  48. package/core/math/isqrt.js +28 -0
  49. package/core/math/isqrt.spec.js +9 -0
  50. package/core/math/max.spec.js +25 -0
  51. package/core/math/min2.spec.js +25 -0
  52. package/core/model/node-graph/node/NodeInstance.js +3 -3
  53. package/core/primitives/strings/prefixTree/PrefixTreeLeaf.js +1 -1
  54. package/core/process/task/util/randomCountTask.js +1 -1
  55. package/editor/ecs/component/editors/primitive/ArrayEditor.js +1 -1
  56. package/editor/tools/v2/BlenderCameraOrientationGizmo.js +6 -0
  57. package/editor/view/ecs/components/common/AutoCanvasView.js +13 -25
  58. package/engine/asset/AssetManager.d.ts +5 -1
  59. package/engine/asset/AssetManager.js +50 -15
  60. package/engine/asset/AssetManager.spec.js +17 -11
  61. package/engine/asset/AssetRequest.js +57 -0
  62. package/engine/asset/loaders/ArrayBufferLoader.js +22 -0
  63. package/engine/asset/loaders/AssetLoader.js +1 -1
  64. package/engine/ecs/System.js +1 -1
  65. package/engine/ecs/dynamic_actions/DynamicActorSystem.js +1 -1
  66. package/engine/graphics/FrameRunner.js +5 -9
  67. package/engine/graphics/ecs/animation/animator/AnimationClipDefinition.js +1 -1
  68. package/engine/graphics/ecs/animation/animator/graph/definition/AnimationGraphDefinition.js +1 -1
  69. package/engine/graphics/ecs/camera/Camera.js +1 -10
  70. package/engine/graphics/ecs/camera/CameraSystem.js +8 -8
  71. package/engine/graphics/ecs/camera/ProjectionType.js +9 -0
  72. package/engine/graphics/ecs/camera/build_three_camera_object.js +3 -3
  73. package/engine/graphics/ecs/decal/v2/prototypeDecalSystem.js +59 -4
  74. package/engine/graphics/geometry/VertexDataSpec.js +1 -1
  75. package/engine/graphics/impostors/octahedral/prototypeBaker.js +3 -3
  76. package/engine/graphics/micron/plugin/GLTFAssetTransformer.js +1 -1
  77. package/engine/graphics/micron/plugin/MicronRenderPlugin.js +3 -1
  78. package/engine/graphics/particles/node-based/codegen/modules/FunctionSignature.js +1 -1
  79. package/engine/graphics/render/forward_plus/LightManager.js +1 -1
  80. package/engine/graphics/render/forward_plus/LightManager.spec.js +4 -0
  81. package/engine/graphics/render/forward_plus/computeFrustumCorners.js +4 -2
  82. package/engine/graphics/render/forward_plus/prototype/prototypeLightManager.js +2 -2
  83. package/engine/graphics/render/layers/RenderLayerUtils.js +2 -2
  84. package/engine/graphics/shaders/DenoiseShader.js +1 -1
  85. package/engine/graphics/texture/atlas/AtlasPatch.js +11 -3
  86. package/engine/graphics/texture/atlas/CachingTextureAtlas.js +2 -2
  87. package/engine/graphics/texture/atlas/TextureAtlas.js +7 -1
  88. package/engine/graphics/texture/atlas/TextureAtlas.spec.js +22 -0
  89. package/engine/graphics/texture/sampler/Sampler2D.js +0 -64
  90. package/engine/graphics/texture/sampler/Sampler2D.spec.js +2 -1
  91. package/engine/graphics/texture/sampler/sampler2d_combine.js +67 -0
  92. package/engine/intelligence/behavior/ecs/BehaviorSystem.spec.js +0 -3
  93. package/engine/network/PriorityFetch.js +192 -0
  94. package/engine/simulation/DormandPrince.js +1 -1
  95. package/engine/ui/DraggableAspect.js +0 -1
  96. package/generation/grid/generation/road/GridTaskGenerateRoads.js +1 -1
  97. package/package.json +1 -1
  98. package/view/elements/CanvasView.js +7 -1
  99. package/view/elements/image/HTMLElementCacheKey.js +1 -1
  100. package/view/util/DomSizeObserver.js +3 -5
  101. package/core/collection/ArrayUtils.js +0 -263
  102. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity.js +0 -73
  103. package/core/geom/3d/tetrahedra/hxt/a.js +0 -524
  104. package/core/geom/3d/tetrahedra/hxt/hxt.js +0 -140
  105. package/core/geom/3d/tetrahedra/hxt/hxt.wasm +0 -0
@@ -1,17 +1,15 @@
1
1
  import { tetrahedron_contains_point } from "./tetrahedron_contains_point.js";
2
- import { assert } from "../../../assert.js";
3
2
  import { BitSet } from "../../../binary/BitSet.js";
4
3
  import { typed_array_copy } from "../../../collection/array/typed/typed_array_copy.js";
5
4
  import { max3 } from "../../../math/max3.js";
6
- import { Cavity } from "./delaunay/Cavity.js";
7
- import { tetrahedral_mesh_compute_cavity } from "./delaunay/tetrahedral_mesh_compute_cavity.js";
5
+ import { assert } from "../../../assert.js";
8
6
 
9
7
  /**
10
8
  * Size in bytes of a single tetrahedron record
11
9
  * @readonly
12
10
  * @type {number}
13
11
  */
14
- export const LAYOUT_TETRA_BYTE_SIZE = 9 * 4;
12
+ export const LAYOUT_TETRA_BYTE_SIZE = 8 * 4;
15
13
 
16
14
  /**
17
15
  * @readonly
@@ -19,11 +17,35 @@ export const LAYOUT_TETRA_BYTE_SIZE = 9 * 4;
19
17
  */
20
18
  export const INVALID_NEIGHBOUR = 0xFFFFFFFF;
21
19
 
22
- const SCRATCH_CAVITY = new Cavity();
20
+ /**
21
+ * @readonly
22
+ * @type {number}
23
+ */
24
+ export const MAX_TET_INDEX = 0xFFFFFFFC;
25
+
26
+ /**
27
+ * @readonly
28
+ * @type {number}
29
+ */
30
+ const DEFAULT_INITIAL_SIZE = 128;
31
+
32
+ /**
33
+ * @readonly
34
+ * @type {number}
35
+ */
36
+ const CAPACITY_GROW_MULTIPLIER = 1.2;
37
+
38
+ /**
39
+ * @readonly
40
+ * @type {number}
41
+ */
42
+ const CAPACITY_GROW_MIN_STEP = 32;
23
43
 
24
44
  /**
25
- * Only keeps track of tetrahedra, actual point coordinates are stored outside. For most useful operations point coordinates are passed in
26
- * Layout:
45
+ * Only keeps track of tetrahedra, actual point coordinates are stored outside.
46
+ * For most useful operations point coordinates are passed in as an extra argument.
47
+ *
48
+ * Binary Layout:
27
49
  * vertex_id_a :: uint32
28
50
  * vertex_id_b :: uint32
29
51
  * vertex_id_c :: uint32
@@ -32,19 +54,51 @@ const SCRATCH_CAVITY = new Cavity();
32
54
  * neighbour_b :: uint32 - neighbour tetrahedron, opposite to vertex B
33
55
  * neighbour_c :: uint32 - neighbour tetrahedron, opposite to vertex C
34
56
  * neighbour_d :: uint32 - neighbour tetrahedron, opposite to vertex D
35
- * sub_determinant :: float - ?
36
- * Layout is similar to [1], but is interleaved for better cache locality
57
+ * Layout is similar to [1], but is interleaved for better cache locality.
58
+ * Also note that sub-determinants are not included, these are only needed for building the mesh, we excluded them to keep structure clean and more compact.
59
+ *
60
+ * Neighbours are encoded in the following manner:
61
+ * MSB -> [tet_id:30bit][opposite_corner_index:2bit] <- LSB
62
+ * Code to get tet index: encoded >> 2
63
+ * Code to get corner index: encoded & 3
37
64
  *
38
65
  * @see [1] 2018 "One machine, one minute, three billion tetrahedra" by Célestin Marot, Jeanne Pellerin and Jean-François Remacle
39
66
  * @see https://git.immc.ucl.ac.be/hextreme/hxt_seqdel (C source code for [1])
40
67
  */
41
68
  export class TetrahedralMesh {
42
- constructor(initial_size) {
69
+ /**
70
+ *
71
+ * @param {number} [initial_size]
72
+ */
73
+ constructor(initial_size = DEFAULT_INITIAL_SIZE) {
74
+ assert.isNonNegativeInteger(initial_size, 'initial_size');
75
+
76
+ /**
77
+ *
78
+ * @type {ArrayBuffer}
79
+ * @private
80
+ */
43
81
  this.__buffer = new ArrayBuffer(initial_size * LAYOUT_TETRA_BYTE_SIZE);
44
82
 
83
+ /**
84
+ *
85
+ * @type {DataView}
86
+ * @private
87
+ */
45
88
  this.__view = new DataView(this.__buffer);
46
89
 
90
+ /**
91
+ *
92
+ * @type {number}
93
+ * @private
94
+ */
47
95
  this.__capacity = initial_size;
96
+
97
+ /**
98
+ *
99
+ * @type {number}
100
+ * @private
101
+ */
48
102
  this.__length = 0;
49
103
 
50
104
 
@@ -56,22 +110,36 @@ export class TetrahedralMesh {
56
110
  this.__occupancy = new BitSet();
57
111
  }
58
112
 
113
+ /**
114
+ * Traverse tetrahedrons
115
+ * @param {(tet:number,mesh:TetrahedralMesh)=>*} visitor
116
+ * @param {*} [thisArg]
117
+ */
118
+ forEach(visitor, thisArg) {
119
+ const occupancy = this.__occupancy;
120
+ for (let i = occupancy.nextSetBit(0); i !== -1; i = occupancy.nextSetBit(i + 1)) {
121
+ visitor.call(thisArg, i, this);
122
+ }
123
+ }
124
+
59
125
  /**
60
126
  *
61
- * @param {number} n
127
+ * @param {number} capacity
62
128
  */
63
- setCapacity(n) {
64
- if (n === this.__capacity) {
129
+ setCapacity(capacity) {
130
+ assert.isNonNegativeInteger(capacity, 'capacity');
131
+
132
+ if (capacity === this.__capacity) {
65
133
  // do nothing
66
134
  return;
67
135
  }
68
136
 
69
- if (n < this.__length) {
70
- throw new Error('Dropping information, not supported');
137
+ if (capacity < this.__capacity && capacity < this.__occupancy.size()) {
138
+ throw new Error('Reducing capacity would result in dropping information. This is an illegal operation. If you need to reduce capacity - either drop data or compact the layout first.');
71
139
  }
72
140
 
73
141
  // allocate new buffer
74
- const new_buffer = new ArrayBuffer(n * LAYOUT_TETRA_BYTE_SIZE);
142
+ const new_buffer = new ArrayBuffer(capacity * LAYOUT_TETRA_BYTE_SIZE);
75
143
 
76
144
  // move data across from old buffer to the new one
77
145
  const destination_uint8 = new Uint8Array(new_buffer);
@@ -82,18 +150,39 @@ export class TetrahedralMesh {
82
150
  // set new buffer
83
151
  this.__buffer = new_buffer;
84
152
  this.__view = new DataView(new_buffer);
153
+
154
+ // write new capacity
155
+ this.__capacity = capacity;
156
+ }
157
+
158
+ /**
159
+ *
160
+ * @return {number}
161
+ */
162
+ getCapacity() {
163
+ return this.__capacity;
164
+ }
165
+
166
+ /**
167
+ * How many tetrahedrons are contained in the mesh
168
+ * @return {number}
169
+ */
170
+ size() {
171
+ return this.__length;
85
172
  }
86
173
 
87
174
  /**
88
175
  *
89
- * @param {number} n
176
+ * @param {number} capacity
90
177
  */
91
- ensureCapacity(n) {
178
+ ensureCapacity(capacity) {
179
+
180
+ const existing_capacity = this.__capacity;
92
181
 
93
182
  const new_capacity = max3(
94
- n,
95
- this.__capacity * 1.2,
96
- this.__capacity + 32
183
+ capacity,
184
+ existing_capacity * CAPACITY_GROW_MULTIPLIER,
185
+ existing_capacity + CAPACITY_GROW_MIN_STEP
97
186
  );
98
187
 
99
188
  this.setCapacity(new_capacity);
@@ -101,31 +190,115 @@ export class TetrahedralMesh {
101
190
 
102
191
  /**
103
192
  *
193
+ * @param {number} tet
194
+ * @return {boolean}
195
+ */
196
+ exists(tet) {
197
+ return this.__occupancy.get(tet);
198
+ }
199
+
200
+ /**
201
+ * NOTE: the neighbour value must be encoded, see format specification for details
104
202
  * @param {number} tetra_index
105
203
  * @param {number} neighbour_index
106
204
  * @returns {number}
107
205
  */
108
206
  getNeighbour(tetra_index, neighbour_index) {
207
+ assert.isNonNegativeInteger(tetra_index, 'tetra_index');
208
+ assert.ok(this.__occupancy.get(tetra_index), 'tetrahedron does not exist');
209
+
210
+ assert.isNonNegativeInteger(neighbour_index, 'neighbour_index');
211
+
212
+ assert.lessThan(neighbour_index, 4, 'neighbour_index');
213
+
109
214
  const tetra_address = LAYOUT_TETRA_BYTE_SIZE * tetra_index;
110
215
  return this.__view.getUint32(tetra_address + (4 + neighbour_index) * 4);
111
216
  }
112
217
 
113
218
  /**
114
- *
219
+ * NOTE: the neighbour value must be encoded, see format specification for details
115
220
  * @param {number} tetra_index
116
221
  * @param {number} neighbour_index which neighbour to set (0..11)
117
222
  * @param {number} neighbour index of the neighbour
118
223
  */
119
224
  setNeighbour(tetra_index, neighbour_index, neighbour) {
225
+
226
+ assert.isNonNegativeInteger(tetra_index, 'tetra_index');
227
+ assert.ok(this.__occupancy.get(tetra_index), 'tetrahedron does not exist');
228
+
229
+ assert.isNonNegativeInteger(neighbour_index, 'neighbour_index');
230
+ assert.isNonNegativeInteger(neighbour, 'neighbour');
231
+
232
+ assert.lessThan(neighbour_index, 4, 'neighbour_index');
233
+
120
234
  const tetra_address = LAYOUT_TETRA_BYTE_SIZE * tetra_index;
121
235
  return this.__view.setUint32(tetra_address + (4 + neighbour_index) * 4, neighbour);
122
236
  }
123
237
 
238
+
239
+ /**
240
+ *
241
+ * @param {number} tet_index
242
+ * @param {number} point_index should be an integer between 0 and 3
243
+ * @returns {number}
244
+ */
245
+ getVertexIndex(tet_index, point_index) {
246
+ assert.isNonNegativeInteger(tet_index, 'tet_index');
247
+ assert.lessThanOrEqual(tet_index, MAX_TET_INDEX, 'max index exceeded');
248
+ assert.ok(this.__occupancy.get(tet_index), 'tetrahedron does not exist');
249
+
250
+ assert.isNonNegativeInteger(point_index, 'point_index');
251
+ assert.lessThan(point_index, 4, 'point_index must be less than 4');
252
+
253
+
254
+ return this.__view.getUint32(tet_index * LAYOUT_TETRA_BYTE_SIZE + point_index * 4);
255
+ }
256
+
257
+ /**
258
+ *
259
+ * @param {number} tet_index
260
+ * @param {number} point_index
261
+ * @param {number} vertex
262
+ */
263
+ setVertexIndex(tet_index, point_index, vertex) {
264
+ assert.isNonNegativeInteger(tet_index, 'tet_index');
265
+ assert.lessThanOrEqual(tet_index, MAX_TET_INDEX, 'max index exceeded');
266
+ assert.ok(this.__occupancy.get(tet_index), 'tetrahedron does not exist');
267
+
268
+ assert.isNonNegativeInteger(point_index, 'point_index');
269
+ assert.lessThan(point_index, 4, 'point_index must be less than 4');
270
+
271
+ assert.isNonNegativeInteger(vertex, 'vertex');
272
+
273
+
274
+ return this.__view.setUint32(
275
+ tet_index * LAYOUT_TETRA_BYTE_SIZE + point_index * 4,
276
+ vertex
277
+ );
278
+ }
279
+
280
+ /**
281
+ * Whether a given tetrahedron contains vertex with a given index
282
+ * @param {number} tet
283
+ * @param {number} vertex
284
+ * @return {boolean}
285
+ */
286
+ tetContainsVertex(tet, vertex) {
287
+ for (let i = 0; i < 4; i++) {
288
+ if (this.getVertexIndex(tet, i) === vertex) {
289
+ return true;
290
+ }
291
+ }
292
+
293
+ return false;
294
+ }
295
+
124
296
  validateLength() {
125
297
  return this.countOccupancy() === this.__length;
126
298
  }
127
299
 
128
300
  /**
301
+ * DEBUG method
129
302
  * Counts number of occupied(used) elements in the pool
130
303
  * Should be the same as __length
131
304
  * @return {number}
@@ -139,6 +312,37 @@ export class TetrahedralMesh {
139
312
  return r;
140
313
  }
141
314
 
315
+ /**
316
+ * Allocate empty tet
317
+ * NOTE: the tet memory might be dirty, please make sure you set/clear it as necessary
318
+ * @return {number} index of allocated tetrahedron
319
+ */
320
+ allocate() {
321
+ const tetra_index = this.__occupancy.nextClearBit(0);
322
+
323
+ const required_capacity = tetra_index + 1;
324
+
325
+ if (required_capacity > this.__capacity) {
326
+ // needs to be increased in size
327
+ this.ensureCapacity(required_capacity);
328
+ }
329
+
330
+ this.__occupancy.set(tetra_index, true);
331
+
332
+ // initialize neighbours
333
+ for (let i = 0; i < 4; i++) {
334
+ this.setNeighbour(tetra_index, i, INVALID_NEIGHBOUR);
335
+ }
336
+
337
+ //assert(this.validateLength());
338
+
339
+ this.__length++;
340
+
341
+ // assert(this.validateLength());
342
+
343
+ return tetra_index;
344
+ }
345
+
142
346
  /**
143
347
  *
144
348
  * @param {number[]|Float32Array} points
@@ -146,16 +350,12 @@ export class TetrahedralMesh {
146
350
  * @param {number} b
147
351
  * @param {number} c
148
352
  * @param {number} d
149
- * @returns {number} index of the new tetra
353
+ * @returns {number} index of the new tetrahedron
150
354
  */
151
355
  append(points, a, b, c, d) {
152
- const tetra_index = this.__occupancy.nextClearBit(0);
153
- const address = tetra_index * LAYOUT_TETRA_BYTE_SIZE;
356
+ const tetra_index = this.allocate();
154
357
 
155
- if (tetra_index >= this.__capacity) {
156
- // needs to be increased in size
157
- this.ensureCapacity(tetra_index);
158
- }
358
+ const address = tetra_index * LAYOUT_TETRA_BYTE_SIZE;
159
359
 
160
360
  const view = this.__view;
161
361
 
@@ -170,26 +370,20 @@ export class TetrahedralMesh {
170
370
  view.setUint32(address + 24, INVALID_NEIGHBOUR);
171
371
  view.setUint32(address + 28, INVALID_NEIGHBOUR);
172
372
 
173
- //assert(this.validateLength());
174
-
175
- this.__occupancy.set(tetra_index, true);
176
-
177
- this.__length++;
178
-
179
- // assert(this.validateLength());
180
-
181
373
  return tetra_index;
182
374
  }
183
375
 
184
376
  /**
185
377
  * Remove tetrahedron
186
- * @param {number} tera_index
378
+ * @param {number} tetra_index
187
379
  */
188
- remove(tera_index) {
380
+ remove(tetra_index) {
381
+ assert.isNonNegativeInteger(tetra_index, 'tera_index');
382
+ assert.equal(this.__occupancy.get(tetra_index), true, 'tetrahedron does not exist');
189
383
 
190
384
  // find neighbours and remove reference to self
191
385
  for (let i = 0; i < 4; i++) {
192
- const neighbour_encoded = this.getNeighbour(tera_index, i);
386
+ const neighbour_encoded = this.getNeighbour(tetra_index, i);
193
387
 
194
388
  if (neighbour_encoded === INVALID_NEIGHBOUR) {
195
389
  // no neighbour
@@ -198,7 +392,7 @@ export class TetrahedralMesh {
198
392
 
199
393
  // get tetrahedra index and point index
200
394
  const neighbour_index = neighbour_encoded >> 2;
201
- const neighbour_point = neighbour_encoded & 4;
395
+ const neighbour_point = neighbour_encoded & 3;
202
396
 
203
397
  // clear reference to self
204
398
  this.setNeighbour(neighbour_index, neighbour_point, INVALID_NEIGHBOUR);
@@ -207,7 +401,7 @@ export class TetrahedralMesh {
207
401
  //assert(this.validateLength());
208
402
 
209
403
  // mark as dead
210
- this.__occupancy.clear(tera_index);
404
+ this.__occupancy.clear(tetra_index);
211
405
  this.__length--;
212
406
 
213
407
  //assert(this.validateLength());
@@ -270,114 +464,4 @@ export class TetrahedralMesh {
270
464
  return -1;
271
465
  }
272
466
 
273
- /**
274
- *
275
- * @param {number} tet_index
276
- * @param {number} point_index should be an integer between 0 and 3
277
- * @returns {number}
278
- */
279
- getCornerIndex(tet_index, point_index) {
280
- return this.__view.getUint32(tet_index * LAYOUT_TETRA_BYTE_SIZE + point_index * 4);
281
- }
282
-
283
-
284
- /**
285
- *
286
- * @param {number[]|Float32Array} points
287
- * @param {number} index
288
- */
289
- insertPoint(points, index) {
290
- const debug_length_initial = this.__length;
291
-
292
- // Compute the cavity, that is the set of tetrahedra that need to be removed and rebuilt
293
- tetrahedral_mesh_compute_cavity(this, SCRATCH_CAVITY, points, index);
294
-
295
-
296
- // identify cavity "edge" - the set of tetras that are neighbours of the tetras that form the cavity
297
- const cavity_size = SCRATCH_CAVITY.size;
298
- const cavity_indices = SCRATCH_CAVITY.indices;
299
-
300
- for (let i = 0; i < cavity_size; i++) {
301
- const data = this.__view;
302
-
303
- const cavity_index = cavity_indices[i];
304
-
305
- // take each side of the tetra
306
- const tetra_address = cavity_index * LAYOUT_TETRA_BYTE_SIZE;
307
-
308
- const a = data.getUint32(tetra_address);
309
- const b = data.getUint32(tetra_address + 4);
310
- const c = data.getUint32(tetra_address + 8);
311
- const d = data.getUint32(tetra_address + 12);
312
-
313
- // attempt to form 4 tetras using the sides of the cavity
314
- const a_neighbour = data.getUint32(tetra_address + 16);
315
- const a_neighbour_index = a_neighbour >> 2;
316
-
317
- const b_neighbour = data.getUint32(tetra_address + 20);
318
- const b_neighbour_index = b_neighbour >> 2;
319
-
320
- const c_neighbour = data.getUint32(tetra_address + 24);
321
- const c_neighbour_index = c_neighbour >> 2;
322
-
323
- const d_neighbour = data.getUint32(tetra_address + 28);
324
- const d_neighbour_index = d_neighbour >> 2;
325
-
326
-
327
- if (a_neighbour === INVALID_NEIGHBOUR || cavity_indices.indexOf(a_neighbour_index) === 0) {
328
- // ABC is a valid side
329
- const tet = this.append(points, b, d, c, index);
330
- // patch in neighbours
331
- if (a_neighbour !== INVALID_NEIGHBOUR) {
332
- this.setNeighbour(tet, 3, a_neighbour);
333
- this.setNeighbour(a_neighbour_index, a_neighbour & 3, tet);
334
- }
335
- }
336
- if (b_neighbour === INVALID_NEIGHBOUR || cavity_indices.indexOf(b_neighbour_index) === 0) {
337
- // ABC is a valid side
338
- const tet = this.append(points, c, d, a, index);
339
- if (b_neighbour !== INVALID_NEIGHBOUR) {
340
- this.setNeighbour(tet, 3, b_neighbour);
341
- this.setNeighbour(b_neighbour_index, b_neighbour & 3, tet);
342
- }
343
- }
344
- if (c_neighbour === INVALID_NEIGHBOUR || cavity_indices.indexOf(c_neighbour_index) === 0) {
345
- // ABC is a valid side
346
- const tet = this.append(points, d, b, a, index);
347
- if (c_neighbour !== INVALID_NEIGHBOUR) {
348
- this.setNeighbour(tet, 3, c_neighbour);
349
- this.setNeighbour(c_neighbour_index, c_neighbour & 3, tet);
350
- }
351
- }
352
- if (d_neighbour === INVALID_NEIGHBOUR || cavity_indices.indexOf(d_neighbour_index) === 0) {
353
- // ABC is a valid side
354
- const tet = this.append(points, a, b, c, index);
355
- if (d_neighbour !== INVALID_NEIGHBOUR) {
356
- this.setNeighbour(tet, 3, d_neighbour);
357
- this.setNeighbour(d_neighbour_index, d_neighbour & 3, tet);
358
- }
359
- }
360
- }
361
-
362
-
363
- // 3. remove the cavity tetrahedrons
364
- for (let i = 0; i < cavity_size; i++) {
365
- const cavity_index = cavity_indices[i];
366
-
367
- // clear out cavity, connectivity was already patched in the previous step
368
- this.__occupancy.set(cavity_index, false);
369
- this.__length--;
370
- }
371
-
372
- assert.greaterThan(this.__length, debug_length_initial, 'after insertion number of tetrahedra must grow, not shrink')
373
- }
374
-
375
- /**
376
- * optimize tetrahedrons to reduce number of thin and long tetras
377
- * @see https://gitlab.onelab.info/gmsh/gmsh/-/blob/master/contrib/hxt/tetMesh/src/HXTSPR.c
378
- */
379
- optimize() {
380
-
381
- throw new Error('Not Implemented');
382
- }
383
467
  }
@@ -0,0 +1,156 @@
1
+ import { TetrahedralMesh } from "./TetrahedralMesh.js";
2
+ import { jest } from "@jest/globals";
3
+
4
+ test("constructor does not throw", () => {
5
+ expect(() => new TetrahedralMesh(1)).not.toThrow();
6
+ });
7
+
8
+ test("capacity set in constructor is respected", () => {
9
+ const mesh = new TetrahedralMesh(7);
10
+
11
+ expect(mesh.getCapacity()).toBe(7);
12
+ });
13
+
14
+ test("attempting to set capacity below what's used will throw", () => {
15
+ const mesh = new TetrahedralMesh(1);
16
+
17
+ mesh.allocate();
18
+ mesh.allocate();
19
+
20
+ expect(() => mesh.setCapacity(1)).toThrow();
21
+ });
22
+
23
+ test("set/get capacity consistence", () => {
24
+ const mesh = new TetrahedralMesh(2);
25
+
26
+ mesh.setCapacity(1);
27
+
28
+ expect(mesh.getCapacity()).toBe(1);
29
+
30
+ mesh.setCapacity(1); // idempotency check
31
+
32
+ expect(mesh.getCapacity()).toBe(1);
33
+
34
+ mesh.setCapacity(2);
35
+
36
+ expect(mesh.getCapacity()).toBe(2);
37
+ });
38
+
39
+
40
+ test("set/get neighbour consistency", () => {
41
+ const mesh = new TetrahedralMesh(7);
42
+
43
+ const tet = mesh.allocate();
44
+
45
+ mesh.setNeighbour(tet, 0, 7);
46
+ mesh.setNeighbour(tet, 1, 3);
47
+ mesh.setNeighbour(tet, 2, 11);
48
+ mesh.setNeighbour(tet, 3, 13);
49
+
50
+ //
51
+ expect(mesh.getNeighbour(tet, 0)).toBe(7);
52
+ expect(mesh.getNeighbour(tet, 1)).toBe(3);
53
+ expect(mesh.getNeighbour(tet, 2)).toBe(11);
54
+ expect(mesh.getNeighbour(tet, 3)).toBe(13);
55
+ });
56
+
57
+ test("set/get vertex index consistency", () => {
58
+ const mesh = new TetrahedralMesh(7);
59
+ const tet = mesh.allocate();
60
+
61
+ mesh.setVertexIndex(tet, 0, 3);
62
+ mesh.setVertexIndex(tet, 1, 7);
63
+ mesh.setVertexIndex(tet, 2, 11);
64
+ mesh.setVertexIndex(tet, 3, 13);
65
+
66
+ //
67
+ expect(mesh.getVertexIndex(tet, 0)).toBe(3);
68
+ expect(mesh.getVertexIndex(tet, 1)).toBe(7);
69
+ expect(mesh.getVertexIndex(tet, 2)).toBe(11);
70
+ expect(mesh.getVertexIndex(tet, 3)).toBe(13);
71
+ });
72
+
73
+ test("allocation within capacity doesn't resize capacity", () => {
74
+ const mesh = new TetrahedralMesh(1);
75
+
76
+ expect(mesh.getCapacity()).toBe(1);
77
+
78
+ mesh.allocate();
79
+
80
+ expect(mesh.getCapacity()).toBe(1); // allocation fits in the capacity, so no growth is expected
81
+ });
82
+
83
+ test("allocation beyond capacity does not destroy existing data", () => {
84
+ const mesh = new TetrahedralMesh(1);
85
+
86
+ const t0 = mesh.allocate();
87
+
88
+ expect(mesh.getCapacity()).toBe(1);
89
+
90
+ for (let i = 0; i < 4; i++) {
91
+ mesh.setNeighbour(t0, i, 1);
92
+ mesh.setVertexIndex(t0, i, 3);
93
+ }
94
+
95
+ mesh.allocate(); // allocation over capacity
96
+
97
+ expect(mesh.getCapacity()).toBeGreaterThan(1);
98
+
99
+ for (let i = 0; i < 4; i++) {
100
+ expect(mesh.getNeighbour(t0, i)).toBe(1);
101
+ expect(mesh.getVertexIndex(t0, i)).toBe(3);
102
+ }
103
+
104
+ });
105
+
106
+ test("allocation increases size", () => {
107
+ const mesh = new TetrahedralMesh(1);
108
+
109
+ expect(mesh.size()).toBe(0);
110
+
111
+ mesh.allocate();
112
+
113
+ expect(mesh.size()).toBe(1);
114
+ });
115
+
116
+ test("remove method reduces size", () => {
117
+ const mesh = new TetrahedralMesh(1);
118
+
119
+ const tet = mesh.allocate();
120
+
121
+ expect(mesh.size()).toBe(1);
122
+
123
+ mesh.remove(tet);
124
+
125
+ expect(mesh.size()).toBe(0);
126
+ });
127
+
128
+
129
+ test("forEach visits all allocated tets", () => {
130
+ const mesh = new TetrahedralMesh();
131
+
132
+ const t0 = mesh.allocate();
133
+ const t1 = mesh.allocate();
134
+
135
+ const mock = jest.fn();
136
+
137
+ mesh.forEach(mock);
138
+
139
+ expect(mock).toHaveBeenCalledTimes(2);
140
+ expect(mock).toHaveBeenCalledWith(t0, mesh);
141
+ expect(mock).toHaveBeenCalledWith(t1, mesh);
142
+ });
143
+
144
+ test("exists method", () => {
145
+ const mesh = new TetrahedralMesh();
146
+
147
+ expect(mesh.exists(0)).toBe(false);
148
+
149
+ const tet = mesh.allocate();
150
+
151
+ expect(mesh.exists(tet)).toBe(true);
152
+
153
+ mesh.remove(tet);
154
+
155
+ expect(mesh.exists(tet)).toBe(false);
156
+ });