@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,191 @@
|
|
|
1
|
+
import { assert } from "../../../../assert.js";
|
|
2
|
+
import { array_copy } from "../../../../collection/array/copyArray.js";
|
|
3
|
+
import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
|
|
4
|
+
import { validate_neighbour } from "../validate_tetrahedral_mesh.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param {TetrahedralMesh} mesh
|
|
9
|
+
* @param {ArrayLike<number>|number[]|Float32Array} points
|
|
10
|
+
* @param {BitSet} visited_flags
|
|
11
|
+
* @param {Cavity} cavity
|
|
12
|
+
* @param {number} cur_tet
|
|
13
|
+
* @returns {number} new current tet
|
|
14
|
+
*/
|
|
15
|
+
export function fill_in_a_cavity(mesh, points, visited_flags, cavity, cur_tet) {
|
|
16
|
+
|
|
17
|
+
let clength = cavity.__deleted_size; // cavity size
|
|
18
|
+
const blength = cavity.__boundary_size; // boundary size
|
|
19
|
+
|
|
20
|
+
assert.equal(blength & 1, 0, 'there is always a pair number of triangle on the boundary. N points, 2N triangle, 3N edges');
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
Re-use tets that would be deleted, if there are not enough dead tets - allocate new ones as necessary
|
|
24
|
+
*/
|
|
25
|
+
const deleted_tets = cavity.__deleted;
|
|
26
|
+
if (blength > clength) {
|
|
27
|
+
|
|
28
|
+
for (let i = clength; i < blength; i++) {
|
|
29
|
+
// allocate tets for the cavity filling, remember them inside the "deleted" set for now
|
|
30
|
+
deleted_tets[i] = mesh.allocate();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
clength = blength;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const boundary = cavity.__boundary;
|
|
37
|
+
const start = clength - blength;
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < blength; i++) {
|
|
40
|
+
// go over the faces in the cavity boundary and matching tet that will be filling the space
|
|
41
|
+
|
|
42
|
+
const current_tet_index = deleted_tets[i + start];
|
|
43
|
+
|
|
44
|
+
const i5 = i * 5;
|
|
45
|
+
|
|
46
|
+
// write filling tet's corners
|
|
47
|
+
mesh.setVertexIndex(current_tet_index, 0, boundary[i5]); // "ball"/cavity center point is written here
|
|
48
|
+
mesh.setVertexIndex(current_tet_index, 1, boundary[i5 + 1]);
|
|
49
|
+
mesh.setVertexIndex(current_tet_index, 2, boundary[i5 + 2]);
|
|
50
|
+
mesh.setVertexIndex(current_tet_index, 3, boundary[i5 + 3]);
|
|
51
|
+
|
|
52
|
+
const encoded_boundary_tet = boundary[i5 + 4];
|
|
53
|
+
|
|
54
|
+
mesh.setNeighbour(current_tet_index, 0, encoded_boundary_tet); // connect opposing neighbour
|
|
55
|
+
|
|
56
|
+
const encoded_current_tet = current_tet_index << 2;
|
|
57
|
+
|
|
58
|
+
if (encoded_boundary_tet !== INVALID_NEIGHBOUR) {
|
|
59
|
+
// update neighbour
|
|
60
|
+
mesh.setNeighbour(encoded_boundary_tet >> 2, encoded_boundary_tet & 3, encoded_current_tet);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// boundary[i5 + 4] = encoded_current_tet;
|
|
64
|
+
|
|
65
|
+
visited_flags.set(current_tet_index, false); // clear flag
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
computeAdjacencies(mesh, cavity, start, blength);
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < start; i++) {
|
|
72
|
+
// cleanup any orphaned tets that we didn't re-use
|
|
73
|
+
mesh.delete(deleted_tets[i]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// debug_validate_mesh(mesh, points);
|
|
77
|
+
|
|
78
|
+
cavity.__boundary_size = 0;
|
|
79
|
+
cavity.__deleted_size = 0;
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
Pick a tet from the created ball, it will generally be a good guess for next point
|
|
83
|
+
*/
|
|
84
|
+
return deleted_tets[start];
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* All tets within the ball must be connected to one another, we test that here
|
|
90
|
+
*/
|
|
91
|
+
function validate_patched_ball_adjacencies(mesh, ball_tets, ball_tets_offset, ball_tets_count, consumer) {
|
|
92
|
+
let valid = true;
|
|
93
|
+
|
|
94
|
+
let ball_index, tet;
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < ball_tets_count; i++) {
|
|
97
|
+
ball_index = ball_tets_offset + i;
|
|
98
|
+
tet = ball_tets[ball_index];
|
|
99
|
+
|
|
100
|
+
// except for 0th neighbour, all others must be valid as they represent connection within the ball itself
|
|
101
|
+
for (let j = 1; j < 4; j++) {
|
|
102
|
+
|
|
103
|
+
if (!validate_neighbour(mesh, tet, j, consumer)) {
|
|
104
|
+
valid = false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return valid;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* filling back the cavity
|
|
114
|
+
* N+2 point on the surface of the cavity
|
|
115
|
+
* 2N triangle on the surface of the cavity, x3 (4*0.5+1) data = 6N+9 uint64_t
|
|
116
|
+
* => enough place for the 3N edge x2 data = 6N uint64_t
|
|
117
|
+
* @param {TetrahedralMesh} mesh
|
|
118
|
+
* @param {Cavity} cavity
|
|
119
|
+
* @param {number} start
|
|
120
|
+
* @param {number} blength
|
|
121
|
+
*/
|
|
122
|
+
function computeAdjacencies(mesh, cavity, start, blength) {
|
|
123
|
+
const boundary = cavity.__boundary;
|
|
124
|
+
|
|
125
|
+
let tlength = 0;
|
|
126
|
+
|
|
127
|
+
const Tmp = boundary; // we know there is enough space...
|
|
128
|
+
|
|
129
|
+
for (let i = 0; i < blength; i++) {
|
|
130
|
+
|
|
131
|
+
const cur_tet = cavity.__deleted[start + i];
|
|
132
|
+
|
|
133
|
+
// pointer to the position of Node[0] in the Tmp array
|
|
134
|
+
|
|
135
|
+
for (let j = 0; j < 3; j++) {
|
|
136
|
+
|
|
137
|
+
// define the edge by the minimum vertex and the other
|
|
138
|
+
const index_0 = (j + 1) % 3 + 1;
|
|
139
|
+
const index_1 = (j + 2) % 3 + 1;
|
|
140
|
+
|
|
141
|
+
const n0 = mesh.getVertexIndex(cur_tet, index_0);
|
|
142
|
+
const n1 = mesh.getVertexIndex(cur_tet, index_1);
|
|
143
|
+
|
|
144
|
+
const current_tet_neighbour_id = j + 1;
|
|
145
|
+
const encoded_current_tet = (cur_tet << 2) | (current_tet_neighbour_id & 3);
|
|
146
|
+
|
|
147
|
+
// linear searching/pushing into Tmp
|
|
148
|
+
let k = 0;
|
|
149
|
+
for (; k < tlength; k++) {
|
|
150
|
+
// this is the only nested loop... the one that cost it all
|
|
151
|
+
|
|
152
|
+
if (Tmp[k * 3] === n0 && Tmp[k * 3 + 1] === n1)
|
|
153
|
+
// found a match, stop here
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (k === tlength) {
|
|
158
|
+
// we did not find it
|
|
159
|
+
const offset = tlength * 3;
|
|
160
|
+
|
|
161
|
+
// flip the edge order and store it. Neighbouring tet will have edge order reversed
|
|
162
|
+
Tmp[offset] = n1;
|
|
163
|
+
Tmp[offset + 1] = n0;
|
|
164
|
+
|
|
165
|
+
Tmp[offset + 2] = encoded_current_tet;
|
|
166
|
+
|
|
167
|
+
tlength++;
|
|
168
|
+
} else {
|
|
169
|
+
// we found the neighbour !
|
|
170
|
+
const encoded_neighbour = Tmp[k * 3 + 2];
|
|
171
|
+
|
|
172
|
+
mesh.setNeighbour(cur_tet, current_tet_neighbour_id, encoded_neighbour);
|
|
173
|
+
mesh.setNeighbour(encoded_neighbour >> 2, encoded_neighbour & 3, encoded_current_tet);
|
|
174
|
+
|
|
175
|
+
// reduce search space, there can only be one neighbour pair, after it's found we can remove the entry from lookup table
|
|
176
|
+
tlength--;
|
|
177
|
+
|
|
178
|
+
if (k < tlength) {
|
|
179
|
+
// put the last entry in the one we just discovered
|
|
180
|
+
array_copy(Tmp, tlength * 3, Tmp, k * 3, 3);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
assert.equal(tlength, 0, 'Failed to compute adjacencies');
|
|
187
|
+
|
|
188
|
+
// if (!validate_patched_ball_adjacencies(mesh, cavity.__deleted, start, blength, console.error)) {
|
|
189
|
+
// debugger;
|
|
190
|
+
// }
|
|
191
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { tetrahedron_compute_signed_volume } from "../tetrahedron_compute_signed_volume.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Debug method that validates cavity tet before pushing it
|
|
5
|
+
* @param {Cavity} cavity
|
|
6
|
+
* @param {number} a
|
|
7
|
+
* @param {number} b
|
|
8
|
+
* @param {number} c
|
|
9
|
+
* @param {number} d
|
|
10
|
+
* @param {number} neighbour
|
|
11
|
+
* @param {number[]} points
|
|
12
|
+
* @param {function(problem:string):*} consumer
|
|
13
|
+
*/
|
|
14
|
+
export function push_boundary_with_validation(points, cavity, a, b, c, d, neighbour, consumer = console.warn) {
|
|
15
|
+
let valid = true;
|
|
16
|
+
const vol = tetrahedron_compute_signed_volume(points, a, b, c, d);
|
|
17
|
+
|
|
18
|
+
if (vol < 0) {
|
|
19
|
+
consumer(`Pushing a negative volume(=${vol}) boundary tet ${[a, b, c, d].join(', ')}`);
|
|
20
|
+
|
|
21
|
+
valid = false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
cavity.push_boundary(a, b, c, d, neighbour);
|
|
25
|
+
|
|
26
|
+
return valid;
|
|
27
|
+
}
|
|
@@ -1,73 +1,89 @@
|
|
|
1
1
|
import { assert } from "../../../../assert.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
|
|
3
|
+
import { in_sphere_robust } from "../in_sphere_robust.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
*
|
|
7
7
|
* @param {TetrahedralMesh} mesh
|
|
8
8
|
* @param {Cavity} cavity
|
|
9
9
|
* @param {number[]|Float32Array} points
|
|
10
|
+
* @param {BitSet} visited_flags
|
|
10
11
|
* @param {number} containing_tetra tetrahedron that contains point
|
|
11
12
|
* @param {number} point_index point that forms the cavity
|
|
12
13
|
*/
|
|
13
|
-
export function tetrahedral_mesh_compute_cavity(
|
|
14
|
+
export function tetrahedral_mesh_compute_cavity(
|
|
15
|
+
mesh,
|
|
16
|
+
points,
|
|
17
|
+
visited_flags,
|
|
18
|
+
cavity,
|
|
19
|
+
containing_tetra,
|
|
20
|
+
point_index
|
|
21
|
+
) {
|
|
14
22
|
assert.isNonNegativeInteger(containing_tetra, 'containing_tetra');
|
|
15
23
|
|
|
16
|
-
cavity
|
|
24
|
+
// add the tet to cavity
|
|
25
|
+
cavity.push_deleted(containing_tetra);
|
|
26
|
+
visited_flags.set(containing_tetra, true);
|
|
17
27
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* @type {number[]}
|
|
21
|
-
*/
|
|
22
|
-
const open_set = [
|
|
23
|
-
containing_tetra
|
|
24
|
-
];
|
|
28
|
+
for (let start = cavity.__deleted_size - 1; start < cavity.__deleted_size; start++) {
|
|
29
|
+
const cur_tet = cavity.__deleted[start];
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* @type {number[]}
|
|
29
|
-
*/
|
|
30
|
-
const closed_set = [];
|
|
31
|
+
for (let i = 0; i < 4; i++) {
|
|
32
|
+
const encoded_neighbour = mesh.getNeighbour(cur_tet, i);
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
const k0 = (1 << i) & 3;
|
|
35
|
+
const k1 = (i + 2) % 3;
|
|
36
|
+
const k2 = (~((i + 1) >> 1) & 2) + 1;
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
closed_set.push(tetra);
|
|
38
|
+
if (encoded_neighbour === INVALID_NEIGHBOUR) {
|
|
39
|
+
// no neighbour, include this side into boundary
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
cavity.push_boundary(
|
|
42
|
+
point_index,
|
|
43
|
+
mesh.getVertexIndex(cur_tet, k0),
|
|
44
|
+
mesh.getVertexIndex(cur_tet, k1),
|
|
45
|
+
mesh.getVertexIndex(cur_tet, k2),
|
|
46
|
+
encoded_neighbour,
|
|
47
|
+
);
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
(neighbour_encoded === INVALID_NEIGHBOUR) // no neighbour
|
|
48
|
-
|| closed_set.includes(neighbour_tetra_index) // already visited
|
|
49
|
-
|| open_set.includes(neighbour_tetra_index) // already in open set
|
|
50
|
-
) {
|
|
52
|
+
const neighbour_index = encoded_neighbour >> 2;
|
|
51
53
|
|
|
54
|
+
if (visited_flags.get(neighbour_index)) {
|
|
55
|
+
// already visited
|
|
52
56
|
continue;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
if (in_sphere_robust(
|
|
60
|
+
points,
|
|
61
|
+
mesh.getVertexIndex(neighbour_index, 0),
|
|
62
|
+
mesh.getVertexIndex(neighbour_index, 1),
|
|
63
|
+
mesh.getVertexIndex(neighbour_index, 2),
|
|
64
|
+
mesh.getVertexIndex(neighbour_index, 3),
|
|
65
|
+
point_index
|
|
66
|
+
) < 0) {
|
|
67
|
+
// outside the cavity, meaning it defines boundary
|
|
59
68
|
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
cavity.push_boundary(
|
|
70
|
+
point_index,
|
|
71
|
+
mesh.getVertexIndex(cur_tet, k0),
|
|
72
|
+
mesh.getVertexIndex(cur_tet, k1),
|
|
73
|
+
mesh.getVertexIndex(cur_tet, k2),
|
|
74
|
+
encoded_neighbour,
|
|
75
|
+
);
|
|
62
76
|
|
|
63
|
-
if (is_in_sphere) {
|
|
64
|
-
open_set.push(neighbour_tetra_index);
|
|
65
77
|
} else {
|
|
66
|
-
//
|
|
67
|
-
closed_set.push(neighbour_tetra_index);
|
|
68
|
-
}
|
|
69
|
-
|
|
78
|
+
// assert.greaterThanOrEqual(visited_flags.length, neighbour_index * 4 + 3, 'subdet array is too small');
|
|
70
79
|
|
|
80
|
+
//part of the cavity, remove
|
|
81
|
+
cavity.push_deleted(neighbour_index);
|
|
82
|
+
visited_flags.set(neighbour_index, true);
|
|
83
|
+
}
|
|
71
84
|
}
|
|
85
|
+
|
|
72
86
|
}
|
|
87
|
+
|
|
88
|
+
// debug_validate_cavity_boundary(mesh, cavity);
|
|
73
89
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute sub-determinants for a given tetrahedron
|
|
3
|
+
* NOTE: this implementation doesn't use GHOST vertices, all tets/vertices are assumed to be real and valid
|
|
4
|
+
* @see https://git.immc.ucl.ac.be/hextreme/hxt_seqdel/-/blob/master/src/hxt_tetrahedra.c#L754
|
|
5
|
+
* @param {number[]|Float32Array} result
|
|
6
|
+
* @param {number} result_offset
|
|
7
|
+
* @param {TetrahedralMesh} mesh
|
|
8
|
+
* @param {number[]|Float32Array} points
|
|
9
|
+
* @param {number} tetrahedron_index
|
|
10
|
+
*/
|
|
11
|
+
export function tetrahedral_mesh_compute_sub_determinant(result, result_offset, mesh, points, tetrahedron_index) {
|
|
12
|
+
/*
|
|
13
|
+
NOTE: the code is re-ordered slightly and inlined for speed, re-ordering is mostly to reduce register pressure
|
|
14
|
+
*/
|
|
15
|
+
const a_index = mesh.getVertexIndex(tetrahedron_index, 0);
|
|
16
|
+
const b_index = mesh.getVertexIndex(tetrahedron_index, 1);
|
|
17
|
+
const c_index = mesh.getVertexIndex(tetrahedron_index, 2);
|
|
18
|
+
const d_index = mesh.getVertexIndex(tetrahedron_index, 3);
|
|
19
|
+
|
|
20
|
+
// read out coordinates
|
|
21
|
+
const a3 = a_index * 3;
|
|
22
|
+
const b3 = b_index * 3;
|
|
23
|
+
const c3 = c_index * 3;
|
|
24
|
+
const d3 = d_index * 3;
|
|
25
|
+
|
|
26
|
+
const a_x = points[a3];
|
|
27
|
+
const a_y = points[a3 + 1];
|
|
28
|
+
const a_z = points[a3 + 2];
|
|
29
|
+
|
|
30
|
+
const b_x = points[b3];
|
|
31
|
+
const b_y = points[b3 + 1];
|
|
32
|
+
const b_z = points[b3 + 2];
|
|
33
|
+
|
|
34
|
+
// construct AB, AC, AD
|
|
35
|
+
const ab_x = b_x - a_x;
|
|
36
|
+
const ab_y = b_y - a_y;
|
|
37
|
+
const ab_z = b_z - a_z;
|
|
38
|
+
|
|
39
|
+
const c_x = points[c3];
|
|
40
|
+
const c_y = points[c3 + 1];
|
|
41
|
+
const c_z = points[c3 + 2];
|
|
42
|
+
|
|
43
|
+
const ac_x = c_x - a_x;
|
|
44
|
+
const ac_y = c_y - a_y;
|
|
45
|
+
const ac_z = c_z - a_z;
|
|
46
|
+
|
|
47
|
+
const d_x = points[d3];
|
|
48
|
+
const d_y = points[d3 + 1];
|
|
49
|
+
const d_z = points[d3 + 2];
|
|
50
|
+
|
|
51
|
+
const ad_x = d_x - a_x;
|
|
52
|
+
const ad_y = d_y - a_y;
|
|
53
|
+
const ad_z = d_z - a_z;
|
|
54
|
+
|
|
55
|
+
const ab_w = ab_x * ab_x + ab_y * ab_y + ab_z * ab_z;
|
|
56
|
+
const ac_w = ac_x * ac_x + ac_y * ac_y + ac_z * ac_z;
|
|
57
|
+
const ad_w = ad_x * ad_x + ad_y * ad_y + ad_z * ad_z;
|
|
58
|
+
|
|
59
|
+
const cd12 = ac_z * ad_y - ac_y * ad_z;
|
|
60
|
+
const db12 = ad_z * ab_y - ad_y * ab_z;
|
|
61
|
+
const bc12 = ab_z * ac_y - ab_y * ac_z;
|
|
62
|
+
|
|
63
|
+
const cd30 = ac_x * ad_w - ac_w * ad_x;
|
|
64
|
+
const db30 = ad_x * ab_w - ad_w * ab_x;
|
|
65
|
+
const bc30 = ab_x * ac_w - ab_w * ac_x;
|
|
66
|
+
|
|
67
|
+
// each subdet is a simple triple product
|
|
68
|
+
const subdet_0 = ab_w * cd12 + ac_w * db12 + ad_w * bc12;
|
|
69
|
+
const subdet_1 = ab_z * cd30 + ac_z * db30 + ad_z * bc30;
|
|
70
|
+
const subdet_2 = ab_y * cd30 + ac_y * db30 + ad_y * bc30;
|
|
71
|
+
const subdet_3 = ab_x * cd12 + ac_x * db12 + ad_x * bc12;
|
|
72
|
+
|
|
73
|
+
result[result_offset] = subdet_0;
|
|
74
|
+
result[result_offset + 1] = subdet_1;
|
|
75
|
+
result[result_offset + 2] = subdet_2;
|
|
76
|
+
result[result_offset + 3] = subdet_3;
|
|
77
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { TetrahedralMesh } from "../TetrahedralMesh.js";
|
|
2
|
+
import { tetrahedral_mesh_compute_sub_determinant } from "./tetrahedral_mesh_compute_sub_determinant.js";
|
|
3
|
+
|
|
4
|
+
test("sanity test", () => {
|
|
5
|
+
const mesh = new TetrahedralMesh(1);
|
|
6
|
+
|
|
7
|
+
const tet = mesh.allocate();
|
|
8
|
+
|
|
9
|
+
mesh.setVertexIndex(tet, 0, 0);
|
|
10
|
+
mesh.setVertexIndex(tet, 1, 1);
|
|
11
|
+
mesh.setVertexIndex(tet, 2, 2);
|
|
12
|
+
mesh.setVertexIndex(tet, 3, 3);
|
|
13
|
+
|
|
14
|
+
const result = [];
|
|
15
|
+
|
|
16
|
+
const points = [
|
|
17
|
+
1, 2, 3,
|
|
18
|
+
4, 5, 6,
|
|
19
|
+
7, 8, 9,
|
|
20
|
+
10, 11, 12
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
tetrahedral_mesh_compute_sub_determinant(result, 0, mesh, points, tet);
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < 4; i++) {
|
|
26
|
+
expect(typeof result[i]).toBe("number");
|
|
27
|
+
expect(result[i]).not.toBeNaN();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
});
|
package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_walk_towards_containing_tetrahedron.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { assert } from "../../../../assert.js";
|
|
2
|
+
import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
|
|
3
|
+
import { orient3d_robust } from "../../plane/orient3d_robust.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Walk from a given tetrahedron in the mesh towards tetrahedron that contains input point
|
|
7
|
+
* This is an essential method for incrementally finding the next tetrahedron for cavity computation
|
|
8
|
+
*
|
|
9
|
+
* @see https://git.immc.ucl.ac.be/hextreme/hxt_seqdel/-/blob/master/src/hxt_tetrahedra.c#L330
|
|
10
|
+
* @see p3 "One machine, one minute, three billion tetrahedra" by Célestin Marot, Jeanne Pellerin, Jean-François Remacle
|
|
11
|
+
* @param {TetrahedralMesh} mesh
|
|
12
|
+
* @param {number[]|Float32Array} points
|
|
13
|
+
* @param {number} hint_tetrahedron we will start here and walk towards our target
|
|
14
|
+
* @param {number} search_vertex_index
|
|
15
|
+
* @returns {number}
|
|
16
|
+
*/
|
|
17
|
+
export function tetrahedral_mesh_walk_towards_containing_tetrahedron(mesh, points, hint_tetrahedron, search_vertex_index) {
|
|
18
|
+
|
|
19
|
+
let entering_face = 4;
|
|
20
|
+
|
|
21
|
+
let cur_tet = hint_tetrahedron;
|
|
22
|
+
|
|
23
|
+
while (true) {
|
|
24
|
+
|
|
25
|
+
let i;
|
|
26
|
+
for (i = 0; i < 4; i++) {
|
|
27
|
+
|
|
28
|
+
// we walk whenever the volume is positive
|
|
29
|
+
const a_i = (i + 1) & 3;
|
|
30
|
+
const b_i = (i & 2) ^ 3;
|
|
31
|
+
const c_i = (i + 3) & 2;
|
|
32
|
+
|
|
33
|
+
const a_index = mesh.getVertexIndex(cur_tet, a_i);
|
|
34
|
+
const b_index = mesh.getVertexIndex(cur_tet, b_i);
|
|
35
|
+
const c_index = mesh.getVertexIndex(cur_tet, c_i);
|
|
36
|
+
|
|
37
|
+
if (i !== entering_face && orient3d_robust(points, a_index, b_index, c_index, search_vertex_index) < 0.0) {
|
|
38
|
+
|
|
39
|
+
// point is outside the tet on the neighbour's side, move in that direction
|
|
40
|
+
const neighbour = mesh.getNeighbour(cur_tet, i);
|
|
41
|
+
|
|
42
|
+
assert.notEqual(neighbour, INVALID_NEIGHBOUR, 'walked outside of the mesh');
|
|
43
|
+
|
|
44
|
+
cur_tet = neighbour >>> 2;
|
|
45
|
+
entering_face = neighbour & 3;
|
|
46
|
+
|
|
47
|
+
break;
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (i === 4) {
|
|
53
|
+
// point is inside the tet
|
|
54
|
+
return cur_tet;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
|
|
2
|
+
import { array_range_equal_strict } from "../../../../collection/array/array_range_equal_strict.js";
|
|
3
|
+
|
|
4
|
+
export function debug_validate_cavity_boundary(mesh, cavity) {
|
|
5
|
+
let valid = true;
|
|
6
|
+
const problems = [];
|
|
7
|
+
|
|
8
|
+
if (!validate_cavity_boundary(cavity, problem => problems.push(problem))) {
|
|
9
|
+
valid = false;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (!valid) {
|
|
13
|
+
debugger;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return valid;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
* @param {Cavity} cavity
|
|
22
|
+
* @param {function(problem:string):*} consumer
|
|
23
|
+
* @returns {boolean}
|
|
24
|
+
*/
|
|
25
|
+
export function validate_cavity_boundary(cavity, consumer) {
|
|
26
|
+
let valid = true;
|
|
27
|
+
|
|
28
|
+
const boundary = cavity.__boundary;
|
|
29
|
+
const boundary_size = cavity.__boundary_size;
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < boundary_size; i++) {
|
|
32
|
+
const encoded_tet_0 = boundary[i * 5 + 4];
|
|
33
|
+
|
|
34
|
+
if (encoded_tet_0 === INVALID_NEIGHBOUR) {
|
|
35
|
+
// this is "no neighbour" marker, there are allowed to be many of these
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (let j = i + 1; j < boundary_size; j++) {
|
|
40
|
+
|
|
41
|
+
const encoded_tet_1 = boundary[j * 5 + 4];
|
|
42
|
+
|
|
43
|
+
if (encoded_tet_0 === encoded_tet_1) {
|
|
44
|
+
valid = false;
|
|
45
|
+
|
|
46
|
+
let message = `Duplicate neighbourhood ${encoded_tet_0} (tet=${(encoded_tet_0 >> 2)}, vert=${(encoded_tet_0 & 3)}), boundary elements [${i}, ${j}]`;
|
|
47
|
+
|
|
48
|
+
if (!array_range_equal_strict(boundary, i * 5, boundary, j * 5, 4)) {
|
|
49
|
+
// boundary definitions are divergent
|
|
50
|
+
message += `, boundary definitions are divergent - ${i}:[${boundary.slice(i * 5, i * 5 + 4).join(', ')}], ${j}:[${boundary.slice(j * 5, j * 5 + 4).join(', ')}]`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
consumer(message);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return valid;
|
|
60
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Approximate 3D insphere test.
|
|
2
|
+
* Approximate 3D insphere test. Non-robust.
|
|
3
3
|
*
|
|
4
4
|
* Return a positive value if the point pe lies inside the
|
|
5
5
|
* sphere passing through pa, pb, pc, and pd; a negative value
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* @param {number} e reference point index that we are testing against the tetrahedron
|
|
20
20
|
* @returns {number}
|
|
21
21
|
*/
|
|
22
|
-
export function
|
|
22
|
+
export function in_sphere_fast(
|
|
23
23
|
points,
|
|
24
24
|
a, b, c, d, e
|
|
25
25
|
) {
|
|
@@ -34,20 +34,20 @@ export function point_in_tetrahedron_circumsphere(
|
|
|
34
34
|
const ey = points[e3 + 1];
|
|
35
35
|
const ez = points[e3 + 2];
|
|
36
36
|
|
|
37
|
-
|
|
38
37
|
const aex = points[a3] - ex;
|
|
39
|
-
const bex = points[b3] - ex;
|
|
40
|
-
const cex = points[c3] - ex;
|
|
41
|
-
const dex = points[d3] - ex;
|
|
42
|
-
|
|
43
38
|
const aey = points[a3 + 1] - ey;
|
|
44
|
-
const bey = points[b3 + 1] - ey;
|
|
45
|
-
const cey = points[c3 + 1] - ey;
|
|
46
|
-
const dey = points[d3 + 1] - ey;
|
|
47
|
-
|
|
48
39
|
const aez = points[a3 + 2] - ez;
|
|
40
|
+
|
|
41
|
+
const bex = points[b3] - ex;
|
|
42
|
+
const bey = points[b3 + 1] - ey;
|
|
49
43
|
const bez = points[b3 + 2] - ez;
|
|
44
|
+
|
|
45
|
+
const cex = points[c3] - ex;
|
|
46
|
+
const cey = points[c3 + 1] - ey;
|
|
50
47
|
const cez = points[c3 + 2] - ez;
|
|
48
|
+
|
|
49
|
+
const dex = points[d3] - ex;
|
|
50
|
+
const dey = points[d3 + 1] - ey;
|
|
51
51
|
const dez = points[d3 + 2] - ez;
|
|
52
52
|
|
|
53
53
|
const aexbey = aex * bey;
|
|
@@ -86,7 +86,5 @@ export function point_in_tetrahedron_circumsphere(
|
|
|
86
86
|
|
|
87
87
|
const det = (dlift * abc - clift * dab) + (blift * cda - alift * bcd);
|
|
88
88
|
|
|
89
|
-
// TODO HXT uses 2 more methods to perform exact calculation (avoiding rounding errors), we could include that if necessary
|
|
90
|
-
|
|
91
89
|
return det;
|
|
92
90
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { insphere } from "robust-predicates";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Approximate 3D insphere test. Robust
|
|
5
|
+
*
|
|
6
|
+
*
|
|
7
|
+
* @param {number[]} points
|
|
8
|
+
* @param {number} a tetrahedral point index
|
|
9
|
+
* @param {number} b tetrahedral point index
|
|
10
|
+
* @param {number} c tetrahedral point index
|
|
11
|
+
* @param {number} d tetrahedral point index
|
|
12
|
+
* @param {number} e reference point index that we are testing against the tetrahedron
|
|
13
|
+
* @returns {number}
|
|
14
|
+
*/
|
|
15
|
+
export function in_sphere_robust(
|
|
16
|
+
points,
|
|
17
|
+
a, b, c, d, e
|
|
18
|
+
) {
|
|
19
|
+
|
|
20
|
+
const a3 = a * 3;
|
|
21
|
+
const b3 = b * 3;
|
|
22
|
+
const c3 = c * 3;
|
|
23
|
+
const d3 = d * 3;
|
|
24
|
+
const e3 = e * 3;
|
|
25
|
+
|
|
26
|
+
const ax = points[a3];
|
|
27
|
+
const ay = points[a3 + 1];
|
|
28
|
+
const az = points[a3 + 2];
|
|
29
|
+
|
|
30
|
+
const bx = points[b3];
|
|
31
|
+
const by = points[b3 + 1];
|
|
32
|
+
const bz = points[b3 + 2];
|
|
33
|
+
|
|
34
|
+
const cx = points[c3];
|
|
35
|
+
const cy = points[c3 + 1];
|
|
36
|
+
const cz = points[c3 + 2];
|
|
37
|
+
|
|
38
|
+
const dx = points[d3];
|
|
39
|
+
const dy = points[d3 + 1];
|
|
40
|
+
const dz = points[d3 + 2];
|
|
41
|
+
|
|
42
|
+
const ex = points[e3];
|
|
43
|
+
const ey = points[e3 + 1];
|
|
44
|
+
const ez = points[e3 + 2];
|
|
45
|
+
|
|
46
|
+
return -insphere(
|
|
47
|
+
ax, ay, az,
|
|
48
|
+
bx, by, bz,
|
|
49
|
+
cx, cy, cz,
|
|
50
|
+
dx, dy, dz,
|
|
51
|
+
ex, ey, ez
|
|
52
|
+
);
|
|
53
|
+
}
|