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