@woosh/meep-engine 2.33.36 → 2.33.40

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 (90) hide show
  1. package/core/bvh2/binary/2/BinaryUint32BVH.js +3 -3
  2. package/core/collection/HashMap.js +19 -12
  3. package/core/collection/array/typed/typed_array_copy.js +2 -2
  4. package/core/collection/table/RowFirstTable.js +13 -8
  5. package/core/collection/table/RowFirstTableSpec.js +6 -0
  6. package/core/color/Color.js +5 -9
  7. package/core/color/ColorUtils.js +4 -150
  8. package/core/color/YCbCr_to_rgb_uint24.js +20 -0
  9. package/core/color/hsv2rgb.js +54 -0
  10. package/core/color/int2rgb.js +12 -0
  11. package/core/color/parseHex.js +18 -0
  12. package/core/color/rgb2hex.js +16 -0
  13. package/core/color/rgb2hsv.js +47 -0
  14. package/core/color/rgb2uint24.js +17 -0
  15. package/core/color/rgb_to_YCbCr_uint24.js +18 -0
  16. package/core/geom/3d/aabb/aabb3_matrix4_project_by_corners.js +1 -1
  17. package/core/geom/3d/aabb/compute_aabb_from_points.js +28 -0
  18. package/core/geom/3d/morton/mortonEncode_magicbits.js +7 -6
  19. package/core/geom/3d/morton/v3_morton_encode_transformed.js +15 -11
  20. package/core/geom/3d/plane/orient3d_fast.js +51 -0
  21. package/core/geom/3d/plane/plane_computeConvex3PlaneIntersection.js +5 -0
  22. package/core/geom/3d/tetrahedra/GetDepthForTetrahedronProbe.glsl +43 -0
  23. package/core/geom/3d/tetrahedra/compute_bounding_simplex_3d.js +177 -0
  24. package/core/geom/3d/tetrahedra/compute_bounding_simplex_3d.spec.js +66 -0
  25. package/core/geom/3d/tetrahedra/compute_circumsphere.js +271 -0
  26. package/core/geom/3d/tetrahedra/compute_delaunay_tetrahedral_mesh.js +50 -0
  27. package/core/geom/3d/tetrahedra/compute_delaunay_tetrahedral_mesh.spec.js +14 -0
  28. package/core/geom/3d/tetrahedra/point_in_tetrahedron_circumsphere.js +77 -0
  29. package/core/geom/3d/tetrahedra/tetrahedra_collection.js +454 -0
  30. package/core/geom/3d/tetrahedra/tetrahedron_contains_point.js +26 -0
  31. package/core/geom/Quaternion.js +4 -0
  32. package/core/math/hash/murmur3_32.js +39 -0
  33. package/core/process/undo/ActionProcessor.d.ts +2 -0
  34. package/core/process/undo/ActionProcessor.js +15 -4
  35. package/editor/tools/FoliagePaintTool.js +1 -1
  36. package/editor/tools/GridPaintTool.js +1 -1
  37. package/engine/asset/loaders/GLTFAssetLoader.js +4 -8
  38. package/engine/ecs/System.d.ts +24 -1
  39. package/engine/ecs/foliage/InstancedFoliage.js +45 -0
  40. package/engine/ecs/foliage/ecs/InstancedMeshLayer.js +4 -0
  41. package/engine/ecs/storage/BinaryBufferSerializer.js +48 -38
  42. package/engine/ecs/storage/binary/collection/BinaryCollectionSerializer.js +1 -1
  43. package/engine/ecs/terrain/ecs/splat/SplatMapOptimizerDebugger.js +1 -1
  44. package/engine/ecs/terrain/tiles/TerrainTile.js +1 -1
  45. package/engine/graphics/GraphicsEngine.js +7 -0
  46. package/engine/graphics/ecs/light/binding/fp/FPLightBinding.js +1 -1
  47. package/engine/graphics/ecs/mesh/Mesh.js +2 -2
  48. package/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +13 -0
  49. package/engine/graphics/geometry/instancing/InstancedMeshGroup.js +339 -314
  50. package/engine/graphics/micron/prototypeVirtualGeometry.js +4 -1
  51. package/engine/graphics/micron/render/v1/VGThreeRenderer.js +6 -2
  52. package/engine/graphics/particles/node-based/rendering/billboard/ParticleBillboardShader.js +1 -1
  53. package/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.js +20 -24
  54. package/engine/graphics/particles/particular/engine/renderers/billboard/ParticleBillboardMaterial.js +100 -55
  55. package/engine/graphics/particles/particular/engine/shader/ShaderManager.js +7 -2
  56. package/engine/graphics/particles/particular/engine/utils/volume/ParticleVolume.js +1 -0
  57. package/engine/graphics/particles/particular/engine/utils/volume/prototypeParticleVolume.js +2 -2
  58. package/engine/graphics/render/buffer/simple-fx/DepthLimitedBlur.js +9 -4
  59. package/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js +7 -1
  60. package/engine/graphics/render/forward_plus/LightManager.js +295 -51
  61. package/engine/graphics/render/forward_plus/LightManager.spec.js +1 -1
  62. package/engine/graphics/render/forward_plus/PointLightData.js +9 -13
  63. package/engine/graphics/render/forward_plus/cluster/compute_light_data_hash_0.js +1 -1
  64. package/engine/graphics/render/forward_plus/data/TextureBackedMemoryRegion.js +10 -1
  65. package/engine/graphics/render/forward_plus/debug/LightClusterVolumeMaterial.js +1 -1
  66. package/engine/graphics/render/forward_plus/debug/buildLightClusterWidget.js +3 -1
  67. package/engine/graphics/render/forward_plus/materials/FP_SHADER_CHUNK_ACCUMULATION.js +31 -18
  68. package/engine/graphics/render/forward_plus/materials/fp_build_fragment_shader.js +18 -4
  69. package/engine/graphics/render/forward_plus/model/AbstractLight.js +50 -0
  70. package/engine/graphics/render/forward_plus/model/Decal.js +68 -0
  71. package/engine/graphics/render/forward_plus/{PointLight.js → model/PointLight.js} +19 -11
  72. package/engine/graphics/render/forward_plus/{SpotLight.js → model/SpotLight.js} +20 -12
  73. package/engine/graphics/render/forward_plus/plugin/ForwardPlusRenderingPlugin.js +1 -1
  74. package/engine/graphics/render/forward_plus/plugin/ptototypeFPPlugin.js +6 -5
  75. package/engine/graphics/render/forward_plus/{prototypeLightManager.js → prototype/prototypeLightManager.js} +100 -25
  76. package/engine/graphics/render/forward_plus/query/query_bvh_frustum_from_texture.js +29 -20
  77. package/engine/graphics/render/visibility/IncrementalDeltaSet.d.ts +2 -0
  78. package/engine/graphics/render/visibility/IncrementalDeltaSet.js +1 -0
  79. package/engine/graphics/render/visibility/VisibilityComputer.js +8 -7
  80. package/engine/graphics/shaders/TerrainShader.js +5 -5
  81. package/engine/graphics/shadows/testShadowMapRendering.js +192 -0
  82. package/engine/graphics/texture/atlas/AbstractTextureAtlas.js +19 -0
  83. package/engine/graphics/texture/atlas/CachingTextureAtlas.js +99 -0
  84. package/engine/graphics/texture/atlas/ManagedTextureAtlas.js +19 -39
  85. package/engine/graphics/texture/atlas/ReferencedTextureAtlas.js +75 -0
  86. package/engine/graphics/texture/atlas/TextureAtlas.js +22 -2
  87. package/engine/reference/v2/Reference.d.ts +4 -0
  88. package/engine/reference/v2/Reference.js +3 -1
  89. package/package.json +2 -1
  90. package/view/elements/ColorPickerView.js +2 -1
@@ -0,0 +1,50 @@
1
+ //
2
+
3
+ import { compute_bounding_simplex_3d } from "./compute_bounding_simplex_3d.js";
4
+ import { TetrahedraCollection } from "./tetrahedra_collection.js";
5
+
6
+ /**
7
+ * @see https://en.wikipedia.org/wiki/Bowyer%E2%80%93Watson_algorithm
8
+ * @see [1] "One machine, one minute, three billion tetrahedra" by Célestin Marot et al
9
+ * @param {number[]} input serialized set of 3d points (x,y,z)
10
+ * @param {number} n number of points in the input
11
+ */
12
+ export function compute_delaunay_tetrahedral_mesh(input, n) {
13
+ if (n < 4) {
14
+ // no mesh can be created, too few points
15
+ return;
16
+ }
17
+
18
+ /**
19
+ * According to [1] number of tetrahedra is around 6 times greater than number of input vertices, we use that fact to pre-allocate memory
20
+ * From the book of Cheng et al. 'Delaunay Mesh Generation' A triangulation of n vertices in R^3 has at most `ntet=(n^2 − 3n − 2)/2` tetrahedra,
21
+ * at most nedg=(n^2 − n)/2` edges (this is simply the 2-combination of n)
22
+ * and at most `ntri=n^2 − 3n = 2*ntet+2` triangles
23
+ *
24
+ * The +7 is for the initial "super" tetrahedron and 6 tetras for intermediate triangulation
25
+ */
26
+ const tetrahedron_count_upper_bound = Math.ceil((n * n - 3 * n - 2) / 2) + 7;
27
+ const tetrahedrons = new TetrahedraCollection(tetrahedron_count_upper_bound);
28
+ // TODO apply spatial sort to point, to get better cache locality
29
+
30
+ // compute bounds over the input
31
+ const points = new Float32Array((n + 4) * 3);
32
+
33
+ points.set(input);
34
+
35
+ /*
36
+ create super bounding volume
37
+ */
38
+ compute_bounding_simplex_3d(points, n * 3, points, n, 1);
39
+
40
+ tetrahedrons.append(points, n, n + 1, n + 2, n + 3);
41
+
42
+ // add all points to the mesh
43
+ for (let i = 0; i < n; i++) {
44
+ tetrahedrons.insertPoint(points, i);
45
+ }
46
+
47
+ // remove tetras formed by with the super bounding volume
48
+ tetrahedrons.removeTetrasConnectedToPoints(points, n, n + 3);
49
+ }
50
+
@@ -0,0 +1,14 @@
1
+ import { compute_delaunay_tetrahedral_mesh } from "./compute_delaunay_tetrahedral_mesh.js";
2
+
3
+ test('single point', () => {
4
+ compute_delaunay_tetrahedral_mesh([1, 2, 3], 1);
5
+ });
6
+
7
+ test('exactly 4 points', () => {
8
+ compute_delaunay_tetrahedral_mesh([
9
+ -1, 0, 0,
10
+ 1, 0, 0,
11
+ 0.5, 1, 0,
12
+ 0.5, 0, 1
13
+ ], 4);
14
+ });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Approximate 3D insphere test. Nonrobust.
3
+ *
4
+ * Return a positive value if the point pe lies inside the
5
+ * sphere passing through pa, pb, pc, and pd; a negative value
6
+ * if it lies outside; and zero if the five points are
7
+ * cospherical. The points pa, pb, pc, and pd must be ordered
8
+ * so that they have a positive orientation (as defined by
9
+ * orient3d()), or the sign of the result will be reversed.
10
+ *
11
+ * @see "Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates." Technical Report CMU-CS-96-140, School of Computer Science, Carnegie Mellon University, Pittsburgh, Pennsylvania, May 1996
12
+ * @see http://www.cs.cmu.edu/~quake/robust.html
13
+ * @see https://gitlab.onelab.info/gmsh/gmsh/-/blob/master/contrib/hxt/predicates/src/predicates.c
14
+ * @param {number[]} points
15
+ * @param {number} a tetrahedral point index
16
+ * @param {number} b tetrahedral point index
17
+ * @param {number} c tetrahedral point index
18
+ * @param {number} d tetrahedral point index
19
+ * @param {number} e reference point index
20
+ * @returns {number}
21
+ */
22
+ export function point_in_tetrahedron_circumsphere(points, a, b, c, d, e) {
23
+ const ex = points[e * 3];
24
+ const ey = points[e * 3 + 1];
25
+ const ez = points[e * 3 + 2];
26
+
27
+ const aex = points[a * 3] - ex;
28
+ const bex = points[b * 3] - ex;
29
+ const cex = points[c * 3] - ex;
30
+ const dex = points[d * 3] - ex;
31
+
32
+ const aey = points[a * 3 + 1] - ey;
33
+ const bey = points[b * 3 + 1] - ey;
34
+ const cey = points[c * 3 + 1] - ey;
35
+ const dey = points[d * 3 + 1] - ey;
36
+
37
+ const aez = points[a * 3 + 2] - ez;
38
+ const bez = points[b * 3 + 2] - ez;
39
+ const cez = points[c * 3 + 2] - ez;
40
+ const dez = points[d * 3 + 1] - ez;
41
+
42
+ const aexbey = aex * bey;
43
+ const bexaey = bex * aey;
44
+ const ab = aexbey - bexaey;
45
+ const bexcey = bex * cey;
46
+ const cexbey = cex * bey;
47
+ const bc = bexcey - cexbey;
48
+ const cexdey = cex * dey;
49
+ const dexcey = dex * cey;
50
+ const cd = cexdey - dexcey;
51
+ const dexaey = dex * aey;
52
+ const aexdey = aex * dey;
53
+ const da = dexaey - aexdey;
54
+
55
+ const aexcey = aex * cey;
56
+ const cexaey = cex * aey;
57
+ const ac = aexcey - cexaey;
58
+ const bexdey = bex * dey;
59
+ const dexbey = dex * bey;
60
+ const bd = bexdey - dexbey;
61
+
62
+ const abc = aez * bc - bez * ac + cez * ab;
63
+ const bcd = bez * cd - cez * bd + dez * bc;
64
+ const cda = cez * da + dez * ac + aez * cd;
65
+ const dab = dez * ab + aez * bd + bez * da;
66
+
67
+ const alift = aex * aex + aey * aey + aez * aez;
68
+ const blift = bex * bex + bey * bey + bez * bez;
69
+ const clift = cex * cex + cey * cey + cez * cez;
70
+ const dlift = dex * dex + dey * dey + dez * dez;
71
+
72
+ const det = (dlift * abc - clift * dab) + (blift * cda - alift * bcd);
73
+
74
+ // TODO HXT uses 2 more methods to perform exact calculation (avoiding rounding errors), we could include that if necessary
75
+
76
+ return det;
77
+ }
@@ -0,0 +1,454 @@
1
+ import { tetrahedron_contains_point } from "./tetrahedron_contains_point.js";
2
+ import { assert } from "../../../assert.js";
3
+ import { point_in_tetrahedron_circumsphere } from "./point_in_tetrahedron_circumsphere.js";
4
+ import { BitSet } from "../../../binary/BitSet.js";
5
+ import { typed_array_copy } from "../../../collection/array/typed/typed_array_copy.js";
6
+ import { max3 } from "../../../math/max3.js";
7
+
8
+ /**
9
+ * @readonly
10
+ * @type {number}
11
+ */
12
+ const LAYOUT_TETRA_BYTE_SIZE = 9 * 4;
13
+
14
+ /**
15
+ * @readonly
16
+ * @type {number}
17
+ */
18
+ const INVALID_NEIGHBOUR = 0xFFFFFFFF;
19
+
20
+ class Cavity {
21
+ constructor() {
22
+ this.indices = [];
23
+
24
+ /**
25
+ *
26
+ * @type {number}
27
+ */
28
+ this.size = 0;
29
+ }
30
+
31
+ update() {
32
+ // compute in/out state for each side of each tetra
33
+ }
34
+
35
+ /**
36
+ *
37
+ * @param {number} i
38
+ */
39
+ push(i) {
40
+ this.indices[this.size++] = i;
41
+ }
42
+
43
+ /**
44
+ *
45
+ * @param {number} i
46
+ * @returns {boolean}
47
+ */
48
+ includes(i) {
49
+ const n = this.size;
50
+ for (let j = 0; j < n; j++) {
51
+ if (this.indices[j] === i) {
52
+ return true;
53
+ }
54
+ }
55
+
56
+ return false;
57
+ }
58
+
59
+ reset() {
60
+ this.size = 0;
61
+ }
62
+ }
63
+
64
+ const SCRATCH_CAVITY = new Cavity();
65
+
66
+ /**
67
+ *
68
+ * Layout:
69
+ * vertex_id_a :: uint
70
+ * vertex_id_b :: uint
71
+ * vertex_id_c :: uint
72
+ * vertex_id_d :: uint
73
+ * neighbour_a :: uint - neighbour tetrahedron, opposite to vertex A
74
+ * neighbour_b :: uint - neighbour tetrahedron, opposite to vertex B
75
+ * neighbour_c :: uint - neighbour tetrahedron, opposite to vertex C
76
+ * neighbour_d :: uint - neighbour tetrahedron, opposite to vertex D
77
+ * sub_determinant :: float - ?
78
+ * Layout is similar to [1], but is interleaved for better cache locality
79
+ */
80
+ export class TetrahedraCollection {
81
+ constructor(initial_size) {
82
+ this.__buffer = new ArrayBuffer(initial_size * LAYOUT_TETRA_BYTE_SIZE);
83
+
84
+ this.__view = new DataView(this.__buffer);
85
+
86
+ this.__capacity = initial_size;
87
+ this.__length = 0;
88
+
89
+
90
+ /**
91
+ * Marks which indices are in use
92
+ * @type {BitSet}
93
+ * @private
94
+ */
95
+ this.__occupancy = new BitSet();
96
+ }
97
+
98
+ /**
99
+ *
100
+ * @param {number} n
101
+ */
102
+ setCapacity(n) {
103
+ if (n === this.__capacity) {
104
+ // do nothing
105
+ return;
106
+ }
107
+
108
+ if (n < this.__length) {
109
+ throw new Error('Dropping information, not supported');
110
+ }
111
+
112
+ // allocate new buffer
113
+ const new_buffer = new ArrayBuffer(n * LAYOUT_TETRA_BYTE_SIZE);
114
+
115
+ // move data across from old buffer to the new one
116
+ const destination_uint8 = new Uint8Array(new_buffer);
117
+ const source_uint8 = new Uint8Array(this.__buffer);
118
+
119
+ typed_array_copy(source_uint8, destination_uint8);
120
+
121
+ // set new buffer
122
+ this.__buffer = new_buffer;
123
+ this.__view = new DataView(new_buffer);
124
+ }
125
+
126
+ /**
127
+ *
128
+ * @param {number} n
129
+ */
130
+ ensureCapacity(n) {
131
+
132
+ const new_capacity = max3(
133
+ n,
134
+ this.__capacity * 1.2,
135
+ this.__capacity + 32
136
+ );
137
+
138
+ this.setCapacity(new_capacity);
139
+ }
140
+
141
+ /**
142
+ *
143
+ * @param {number} tetra_index
144
+ * @param {number} neighbour_index
145
+ * @returns {number}
146
+ */
147
+ getNeighbour(tetra_index, neighbour_index) {
148
+ const tetra_address = LAYOUT_TETRA_BYTE_SIZE * tetra_index;
149
+ return this.__view.getUint32(tetra_address + (4 + neighbour_index) * 4);
150
+ }
151
+
152
+ /**
153
+ *
154
+ * @param {number} tetra_index
155
+ * @param {number} neighbour_index which neighbour to set (0..11)
156
+ * @param {number} neighbour index of the neighbour
157
+ */
158
+ setNeighbour(tetra_index, neighbour_index, neighbour) {
159
+ const tetra_address = LAYOUT_TETRA_BYTE_SIZE * tetra_index;
160
+ return this.__view.setUint32(tetra_address + (4 + neighbour_index) * 4, neighbour);
161
+ }
162
+
163
+ /**
164
+ *
165
+ * @param {number[]|Float32Array} points
166
+ * @param {number} a
167
+ * @param {number} b
168
+ * @param {number} c
169
+ * @param {number} d
170
+ * @returns {number} index of the new tetra
171
+ */
172
+ append(points, a, b, c, d) {
173
+ const tetra_index = this.__occupancy.nextClearBit(0);
174
+ const address = tetra_index * LAYOUT_TETRA_BYTE_SIZE;
175
+
176
+ if (tetra_index >= this.__capacity) {
177
+ // needs to be increased in size
178
+ this.ensureCapacity(tetra_index);
179
+ }
180
+
181
+ const view = this.__view;
182
+
183
+ view.setUint32(address, a);
184
+ view.setUint32(address + 4, b);
185
+ view.setUint32(address + 8, c);
186
+ view.setUint32(address + 12, d);
187
+
188
+ // set neighbours
189
+ view.setUint32(address + 16, INVALID_NEIGHBOUR);
190
+ view.setUint32(address + 20, INVALID_NEIGHBOUR);
191
+ view.setUint32(address + 24, INVALID_NEIGHBOUR);
192
+ view.setUint32(address + 28, INVALID_NEIGHBOUR);
193
+
194
+ this.__occupancy.set(tetra_index, true);
195
+
196
+ this.__length++;
197
+
198
+ return tetra_index;
199
+ }
200
+
201
+ /**
202
+ * Remove tetrahedron
203
+ * @param {number} tera_index
204
+ */
205
+ remove(tera_index) {
206
+
207
+ // find neighbours and remove reference to self
208
+ for (let i = 0; i < 4; i++) {
209
+ const neighbour_encoded = this.getNeighbour(tera_index, i);
210
+
211
+ if (neighbour_encoded === INVALID_NEIGHBOUR) {
212
+ // no neighbour
213
+ continue;
214
+ }
215
+
216
+ // get tetrahedra index and point index
217
+ const neighbour_index = neighbour_encoded >> 2;
218
+ const neighbour_point = neighbour_encoded & 4;
219
+
220
+ // clear reference to self
221
+ this.setNeighbour(neighbour_index, neighbour_point, INVALID_NEIGHBOUR);
222
+ }
223
+
224
+ // mark as dead
225
+ this.__occupancy.clear(tera_index);
226
+ this.__length--;
227
+ }
228
+
229
+ /**
230
+ * Remove tetrahedrons who's points touch the "super-tetrahedron's" points that was inserted originally
231
+ * These points are identified by an offset + count parameters
232
+ * @param {number[]|Float32Array} points
233
+ * @param {number} range_start
234
+ * @param {number} range_end
235
+ */
236
+ removeTetrasConnectedToPoints(points, range_start, range_end) {
237
+ const data_view = this.__view;
238
+
239
+ let i = 0;
240
+ let j = 0;
241
+
242
+ const occupancy = this.__occupancy;
243
+ for (i = occupancy.nextSetBit(0); i !== -1; i = occupancy.nextSetBit(i + 1)) {
244
+
245
+ const tet_address = i * LAYOUT_TETRA_BYTE_SIZE;
246
+
247
+ for (j = 0; j < 4; j++) {
248
+ const point_index = data_view.getUint32(tet_address + 4 * i);
249
+
250
+ if (point_index >= range_start && point_index <= range_end) {
251
+ // point index is in range, tetra should be removed
252
+ this.remove(i);
253
+ break;
254
+ }
255
+ }
256
+ }
257
+ }
258
+
259
+ /**
260
+ *
261
+ * @param {number[]} points
262
+ * @param {number} index
263
+ * @returns {number} index of tetra or -1 if no containing tetra found
264
+ */
265
+ findTetraContainingPoint(points, index) {
266
+ const data_view = this.__view;
267
+
268
+ // TODO improve this
269
+ const n = this.__length;
270
+ for (let i = 0; i < n; i++) {
271
+
272
+ const tet_address = i * LAYOUT_TETRA_BYTE_SIZE;
273
+
274
+ const a = data_view.getUint32(tet_address);
275
+ const b = data_view.getUint32(tet_address + 4);
276
+ const c = data_view.getUint32(tet_address + 8);
277
+ const d = data_view.getUint32(tet_address + 12);
278
+
279
+ if (tetrahedron_contains_point(points, a, b, c, d, index)) {
280
+ return i;
281
+ }
282
+ }
283
+
284
+ return -1;
285
+ }
286
+
287
+ /**
288
+ *
289
+ * @param {Cavity} cavity
290
+ * @param {number[]|Float32Array} points
291
+ * @param {number} index
292
+ */
293
+ computeCavity(cavity, points, index) {
294
+
295
+ // 1.identify tetrahedron that contains this point
296
+ const containing_tetra = this.findTetraContainingPoint(points, index);
297
+
298
+ assert.isNonNegativeInteger(containing_tetra, 'containing_tetra');
299
+
300
+ cavity.reset();
301
+
302
+ /**
303
+ *
304
+ * @type {number[]}
305
+ */
306
+ const open_set = [
307
+ containing_tetra
308
+ ];
309
+
310
+ /**
311
+ *
312
+ * @type {number[]}
313
+ */
314
+ const closed_set = [];
315
+
316
+ const data_view = this.__view;
317
+
318
+ // 2. find the Delaunay "cavity", a set of tetrahedrons who's circumsphere overlarlaps the point
319
+ // perform breadth-first expansion on the containing tetra to identify the cavity
320
+ while (open_set.length > 0) {
321
+ const tetra = open_set.pop();
322
+ cavity.push(tetra);
323
+ closed_set.push(tetra);
324
+
325
+ // get neighbours
326
+ for (let i = 0; i < 4; i++) {
327
+ const neighbour_encoded = this.getNeighbour(tetra, i);
328
+ const neighbour_tetra_index = neighbour_encoded >> 2;
329
+
330
+ if (
331
+ (neighbour_encoded === INVALID_NEIGHBOUR) // no neighbour
332
+ || closed_set.includes(neighbour_tetra_index) // already visited
333
+ || open_set.includes(neighbour_tetra_index) // already in open set
334
+ ) {
335
+
336
+ continue;
337
+ }
338
+
339
+ const neighbour_address = neighbour_tetra_index * LAYOUT_TETRA_BYTE_SIZE;
340
+
341
+ const a = data_view.getUint32(neighbour_address);
342
+ const b = data_view.getUint32(neighbour_address + 4);
343
+ const c = data_view.getUint32(neighbour_address + 8);
344
+ const d = data_view.getUint32(neighbour_address + 12);
345
+
346
+ // check neighbour
347
+ const is_in_sphere = point_in_tetrahedron_circumsphere(points, a, b, c, d, index);
348
+
349
+ if (is_in_sphere) {
350
+ open_set.push(neighbour_tetra_index);
351
+ } else {
352
+ // move directly to closed set
353
+ closed_set.push(neighbour_tetra_index);
354
+ }
355
+
356
+
357
+ }
358
+ }
359
+ }
360
+
361
+ /**
362
+ *
363
+ * @param {number[]|Float32Array} points
364
+ * @param {number} index
365
+ */
366
+ insertPoint(points, index) {
367
+ // Compute the cavity, that is the set of tetrahedra that need to be removed and rebuilt
368
+ this.computeCavity(SCRATCH_CAVITY, points, index);
369
+
370
+
371
+ // identify cavity "edge" - the set of tetras that are neighbours of the tetras that form the cavity
372
+ const cavity_size = SCRATCH_CAVITY.size;
373
+ const cavity_indices = SCRATCH_CAVITY.indices;
374
+ const data = this.__view;
375
+
376
+ for (let i = 0; i < cavity_size; i++) {
377
+ const cavity_index = cavity_indices[i];
378
+
379
+ // take each side of the tetra
380
+ const tetra_address = cavity_index * LAYOUT_TETRA_BYTE_SIZE;
381
+
382
+ const a = data.getUint32(tetra_address);
383
+ const b = data.getUint32(tetra_address + 4);
384
+ const c = data.getUint32(tetra_address + 8);
385
+ const d = data.getUint32(tetra_address + 12);
386
+
387
+ // attempt to form 4 tetras using the sides of the cavity
388
+ const a_neighbour = data.getUint32(tetra_address + 16);
389
+ const a_neighbour_index = a_neighbour >> 2;
390
+
391
+ const b_neighbour = data.getUint32(tetra_address + 20);
392
+ const b_neighbour_index = b_neighbour >> 2;
393
+
394
+ const c_neighbour = data.getUint32(tetra_address + 24);
395
+ const c_neighbour_index = c_neighbour >> 2;
396
+
397
+ const d_neighbour = data.getUint32(tetra_address + 28);
398
+ const d_neighbour_index = d_neighbour >> 2;
399
+
400
+
401
+ if (a_neighbour === INVALID_NEIGHBOUR || cavity_indices.indexOf(a_neighbour_index) === 0) {
402
+ // ABC is a valid side
403
+ const tet = this.append(points, b, d, c, index);
404
+ // patch in neighbours
405
+ if (a_neighbour !== INVALID_NEIGHBOUR) {
406
+ this.setNeighbour(tet, 3, a_neighbour);
407
+ this.setNeighbour(a_neighbour_index, a_neighbour & 3, tet);
408
+ }
409
+ }
410
+ if (b_neighbour === INVALID_NEIGHBOUR || cavity_indices.indexOf(b_neighbour_index) === 0) {
411
+ // ABC is a valid side
412
+ const tet = this.append(points, c, d, a, index);
413
+ if (b_neighbour !== INVALID_NEIGHBOUR) {
414
+ this.setNeighbour(tet, 3, b_neighbour);
415
+ this.setNeighbour(b_neighbour_index, b_neighbour & 3, tet);
416
+ }
417
+ }
418
+ if (c_neighbour === INVALID_NEIGHBOUR || cavity_indices.indexOf(c_neighbour_index) === 0) {
419
+ // ABC is a valid side
420
+ const tet = this.append(points, d, b, a, index);
421
+ if (c_neighbour !== INVALID_NEIGHBOUR) {
422
+ this.setNeighbour(tet, 3, c_neighbour);
423
+ this.setNeighbour(c_neighbour_index, c_neighbour & 3, tet);
424
+ }
425
+ }
426
+ if (d_neighbour === INVALID_NEIGHBOUR || cavity_indices.indexOf(d_neighbour_index) === 0) {
427
+ // ABC is a valid side
428
+ const tet = this.append(points, a, b, c, index);
429
+ if (d_neighbour !== INVALID_NEIGHBOUR) {
430
+ this.setNeighbour(tet, 3, d_neighbour);
431
+ this.setNeighbour(d_neighbour_index, d_neighbour & 3, tet);
432
+ }
433
+ }
434
+ }
435
+
436
+
437
+ // 3. remove the cavity tetrahedrons
438
+ for (let i = 0; i < cavity_size; i++) {
439
+ const cavity_index = cavity_indices[i];
440
+
441
+ // clear out cavity, connectivity was already patched in the previous step
442
+ this.__occupancy.set(cavity_index, false);
443
+ }
444
+ }
445
+
446
+ /**
447
+ * optimize tetrahedrons to reduce number of thin and long tetras
448
+ * @see https://gitlab.onelab.info/gmsh/gmsh/-/blob/master/contrib/hxt/tetMesh/src/HXTSPR.c
449
+ */
450
+ optimize() {
451
+
452
+ throw new Error('Not Implemented');
453
+ }
454
+ }
@@ -0,0 +1,26 @@
1
+ import { orient3d_fast } from "../plane/orient3d_fast.js";
2
+
3
+ /**
4
+ * Containment test, whether tetrahedron defined by A,B,C,D contains point E or not
5
+ * Implemented using planar tests on each of side of the tetrahedron
6
+ *
7
+ * NOTE: similar to https://gitlab.onelab.info/gmsh/gmsh/-/blob/master/contrib/hxt/tetMesh/src/HXTSPR.c
8
+ * We skip identity checks and bounding box checks. Identity is a waste of time when building tetrahedral mesh,
9
+ * since each inserted point will be new to the mesh, box checks are not relevant as we don't track those
10
+ *
11
+ * @param {number[]} points
12
+ * @param {number} a
13
+ * @param {number} b
14
+ * @param {number} c
15
+ * @param {number} d
16
+ * @param {number} e
17
+ * @returns {boolean}
18
+ */
19
+ export function tetrahedron_contains_point(points, a, b, c, d, e) {
20
+ return !(
21
+ orient3d_fast(points, b, d, c, e) > 0
22
+ || orient3d_fast(points, c, d, a, e) > 0
23
+ || orient3d_fast(points, d, b, a, e) > 0
24
+ || orient3d_fast(points, a, b, c, e) > 0
25
+ );
26
+ }
@@ -1298,6 +1298,10 @@ class Quaternion {
1298
1298
  this.set(x, y, z, w);
1299
1299
  }
1300
1300
 
1301
+ /**
1302
+ * Based on GDC talk from Bungie on destiny, compressing quaternions for animation
1303
+ * @param value
1304
+ */
1301
1305
  decodeFromUint32(value) {
1302
1306
  //read components
1303
1307
  const max = value & 0x3;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * NOTE: based on https://github.com/garycourt/murmurhash-js/blob/master/murmurhash3_gc.js
3
+ * @param {Uint32Array} key
4
+ * @param {number} seed
5
+ * @returns {number}
6
+ */
7
+ export function murmur3_32(key, seed) {
8
+ let h1 = seed;
9
+ let k1;
10
+ let i = 0;
11
+
12
+ const bytes = key.length;
13
+
14
+ while (i < bytes) {
15
+ k1 = key[i];
16
+ ++i;
17
+
18
+ k1 = ((((k1 & 0xffff) * 0xcc9e2d51) + ((((k1 >>> 16) * 0xcc9e2d51) & 0xffff) << 16)));
19
+ k1 = (k1 << 15) | (k1 >>> 17);
20
+ k1 = ((((k1 & 0xffff) * 0x1b873593) + ((((k1 >>> 16) * 0x1b873593) & 0xffff) << 16)));
21
+
22
+ h1 ^= k1;
23
+ h1 = (h1 << 13) | (h1 >>> 19);
24
+ h1 = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16)));
25
+ h1 = (((h1 & 0xffff) + 0x6b64) + ((((h1 >>> 16) + 0xe654) & 0xffff) << 16));
26
+ }
27
+
28
+ // mix in array length
29
+ h1 ^= bytes;
30
+
31
+ h1 ^= h1 >>> 16;
32
+ h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16));
33
+ h1 ^= h1 >>> 13;
34
+ h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16)));
35
+ h1 ^= h1 >>> 16;
36
+
37
+ // cast to uint32
38
+ return h1 >>> 0;
39
+ }
@@ -22,4 +22,6 @@ export class ActionProcessor<CTX, MD> {
22
22
  doMany(actions: Action<CTX>[]): void
23
23
 
24
24
  do(action: Action<CTX>): this
25
+
26
+ clear(): void
25
27
  }