@woosh/meep-engine 2.43.1 → 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/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/array_range_equal_strict.js +22 -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/color/sRGB_to_linear.js +9 -4
- 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 +11 -10
- package/core/geom/3d/plane/orient3d_robust.js +41 -0
- 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/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/README.md +10 -1
- package/core/geom/3d/tetrahedra/TetrahedralMesh.js +650 -0
- package/core/geom/3d/tetrahedra/TetrahedralMesh.spec.js +233 -0
- package/core/geom/3d/tetrahedra/build_tetrahedral_mesh_buffer_geometry.js +75 -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 +49 -7
- package/core/geom/3d/tetrahedra/delaunay/compute_delaunay_tetrahedral_mesh.js +51 -17
- package/core/geom/3d/tetrahedra/delaunay/debug_validate_mesh.js +19 -0
- package/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +191 -0
- package/core/geom/3d/tetrahedra/delaunay/push_boundary_with_validation.js +27 -0
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity.js +59 -43
- 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_walk_towards_containing_tetrahedron.js +58 -0
- 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} +11 -13
- 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/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 +166 -0
- package/core/geom/3d/util/make_justified_point_grid.js +31 -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/delay.js +5 -0
- package/core/process/task/util/randomCountTask.js +1 -1
- 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/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/EngineHarness.js +11 -5
- package/engine/asset/AssetManager.d.ts +5 -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/ecs/terrain/ecs/TerrainSystem.js +7 -1
- package/engine/ecs/transform/copy_three_transform.js +15 -0
- 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/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/VertexDataSpec.js +1 -1
- package/engine/graphics/geometry/instancing/InstancedMeshGroup.js +2 -2
- 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/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/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 +22 -4
- 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/intelligence/blackboard/AbstractBlackboard.d.ts +1 -1
- 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 +2 -1
- package/samples/terrain/from_image_2.js +127 -82
- 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_walk_toward_cavity.js +0 -48
- 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
- package/core/geom/3d/tetrahedra/tetrahedra_collection.js +0 -383
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
import { tetrahedron_contains_point } from "./tetrahedron_contains_point.js";
|
|
2
|
+
import { typed_array_copy } from "../../../collection/array/typed/typed_array_copy.js";
|
|
3
|
+
import { max3 } from "../../../math/max3.js";
|
|
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;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Size in bytes of a single tetrahedron record
|
|
19
|
+
* @readonly
|
|
20
|
+
* @type {number}
|
|
21
|
+
*/
|
|
22
|
+
export const LAYOUT_TETRA_BYTE_SIZE = LAYOUT_TETRA_WORD_COUNT * 4;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @readonly
|
|
26
|
+
* @type {number}
|
|
27
|
+
*/
|
|
28
|
+
export const INVALID_NEIGHBOUR = 0xFFFFFFFF;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @readonly
|
|
32
|
+
* @type {number}
|
|
33
|
+
*/
|
|
34
|
+
export const MAX_TET_INDEX = 0xFFFFFFFC;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @readonly
|
|
38
|
+
* @type {number}
|
|
39
|
+
*/
|
|
40
|
+
const DEFAULT_INITIAL_SIZE = 128;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @readonly
|
|
44
|
+
* @type {number}
|
|
45
|
+
*/
|
|
46
|
+
const CAPACITY_GROW_MULTIPLIER = 1.2;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @readonly
|
|
50
|
+
* @type {number}
|
|
51
|
+
*/
|
|
52
|
+
const CAPACITY_GROW_MIN_STEP = 32;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Only keeps track of tetrahedra, actual point coordinates are stored outside.
|
|
56
|
+
* For most useful operations point coordinates are passed in as an extra argument.
|
|
57
|
+
*
|
|
58
|
+
* Binary Layout:
|
|
59
|
+
* vertex_id_a :: uint32
|
|
60
|
+
* vertex_id_b :: uint32
|
|
61
|
+
* vertex_id_c :: uint32
|
|
62
|
+
* vertex_id_d :: uint32
|
|
63
|
+
* neighbour_a :: uint32 - neighbour tetrahedron, opposite to vertex A
|
|
64
|
+
* neighbour_b :: uint32 - neighbour tetrahedron, opposite to vertex B
|
|
65
|
+
* neighbour_c :: uint32 - neighbour tetrahedron, opposite to vertex C
|
|
66
|
+
* neighbour_d :: uint32 - neighbour tetrahedron, opposite to vertex D
|
|
67
|
+
* Layout is similar to [1], but is interleaved for better cache locality.
|
|
68
|
+
* 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.
|
|
69
|
+
*
|
|
70
|
+
* Neighbours are encoded in the following manner:
|
|
71
|
+
* MSB -> [tet_id:30bit][opposite_corner_index:2bit] <- LSB
|
|
72
|
+
* Code to get tet index: encoded >> 2
|
|
73
|
+
* Code to get corner index: encoded & 3
|
|
74
|
+
*
|
|
75
|
+
* @see [1] 2018 "One machine, one minute, three billion tetrahedra" by Célestin Marot, Jeanne Pellerin and Jean-François Remacle
|
|
76
|
+
* @see https://git.immc.ucl.ac.be/hextreme/hxt_seqdel (C source code for [1])
|
|
77
|
+
*/
|
|
78
|
+
export class TetrahedralMesh {
|
|
79
|
+
/**
|
|
80
|
+
*
|
|
81
|
+
* @param {number} [initial_size]
|
|
82
|
+
*/
|
|
83
|
+
constructor(initial_size = DEFAULT_INITIAL_SIZE) {
|
|
84
|
+
assert.isNonNegativeInteger(initial_size, 'initial_size');
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
*
|
|
88
|
+
* @type {ArrayBuffer}
|
|
89
|
+
* @private
|
|
90
|
+
*/
|
|
91
|
+
this.__buffer = new ArrayBuffer(initial_size * LAYOUT_TETRA_BYTE_SIZE);
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
*
|
|
95
|
+
* @type {Uint32Array}
|
|
96
|
+
* @private
|
|
97
|
+
*/
|
|
98
|
+
this.__data_uint32 = new Uint32Array(this.__buffer);
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
*
|
|
102
|
+
* @type {DataView}
|
|
103
|
+
* @private
|
|
104
|
+
*/
|
|
105
|
+
this.__view = new DataView(this.__buffer);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
*
|
|
109
|
+
* @type {number}
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
this.__capacity = initial_size;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
*
|
|
116
|
+
* @type {number}
|
|
117
|
+
* @private
|
|
118
|
+
*/
|
|
119
|
+
this.__used_end = 0;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Unused slots
|
|
123
|
+
* @type {number[]}
|
|
124
|
+
* @private
|
|
125
|
+
*/
|
|
126
|
+
this.__free = [];
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
*
|
|
130
|
+
* @type {number}
|
|
131
|
+
* @private
|
|
132
|
+
*/
|
|
133
|
+
this.__free_pointer = 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Traverse tetrahedrons
|
|
138
|
+
* @param {(tet:number,mesh:TetrahedralMesh)=>*} visitor
|
|
139
|
+
* @param {*} [thisArg]
|
|
140
|
+
*/
|
|
141
|
+
forEach(visitor, thisArg) {
|
|
142
|
+
for (let i = 0; i < this.__used_end; i++) {
|
|
143
|
+
if (!this.exists(i)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
visitor.call(thisArg, i, this);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
*
|
|
153
|
+
* @param {number} capacity
|
|
154
|
+
*/
|
|
155
|
+
setCapacity(capacity) {
|
|
156
|
+
assert.isNonNegativeInteger(capacity, 'capacity');
|
|
157
|
+
|
|
158
|
+
if (capacity === this.__capacity) {
|
|
159
|
+
// do nothing
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (capacity < this.__capacity && capacity < this.__used_end) {
|
|
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.');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// allocate new buffer
|
|
168
|
+
const new_buffer = new ArrayBuffer(capacity * LAYOUT_TETRA_BYTE_SIZE);
|
|
169
|
+
|
|
170
|
+
// move data across from old buffer to the new one
|
|
171
|
+
const destination_uint8 = new Uint8Array(new_buffer);
|
|
172
|
+
const source_uint8 = new Uint8Array(this.__buffer);
|
|
173
|
+
|
|
174
|
+
typed_array_copy(source_uint8, destination_uint8);
|
|
175
|
+
|
|
176
|
+
// set new buffer
|
|
177
|
+
this.__buffer = new_buffer;
|
|
178
|
+
this.__view = new DataView(new_buffer);
|
|
179
|
+
this.__data_uint32 = new Uint32Array(new_buffer);
|
|
180
|
+
|
|
181
|
+
// write new capacity
|
|
182
|
+
this.__capacity = capacity;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
*
|
|
187
|
+
* @return {number}
|
|
188
|
+
*/
|
|
189
|
+
getCapacity() {
|
|
190
|
+
return this.__capacity;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* How many tetrahedrons are contained in the mesh
|
|
195
|
+
* @return {number}
|
|
196
|
+
*/
|
|
197
|
+
size() {
|
|
198
|
+
return this.__used_end;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Grow capacity to at least the specified size
|
|
203
|
+
* @param {number} capacity
|
|
204
|
+
*/
|
|
205
|
+
growCapacity(capacity) {
|
|
206
|
+
|
|
207
|
+
const existing_capacity = this.__capacity;
|
|
208
|
+
|
|
209
|
+
const new_capacity = max3(
|
|
210
|
+
capacity,
|
|
211
|
+
Math.ceil(existing_capacity * CAPACITY_GROW_MULTIPLIER),
|
|
212
|
+
existing_capacity + CAPACITY_GROW_MIN_STEP
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
this.setCapacity(new_capacity);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* NOTE: this method can be quite slow in cases of sparse allocation, please prefer not to use it
|
|
220
|
+
* @param {number} tet
|
|
221
|
+
* @return {boolean}
|
|
222
|
+
*/
|
|
223
|
+
exists(tet) {
|
|
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;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* NOTE: the neighbour value must be encoded, see format specification for details
|
|
242
|
+
* @param {number} tetra_index
|
|
243
|
+
* @param {number} neighbour_index
|
|
244
|
+
* @returns {number} index of the neighbour encoded with the opposite corner
|
|
245
|
+
*/
|
|
246
|
+
getNeighbour(tetra_index, neighbour_index) {
|
|
247
|
+
assert.isNonNegativeInteger(tetra_index, 'tetra_index');
|
|
248
|
+
assert.ok(this.exists(tetra_index), 'tetrahedron does not exist');
|
|
249
|
+
|
|
250
|
+
assert.isNonNegativeInteger(neighbour_index, 'neighbour_index');
|
|
251
|
+
|
|
252
|
+
assert.lessThan(neighbour_index, 4, 'neighbour_index');
|
|
253
|
+
|
|
254
|
+
const tetra_address = LAYOUT_TETRA_BYTE_SIZE * tetra_index;
|
|
255
|
+
return this.__view.getUint32(tetra_address + (4 + neighbour_index) * 4);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* NOTE: the neighbour value must be encoded, see format specification for details
|
|
260
|
+
* @param {number} tetra_index
|
|
261
|
+
* @param {number} neighbour_index which neighbour to set (0..11)
|
|
262
|
+
* @param {number} neighbour index of the neighbour encoded with the opposite corner
|
|
263
|
+
*/
|
|
264
|
+
setNeighbour(tetra_index, neighbour_index, neighbour) {
|
|
265
|
+
|
|
266
|
+
assert.isNonNegativeInteger(tetra_index, 'tetra_index');
|
|
267
|
+
assert.ok(this.exists(tetra_index), 'tetrahedron does not exist');
|
|
268
|
+
|
|
269
|
+
assert.isNonNegativeInteger(neighbour_index, 'neighbour_index');
|
|
270
|
+
assert.isNonNegativeInteger(neighbour, 'neighbour');
|
|
271
|
+
|
|
272
|
+
assert.lessThan(neighbour_index, 4, 'neighbour_index');
|
|
273
|
+
|
|
274
|
+
const tetra_address = LAYOUT_TETRA_BYTE_SIZE * tetra_index;
|
|
275
|
+
return this.__view.setUint32(tetra_address + (4 + neighbour_index) * 4, neighbour);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
*
|
|
281
|
+
* @param {number} tet_index
|
|
282
|
+
* @param {number} point_index should be an integer between 0 and 3
|
|
283
|
+
* @returns {number}
|
|
284
|
+
*/
|
|
285
|
+
getVertexIndex(tet_index, point_index) {
|
|
286
|
+
assert.isNonNegativeInteger(tet_index, 'tet_index');
|
|
287
|
+
assert.lessThanOrEqual(tet_index, MAX_TET_INDEX, 'max index exceeded');
|
|
288
|
+
//assert.ok(this.exists(tet_index), 'tetrahedron does not exist');
|
|
289
|
+
|
|
290
|
+
assert.isNonNegativeInteger(point_index, 'point_index');
|
|
291
|
+
assert.lessThan(point_index, 4, 'point_index must be less than 4');
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
return this.__view.getUint32(tet_index * LAYOUT_TETRA_BYTE_SIZE + point_index * 4);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
*
|
|
299
|
+
* @param {number} tet_index
|
|
300
|
+
* @param {number} point_index
|
|
301
|
+
* @param {number} vertex
|
|
302
|
+
*/
|
|
303
|
+
setVertexIndex(tet_index, point_index, vertex) {
|
|
304
|
+
assert.isNonNegativeInteger(tet_index, 'tet_index');
|
|
305
|
+
assert.lessThanOrEqual(tet_index, MAX_TET_INDEX, 'max index exceeded');
|
|
306
|
+
//assert.ok(this.exists(tet_index), 'tetrahedron does not exist');
|
|
307
|
+
|
|
308
|
+
assert.isNonNegativeInteger(point_index, 'point_index');
|
|
309
|
+
assert.lessThan(point_index, 4, 'point_index must be less than 4');
|
|
310
|
+
|
|
311
|
+
assert.isNonNegativeInteger(vertex, 'vertex');
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
return this.__view.setUint32(
|
|
315
|
+
tet_index * LAYOUT_TETRA_BYTE_SIZE + point_index * 4,
|
|
316
|
+
vertex
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Whether a given tetrahedron contains vertex with a given index
|
|
322
|
+
* @param {number} tet
|
|
323
|
+
* @param {number} vertex
|
|
324
|
+
* @return {boolean}
|
|
325
|
+
*/
|
|
326
|
+
tetContainsVertex(tet, vertex) {
|
|
327
|
+
for (let i = 0; i < 4; i++) {
|
|
328
|
+
if (this.getVertexIndex(tet, i) === vertex) {
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Allocate empty tet
|
|
339
|
+
* NOTE: the tet memory might be dirty, please make sure you set/clear it as necessary
|
|
340
|
+
* @return {number} index of allocated tetrahedron
|
|
341
|
+
*/
|
|
342
|
+
allocate() {
|
|
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;
|
|
349
|
+
|
|
350
|
+
this.__used_end++;
|
|
351
|
+
|
|
352
|
+
if (tetra_index >= this.__capacity) {
|
|
353
|
+
// needs to be increased in size
|
|
354
|
+
this.growCapacity(tetra_index);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// initialize neighbours
|
|
358
|
+
for (let i = 0; i < 4; i++) {
|
|
359
|
+
this.setNeighbour(tetra_index, i, INVALID_NEIGHBOUR);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
//assert(this.validateLength());
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
// assert(this.validateLength());
|
|
366
|
+
|
|
367
|
+
return tetra_index;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
*
|
|
372
|
+
* @param {number[]|Float32Array} points
|
|
373
|
+
* @param {number} a
|
|
374
|
+
* @param {number} b
|
|
375
|
+
* @param {number} c
|
|
376
|
+
* @param {number} d
|
|
377
|
+
* @returns {number} index of the new tetrahedron
|
|
378
|
+
*/
|
|
379
|
+
append(points, a, b, c, d) {
|
|
380
|
+
const tetra_index = this.allocate();
|
|
381
|
+
|
|
382
|
+
const address = tetra_index * LAYOUT_TETRA_BYTE_SIZE;
|
|
383
|
+
|
|
384
|
+
const view = this.__view;
|
|
385
|
+
|
|
386
|
+
view.setUint32(address, a);
|
|
387
|
+
view.setUint32(address + 4, b);
|
|
388
|
+
view.setUint32(address + 8, c);
|
|
389
|
+
view.setUint32(address + 12, d);
|
|
390
|
+
|
|
391
|
+
// set neighbours
|
|
392
|
+
view.setUint32(address + 16, INVALID_NEIGHBOUR);
|
|
393
|
+
view.setUint32(address + 20, INVALID_NEIGHBOUR);
|
|
394
|
+
view.setUint32(address + 24, INVALID_NEIGHBOUR);
|
|
395
|
+
view.setUint32(address + 28, INVALID_NEIGHBOUR);
|
|
396
|
+
|
|
397
|
+
return tetra_index;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
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
|
|
403
|
+
* @param {number} tetra_index
|
|
404
|
+
*/
|
|
405
|
+
disconnect(tetra_index) {
|
|
406
|
+
// find neighbours and remove reference to self
|
|
407
|
+
for (let i = 0; i < 4; i++) {
|
|
408
|
+
const neighbour_encoded = this.getNeighbour(tetra_index, i);
|
|
409
|
+
|
|
410
|
+
if (neighbour_encoded === INVALID_NEIGHBOUR) {
|
|
411
|
+
// no neighbour
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// get tetrahedra index and point index
|
|
416
|
+
const neighbour_index = neighbour_encoded >> 2;
|
|
417
|
+
const neighbour_point = neighbour_encoded & 3;
|
|
418
|
+
|
|
419
|
+
// clear reference to self
|
|
420
|
+
this.setNeighbour(neighbour_index, neighbour_point, INVALID_NEIGHBOUR);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
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
|
+
}
|
|
442
|
+
|
|
443
|
+
//assert(this.validateLength());
|
|
444
|
+
|
|
445
|
+
//assert(this.validateLength());
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Used mainly to remove tetrahedrons whos points touch the "super-tetrahedron's" points that was inserted originally
|
|
450
|
+
* These points are identified by an offset + count parameters
|
|
451
|
+
* @param {number} range_start
|
|
452
|
+
* @param {number} range_end
|
|
453
|
+
*/
|
|
454
|
+
removeTetrasConnectedToPoints(range_start, range_end) {
|
|
455
|
+
|
|
456
|
+
for (let i = this.__used_end - 1; i >= 0; i--) {
|
|
457
|
+
|
|
458
|
+
for (let j = 0; j < 4; j++) {
|
|
459
|
+
const point_index = this.getVertexIndex(i, j);
|
|
460
|
+
|
|
461
|
+
if (point_index >= range_start && point_index <= range_end) {
|
|
462
|
+
|
|
463
|
+
if (!this.exists(i)) {
|
|
464
|
+
// tet doesn't actually exist (deallocated)
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// point index is in range, tetra should be removed
|
|
469
|
+
this.disconnect(i);
|
|
470
|
+
this.delete(i);
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
*
|
|
480
|
+
* @param {number[]} points
|
|
481
|
+
* @param {number} index
|
|
482
|
+
* @returns {number} index of tetra or -1 if no containing tetra found
|
|
483
|
+
*/
|
|
484
|
+
findTetraContainingPoint(points, index) {
|
|
485
|
+
const data_view = this.__view;
|
|
486
|
+
|
|
487
|
+
// TODO improve this
|
|
488
|
+
const n = this.__used_end;
|
|
489
|
+
for (let i = 0; i < n; i++) {
|
|
490
|
+
|
|
491
|
+
const tet_address = i * LAYOUT_TETRA_BYTE_SIZE;
|
|
492
|
+
|
|
493
|
+
const a = data_view.getUint32(tet_address);
|
|
494
|
+
const b = data_view.getUint32(tet_address + 4);
|
|
495
|
+
const c = data_view.getUint32(tet_address + 8);
|
|
496
|
+
const d = data_view.getUint32(tet_address + 12);
|
|
497
|
+
|
|
498
|
+
if (tetrahedron_contains_point(points, a, b, c, d, index)) {
|
|
499
|
+
return i;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return -1;
|
|
504
|
+
}
|
|
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
|
+
}
|
|
650
|
+
}
|