@woosh/meep-engine 2.43.3 → 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.
- package/core/binary/BinaryBuffer.js +13 -1
- package/core/binary/BitSet.js +2 -2
- package/core/collection/array/array_range_equal_strict.js +22 -0
- package/core/color/sRGB_to_linear.js +9 -4
- package/core/geom/3d/plane/orient3d_fast.js +3 -0
- package/core/geom/3d/plane/orient3d_robust.js +41 -0
- package/core/geom/3d/sphere/harmonics/README.md +15 -0
- package/core/geom/3d/sphere/harmonics/sh3_add.js +21 -0
- package/core/geom/3d/sphere/harmonics/sh3_dering_optimize_positive.js +618 -0
- package/core/geom/3d/sphere/harmonics/sh3_sample_by_direction.js +49 -0
- package/core/geom/3d/sphere/harmonics/sh3_sample_irradiance_by_direction.js +53 -0
- package/core/geom/3d/tetrahedra/TetrahedralMesh.js +251 -68
- package/core/geom/3d/tetrahedra/TetrahedralMesh.spec.js +80 -3
- package/core/geom/3d/tetrahedra/build_tetrahedral_mesh_buffer_geometry.js +75 -0
- package/core/geom/3d/tetrahedra/delaunay/Cavity.js +5 -1
- package/core/geom/3d/tetrahedra/delaunay/compute_delaunay_tetrahedral_mesh.js +30 -31
- package/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +54 -18
- package/core/geom/3d/tetrahedra/delaunay/push_boundary_with_validation.js +27 -0
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity.js +89 -0
- package/core/geom/3d/tetrahedra/delaunay/{tetrahedral_mesh_walk_toward_cavity.js → tetrahedral_mesh_walk_towards_containing_tetrahedron.js} +15 -12
- package/core/geom/3d/tetrahedra/delaunay/validate_cavity_boundary.js +60 -0
- package/core/geom/3d/tetrahedra/{point_in_tetrahedron_circumsphere.js → in_sphere_fast.js} +2 -4
- package/core/geom/3d/tetrahedra/in_sphere_robust.js +53 -0
- package/core/geom/3d/tetrahedra/prototypeTetrahedraBuilder.js +44 -35
- package/core/geom/3d/tetrahedra/validate_tetrahedral_mesh.js +85 -38
- package/core/geom/3d/util/make_justified_point_grid.js +31 -0
- package/core/process/delay.js +5 -0
- package/editor/Editor.js +3 -0
- package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +195 -11
- package/editor/ecs/component/editors/ecs/ParameterTrackSetEditor.js +16 -0
- package/editor/ecs/component/editors/ecs/ParticleEmitterLayerEditor.js +4 -0
- package/engine/EngineHarness.js +11 -5
- package/engine/ecs/terrain/ecs/TerrainSystem.js +7 -1
- package/engine/ecs/transform/copy_three_transform.js +15 -0
- package/engine/graphics/ecs/light/Light.js +6 -1
- package/engine/graphics/ecs/light/LightSystem.d.ts +1 -1
- package/engine/graphics/ecs/mesh-v2/three_object_to_entity_composition.js +2 -17
- package/engine/graphics/geometry/instancing/InstancedMeshGroup.js +2 -2
- package/engine/graphics/sh3/LightProbeVolume.js +595 -0
- package/engine/graphics/sh3/SH3VisualisationMaterial.js +79 -0
- package/engine/graphics/sh3/prototypeSH3Probe.js +427 -0
- package/engine/graphics/sh3/visualise_probe.js +40 -0
- package/engine/graphics/texture/atlas/TextureAtlas.js +15 -3
- package/engine/intelligence/blackboard/AbstractBlackboard.d.ts +1 -1
- package/package.json +2 -1
- package/samples/terrain/from_image_2.js +127 -82
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity2.js +0 -224
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_insert_point.js +0 -98
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
*
|
|
4
|
+
* c1 = 0.42904276540489171563379376569857; // 4 * Â2.Y22 = 1/4 * sqrt(15.PI)
|
|
5
|
+
* c2 = 0.51166335397324424423977581244463; // 0.5 * Â1.Y10 = 1/2 * sqrt(PI/3)
|
|
6
|
+
* c3 = 0.24770795610037568833406429782001; // Â2.Y20 = 1/16 * sqrt(5.PI)
|
|
7
|
+
* c4 = 0.88622692545275801364908374167057; // Â0.Y00 = 1/2 * sqrt(PI)
|
|
8
|
+
*
|
|
9
|
+
* @see https://github.com/mrdoob/three.js/blob/d081c5a3501d272d19375fab1b01fedf9df29b22/src/math/SphericalHarmonics3.js#L85
|
|
10
|
+
* @see https://graphics.stanford.edu/papers/envmap/envmap.pdf
|
|
11
|
+
* @see https://www.ppsloan.org/publications/StupidSH36.pdf
|
|
12
|
+
*
|
|
13
|
+
* @param {number[]} result Result will be written here
|
|
14
|
+
* @param {number} result_offset
|
|
15
|
+
* @param {number[]} harmonics coefficients are read from here
|
|
16
|
+
* @param {number} harmonics_offset offset into coefficients array where to start reading the data
|
|
17
|
+
* @param {number} dimension_count number of encoded dimensions, this is essentially a shortcut for multiple harmonics being read at the same time, such as with RGB values
|
|
18
|
+
* @param {number[]} direction 3d vector read from here, we will read 3 values from here to sample spherical harmonic, direction is assumed to be normalized
|
|
19
|
+
* @param {number} direction_offset offset into direction array
|
|
20
|
+
*/
|
|
21
|
+
export function sh3_sample_irradiance_by_direction(
|
|
22
|
+
result, result_offset,
|
|
23
|
+
harmonics, harmonics_offset,
|
|
24
|
+
dimension_count,
|
|
25
|
+
direction, direction_offset
|
|
26
|
+
) {
|
|
27
|
+
const x = direction[direction_offset];
|
|
28
|
+
const y = direction[direction_offset + 1];
|
|
29
|
+
const z = direction[direction_offset + 2];
|
|
30
|
+
|
|
31
|
+
let channel_value;
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < dimension_count; i++) {
|
|
34
|
+
|
|
35
|
+
// band 0
|
|
36
|
+
channel_value = harmonics[harmonics_offset + i] * 0.88622692545275801364908374167057;
|
|
37
|
+
|
|
38
|
+
// band 1
|
|
39
|
+
channel_value += harmonics[harmonics_offset + dimension_count + i] * 2.0 * 0.51166335397324424423977581244463 * y;
|
|
40
|
+
channel_value += harmonics[harmonics_offset + dimension_count * 2 + i] * 2.0 * 0.51166335397324424423977581244463 * z;
|
|
41
|
+
channel_value += harmonics[harmonics_offset + dimension_count * 3 + i] * 2.0 * 0.51166335397324424423977581244463 * x;
|
|
42
|
+
|
|
43
|
+
// band 2
|
|
44
|
+
channel_value += harmonics[harmonics_offset + dimension_count * 4 + i] * 2.0 * 0.42904276540489171563379376569857 * x * y;
|
|
45
|
+
channel_value += harmonics[harmonics_offset + dimension_count * 5 + i] * 2.0 * 0.42904276540489171563379376569857 * y * z;
|
|
46
|
+
channel_value += harmonics[harmonics_offset + dimension_count * 6 + i] * (3 * z * z - 1) * 0.24770795610037568833406429782001;
|
|
47
|
+
channel_value += harmonics[harmonics_offset + dimension_count * 7 + i] * 2.0 * 0.42904276540489171563379376569857 * x * z;
|
|
48
|
+
channel_value += harmonics[harmonics_offset + dimension_count * 8 + i] * 0.42904276540489171563379376569857 * (x * x - y * y);
|
|
49
|
+
|
|
50
|
+
// write out
|
|
51
|
+
result[result_offset + i] = channel_value;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import { tetrahedron_contains_point } from "./tetrahedron_contains_point.js";
|
|
2
|
-
import { BitSet } from "../../../binary/BitSet.js";
|
|
3
2
|
import { typed_array_copy } from "../../../collection/array/typed/typed_array_copy.js";
|
|
4
3
|
import { max3 } from "../../../math/max3.js";
|
|
5
4
|
import { assert } from "../../../assert.js";
|
|
5
|
+
import { array_copy } from "../../../collection/array/copyArray.js";
|
|
6
|
+
import { array_quick_sort_by_comparator } from "../../../collection/array/arrayQuickSort.js";
|
|
7
|
+
import { compareNumbersDescending } from "../../../function/Functions.js";
|
|
8
|
+
import { BinaryBuffer } from "../../../binary/BinaryBuffer.js";
|
|
9
|
+
import { Base64 } from "../../../binary/Base64.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @readonly
|
|
13
|
+
* @type {number}
|
|
14
|
+
*/
|
|
15
|
+
const LAYOUT_TETRA_WORD_COUNT = 8;
|
|
6
16
|
|
|
7
17
|
/**
|
|
8
18
|
* Size in bytes of a single tetrahedron record
|
|
9
19
|
* @readonly
|
|
10
20
|
* @type {number}
|
|
11
21
|
*/
|
|
12
|
-
export const LAYOUT_TETRA_BYTE_SIZE =
|
|
22
|
+
export const LAYOUT_TETRA_BYTE_SIZE = LAYOUT_TETRA_WORD_COUNT * 4;
|
|
13
23
|
|
|
14
24
|
/**
|
|
15
25
|
* @readonly
|
|
@@ -80,6 +90,13 @@ export class TetrahedralMesh {
|
|
|
80
90
|
*/
|
|
81
91
|
this.__buffer = new ArrayBuffer(initial_size * LAYOUT_TETRA_BYTE_SIZE);
|
|
82
92
|
|
|
93
|
+
/**
|
|
94
|
+
*
|
|
95
|
+
* @type {Uint32Array}
|
|
96
|
+
* @private
|
|
97
|
+
*/
|
|
98
|
+
this.__data_uint32 = new Uint32Array(this.__buffer);
|
|
99
|
+
|
|
83
100
|
/**
|
|
84
101
|
*
|
|
85
102
|
* @type {DataView}
|
|
@@ -99,15 +116,21 @@ export class TetrahedralMesh {
|
|
|
99
116
|
* @type {number}
|
|
100
117
|
* @private
|
|
101
118
|
*/
|
|
102
|
-
this.
|
|
119
|
+
this.__used_end = 0;
|
|
103
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Unused slots
|
|
123
|
+
* @type {number[]}
|
|
124
|
+
* @private
|
|
125
|
+
*/
|
|
126
|
+
this.__free = [];
|
|
104
127
|
|
|
105
128
|
/**
|
|
106
|
-
*
|
|
107
|
-
* @type {
|
|
129
|
+
*
|
|
130
|
+
* @type {number}
|
|
108
131
|
* @private
|
|
109
132
|
*/
|
|
110
|
-
this.
|
|
133
|
+
this.__free_pointer = 0;
|
|
111
134
|
}
|
|
112
135
|
|
|
113
136
|
/**
|
|
@@ -116,8 +139,11 @@ export class TetrahedralMesh {
|
|
|
116
139
|
* @param {*} [thisArg]
|
|
117
140
|
*/
|
|
118
141
|
forEach(visitor, thisArg) {
|
|
119
|
-
|
|
120
|
-
|
|
142
|
+
for (let i = 0; i < this.__used_end; i++) {
|
|
143
|
+
if (!this.exists(i)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
121
147
|
visitor.call(thisArg, i, this);
|
|
122
148
|
}
|
|
123
149
|
}
|
|
@@ -134,7 +160,7 @@ export class TetrahedralMesh {
|
|
|
134
160
|
return;
|
|
135
161
|
}
|
|
136
162
|
|
|
137
|
-
if (capacity < this.__capacity && capacity < this.
|
|
163
|
+
if (capacity < this.__capacity && capacity < this.__used_end) {
|
|
138
164
|
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.');
|
|
139
165
|
}
|
|
140
166
|
|
|
@@ -150,6 +176,7 @@ export class TetrahedralMesh {
|
|
|
150
176
|
// set new buffer
|
|
151
177
|
this.__buffer = new_buffer;
|
|
152
178
|
this.__view = new DataView(new_buffer);
|
|
179
|
+
this.__data_uint32 = new Uint32Array(new_buffer);
|
|
153
180
|
|
|
154
181
|
// write new capacity
|
|
155
182
|
this.__capacity = capacity;
|
|
@@ -168,20 +195,20 @@ export class TetrahedralMesh {
|
|
|
168
195
|
* @return {number}
|
|
169
196
|
*/
|
|
170
197
|
size() {
|
|
171
|
-
return this.
|
|
198
|
+
return this.__used_end;
|
|
172
199
|
}
|
|
173
200
|
|
|
174
201
|
/**
|
|
175
|
-
*
|
|
202
|
+
* Grow capacity to at least the specified size
|
|
176
203
|
* @param {number} capacity
|
|
177
204
|
*/
|
|
178
|
-
|
|
205
|
+
growCapacity(capacity) {
|
|
179
206
|
|
|
180
207
|
const existing_capacity = this.__capacity;
|
|
181
208
|
|
|
182
209
|
const new_capacity = max3(
|
|
183
210
|
capacity,
|
|
184
|
-
existing_capacity * CAPACITY_GROW_MULTIPLIER,
|
|
211
|
+
Math.ceil(existing_capacity * CAPACITY_GROW_MULTIPLIER),
|
|
185
212
|
existing_capacity + CAPACITY_GROW_MIN_STEP
|
|
186
213
|
);
|
|
187
214
|
|
|
@@ -189,23 +216,36 @@ export class TetrahedralMesh {
|
|
|
189
216
|
}
|
|
190
217
|
|
|
191
218
|
/**
|
|
192
|
-
*
|
|
219
|
+
* NOTE: this method can be quite slow in cases of sparse allocation, please prefer not to use it
|
|
193
220
|
* @param {number} tet
|
|
194
221
|
* @return {boolean}
|
|
195
222
|
*/
|
|
196
223
|
exists(tet) {
|
|
197
|
-
|
|
224
|
+
|
|
225
|
+
if (tet < 0 || tet >= this.__used_end) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (let i = 0; i < this.__free_pointer; i++) {
|
|
230
|
+
const free = this.__free[i];
|
|
231
|
+
|
|
232
|
+
if (tet === free) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return true;
|
|
198
238
|
}
|
|
199
239
|
|
|
200
240
|
/**
|
|
201
241
|
* NOTE: the neighbour value must be encoded, see format specification for details
|
|
202
242
|
* @param {number} tetra_index
|
|
203
243
|
* @param {number} neighbour_index
|
|
204
|
-
* @returns {number}
|
|
244
|
+
* @returns {number} index of the neighbour encoded with the opposite corner
|
|
205
245
|
*/
|
|
206
246
|
getNeighbour(tetra_index, neighbour_index) {
|
|
207
247
|
assert.isNonNegativeInteger(tetra_index, 'tetra_index');
|
|
208
|
-
assert.ok(this.
|
|
248
|
+
assert.ok(this.exists(tetra_index), 'tetrahedron does not exist');
|
|
209
249
|
|
|
210
250
|
assert.isNonNegativeInteger(neighbour_index, 'neighbour_index');
|
|
211
251
|
|
|
@@ -219,12 +259,12 @@ export class TetrahedralMesh {
|
|
|
219
259
|
* NOTE: the neighbour value must be encoded, see format specification for details
|
|
220
260
|
* @param {number} tetra_index
|
|
221
261
|
* @param {number} neighbour_index which neighbour to set (0..11)
|
|
222
|
-
* @param {number} neighbour index of the neighbour
|
|
262
|
+
* @param {number} neighbour index of the neighbour encoded with the opposite corner
|
|
223
263
|
*/
|
|
224
264
|
setNeighbour(tetra_index, neighbour_index, neighbour) {
|
|
225
265
|
|
|
226
266
|
assert.isNonNegativeInteger(tetra_index, 'tetra_index');
|
|
227
|
-
assert.ok(this.
|
|
267
|
+
assert.ok(this.exists(tetra_index), 'tetrahedron does not exist');
|
|
228
268
|
|
|
229
269
|
assert.isNonNegativeInteger(neighbour_index, 'neighbour_index');
|
|
230
270
|
assert.isNonNegativeInteger(neighbour, 'neighbour');
|
|
@@ -245,7 +285,7 @@ export class TetrahedralMesh {
|
|
|
245
285
|
getVertexIndex(tet_index, point_index) {
|
|
246
286
|
assert.isNonNegativeInteger(tet_index, 'tet_index');
|
|
247
287
|
assert.lessThanOrEqual(tet_index, MAX_TET_INDEX, 'max index exceeded');
|
|
248
|
-
assert.ok(this.
|
|
288
|
+
//assert.ok(this.exists(tet_index), 'tetrahedron does not exist');
|
|
249
289
|
|
|
250
290
|
assert.isNonNegativeInteger(point_index, 'point_index');
|
|
251
291
|
assert.lessThan(point_index, 4, 'point_index must be less than 4');
|
|
@@ -263,7 +303,7 @@ export class TetrahedralMesh {
|
|
|
263
303
|
setVertexIndex(tet_index, point_index, vertex) {
|
|
264
304
|
assert.isNonNegativeInteger(tet_index, 'tet_index');
|
|
265
305
|
assert.lessThanOrEqual(tet_index, MAX_TET_INDEX, 'max index exceeded');
|
|
266
|
-
assert.ok(this.
|
|
306
|
+
//assert.ok(this.exists(tet_index), 'tetrahedron does not exist');
|
|
267
307
|
|
|
268
308
|
assert.isNonNegativeInteger(point_index, 'point_index');
|
|
269
309
|
assert.lessThan(point_index, 4, 'point_index must be less than 4');
|
|
@@ -293,24 +333,6 @@ export class TetrahedralMesh {
|
|
|
293
333
|
return false;
|
|
294
334
|
}
|
|
295
335
|
|
|
296
|
-
validateLength() {
|
|
297
|
-
return this.countOccupancy() === this.__length;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* DEBUG method
|
|
302
|
-
* Counts number of occupied(used) elements in the pool
|
|
303
|
-
* Should be the same as __length
|
|
304
|
-
* @return {number}
|
|
305
|
-
*/
|
|
306
|
-
countOccupancy() {
|
|
307
|
-
let r = 0;
|
|
308
|
-
for (let i = this.__occupancy.nextSetBit(0); i !== -1; i = this.__occupancy.nextSetBit(i + 1)) {
|
|
309
|
-
r++;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return r;
|
|
313
|
-
}
|
|
314
336
|
|
|
315
337
|
/**
|
|
316
338
|
* Allocate empty tet
|
|
@@ -318,17 +340,20 @@ export class TetrahedralMesh {
|
|
|
318
340
|
* @return {number} index of allocated tetrahedron
|
|
319
341
|
*/
|
|
320
342
|
allocate() {
|
|
321
|
-
|
|
343
|
+
if (this.__free_pointer > 0) {
|
|
344
|
+
this.__free_pointer--;
|
|
345
|
+
return this.__free[this.__free_pointer];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const tetra_index = this.__used_end;
|
|
322
349
|
|
|
323
|
-
|
|
350
|
+
this.__used_end++;
|
|
324
351
|
|
|
325
|
-
if (
|
|
352
|
+
if (tetra_index >= this.__capacity) {
|
|
326
353
|
// needs to be increased in size
|
|
327
|
-
this.
|
|
354
|
+
this.growCapacity(tetra_index);
|
|
328
355
|
}
|
|
329
356
|
|
|
330
|
-
this.__occupancy.set(tetra_index, true);
|
|
331
|
-
|
|
332
357
|
// initialize neighbours
|
|
333
358
|
for (let i = 0; i < 4; i++) {
|
|
334
359
|
this.setNeighbour(tetra_index, i, INVALID_NEIGHBOUR);
|
|
@@ -336,7 +361,6 @@ export class TetrahedralMesh {
|
|
|
336
361
|
|
|
337
362
|
//assert(this.validateLength());
|
|
338
363
|
|
|
339
|
-
this.__length++;
|
|
340
364
|
|
|
341
365
|
// assert(this.validateLength());
|
|
342
366
|
|
|
@@ -374,13 +398,11 @@ export class TetrahedralMesh {
|
|
|
374
398
|
}
|
|
375
399
|
|
|
376
400
|
/**
|
|
377
|
-
*
|
|
401
|
+
* Sets back-links on neighbours to this tet to INVALID_NEIGHBOUR basically making them into mesh surface
|
|
402
|
+
* This is a useful method for when you want to completely remove a given tet from the mesh to make sure that no dangling references will remain
|
|
378
403
|
* @param {number} tetra_index
|
|
379
404
|
*/
|
|
380
|
-
|
|
381
|
-
assert.isNonNegativeInteger(tetra_index, 'tera_index');
|
|
382
|
-
assert.equal(this.__occupancy.get(tetra_index), true, 'tetrahedron does not exist');
|
|
383
|
-
|
|
405
|
+
disconnect(tetra_index) {
|
|
384
406
|
// find neighbours and remove reference to self
|
|
385
407
|
for (let i = 0; i < 4; i++) {
|
|
386
408
|
const neighbour_encoded = this.getNeighbour(tetra_index, i);
|
|
@@ -397,43 +419,60 @@ export class TetrahedralMesh {
|
|
|
397
419
|
// clear reference to self
|
|
398
420
|
this.setNeighbour(neighbour_index, neighbour_point, INVALID_NEIGHBOUR);
|
|
399
421
|
}
|
|
422
|
+
}
|
|
400
423
|
|
|
401
|
-
|
|
424
|
+
/**
|
|
425
|
+
* Remove tetrahedron
|
|
426
|
+
* Please note that if there are any dangling references in the mesh neighbourhood - you will need to take care of that separately
|
|
427
|
+
* @param {number} tetra_index
|
|
428
|
+
*/
|
|
429
|
+
delete(tetra_index) {
|
|
430
|
+
assert.isNonNegativeInteger(tetra_index, 'tera_index');
|
|
431
|
+
assert.lessThan(tetra_index, this.__used_end, 'attempting to remove tet outside of valid region');
|
|
432
|
+
// assert.equal(this.__occupancy.get(tetra_index), true, 'tetrahedron does not exist');
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
if (tetra_index === this.__used_end - 1) {
|
|
436
|
+
// tet was at the end of the allocated space
|
|
437
|
+
this.__used_end--;
|
|
438
|
+
} else {
|
|
439
|
+
// mark as dead
|
|
440
|
+
this.__free[this.__free_pointer++] = tetra_index;
|
|
441
|
+
}
|
|
402
442
|
|
|
403
|
-
//
|
|
404
|
-
this.__occupancy.clear(tetra_index);
|
|
405
|
-
this.__length--;
|
|
443
|
+
//assert(this.validateLength());
|
|
406
444
|
|
|
407
445
|
//assert(this.validateLength());
|
|
408
446
|
}
|
|
409
447
|
|
|
410
448
|
/**
|
|
411
|
-
*
|
|
449
|
+
* Used mainly to remove tetrahedrons whos points touch the "super-tetrahedron's" points that was inserted originally
|
|
412
450
|
* These points are identified by an offset + count parameters
|
|
413
|
-
* @param {number[]|Float32Array} points
|
|
414
451
|
* @param {number} range_start
|
|
415
452
|
* @param {number} range_end
|
|
416
453
|
*/
|
|
417
|
-
removeTetrasConnectedToPoints(
|
|
418
|
-
const data_view = this.__view;
|
|
454
|
+
removeTetrasConnectedToPoints(range_start, range_end) {
|
|
419
455
|
|
|
420
|
-
let
|
|
456
|
+
for (let i = this.__used_end - 1; i >= 0; i--) {
|
|
421
457
|
|
|
422
|
-
|
|
423
|
-
|
|
458
|
+
for (let j = 0; j < 4; j++) {
|
|
459
|
+
const point_index = this.getVertexIndex(i, j);
|
|
424
460
|
|
|
425
|
-
|
|
461
|
+
if (point_index >= range_start && point_index <= range_end) {
|
|
426
462
|
|
|
427
|
-
|
|
428
|
-
|
|
463
|
+
if (!this.exists(i)) {
|
|
464
|
+
// tet doesn't actually exist (deallocated)
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
429
467
|
|
|
430
|
-
if (point_index >= range_start && point_index <= range_end) {
|
|
431
468
|
// point index is in range, tetra should be removed
|
|
432
|
-
this.
|
|
469
|
+
this.disconnect(i);
|
|
470
|
+
this.delete(i);
|
|
433
471
|
break;
|
|
434
472
|
}
|
|
435
473
|
}
|
|
436
474
|
}
|
|
475
|
+
|
|
437
476
|
}
|
|
438
477
|
|
|
439
478
|
/**
|
|
@@ -446,7 +485,7 @@ export class TetrahedralMesh {
|
|
|
446
485
|
const data_view = this.__view;
|
|
447
486
|
|
|
448
487
|
// TODO improve this
|
|
449
|
-
const n = this.
|
|
488
|
+
const n = this.__used_end;
|
|
450
489
|
for (let i = 0; i < n; i++) {
|
|
451
490
|
|
|
452
491
|
const tet_address = i * LAYOUT_TETRA_BYTE_SIZE;
|
|
@@ -464,4 +503,148 @@ export class TetrahedralMesh {
|
|
|
464
503
|
return -1;
|
|
465
504
|
}
|
|
466
505
|
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Relocate tetrahedron in memory, patches neighbourhood links as well
|
|
509
|
+
* NOTE: The destination slot will be overwritten. This is a dangerous method that can break the topology, make sure you fully understand what you are doing when using it
|
|
510
|
+
* @param {number} source_index index of source tetrahedron
|
|
511
|
+
* @param {number} destination_index new index, where the source tetrahedron is to be moved
|
|
512
|
+
*/
|
|
513
|
+
relocate(source_index, destination_index) {
|
|
514
|
+
// validate_tetrahedron_neighbourhood(this, source_index, console.error);
|
|
515
|
+
|
|
516
|
+
// patch neighbours
|
|
517
|
+
for (let i = 0; i < 4; i++) {
|
|
518
|
+
const encoded_neighbour = this.getNeighbour(source_index, i);
|
|
519
|
+
|
|
520
|
+
if (encoded_neighbour === INVALID_NEIGHBOUR) {
|
|
521
|
+
// no neighbour
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const neighbour_index = encoded_neighbour >> 2;
|
|
526
|
+
const neighbour_vertex = encoded_neighbour & 3;
|
|
527
|
+
|
|
528
|
+
const encoded_tet = (destination_index << 2) | (i & 3);
|
|
529
|
+
|
|
530
|
+
assert.equal(this.getNeighbour(neighbour_index, neighbour_vertex), (source_index << 2) | (i & 3), 'invalid source state');
|
|
531
|
+
|
|
532
|
+
this.setNeighbour(neighbour_index, neighbour_vertex, encoded_tet);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
const layout_word_size = LAYOUT_TETRA_BYTE_SIZE >> 2;
|
|
537
|
+
array_copy(this.__data_uint32, source_index * layout_word_size, this.__data_uint32, destination_index * layout_word_size, layout_word_size);
|
|
538
|
+
|
|
539
|
+
// validate_tetrahedron_neighbourhood(this, destination_index, console.error);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Perform compaction, removing unused memory slots
|
|
544
|
+
* NOTE: existing tetrahedron indices can become invalidated as tets are moved into free slots
|
|
545
|
+
* @returns {number} number of relocated elements
|
|
546
|
+
*/
|
|
547
|
+
compact() {
|
|
548
|
+
// sort free
|
|
549
|
+
array_quick_sort_by_comparator(this.__free, compareNumbersDescending, null, 0, this.__free_pointer - 1);
|
|
550
|
+
|
|
551
|
+
let relocation_count = 0;
|
|
552
|
+
let free_head_pointer = 0;
|
|
553
|
+
|
|
554
|
+
while (this.__free_pointer > free_head_pointer) {
|
|
555
|
+
const last_used = this.__used_end - 1;
|
|
556
|
+
|
|
557
|
+
if (this.__free[free_head_pointer] >= last_used) {
|
|
558
|
+
/*
|
|
559
|
+
The slot is actually free.
|
|
560
|
+
As we have sorted the free slots, it's expected to be at the start of the list
|
|
561
|
+
Adjust pointers and continue onto the next slot
|
|
562
|
+
*/
|
|
563
|
+
free_head_pointer++
|
|
564
|
+
this.__used_end = last_used;
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const free_slot = this.__free[this.__free_pointer - 1];
|
|
569
|
+
|
|
570
|
+
this.__free_pointer--;
|
|
571
|
+
|
|
572
|
+
if (last_used <= free_slot) {
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
this.relocate(last_used, free_slot);
|
|
578
|
+
relocation_count++;
|
|
579
|
+
|
|
580
|
+
this.__used_end--;
|
|
581
|
+
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
this.__free.splice(0, this.__free.length); // release memory
|
|
585
|
+
this.__free_pointer = 0;
|
|
586
|
+
|
|
587
|
+
return relocation_count;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
*
|
|
592
|
+
* @param {BinaryBuffer} buffer
|
|
593
|
+
*/
|
|
594
|
+
serialize(buffer) {
|
|
595
|
+
buffer.writeUint32(1); // format version
|
|
596
|
+
buffer.writeUintVar(this.__used_end);
|
|
597
|
+
buffer.writeUintVar(this.__free_pointer);
|
|
598
|
+
|
|
599
|
+
// record main buffer
|
|
600
|
+
buffer.writeUint32Array(this.__data_uint32, 0, this.__used_end * LAYOUT_TETRA_WORD_COUNT);
|
|
601
|
+
buffer.writeUint32Array(this.__free, 0, this.__free_pointer);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
*
|
|
606
|
+
* @param {BinaryBuffer} buffer
|
|
607
|
+
*/
|
|
608
|
+
deserialize(buffer) {
|
|
609
|
+
const version_number = buffer.readUint32();
|
|
610
|
+
|
|
611
|
+
if (version_number !== 1) {
|
|
612
|
+
throw new Error(`Unsupported version number, expected ${1}, instead got ${version_number}`);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
this.__used_end = buffer.readUintVar();
|
|
616
|
+
this.__free_pointer = buffer.readUintVar();
|
|
617
|
+
|
|
618
|
+
this.growCapacity(this.__used_end);
|
|
619
|
+
|
|
620
|
+
buffer.readUint32Array(this.__data_uint32, 0, this.__used_end);
|
|
621
|
+
|
|
622
|
+
buffer.readUint32Array(this.__free, 0, this.__free_pointer);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
*
|
|
627
|
+
* @return {string}
|
|
628
|
+
*/
|
|
629
|
+
serialize_base64() {
|
|
630
|
+
const buffer = new BinaryBuffer();
|
|
631
|
+
|
|
632
|
+
this.serialize(buffer);
|
|
633
|
+
|
|
634
|
+
buffer.trim();
|
|
635
|
+
|
|
636
|
+
return Base64.encode(buffer.data);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
*
|
|
641
|
+
* @param {string} str
|
|
642
|
+
*/
|
|
643
|
+
deserialize_base64(str) {
|
|
644
|
+
const arrayBuffer = Base64.decode(str);
|
|
645
|
+
|
|
646
|
+
const binaryBuffer = BinaryBuffer.fromArrayBuffer(arrayBuffer);
|
|
647
|
+
|
|
648
|
+
this.deserialize(binaryBuffer);
|
|
649
|
+
}
|
|
467
650
|
}
|
|
@@ -120,7 +120,7 @@ test("remove method reduces size", () => {
|
|
|
120
120
|
|
|
121
121
|
expect(mesh.size()).toBe(1);
|
|
122
122
|
|
|
123
|
-
mesh.
|
|
123
|
+
mesh.delete(tet);
|
|
124
124
|
|
|
125
125
|
expect(mesh.size()).toBe(0);
|
|
126
126
|
});
|
|
@@ -141,7 +141,7 @@ test("forEach visits all allocated tets", () => {
|
|
|
141
141
|
expect(mock).toHaveBeenCalledWith(t1, mesh);
|
|
142
142
|
});
|
|
143
143
|
|
|
144
|
-
test("exists method", () => {
|
|
144
|
+
test("'exists' method", () => {
|
|
145
145
|
const mesh = new TetrahedralMesh();
|
|
146
146
|
|
|
147
147
|
expect(mesh.exists(0)).toBe(false);
|
|
@@ -149,8 +149,85 @@ test("exists method", () => {
|
|
|
149
149
|
const tet = mesh.allocate();
|
|
150
150
|
|
|
151
151
|
expect(mesh.exists(tet)).toBe(true);
|
|
152
|
+
expect(mesh.exists(tet + 1)).toBe(false);
|
|
152
153
|
|
|
153
|
-
mesh.
|
|
154
|
+
mesh.delete(tet);
|
|
154
155
|
|
|
155
156
|
expect(mesh.exists(tet)).toBe(false);
|
|
157
|
+
expect(mesh.exists(tet + 1)).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
test("after calling 'removeTetrasConnectedToPoints' matching points are no logner indexed by live tetrahedrons", () => {
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
const tetrahedra = new TetrahedralMesh();
|
|
165
|
+
|
|
166
|
+
/*
|
|
167
|
+
* This "magic" mesh is a result of tetrahedral mesh generator with following input: 0, 0, 0, 10, 0, 0, 10, 0, 10, 0, 0, 10, 0, 10, 0,
|
|
168
|
+
* The mesh was generated on 27/10/2022
|
|
169
|
+
* It's a very small example with 21 total tets that was failing this test
|
|
170
|
+
*
|
|
171
|
+
*/
|
|
172
|
+
tetrahedra.deserialize_base64('AAAAARUAAQAAAAAAAAAFAAAABgAAAAsAAAAUAAAAHwAAADQAAAADAAAABwAAAAUAAAAIAAAA/////yoAAABAAAAALgAAAAAAAAAFAAAABgAAAAgAAAD/////HAAAACgAAAAAAAAAAgAAAAUAAAAHAAAABgAAAP////8TAAAAFQAAACwAAAACAAAABgAAAAcAAAAIAAAA/////zwAAAAwAAAADQAAAAIAAAABAAAABQAAAAYAAAABAAAADgAAACAAAAA4AAAABAAAAAEAAAAGAAAACAAAAB0AAAAyAAAASQAAACEAAAABAAAAAAAAAAYAAAAIAAAACQAAABgAAABIAAAAAgAAAAQAAAACAAAABgAAAAEAAAAWAAAAGwAAACUAAAAzAAAABAAAAAMAAAACAAAAAQAAADsAAAAiAAAAUwAAAEYAAAADAAAABQAAAAAAAAAIAAAACgAAAEwAAAAFAAAANQAAAAMAAAAHAAAAAgAAAAUAAAAPAAAAOgAAAAcAAABEAAAABAAAAAYAAAACAAAACAAAABIAAAA+AAAAGQAAACMAAAADAAAAAQAAAAAAAAAFAAAAAwAAACsAAAA5AAAAUAAAAAMAAAACAAAAAQAAAAUAAAAXAAAANgAAAC0AAAAkAAAABAAAAAIAAAAHAAAACAAAABEAAABCAAAAMQAAAEUAAAAEAAAABwAAAAMAAAAIAAAABgAAAE4AAAA9AAAARwAAAAQAAAADAAAABwAAAAIAAAAvAAAAPwAAACcAAABDAAAABAAAAAAAAAABAAAACAAAAB4AAAAaAAAATQAAAFEAAAAEAAAAAwAAAAAAAAAIAAAAKQAAAEoAAABBAAAAUgAAAAQAAAADAAAAAQAAAAAAAAA3AAAASwAAAE8AAAAmAAAA');
|
|
173
|
+
|
|
174
|
+
tetrahedra.removeTetrasConnectedToPoints(5, 8);
|
|
175
|
+
|
|
176
|
+
//The mesh should contain only 5 vertices
|
|
177
|
+
|
|
178
|
+
tetrahedra.forEach((tet, mesh) => {
|
|
179
|
+
|
|
180
|
+
for (let i = 0; i < 4; i++) {
|
|
181
|
+
expect(mesh.getVertexIndex(tet, i)).toBeLessThan(5);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('relocate operation copies tet data and updates back-links correctly', () => {
|
|
188
|
+
const mesh = new TetrahedralMesh();
|
|
189
|
+
|
|
190
|
+
const a = mesh.allocate();
|
|
191
|
+
const b = mesh.allocate();
|
|
192
|
+
const dest = mesh.allocate();
|
|
193
|
+
|
|
194
|
+
mesh.setVertexIndex(a, 0, 3);
|
|
195
|
+
mesh.setVertexIndex(a, 1, 7);
|
|
196
|
+
mesh.setVertexIndex(a, 2, 11);
|
|
197
|
+
mesh.setVertexIndex(a, 3, 13);
|
|
198
|
+
|
|
199
|
+
mesh.setNeighbour(a, 0, b << 2);
|
|
200
|
+
mesh.setNeighbour(b, 0, a << 2);
|
|
201
|
+
|
|
202
|
+
mesh.relocate(a, dest);
|
|
203
|
+
|
|
204
|
+
expect(mesh.getVertexIndex(dest, 0)).toBe(3);
|
|
205
|
+
expect(mesh.getVertexIndex(dest, 1)).toBe(7);
|
|
206
|
+
expect(mesh.getVertexIndex(dest, 2)).toBe(11);
|
|
207
|
+
expect(mesh.getVertexIndex(dest, 3)).toBe(13);
|
|
208
|
+
|
|
209
|
+
expect(mesh.getNeighbour(dest, 0)).toBe(b << 2);
|
|
210
|
+
|
|
211
|
+
// check back-link
|
|
212
|
+
expect(mesh.getNeighbour(b, 0)).toBe(dest << 2);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('Compaction is non-destructive when moving resident data from the very end of the allocated region', () => {
|
|
216
|
+
const mesh = new TetrahedralMesh();
|
|
217
|
+
|
|
218
|
+
const a = mesh.allocate();
|
|
219
|
+
const b = mesh.allocate();
|
|
220
|
+
|
|
221
|
+
// force move "a" to free set
|
|
222
|
+
mesh.__free[mesh.__free_pointer++] = a;
|
|
223
|
+
|
|
224
|
+
expect(mesh.exists(b)).toBe(true);
|
|
225
|
+
expect(mesh.exists(a)).toBe(false);
|
|
226
|
+
|
|
227
|
+
mesh.compact();
|
|
228
|
+
|
|
229
|
+
expect(mesh.size()).toBe(1);
|
|
230
|
+
|
|
231
|
+
expect(mesh.exists(0)).toBe(true);
|
|
232
|
+
expect(mesh.exists(1)).toBe(false);
|
|
156
233
|
});
|