@woosh/meep-engine 2.43.0 → 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.
- package/core/bvh2/aabb3/aabb3_array_combine.js +2 -2
- package/core/collection/RingBuffer.js +4 -2
- package/core/collection/RingBuffer.spec.js +59 -0
- package/core/collection/array/ArrayIteratorRandom.js +1 -1
- package/core/collection/{ArrayUtils.spec.js → array/arrayPickBestElement.spec.js} +1 -1
- package/core/collection/array/arrayPickBestElements.js +51 -0
- package/core/collection/array/arrayPickMinElement.js +43 -0
- package/core/collection/array/arrayQuickSort.js +1 -1
- package/core/collection/array/arraySetSortingDiff.js +1 -1
- package/core/collection/array/arraySwapElements.js +12 -0
- package/core/collection/array/groupArrayBy.js +42 -0
- package/core/collection/array/isArrayEqual.js +50 -0
- package/core/collection/array/randomMultipleFromArray.js +34 -0
- package/core/collection/array/randomizeArrayElementOrder.js +23 -0
- package/core/geom/2d/convex-hull/convex_hull_monotone_2d.js +1 -1
- package/core/geom/3d/aabb/aabb3_build_frustum.js +1 -1
- package/core/geom/3d/aabb/compute_aabb_from_points.js +1 -1
- package/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +3 -1
- package/core/geom/3d/morton/v3_morton_encode_transformed.spec.js +20 -0
- package/core/geom/3d/plane/orient3d_fast.js +8 -10
- package/core/geom/3d/plane/plane_computeConvex3PlaneIntersection.js +0 -23
- package/core/geom/3d/plane/plane_three_compute_convex3_plane_intersection.js +24 -0
- package/core/geom/3d/shape/UnionShape3D.js +1 -1
- package/core/geom/3d/tetrahedra/README.md +10 -1
- package/core/geom/3d/tetrahedra/{tetrahedra_collection.js → TetrahedralMesh.js} +236 -152
- package/core/geom/3d/tetrahedra/TetrahedralMesh.spec.js +156 -0
- package/core/geom/3d/tetrahedra/compute_bounding_simplex_3d.js +2 -2
- package/core/geom/3d/tetrahedra/compute_bounding_simplex_3d.spec.js +4 -4
- package/core/geom/3d/tetrahedra/delaunay/Cavity.js +45 -7
- package/core/geom/3d/tetrahedra/delaunay/compute_delaunay_tetrahedral_mesh.js +44 -9
- package/core/geom/3d/tetrahedra/delaunay/debug_validate_mesh.js +19 -0
- package/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +155 -0
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity2.js +224 -0
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_sub_determinant.js +77 -0
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_sub_determinant.spec.js +30 -0
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_insert_point.js +98 -0
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_walk_toward_cavity.js +13 -6
- package/core/geom/3d/tetrahedra/point_in_tetrahedron_circumsphere.js +9 -9
- package/core/geom/3d/tetrahedra/prototypeTetrahedraBuilder.js +1 -1
- package/core/geom/3d/tetrahedra/tetrahedron_compute_signed_volume.js +83 -0
- package/core/geom/3d/tetrahedra/tetrahedron_compute_signed_volume.spec.js +24 -0
- package/core/geom/3d/tetrahedra/tetrahedron_contains_point.spec.js +66 -0
- package/core/geom/3d/tetrahedra/validate_tetrahedral_mesh.js +119 -0
- package/core/geom/Bezier.js +0 -27
- package/core/geom/Plane.js +0 -4
- package/core/geom/packing/miniball/Subspan.js +2 -2
- package/core/geom/v3_lerp.js +6 -1
- package/core/math/isqrt.js +28 -0
- package/core/math/isqrt.spec.js +9 -0
- package/core/math/max.spec.js +25 -0
- package/core/math/min2.spec.js +25 -0
- package/core/model/node-graph/node/NodeInstance.js +3 -3
- package/core/primitives/strings/prefixTree/PrefixTreeLeaf.js +1 -1
- package/core/process/task/util/randomCountTask.js +1 -1
- package/editor/ecs/component/editors/primitive/ArrayEditor.js +1 -1
- package/editor/tools/v2/BlenderCameraOrientationGizmo.js +6 -0
- package/editor/view/ecs/components/common/AutoCanvasView.js +13 -25
- package/engine/asset/AssetManager.d.ts +7 -1
- package/engine/asset/AssetManager.js +50 -15
- package/engine/asset/AssetManager.spec.js +17 -11
- package/engine/asset/AssetRequest.js +57 -0
- package/engine/asset/loaders/ArrayBufferLoader.js +22 -0
- package/engine/asset/loaders/AssetLoader.js +1 -1
- package/engine/ecs/System.js +1 -1
- package/engine/ecs/dynamic_actions/DynamicActorSystem.js +1 -1
- package/engine/graphics/FrameRunner.js +5 -9
- package/engine/graphics/ecs/animation/animator/AnimationClipDefinition.js +1 -1
- package/engine/graphics/ecs/animation/animator/graph/definition/AnimationGraphDefinition.js +1 -1
- package/engine/graphics/ecs/camera/Camera.js +1 -10
- package/engine/graphics/ecs/camera/CameraSystem.js +8 -8
- package/engine/graphics/ecs/camera/ProjectionType.js +9 -0
- package/engine/graphics/ecs/camera/build_three_camera_object.js +3 -3
- package/engine/graphics/ecs/decal/v2/prototypeDecalSystem.js +59 -4
- package/engine/graphics/geometry/VertexDataSpec.js +1 -1
- package/engine/graphics/impostors/octahedral/prototypeBaker.js +3 -3
- package/engine/graphics/micron/plugin/GLTFAssetTransformer.js +1 -1
- package/engine/graphics/micron/plugin/MicronRenderPlugin.js +3 -1
- package/engine/graphics/particles/node-based/codegen/modules/FunctionSignature.js +1 -1
- package/engine/graphics/render/forward_plus/LightManager.js +1 -1
- package/engine/graphics/render/forward_plus/LightManager.spec.js +4 -0
- package/engine/graphics/render/forward_plus/computeFrustumCorners.js +4 -2
- package/engine/graphics/render/forward_plus/prototype/prototypeLightManager.js +2 -2
- package/engine/graphics/render/layers/RenderLayerUtils.js +2 -2
- package/engine/graphics/shaders/DenoiseShader.js +1 -1
- package/engine/graphics/texture/atlas/AtlasPatch.js +11 -3
- package/engine/graphics/texture/atlas/CachingTextureAtlas.js +2 -2
- package/engine/graphics/texture/atlas/TextureAtlas.js +7 -1
- package/engine/graphics/texture/atlas/TextureAtlas.spec.js +22 -0
- package/engine/graphics/texture/sampler/Sampler2D.js +0 -64
- package/engine/graphics/texture/sampler/Sampler2D.spec.js +2 -1
- package/engine/graphics/texture/sampler/sampler2d_combine.js +67 -0
- package/engine/intelligence/behavior/ecs/BehaviorSystem.spec.js +0 -3
- package/engine/network/PriorityFetch.js +192 -0
- package/engine/simulation/DormandPrince.js +1 -1
- package/engine/ui/DraggableAspect.js +0 -1
- package/generation/grid/generation/road/GridTaskGenerateRoads.js +1 -1
- package/package.json +1 -1
- package/view/elements/CanvasView.js +7 -1
- package/view/elements/image/HTMLElementCacheKey.js +1 -1
- package/view/util/DomSizeObserver.js +3 -5
- package/core/collection/ArrayUtils.js +0 -263
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity.js +0 -73
- package/core/geom/3d/tetrahedra/hxt/a.js +0 -524
- package/core/geom/3d/tetrahedra/hxt/hxt.js +0 -140
- 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 {
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
26
|
-
*
|
|
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
|
-
*
|
|
36
|
-
*
|
|
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
|
-
|
|
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}
|
|
127
|
+
* @param {number} capacity
|
|
62
128
|
*/
|
|
63
|
-
setCapacity(
|
|
64
|
-
|
|
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 (
|
|
70
|
-
throw new Error('
|
|
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(
|
|
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}
|
|
176
|
+
* @param {number} capacity
|
|
90
177
|
*/
|
|
91
|
-
ensureCapacity(
|
|
178
|
+
ensureCapacity(capacity) {
|
|
179
|
+
|
|
180
|
+
const existing_capacity = this.__capacity;
|
|
92
181
|
|
|
93
182
|
const new_capacity = max3(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
353
|
+
* @returns {number} index of the new tetrahedron
|
|
150
354
|
*/
|
|
151
355
|
append(points, a, b, c, d) {
|
|
152
|
-
const tetra_index = this.
|
|
153
|
-
const address = tetra_index * LAYOUT_TETRA_BYTE_SIZE;
|
|
356
|
+
const tetra_index = this.allocate();
|
|
154
357
|
|
|
155
|
-
|
|
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}
|
|
378
|
+
* @param {number} tetra_index
|
|
187
379
|
*/
|
|
188
|
-
remove(
|
|
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(
|
|
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 &
|
|
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(
|
|
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
|
+
});
|