@woosh/meep-engine 2.43.3 → 2.43.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/binary/BinaryBuffer.js +13 -1
- package/core/binary/BitSet.js +2 -2
- package/core/collection/array/array_range_equal_strict.js +22 -0
- package/core/collection/map/AsyncMapWrapper.js +13 -1
- package/core/collection/map/CachedAsyncMap.js +9 -2
- package/core/collection/map/CachedAsyncMap.spec.js +47 -0
- package/core/color/sRGB_to_linear.js +9 -4
- package/core/geom/3d/plane/orient3d_fast.js +3 -0
- package/core/geom/3d/plane/orient3d_robust.js +41 -0
- package/core/geom/3d/sphere/harmonics/README.md +15 -0
- package/core/geom/3d/sphere/harmonics/sh3_add.js +21 -0
- package/core/geom/3d/sphere/harmonics/sh3_dering_optimize_positive.js +618 -0
- package/core/geom/3d/sphere/harmonics/sh3_sample_by_direction.js +49 -0
- package/core/geom/3d/sphere/harmonics/sh3_sample_irradiance_by_direction.js +53 -0
- package/core/geom/3d/tetrahedra/TetrahedralMesh.js +251 -68
- package/core/geom/3d/tetrahedra/TetrahedralMesh.spec.js +80 -3
- package/core/geom/3d/tetrahedra/build_tetrahedral_mesh_buffer_geometry.js +75 -0
- package/core/geom/3d/tetrahedra/delaunay/Cavity.js +5 -1
- package/core/geom/3d/tetrahedra/delaunay/compute_delaunay_tetrahedral_mesh.js +30 -31
- package/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +54 -18
- package/core/geom/3d/tetrahedra/delaunay/push_boundary_with_validation.js +27 -0
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity.js +89 -0
- package/core/geom/3d/tetrahedra/delaunay/{tetrahedral_mesh_walk_toward_cavity.js → tetrahedral_mesh_walk_towards_containing_tetrahedron.js} +15 -12
- package/core/geom/3d/tetrahedra/delaunay/validate_cavity_boundary.js +60 -0
- package/core/geom/3d/tetrahedra/{point_in_tetrahedron_circumsphere.js → in_sphere_fast.js} +2 -4
- package/core/geom/3d/tetrahedra/in_sphere_robust.js +53 -0
- package/core/geom/3d/tetrahedra/prototypeTetrahedraBuilder.js +44 -35
- package/core/geom/3d/tetrahedra/validate_tetrahedral_mesh.js +85 -38
- package/core/geom/3d/util/make_justified_point_grid.js +31 -0
- package/core/process/delay.js +5 -0
- package/editor/Editor.js +3 -0
- package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +195 -11
- package/editor/ecs/component/editors/ecs/ParameterTrackSetEditor.js +16 -0
- package/editor/ecs/component/editors/ecs/ParticleEmitterLayerEditor.js +4 -0
- package/engine/EngineHarness.js +11 -5
- package/engine/ecs/terrain/ecs/TerrainSystem.js +7 -1
- package/engine/ecs/transform/copy_three_transform.js +15 -0
- package/engine/graphics/ecs/light/Light.js +6 -1
- package/engine/graphics/ecs/light/LightSystem.d.ts +1 -1
- package/engine/graphics/ecs/mesh-v2/three_object_to_entity_composition.js +2 -17
- package/engine/graphics/geometry/instancing/InstancedMeshGroup.js +2 -2
- package/engine/graphics/micron/plugin/shaded_geometry/MicronShadedGeometryRenderAdapter.js +9 -1
- package/engine/graphics/sh3/LightProbeVolume.js +595 -0
- package/engine/graphics/sh3/SH3VisualisationMaterial.js +79 -0
- package/engine/graphics/sh3/prototypeSH3Probe.js +427 -0
- package/engine/graphics/sh3/visualise_probe.js +40 -0
- package/engine/graphics/texture/atlas/TextureAtlas.js +15 -3
- package/engine/intelligence/blackboard/AbstractBlackboard.d.ts +1 -1
- package/package.json +2 -1
- package/samples/terrain/from_image_2.js +127 -82
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity2.js +0 -224
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_insert_point.js +0 -98
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { BufferGeometry, Float32BufferAttribute, Uint32BufferAttribute } from "three";
|
|
2
|
+
import { BitSet } from "../../../binary/BitSet.js";
|
|
3
|
+
import { assert } from "../../../assert.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param {TetrahedralMesh} tetrahedra
|
|
8
|
+
* @param {Float32Array|number[]} points
|
|
9
|
+
* @return {BufferGeometry}
|
|
10
|
+
*/
|
|
11
|
+
export function build_tetrahedral_mesh_buffer_geometry(tetrahedra, points) {
|
|
12
|
+
const lines = [];
|
|
13
|
+
|
|
14
|
+
const occupancy = new BitSet();
|
|
15
|
+
|
|
16
|
+
const point_count = points.length / 3;
|
|
17
|
+
|
|
18
|
+
assert.isNonNegativeInteger(point_count, 'point_count');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param {number} a
|
|
23
|
+
* @param {number} b
|
|
24
|
+
*/
|
|
25
|
+
function ensure_line(a, b) {
|
|
26
|
+
assert.isNonNegativeInteger(a, 'a');
|
|
27
|
+
assert.isNonNegativeInteger(b, 'b');
|
|
28
|
+
|
|
29
|
+
assert.lessThan(a, point_count, 'index A is out of bounds');
|
|
30
|
+
assert.lessThan(b, point_count, 'index B is out of bounds');
|
|
31
|
+
|
|
32
|
+
let v0, v1;
|
|
33
|
+
|
|
34
|
+
if (a < b) {
|
|
35
|
+
v0 = a;
|
|
36
|
+
v1 = b;
|
|
37
|
+
} else {
|
|
38
|
+
v0 = b;
|
|
39
|
+
v1 = a;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const index = v0 * point_count + v1;
|
|
43
|
+
|
|
44
|
+
if (occupancy.getAndSet(index)) {
|
|
45
|
+
// already recorded
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// no line record yet, create it
|
|
50
|
+
lines.push(v0, v1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
tetrahedra.forEach((tet, mesh) => {
|
|
54
|
+
assert.ok(mesh.exists(tet), `Visited un-allocated tet ${tet}`);
|
|
55
|
+
|
|
56
|
+
const a = mesh.getVertexIndex(tet, 0);
|
|
57
|
+
const b = mesh.getVertexIndex(tet, 1);
|
|
58
|
+
const c = mesh.getVertexIndex(tet, 2);
|
|
59
|
+
const d = mesh.getVertexIndex(tet, 3);
|
|
60
|
+
|
|
61
|
+
ensure_line(a, b);
|
|
62
|
+
ensure_line(a, c);
|
|
63
|
+
ensure_line(a, d);
|
|
64
|
+
|
|
65
|
+
ensure_line(b, c);
|
|
66
|
+
ensure_line(c, d);
|
|
67
|
+
ensure_line(b, d);
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const geometry = new BufferGeometry();
|
|
71
|
+
|
|
72
|
+
geometry.setIndex(new Uint32BufferAttribute(lines, 1, false));
|
|
73
|
+
geometry.setAttribute('position', new Float32BufferAttribute(points, 3, false));
|
|
74
|
+
return geometry;
|
|
75
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { assert } from "../../../../assert.js";
|
|
2
|
+
|
|
1
3
|
export class Cavity {
|
|
2
4
|
constructor() {
|
|
3
5
|
/**
|
|
@@ -13,7 +15,7 @@ export class Cavity {
|
|
|
13
15
|
this.__deleted_size = 0;
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
|
-
* 5-tuples
|
|
18
|
+
* flat array of 5-tuples
|
|
17
19
|
* @type {number[]}
|
|
18
20
|
* @private
|
|
19
21
|
*/
|
|
@@ -59,6 +61,8 @@ export class Cavity {
|
|
|
59
61
|
* @param {number} i
|
|
60
62
|
*/
|
|
61
63
|
push_deleted(i) {
|
|
64
|
+
assert.equal(this.includes(i), false, `ball element ${i} already exists`);
|
|
65
|
+
|
|
62
66
|
this.__deleted[this.__deleted_size++] = i;
|
|
63
67
|
}
|
|
64
68
|
|
|
@@ -2,22 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
import { compute_bounding_simplex_3d } from "../compute_bounding_simplex_3d.js";
|
|
4
4
|
import { TetrahedralMesh } from "../TetrahedralMesh.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
tetrahedral_mesh_walk_towards_containing_tetrahedron
|
|
7
|
+
} from "./tetrahedral_mesh_walk_towards_containing_tetrahedron.js";
|
|
7
8
|
import { Cavity } from "./Cavity.js";
|
|
8
|
-
import {
|
|
9
|
+
import { tetrahedral_mesh_compute_cavity } from "./tetrahedral_mesh_compute_cavity.js";
|
|
9
10
|
import { fill_in_a_cavity } from "./fill_in_a_cavity.js";
|
|
10
|
-
import {
|
|
11
|
+
import { BitSet } from "../../../../binary/BitSet.js";
|
|
12
|
+
import { assert } from "../../../../assert.js";
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* @see https://en.wikipedia.org/wiki/Bowyer%E2%80%93Watson_algorithm
|
|
15
17
|
* @see [1] "One machine, one minute, three billion tetrahedra" by Célestin Marot et al
|
|
18
|
+
* @param {TetrahedralMesh} mesh
|
|
16
19
|
* @param {number[]} input serialized set of 3d points (x,y,z)
|
|
17
20
|
* @param {number} n number of points in the input
|
|
18
|
-
* @returns {
|
|
21
|
+
* @returns {boolean}
|
|
19
22
|
*/
|
|
20
|
-
export function compute_delaunay_tetrahedral_mesh(input, n) {
|
|
23
|
+
export function compute_delaunay_tetrahedral_mesh(mesh, input, n) {
|
|
24
|
+
assert.notNull(mesh, 'mesh');
|
|
25
|
+
assert.defined(mesh, 'mesh');
|
|
26
|
+
|
|
27
|
+
assert.isNonNegativeInteger(n, 'n');
|
|
28
|
+
|
|
29
|
+
if (n < 4) {
|
|
30
|
+
// no mesh can be created, too few points
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
21
33
|
|
|
22
34
|
/**
|
|
23
35
|
* According to [1] number of tetrahedra is around 6 times greater than number of input vertices, we use that fact to pre-allocate memory
|
|
@@ -27,21 +39,17 @@ export function compute_delaunay_tetrahedral_mesh(input, n) {
|
|
|
27
39
|
*
|
|
28
40
|
* The +7 is for the initial "super" tetrahedron and 6 tetras for intermediate triangulation
|
|
29
41
|
*/
|
|
30
|
-
const tetrahedron_count_upper_bound = Math.ceil((n * n - 3 * n - 2) / 2) + 7;
|
|
31
42
|
|
|
32
|
-
|
|
43
|
+
// start with a fairly low capacity for best-case scenario, rely on internal resizing after that
|
|
33
44
|
|
|
34
|
-
|
|
35
|
-
// no mesh can be created, too few points
|
|
36
|
-
return mesh;
|
|
37
|
-
}
|
|
45
|
+
mesh.growCapacity(n + 1);
|
|
38
46
|
|
|
39
47
|
const CAVITY = new Cavity();
|
|
40
48
|
|
|
41
49
|
// TODO apply spatial sort to point, to get better cache locality
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
const build_flags = BitSet.fixedSize(n);
|
|
52
|
+
|
|
45
53
|
const points = new Float32Array((n + 4) * 3);
|
|
46
54
|
|
|
47
55
|
points.set(input);
|
|
@@ -49,7 +57,7 @@ export function compute_delaunay_tetrahedral_mesh(input, n) {
|
|
|
49
57
|
/*
|
|
50
58
|
create bounding volume tetrahedron over input set
|
|
51
59
|
*/
|
|
52
|
-
compute_bounding_simplex_3d(points, n * 3, points, n,
|
|
60
|
+
compute_bounding_simplex_3d(points, n * 3, points, n, 10);
|
|
53
61
|
|
|
54
62
|
const super_tet = mesh.allocate();
|
|
55
63
|
|
|
@@ -58,36 +66,27 @@ export function compute_delaunay_tetrahedral_mesh(input, n) {
|
|
|
58
66
|
mesh.setVertexIndex(super_tet, 2, n + 2);
|
|
59
67
|
mesh.setVertexIndex(super_tet, 3, n + 3);
|
|
60
68
|
|
|
61
|
-
//
|
|
62
|
-
// mesh.setNeighbour(super_tet, 0, super_tet << 2 | 0);
|
|
63
|
-
// mesh.setNeighbour(super_tet, 1, super_tet << 2 | 1);
|
|
64
|
-
// mesh.setNeighbour(super_tet, 2, super_tet << 2 | 2);
|
|
65
|
-
// mesh.setNeighbour(super_tet, 3, super_tet << 2 | 3);
|
|
66
|
-
|
|
67
|
-
tetrahedral_mesh_compute_sub_determinant(sub_determinants, 0, mesh, points, 0);
|
|
68
|
-
|
|
69
|
-
debug_validate_mesh(mesh, points);
|
|
69
|
+
// debug_validate_mesh(mesh, points);
|
|
70
70
|
|
|
71
71
|
// add all points to the mesh
|
|
72
72
|
let current_tet = 0;
|
|
73
73
|
|
|
74
74
|
for (let i = 0; i < n; i++) {
|
|
75
75
|
|
|
76
|
-
current_tet =
|
|
76
|
+
current_tet = tetrahedral_mesh_walk_towards_containing_tetrahedron(mesh, points, current_tet, i);
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
// tetrahedral_mesh_compute_cavity(mesh, CAVITY, points, current_tet, i);
|
|
78
|
+
tetrahedral_mesh_compute_cavity(mesh, points, build_flags, CAVITY, current_tet, i);
|
|
80
79
|
|
|
81
|
-
current_tet = fill_in_a_cavity(mesh, points,
|
|
80
|
+
current_tet = fill_in_a_cavity(mesh, points, build_flags, CAVITY, current_tet);
|
|
82
81
|
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
// remove tetras formed by with the super bounding volume
|
|
86
|
-
mesh.removeTetrasConnectedToPoints(
|
|
85
|
+
mesh.removeTetrasConnectedToPoints(n, n + 3);
|
|
87
86
|
|
|
88
87
|
|
|
89
|
-
debug_validate_mesh(mesh, points);
|
|
88
|
+
// debug_validate_mesh(mesh, points);
|
|
90
89
|
|
|
91
|
-
return
|
|
90
|
+
return true;
|
|
92
91
|
}
|
|
93
92
|
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { assert } from "../../../../assert.js";
|
|
2
|
-
import { tetrahedral_mesh_compute_sub_determinant } from "./tetrahedral_mesh_compute_sub_determinant.js";
|
|
3
2
|
import { array_copy } from "../../../../collection/array/copyArray.js";
|
|
4
3
|
import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
|
|
5
|
-
import {
|
|
4
|
+
import { validate_neighbour } from "../validate_tetrahedral_mesh.js";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
*
|
|
9
8
|
* @param {TetrahedralMesh} mesh
|
|
10
9
|
* @param {ArrayLike<number>|number[]|Float32Array} points
|
|
11
|
-
* @param {
|
|
10
|
+
* @param {BitSet} visited_flags
|
|
12
11
|
* @param {Cavity} cavity
|
|
13
12
|
* @param {number} cur_tet
|
|
14
13
|
* @returns {number} new current tet
|
|
15
14
|
*/
|
|
16
|
-
export function fill_in_a_cavity(mesh, points,
|
|
15
|
+
export function fill_in_a_cavity(mesh, points, visited_flags, cavity, cur_tet) {
|
|
17
16
|
|
|
18
17
|
let clength = cavity.__deleted_size; // cavity size
|
|
19
18
|
const blength = cavity.__boundary_size; // boundary size
|
|
@@ -23,11 +22,12 @@ export function fill_in_a_cavity(mesh, points, subdets, cavity, cur_tet) {
|
|
|
23
22
|
/*
|
|
24
23
|
Re-use tets that would be deleted, if there are not enough dead tets - allocate new ones as necessary
|
|
25
24
|
*/
|
|
25
|
+
const deleted_tets = cavity.__deleted;
|
|
26
26
|
if (blength > clength) {
|
|
27
27
|
|
|
28
28
|
for (let i = clength; i < blength; i++) {
|
|
29
29
|
// allocate tets for the cavity filling, remember them inside the "deleted" set for now
|
|
30
|
-
|
|
30
|
+
deleted_tets[i] = mesh.allocate();
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
clength = blength;
|
|
@@ -39,7 +39,7 @@ export function fill_in_a_cavity(mesh, points, subdets, cavity, cur_tet) {
|
|
|
39
39
|
for (let i = 0; i < blength; i++) {
|
|
40
40
|
// go over the faces in the cavity boundary and matching tet that will be filling the space
|
|
41
41
|
|
|
42
|
-
const current_tet_index =
|
|
42
|
+
const current_tet_index = deleted_tets[i + start];
|
|
43
43
|
|
|
44
44
|
const i5 = i * 5;
|
|
45
45
|
|
|
@@ -62,21 +62,51 @@ export function fill_in_a_cavity(mesh, points, subdets, cavity, cur_tet) {
|
|
|
62
62
|
|
|
63
63
|
// boundary[i5 + 4] = encoded_current_tet;
|
|
64
64
|
|
|
65
|
-
//
|
|
66
|
-
tetrahedral_mesh_compute_sub_determinant(subdets, current_tet_index * 4, mesh, points, current_tet_index);
|
|
65
|
+
visited_flags.set(current_tet_index, false); // clear flag
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
computeAdjacencies(mesh, cavity, start, blength);
|
|
70
|
-
// computeAdjacenciesSlow(mesh, cavity, start, blength);
|
|
71
69
|
|
|
72
|
-
|
|
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);
|
|
73
77
|
|
|
74
78
|
cavity.__boundary_size = 0;
|
|
75
|
-
cavity.__deleted_size =
|
|
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;
|
|
76
95
|
|
|
96
|
+
for (let i = 0; i < ball_tets_count; i++) {
|
|
97
|
+
ball_index = ball_tets_offset + i;
|
|
98
|
+
tet = ball_tets[ball_index];
|
|
77
99
|
|
|
78
|
-
|
|
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++) {
|
|
79
102
|
|
|
103
|
+
if (!validate_neighbour(mesh, tet, j, consumer)) {
|
|
104
|
+
valid = false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return valid;
|
|
80
110
|
}
|
|
81
111
|
|
|
82
112
|
/**
|
|
@@ -95,7 +125,6 @@ function computeAdjacencies(mesh, cavity, start, blength) {
|
|
|
95
125
|
let tlength = 0;
|
|
96
126
|
|
|
97
127
|
const Tmp = boundary; // we know there is enough space...
|
|
98
|
-
const index = [2, 3, 1, 2];
|
|
99
128
|
|
|
100
129
|
for (let i = 0; i < blength; i++) {
|
|
101
130
|
|
|
@@ -104,9 +133,13 @@ function computeAdjacencies(mesh, cavity, start, blength) {
|
|
|
104
133
|
// pointer to the position of Node[0] in the Tmp array
|
|
105
134
|
|
|
106
135
|
for (let j = 0; j < 3; j++) {
|
|
136
|
+
|
|
107
137
|
// define the edge by the minimum vertex and the other
|
|
108
|
-
const
|
|
109
|
-
const
|
|
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);
|
|
110
143
|
|
|
111
144
|
const current_tet_neighbour_id = j + 1;
|
|
112
145
|
const encoded_current_tet = (cur_tet << 2) | (current_tet_neighbour_id & 3);
|
|
@@ -134,10 +167,10 @@ function computeAdjacencies(mesh, cavity, start, blength) {
|
|
|
134
167
|
tlength++;
|
|
135
168
|
} else {
|
|
136
169
|
// we found the neighbour !
|
|
137
|
-
const
|
|
170
|
+
const encoded_neighbour = Tmp[k * 3 + 2];
|
|
138
171
|
|
|
139
|
-
mesh.setNeighbour(cur_tet, current_tet_neighbour_id,
|
|
140
|
-
mesh.setNeighbour(
|
|
172
|
+
mesh.setNeighbour(cur_tet, current_tet_neighbour_id, encoded_neighbour);
|
|
173
|
+
mesh.setNeighbour(encoded_neighbour >> 2, encoded_neighbour & 3, encoded_current_tet);
|
|
141
174
|
|
|
142
175
|
// reduce search space, there can only be one neighbour pair, after it's found we can remove the entry from lookup table
|
|
143
176
|
tlength--;
|
|
@@ -152,4 +185,7 @@ function computeAdjacencies(mesh, cavity, start, blength) {
|
|
|
152
185
|
|
|
153
186
|
assert.equal(tlength, 0, 'Failed to compute adjacencies');
|
|
154
187
|
|
|
188
|
+
// if (!validate_patched_ball_adjacencies(mesh, cavity.__deleted, start, blength, console.error)) {
|
|
189
|
+
// debugger;
|
|
190
|
+
// }
|
|
155
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
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { assert } from "../../../../assert.js";
|
|
2
|
+
import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
|
|
3
|
+
import { in_sphere_robust } from "../in_sphere_robust.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param {TetrahedralMesh} mesh
|
|
8
|
+
* @param {Cavity} cavity
|
|
9
|
+
* @param {number[]|Float32Array} points
|
|
10
|
+
* @param {BitSet} visited_flags
|
|
11
|
+
* @param {number} containing_tetra tetrahedron that contains point
|
|
12
|
+
* @param {number} point_index point that forms the cavity
|
|
13
|
+
*/
|
|
14
|
+
export function tetrahedral_mesh_compute_cavity(
|
|
15
|
+
mesh,
|
|
16
|
+
points,
|
|
17
|
+
visited_flags,
|
|
18
|
+
cavity,
|
|
19
|
+
containing_tetra,
|
|
20
|
+
point_index
|
|
21
|
+
) {
|
|
22
|
+
assert.isNonNegativeInteger(containing_tetra, 'containing_tetra');
|
|
23
|
+
|
|
24
|
+
// add the tet to cavity
|
|
25
|
+
cavity.push_deleted(containing_tetra);
|
|
26
|
+
visited_flags.set(containing_tetra, true);
|
|
27
|
+
|
|
28
|
+
for (let start = cavity.__deleted_size - 1; start < cavity.__deleted_size; start++) {
|
|
29
|
+
const cur_tet = cavity.__deleted[start];
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < 4; i++) {
|
|
32
|
+
const encoded_neighbour = mesh.getNeighbour(cur_tet, i);
|
|
33
|
+
|
|
34
|
+
const k0 = (1 << i) & 3;
|
|
35
|
+
const k1 = (i + 2) % 3;
|
|
36
|
+
const k2 = (~((i + 1) >> 1) & 2) + 1;
|
|
37
|
+
|
|
38
|
+
if (encoded_neighbour === INVALID_NEIGHBOUR) {
|
|
39
|
+
// no neighbour, include this side into boundary
|
|
40
|
+
|
|
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
|
+
);
|
|
48
|
+
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const neighbour_index = encoded_neighbour >> 2;
|
|
53
|
+
|
|
54
|
+
if (visited_flags.get(neighbour_index)) {
|
|
55
|
+
// already visited
|
|
56
|
+
continue;
|
|
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
|
|
68
|
+
|
|
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
|
+
);
|
|
76
|
+
|
|
77
|
+
} else {
|
|
78
|
+
// assert.greaterThanOrEqual(visited_flags.length, neighbour_index * 4 + 3, 'subdet array is too small');
|
|
79
|
+
|
|
80
|
+
//part of the cavity, remove
|
|
81
|
+
cavity.push_deleted(neighbour_index);
|
|
82
|
+
visited_flags.set(neighbour_index, true);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// debug_validate_cavity_boundary(mesh, cavity);
|
|
89
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { orient3d_fast } from "../../plane/orient3d_fast.js";
|
|
2
1
|
import { assert } from "../../../../assert.js";
|
|
3
2
|
import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
|
|
3
|
+
import { orient3d_robust } from "../../plane/orient3d_robust.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Walk from a given tetrahedron in the mesh towards tetrahedron that contains input point
|
|
@@ -10,15 +10,15 @@ import { INVALID_NEIGHBOUR } from "../TetrahedralMesh.js";
|
|
|
10
10
|
* @see p3 "One machine, one minute, three billion tetrahedra" by Célestin Marot, Jeanne Pellerin, Jean-François Remacle
|
|
11
11
|
* @param {TetrahedralMesh} mesh
|
|
12
12
|
* @param {number[]|Float32Array} points
|
|
13
|
-
* @param {number}
|
|
14
|
-
* @param {number}
|
|
13
|
+
* @param {number} hint_tetrahedron we will start here and walk towards our target
|
|
14
|
+
* @param {number} search_vertex_index
|
|
15
15
|
* @returns {number}
|
|
16
16
|
*/
|
|
17
|
-
export function
|
|
17
|
+
export function tetrahedral_mesh_walk_towards_containing_tetrahedron(mesh, points, hint_tetrahedron, search_vertex_index) {
|
|
18
18
|
|
|
19
19
|
let entering_face = 4;
|
|
20
20
|
|
|
21
|
-
let
|
|
21
|
+
let cur_tet = hint_tetrahedron;
|
|
22
22
|
|
|
23
23
|
while (true) {
|
|
24
24
|
|
|
@@ -30,25 +30,28 @@ export function tetrahedral_mesh_walk_toward_cavity(mesh, points, current_tet, c
|
|
|
30
30
|
const b_i = (i & 2) ^ 3;
|
|
31
31
|
const c_i = (i + 3) & 2;
|
|
32
32
|
|
|
33
|
-
const a_index = mesh.getVertexIndex(
|
|
34
|
-
const b_index = mesh.getVertexIndex(
|
|
35
|
-
const c_index = mesh.getVertexIndex(
|
|
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) {
|
|
36
38
|
|
|
37
|
-
if (i !== entering_face && orient3d_fast(points, a_index, b_index, c_index, current_vertex) < 0.0) {
|
|
38
39
|
// point is outside the tet on the neighbour's side, move in that direction
|
|
39
|
-
const neighbour = mesh.getNeighbour(
|
|
40
|
+
const neighbour = mesh.getNeighbour(cur_tet, i);
|
|
40
41
|
|
|
41
42
|
assert.notEqual(neighbour, INVALID_NEIGHBOUR, 'walked outside of the mesh');
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
cur_tet = neighbour >>> 2;
|
|
44
45
|
entering_face = neighbour & 3;
|
|
46
|
+
|
|
45
47
|
break;
|
|
48
|
+
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
if (i === 4) {
|
|
50
53
|
// point is inside the tet
|
|
51
|
-
return
|
|
54
|
+
return cur_tet;
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
|
|
@@ -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
|
) {
|
|
@@ -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
|
+
}
|