@woosh/meep-engine 2.163.6 → 2.163.8
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/package.json +1 -1
- package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.d.ts +21 -0
- package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.d.ts.map +1 -0
- package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.js +42 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.d.ts +2 -2
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js +120 -179
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.d.ts +9 -10
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.js +20 -14
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.d.ts +17 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.js +45 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.d.ts +40 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.js +84 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js +53 -78
- package/src/core/geom/vec3/v3_matrix3_rotate.d.ts +16 -0
- package/src/core/geom/vec3/v3_matrix3_rotate.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_matrix3_rotate.js +49 -0
- package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.d.ts +2 -2
- package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.js +46 -46
- package/src/engine/graphics/sh3/path_tracer/sampling/getBiasedNormalSample.d.ts.map +1 -1
- package/src/engine/graphics/sh3/path_tracer/sampling/getBiasedNormalSample.js +6 -28
- package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +185 -0
- package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts +11 -0
- package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts.map +1 -1
- package/src/engine/navigation/mesh/bt_mesh_face_find_path.js +623 -100
- package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.d.ts +11 -0
- package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.d.ts.map +1 -0
- package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.js +472 -0
- package/src/engine/navigation/mesh/build/navmesh_build_topology.d.ts.map +1 -1
- package/src/engine/navigation/mesh/build/navmesh_build_topology.js +36 -39
- package/src/engine/navigation/mesh/navmesh_polyanya_find_path.d.ts +17 -0
- package/src/engine/navigation/mesh/navmesh_polyanya_find_path.d.ts.map +1 -0
- package/src/engine/navigation/mesh/navmesh_polyanya_find_path.js +613 -0
- package/src/engine/navigation/mesh/build/bt_mesh_carve_height_clearance.d.ts +0 -28
- package/src/engine/navigation/mesh/build/bt_mesh_carve_height_clearance.d.ts.map +0 -1
- package/src/engine/navigation/mesh/build/bt_mesh_carve_height_clearance.js +0 -358
- package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.d.ts +0 -23
- package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.d.ts.map +0 -1
- package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.js +0 -319
|
@@ -94,23 +94,29 @@ function fill_loop(mesh, cycle) {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
|
-
* Fill boundary holes
|
|
97
|
+
* Fill inner boundary holes whose mean width falls below `min_width`.
|
|
98
98
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* largest-area loop (the outer boundary) is never filled.
|
|
99
|
+
* A mesh can contain hair-thin sliver holes - a strip of degenerate faces pinched off into its own
|
|
100
|
+
* tiny boundary loop - that are numerical noise rather than meaningful openings. Such slivers inflate
|
|
101
|
+
* the boundary length, the face count, and the loop count. This measures every boundary loop's mean
|
|
102
|
+
* width (2*area/perimeter, the inradius of the area-equivalent disc) and fills each inner loop narrower
|
|
103
|
+
* than `min_width`. The single largest-area loop is treated as the outer boundary and is never filled.
|
|
105
104
|
*
|
|
106
|
-
* @param {BinaryTopology} mesh
|
|
107
|
-
* @param {number}
|
|
105
|
+
* @param {BinaryTopology} mesh mesh whose boundary loops are inspected and (where too thin) filled in place
|
|
106
|
+
* @param {number} min_width minimum mean width an inner loop must have to be kept; thinner loops are filled
|
|
108
107
|
* @returns {number} number of holes filled
|
|
109
108
|
*/
|
|
110
|
-
export function bt_mesh_fill_small_holes(mesh,
|
|
111
|
-
if (
|
|
109
|
+
export function bt_mesh_fill_small_holes(mesh, min_width) {
|
|
110
|
+
if (min_width <= 0) return 0;
|
|
112
111
|
|
|
113
|
-
|
|
112
|
+
let cycles;
|
|
113
|
+
try {
|
|
114
|
+
cycles = bt_mesh_walk_boundary_loops(mesh);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
// a malformed (pinched) boundary can arise at float32-precision limits (e.g. geometry far from
|
|
117
|
+
// the origin); skip hole-filling rather than aborting the whole build
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
114
120
|
if (cycles.length <= 1) return 0;
|
|
115
121
|
|
|
116
122
|
// measure every loop; the largest-area one is the outer boundary and is never filled
|
|
@@ -133,8 +139,8 @@ export function bt_mesh_fill_small_holes(mesh, agent_radius) {
|
|
|
133
139
|
if (i === outer) continue;
|
|
134
140
|
const { perimeter, area } = measures[i];
|
|
135
141
|
if (perimeter <= 0) continue;
|
|
136
|
-
// mean width of the hole = 2*area/perimeter; fill when
|
|
137
|
-
if (2 * area / perimeter <
|
|
142
|
+
// mean width of the hole = 2*area/perimeter; fill when it is below the requested threshold
|
|
143
|
+
if (2 * area / perimeter < min_width) {
|
|
138
144
|
fill_loop(mesh, cycles[i]);
|
|
139
145
|
filled++;
|
|
140
146
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flood-fill the connected component(s) of faces reachable from `seed_faces`, crossing shared edges.
|
|
3
|
+
*
|
|
4
|
+
* Faces already marked in `visited` are skipped; every face this call reaches is marked in `visited`
|
|
5
|
+
* and appended to `out`. Sharing one `visited` set across calls lets a caller partition a whole mesh
|
|
6
|
+
* into disjoint islands (see {@link bt_mesh_compute_face_islands}), while a fresh `visited` set
|
|
7
|
+
* re-derives a single island from its seeds (e.g. after the island's faces were subdivided - the new
|
|
8
|
+
* split halves stay edge-connected to the seed faces).
|
|
9
|
+
*
|
|
10
|
+
* @param {BinaryTopology} mesh
|
|
11
|
+
* @param {ArrayLike<number>} seed_faces face IDs to start from; non-allocated seeds are ignored
|
|
12
|
+
* @param {number[]} out destination array; discovered face IDs are appended in place
|
|
13
|
+
* @param {BitSet} visited shared visited set; faces already set are not revisited
|
|
14
|
+
* @returns {number} the number of faces appended to `out`
|
|
15
|
+
*/
|
|
16
|
+
export function bt_face_island_flood_fill(mesh: BinaryTopology, seed_faces: ArrayLike<number>, out: number[], visited: BitSet): number;
|
|
17
|
+
//# sourceMappingURL=bt_face_island_flood_fill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bt_face_island_flood_fill.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AACH,4EALW,UAAU,MAAM,CAAC,OACjB,MAAM,EAAE,oBAEN,MAAM,CA2BlB"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { bt_face_get_neighbour_faces } from "./bt_face_get_neighbour_faces.js";
|
|
2
|
+
|
|
3
|
+
const _neighbours = [];
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Flood-fill the connected component(s) of faces reachable from `seed_faces`, crossing shared edges.
|
|
7
|
+
*
|
|
8
|
+
* Faces already marked in `visited` are skipped; every face this call reaches is marked in `visited`
|
|
9
|
+
* and appended to `out`. Sharing one `visited` set across calls lets a caller partition a whole mesh
|
|
10
|
+
* into disjoint islands (see {@link bt_mesh_compute_face_islands}), while a fresh `visited` set
|
|
11
|
+
* re-derives a single island from its seeds (e.g. after the island's faces were subdivided - the new
|
|
12
|
+
* split halves stay edge-connected to the seed faces).
|
|
13
|
+
*
|
|
14
|
+
* @param {BinaryTopology} mesh
|
|
15
|
+
* @param {ArrayLike<number>} seed_faces face IDs to start from; non-allocated seeds are ignored
|
|
16
|
+
* @param {number[]} out destination array; discovered face IDs are appended in place
|
|
17
|
+
* @param {BitSet} visited shared visited set; faces already set are not revisited
|
|
18
|
+
* @returns {number} the number of faces appended to `out`
|
|
19
|
+
*/
|
|
20
|
+
export function bt_face_island_flood_fill(mesh, seed_faces, out, visited) {
|
|
21
|
+
const start = out.length;
|
|
22
|
+
const faces = mesh.faces;
|
|
23
|
+
|
|
24
|
+
const open = [];
|
|
25
|
+
for (let i = 0; i < seed_faces.length; i++) {
|
|
26
|
+
const s = seed_faces[i];
|
|
27
|
+
if (!faces.is_allocated(s)) continue;
|
|
28
|
+
if (visited.getAndSet(s)) continue;
|
|
29
|
+
open.push(s);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
while (open.length > 0) {
|
|
33
|
+
const f = open.pop();
|
|
34
|
+
out.push(f);
|
|
35
|
+
|
|
36
|
+
const neighbour_count = bt_face_get_neighbour_faces(_neighbours, 0, mesh, f);
|
|
37
|
+
for (let i = 0; i < neighbour_count; i++) {
|
|
38
|
+
const g = _neighbours[i];
|
|
39
|
+
if (visited.getAndSet(g)) continue;
|
|
40
|
+
open.push(g);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return out.length - start;
|
|
45
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gather the geometry of a set of boundary edges into a flat segment buffer for fast distance queries.
|
|
3
|
+
*
|
|
4
|
+
* Each edge contributes 6 consecutive numbers: `ax, ay, az, bx, by, bz`. The buffer is reused: it is
|
|
5
|
+
* written from index 0 and truncated to the exact number of values produced, so callers can keep a
|
|
6
|
+
* single array alive across many invocations without churning the garbage collector.
|
|
7
|
+
*
|
|
8
|
+
* @param {BinaryTopology} mesh
|
|
9
|
+
* @param {Iterable<number>} boundary_edges boundary edge IDs
|
|
10
|
+
* @param {number[]} out_segments reused output buffer (filled and length-truncated in place)
|
|
11
|
+
* @returns {number} the number of segments written
|
|
12
|
+
*/
|
|
13
|
+
export function bt_collect_boundary_segments(mesh: BinaryTopology, boundary_edges: Iterable<number>, out_segments: number[]): number;
|
|
14
|
+
/**
|
|
15
|
+
* Exact straight-line (Euclidean) distance from a point to the nearest boundary segment.
|
|
16
|
+
*
|
|
17
|
+
* @param {number[]} segments flat segment buffer (6 numbers per segment) from {@link bt_collect_boundary_segments}
|
|
18
|
+
* @param {number} px
|
|
19
|
+
* @param {number} py
|
|
20
|
+
* @param {number} pz
|
|
21
|
+
* @returns {number} the distance, or `Infinity` when the buffer is empty
|
|
22
|
+
*/
|
|
23
|
+
export function bt_point_distance_to_segments(segments: number[], px: number, py: number, pz: number): number;
|
|
24
|
+
/**
|
|
25
|
+
* Build a Euclidean boundary distance field for an island: for every island vertex, the exact
|
|
26
|
+
* straight-line distance to the nearest boundary segment.
|
|
27
|
+
*
|
|
28
|
+
* This is line-of-sight clearance to the nearest wall. It differs from
|
|
29
|
+
* {@link bt_mesh_build_boundary_distance_field}, which measures geodesic (along-surface) distance via
|
|
30
|
+
* an eikonal solve. Euclidean clearance is the correct metric for "how far is this point from any
|
|
31
|
+
* obstacle as the crow flies" - e.g. agent clearance during navmesh erosion - and, unlike the
|
|
32
|
+
* geodesic field, it is independent of how the interior happens to be triangulated.
|
|
33
|
+
*
|
|
34
|
+
* @param {Map<number, number>} vertex_boundary_distances result map (vertex ID -> distance); cleared then filled
|
|
35
|
+
* @param {BinaryTopology} mesh
|
|
36
|
+
* @param {Iterable<number>} island_vertices the vertices to evaluate
|
|
37
|
+
* @param {number[]} segments flat boundary segment buffer from {@link bt_collect_boundary_segments}
|
|
38
|
+
*/
|
|
39
|
+
export function bt_mesh_build_boundary_euclidean_distance_field(vertex_boundary_distances: Map<number, number>, mesh: BinaryTopology, island_vertices: Iterable<number>, segments: number[]): void;
|
|
40
|
+
//# sourceMappingURL=bt_mesh_build_boundary_euclidean_distance_field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bt_mesh_build_boundary_euclidean_distance_field.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AACH,mFAJW,SAAS,MAAM,CAAC,gBAChB,MAAM,EAAE,GACN,MAAM,CAgBlB;AAED;;;;;;;;GAQG;AACH,wDANW,MAAM,EAAE,MACR,MAAM,MACN,MAAM,MACN,MAAM,GACJ,MAAM,CAelB;AAED;;;;;;;;;;;;;;GAcG;AACH,2FALW,IAAI,MAAM,EAAE,MAAM,CAAC,yCAEnB,SAAS,MAAM,CAAC,YAChB,MAAM,EAAE,QAclB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { line3_compute_segment_point_distance_sqr } from "../../../../line/line3_compute_segment_point_distance_sqr.js";
|
|
2
|
+
|
|
3
|
+
const _v = new Float32Array(3);
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gather the geometry of a set of boundary edges into a flat segment buffer for fast distance queries.
|
|
7
|
+
*
|
|
8
|
+
* Each edge contributes 6 consecutive numbers: `ax, ay, az, bx, by, bz`. The buffer is reused: it is
|
|
9
|
+
* written from index 0 and truncated to the exact number of values produced, so callers can keep a
|
|
10
|
+
* single array alive across many invocations without churning the garbage collector.
|
|
11
|
+
*
|
|
12
|
+
* @param {BinaryTopology} mesh
|
|
13
|
+
* @param {Iterable<number>} boundary_edges boundary edge IDs
|
|
14
|
+
* @param {number[]} out_segments reused output buffer (filled and length-truncated in place)
|
|
15
|
+
* @returns {number} the number of segments written
|
|
16
|
+
*/
|
|
17
|
+
export function bt_collect_boundary_segments(mesh, boundary_edges, out_segments) {
|
|
18
|
+
let w = 0;
|
|
19
|
+
let count = 0;
|
|
20
|
+
|
|
21
|
+
for (const e of boundary_edges) {
|
|
22
|
+
mesh.vertex_read_coordinate(_v, 0, mesh.edge_read_vertex1(e));
|
|
23
|
+
out_segments[w++] = _v[0]; out_segments[w++] = _v[1]; out_segments[w++] = _v[2];
|
|
24
|
+
mesh.vertex_read_coordinate(_v, 0, mesh.edge_read_vertex2(e));
|
|
25
|
+
out_segments[w++] = _v[0]; out_segments[w++] = _v[1]; out_segments[w++] = _v[2];
|
|
26
|
+
count++;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
out_segments.length = w;
|
|
30
|
+
return count;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Exact straight-line (Euclidean) distance from a point to the nearest boundary segment.
|
|
35
|
+
*
|
|
36
|
+
* @param {number[]} segments flat segment buffer (6 numbers per segment) from {@link bt_collect_boundary_segments}
|
|
37
|
+
* @param {number} px
|
|
38
|
+
* @param {number} py
|
|
39
|
+
* @param {number} pz
|
|
40
|
+
* @returns {number} the distance, or `Infinity` when the buffer is empty
|
|
41
|
+
*/
|
|
42
|
+
export function bt_point_distance_to_segments(segments, px, py, pz) {
|
|
43
|
+
let best_sq = Infinity;
|
|
44
|
+
|
|
45
|
+
for (let i = 0; i < segments.length; i += 6) {
|
|
46
|
+
const d_sq = line3_compute_segment_point_distance_sqr(
|
|
47
|
+
segments[i], segments[i + 1], segments[i + 2],
|
|
48
|
+
segments[i + 3], segments[i + 4], segments[i + 5],
|
|
49
|
+
px, py, pz
|
|
50
|
+
);
|
|
51
|
+
if (d_sq < best_sq) best_sq = d_sq;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return best_sq === Infinity ? Infinity : Math.sqrt(best_sq);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Build a Euclidean boundary distance field for an island: for every island vertex, the exact
|
|
59
|
+
* straight-line distance to the nearest boundary segment.
|
|
60
|
+
*
|
|
61
|
+
* This is line-of-sight clearance to the nearest wall. It differs from
|
|
62
|
+
* {@link bt_mesh_build_boundary_distance_field}, which measures geodesic (along-surface) distance via
|
|
63
|
+
* an eikonal solve. Euclidean clearance is the correct metric for "how far is this point from any
|
|
64
|
+
* obstacle as the crow flies" - e.g. agent clearance during navmesh erosion - and, unlike the
|
|
65
|
+
* geodesic field, it is independent of how the interior happens to be triangulated.
|
|
66
|
+
*
|
|
67
|
+
* @param {Map<number, number>} vertex_boundary_distances result map (vertex ID -> distance); cleared then filled
|
|
68
|
+
* @param {BinaryTopology} mesh
|
|
69
|
+
* @param {Iterable<number>} island_vertices the vertices to evaluate
|
|
70
|
+
* @param {number[]} segments flat boundary segment buffer from {@link bt_collect_boundary_segments}
|
|
71
|
+
*/
|
|
72
|
+
export function bt_mesh_build_boundary_euclidean_distance_field(
|
|
73
|
+
vertex_boundary_distances,
|
|
74
|
+
mesh,
|
|
75
|
+
island_vertices,
|
|
76
|
+
segments
|
|
77
|
+
) {
|
|
78
|
+
vertex_boundary_distances.clear();
|
|
79
|
+
|
|
80
|
+
for (const v of island_vertices) {
|
|
81
|
+
mesh.vertex_read_coordinate(_v, 0, v);
|
|
82
|
+
vertex_boundary_distances.set(v, bt_point_distance_to_segments(segments, _v[0], _v[1], _v[2]));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bt_mesh_compute_face_islands.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,oEAFa,MAAM,EAAE,EAAE,
|
|
1
|
+
{"version":3,"file":"bt_mesh_compute_face_islands.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,oEAFa,MAAM,EAAE,EAAE,CA4CtB"}
|
|
@@ -1,78 +1,53 @@
|
|
|
1
|
-
import { BitSet } from "../../../../../../binary/BitSet.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Find groups of connected faces in the mesh. Faces are considered connected if they share an edge.
|
|
6
|
-
* If the entire mesh is connected - there will be a single island.
|
|
7
|
-
*
|
|
8
|
-
* @param {BinaryTopology} mesh
|
|
9
|
-
* @returns {number[][]} each island is a sequence of face IDs
|
|
10
|
-
*/
|
|
11
|
-
export function bt_mesh_compute_face_islands(mesh) {
|
|
12
|
-
/**
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const open = [seed_face_id];
|
|
56
|
-
|
|
57
|
-
while (open.length > 0) {
|
|
58
|
-
const face_id = open.pop();
|
|
59
|
-
|
|
60
|
-
island.push(face_id);
|
|
61
|
-
|
|
62
|
-
const neighbour_count = bt_face_get_neighbour_faces(scratch_neighbours, 0, mesh, face_id);
|
|
63
|
-
|
|
64
|
-
for (let i = 0; i < neighbour_count; i++) {
|
|
65
|
-
const neighbour_id = scratch_neighbours[i];
|
|
66
|
-
|
|
67
|
-
if (closed_set.getAndSet(neighbour_id)) {
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
open.push(neighbour_id);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return result;
|
|
78
|
-
}
|
|
1
|
+
import { BitSet } from "../../../../../../binary/BitSet.js";
|
|
2
|
+
import { bt_face_island_flood_fill } from "./bt_face_island_flood_fill.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Find groups of connected faces in the mesh. Faces are considered connected if they share an edge.
|
|
6
|
+
* If the entire mesh is connected - there will be a single island.
|
|
7
|
+
*
|
|
8
|
+
* @param {BinaryTopology} mesh
|
|
9
|
+
* @returns {number[][]} each island is a sequence of face IDs
|
|
10
|
+
*/
|
|
11
|
+
export function bt_mesh_compute_face_islands(mesh) {
|
|
12
|
+
/**
|
|
13
|
+
* @type {number[][]}
|
|
14
|
+
*/
|
|
15
|
+
const result = [];
|
|
16
|
+
|
|
17
|
+
// shared visited set; flood fill marks every face it reaches so each face lands in exactly one island
|
|
18
|
+
const closed_set = new BitSet();
|
|
19
|
+
|
|
20
|
+
const faces = mesh.faces;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @type {number}
|
|
24
|
+
*/
|
|
25
|
+
const face_count = faces.size;
|
|
26
|
+
|
|
27
|
+
// reused single-element seed buffer, avoids allocating an array per island
|
|
28
|
+
const seed = [0];
|
|
29
|
+
|
|
30
|
+
for (let seed_face_id = 0; seed_face_id < face_count; seed_face_id++) {
|
|
31
|
+
if (closed_set.get(seed_face_id)) {
|
|
32
|
+
// already part of an island
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!faces.is_allocated(seed_face_id)) {
|
|
37
|
+
// a hole
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @type {number[]}
|
|
43
|
+
*/
|
|
44
|
+
const island = [];
|
|
45
|
+
|
|
46
|
+
seed[0] = seed_face_id;
|
|
47
|
+
bt_face_island_flood_fill(mesh, seed, island, closed_set);
|
|
48
|
+
|
|
49
|
+
result.push(island);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Perform rotation on a direction vector using a 3x3 (column-major) transform matrix.
|
|
4
|
+
* Input is expected to be normalized.
|
|
5
|
+
* Correctly handles non-uniform scaling.
|
|
6
|
+
* Output is normalized.
|
|
7
|
+
*
|
|
8
|
+
* @param {number[]|Float32Array} output
|
|
9
|
+
* @param {number} output_offset
|
|
10
|
+
* @param {number} x
|
|
11
|
+
* @param {number} y
|
|
12
|
+
* @param {number} z
|
|
13
|
+
* @param {number[]|Float32Array} mat3
|
|
14
|
+
*/
|
|
15
|
+
export function v3_matrix3_rotate(output: number[] | Float32Array, output_offset: number, x: number, y: number, z: number, mat3: number[] | Float32Array): void;
|
|
16
|
+
//# sourceMappingURL=v3_matrix3_rotate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v3_matrix3_rotate.d.ts","sourceRoot":"","sources":["../../../../../src/core/geom/vec3/v3_matrix3_rotate.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,0CAPW,MAAM,EAAE,GAAC,YAAY,iBACrB,MAAM,KACN,MAAM,KACN,MAAM,KACN,MAAM,QACN,MAAM,EAAE,GAAC,YAAY,QAkC/B"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { v3_length } from "./v3_length.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* Perform rotation on a direction vector using a 3x3 (column-major) transform matrix.
|
|
6
|
+
* Input is expected to be normalized.
|
|
7
|
+
* Correctly handles non-uniform scaling.
|
|
8
|
+
* Output is normalized.
|
|
9
|
+
*
|
|
10
|
+
* @param {number[]|Float32Array} output
|
|
11
|
+
* @param {number} output_offset
|
|
12
|
+
* @param {number} x
|
|
13
|
+
* @param {number} y
|
|
14
|
+
* @param {number} z
|
|
15
|
+
* @param {number[]|Float32Array} mat3
|
|
16
|
+
*/
|
|
17
|
+
export function v3_matrix3_rotate(
|
|
18
|
+
output, output_offset,
|
|
19
|
+
x, y, z,
|
|
20
|
+
mat3
|
|
21
|
+
) {
|
|
22
|
+
|
|
23
|
+
const _x = mat3[0] * x + mat3[3] * y + mat3[6] * z;
|
|
24
|
+
const _y = mat3[1] * x + mat3[4] * y + mat3[7] * z;
|
|
25
|
+
const _z = mat3[2] * x + mat3[5] * y + mat3[8] * z;
|
|
26
|
+
|
|
27
|
+
// perform normalization
|
|
28
|
+
const length = v3_length(_x, _y, _z);
|
|
29
|
+
|
|
30
|
+
if (length > 1e-7) {
|
|
31
|
+
|
|
32
|
+
// re-normalize
|
|
33
|
+
const norm = 1 / length;
|
|
34
|
+
|
|
35
|
+
output[output_offset] = _x * norm;
|
|
36
|
+
output[output_offset + 1] = _y * norm;
|
|
37
|
+
output[output_offset + 2] = _z * norm;
|
|
38
|
+
|
|
39
|
+
} else {
|
|
40
|
+
|
|
41
|
+
// The transform is invalid.
|
|
42
|
+
// Provide arbitrary vector.
|
|
43
|
+
|
|
44
|
+
output[output_offset] = 0;
|
|
45
|
+
output[output_offset + 1] = 0;
|
|
46
|
+
output[output_offset + 2] = 0;
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build orthonormal matrix from direction vector
|
|
3
|
-
* @param {number[]} out
|
|
3
|
+
* @param {number[]|Float32Array|Float64Array} out
|
|
4
4
|
* @param {number} out_offset
|
|
5
5
|
* @param {number} dir_x
|
|
6
6
|
* @param {number} dir_y
|
|
7
7
|
* @param {number} dir_z
|
|
8
8
|
*/
|
|
9
|
-
export function v3_orthonormal_matrix_from_normal(out: number[], out_offset: number, dir_x: number, dir_y: number, dir_z: number): void;
|
|
9
|
+
export function v3_orthonormal_matrix_from_normal(out: number[] | Float32Array | Float64Array, out_offset: number, dir_x: number, dir_y: number, dir_z: number): void;
|
|
10
10
|
//# sourceMappingURL=v3_orthonormal_matrix_from_normal.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v3_orthonormal_matrix_from_normal.d.ts","sourceRoot":"","sources":["../../../../../src/core/geom/vec3/v3_orthonormal_matrix_from_normal.js"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,uDANW,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"v3_orthonormal_matrix_from_normal.d.ts","sourceRoot":"","sources":["../../../../../src/core/geom/vec3/v3_orthonormal_matrix_from_normal.js"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,uDANW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,cAClC,MAAM,SACN,MAAM,SACN,MAAM,SACN,MAAM,QAsChB"}
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
import { v3_length } from "./v3_length.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Build orthonormal matrix from direction vector
|
|
5
|
-
* @param {number[]} out
|
|
6
|
-
* @param {number} out_offset
|
|
7
|
-
* @param {number} dir_x
|
|
8
|
-
* @param {number} dir_y
|
|
9
|
-
* @param {number} dir_z
|
|
10
|
-
*/
|
|
11
|
-
export function v3_orthonormal_matrix_from_normal(out, out_offset, dir_x, dir_y, dir_z) {
|
|
12
|
-
// we build orthonormal vectors with respect to the direction vector
|
|
13
|
-
let o1_x, o1_y, o1_z;
|
|
14
|
-
|
|
15
|
-
if (Math.abs(dir_x) > Math.abs(dir_z)) {
|
|
16
|
-
o1_x = -dir_y;
|
|
17
|
-
o1_y = dir_x;
|
|
18
|
-
o1_z = 0;
|
|
19
|
-
} else {
|
|
20
|
-
o1_x = 0;
|
|
21
|
-
o1_y = -dir_z;
|
|
22
|
-
o1_z = dir_y;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// normalize orthonormal vector
|
|
26
|
-
const o1_norm = 1 / v3_length(o1_x, o1_y, o1_z);
|
|
27
|
-
|
|
28
|
-
o1_x *= o1_norm;
|
|
29
|
-
o1_y *= o1_norm;
|
|
30
|
-
o1_z *= o1_norm;
|
|
31
|
-
|
|
32
|
-
const o2_x = dir_y * o1_z - dir_z * o1_y;
|
|
33
|
-
const o2_y = dir_z * o1_x - dir_x * o1_z;
|
|
34
|
-
const o2_z = dir_x * o1_y - dir_y * o1_x;
|
|
35
|
-
|
|
36
|
-
out[out_offset] = o1_x;
|
|
37
|
-
out[out_offset + 1] = o1_y;
|
|
38
|
-
out[out_offset + 2] = o1_z;
|
|
39
|
-
|
|
40
|
-
out[out_offset + 3] = o2_x;
|
|
41
|
-
out[out_offset + 4] = o2_y;
|
|
42
|
-
out[out_offset + 5] = o2_z;
|
|
43
|
-
|
|
44
|
-
out[out_offset + 6] = dir_x;
|
|
45
|
-
out[out_offset + 7] = dir_y;
|
|
46
|
-
out[out_offset + 8] = dir_z;
|
|
1
|
+
import { v3_length } from "./v3_length.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build orthonormal matrix from direction vector
|
|
5
|
+
* @param {number[]|Float32Array|Float64Array} out
|
|
6
|
+
* @param {number} out_offset
|
|
7
|
+
* @param {number} dir_x
|
|
8
|
+
* @param {number} dir_y
|
|
9
|
+
* @param {number} dir_z
|
|
10
|
+
*/
|
|
11
|
+
export function v3_orthonormal_matrix_from_normal(out, out_offset, dir_x, dir_y, dir_z) {
|
|
12
|
+
// we build orthonormal vectors with respect to the direction vector
|
|
13
|
+
let o1_x, o1_y, o1_z;
|
|
14
|
+
|
|
15
|
+
if (Math.abs(dir_x) > Math.abs(dir_z)) {
|
|
16
|
+
o1_x = -dir_y;
|
|
17
|
+
o1_y = dir_x;
|
|
18
|
+
o1_z = 0;
|
|
19
|
+
} else {
|
|
20
|
+
o1_x = 0;
|
|
21
|
+
o1_y = -dir_z;
|
|
22
|
+
o1_z = dir_y;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// normalize orthonormal vector
|
|
26
|
+
const o1_norm = 1 / v3_length(o1_x, o1_y, o1_z);
|
|
27
|
+
|
|
28
|
+
o1_x *= o1_norm;
|
|
29
|
+
o1_y *= o1_norm;
|
|
30
|
+
o1_z *= o1_norm;
|
|
31
|
+
|
|
32
|
+
const o2_x = dir_y * o1_z - dir_z * o1_y;
|
|
33
|
+
const o2_y = dir_z * o1_x - dir_x * o1_z;
|
|
34
|
+
const o2_z = dir_x * o1_y - dir_y * o1_x;
|
|
35
|
+
|
|
36
|
+
out[out_offset] = o1_x;
|
|
37
|
+
out[out_offset + 1] = o1_y;
|
|
38
|
+
out[out_offset + 2] = o1_z;
|
|
39
|
+
|
|
40
|
+
out[out_offset + 3] = o2_x;
|
|
41
|
+
out[out_offset + 4] = o2_y;
|
|
42
|
+
out[out_offset + 5] = o2_z;
|
|
43
|
+
|
|
44
|
+
out[out_offset + 6] = dir_x;
|
|
45
|
+
out[out_offset + 7] = dir_y;
|
|
46
|
+
out[out_offset + 8] = dir_z;
|
|
47
47
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getBiasedNormalSample.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/graphics/sh3/path_tracer/sampling/getBiasedNormalSample.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"getBiasedNormalSample.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/graphics/sh3/path_tracer/sampling/getBiasedNormalSample.js"],"names":[],"mappings":"AAMA;;;;;;;;;GASG;AACH,2CAPW,MAAM,EAAE,cACR,MAAM,UACN,MAAM,EAAE,iBACR,MAAM,SACN,MAAM,gBACK,MAAM,QA6B3B"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { v3_matrix3_rotate } from "../../../../../core/geom/vec3/v3_matrix3_rotate.js";
|
|
2
|
+
import { v3_orthonormal_matrix_from_normal } from "../../../../../core/geom/vec3/v3_orthonormal_matrix_from_normal.js";
|
|
2
3
|
import { PI2 } from "../../../../../core/math/PI2.js";
|
|
3
4
|
|
|
5
|
+
const mat3 = new Float32Array(9);
|
|
6
|
+
|
|
4
7
|
/**
|
|
5
8
|
* @see 2003 "Global Illumination Compendium" by Philip Dutré (equation 36)
|
|
6
9
|
* @see http://blog.hvidtfeldts.net/index.php/2015/01/path-tracing-3d-fractals/
|
|
@@ -24,29 +27,7 @@ export function getBiasedNormalSample(
|
|
|
24
27
|
const dir_y = normal[normal_offset + 1];
|
|
25
28
|
const dir_z = normal[normal_offset + 2];
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
let o1_x, o1_y, o1_z;
|
|
29
|
-
|
|
30
|
-
if (Math.abs(dir_x) > Math.abs(dir_z)) {
|
|
31
|
-
o1_x = -dir_y;
|
|
32
|
-
o1_y = dir_x;
|
|
33
|
-
o1_z = 0;
|
|
34
|
-
} else {
|
|
35
|
-
o1_x = 0;
|
|
36
|
-
o1_y = -dir_z;
|
|
37
|
-
o1_z = dir_y;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// normalize orthonormal vector
|
|
41
|
-
const o1_norm = 1 / v3_length(o1_x, o1_y, o1_z);
|
|
42
|
-
|
|
43
|
-
o1_x *= o1_norm;
|
|
44
|
-
o1_y *= o1_norm;
|
|
45
|
-
o1_z *= o1_norm;
|
|
46
|
-
|
|
47
|
-
const o2_x = dir_y * o1_z - dir_z * o1_y;
|
|
48
|
-
const o2_y = dir_z * o1_x - dir_x * o1_z;
|
|
49
|
-
const o2_z = dir_x * o1_y - dir_y * o1_x;
|
|
30
|
+
v3_orthonormal_matrix_from_normal(mat3, 0, dir_x, dir_y, dir_z);
|
|
50
31
|
|
|
51
32
|
// we can skip normalizing second orthonormal vector, as it will be guaranteed to be unit length already
|
|
52
33
|
// for explanation, see https://math.stackexchange.com/questions/23259/is-the-cross-product-of-two-unit-vectors-itself-a-unit-vector#:~:text=If%20you%20know%20that%20the,(a%20length%20of%20one).
|
|
@@ -59,9 +40,6 @@ export function getBiasedNormalSample(
|
|
|
59
40
|
const k0 = Math.cos(r_x) * oneminus;
|
|
60
41
|
const k1 = Math.sin(r_x) * oneminus;
|
|
61
42
|
|
|
62
|
-
|
|
63
|
-
out[out_offset] = k0 * o1_x + k1 * o2_x + r_y * dir_x;
|
|
64
|
-
out[out_offset + 1] = k0 * o1_y + k1 * o2_y + r_y * dir_y;
|
|
65
|
-
out[out_offset + 2] = k0 * o1_z + k1 * o2_z + r_y * dir_z;
|
|
43
|
+
v3_matrix3_rotate(out, 3, k0, k1, r_y, mat3);
|
|
66
44
|
}
|
|
67
45
|
|