@woosh/meep-engine 2.163.7 → 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.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.d.ts +21 -0
  3. package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.d.ts.map +1 -0
  4. package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.js +42 -0
  5. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.d.ts +2 -2
  6. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.d.ts.map +1 -1
  7. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js +120 -179
  8. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.d.ts +9 -10
  9. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.d.ts.map +1 -1
  10. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.js +12 -13
  11. package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.d.ts +17 -0
  12. package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.d.ts.map +1 -0
  13. package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.js +45 -0
  14. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.d.ts +40 -0
  15. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.d.ts.map +1 -0
  16. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.js +84 -0
  17. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.d.ts.map +1 -1
  18. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js +53 -78
  19. package/src/core/geom/vec3/v3_matrix3_rotate.d.ts +16 -0
  20. package/src/core/geom/vec3/v3_matrix3_rotate.d.ts.map +1 -0
  21. package/src/core/geom/vec3/v3_matrix3_rotate.js +49 -0
  22. package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.d.ts +2 -2
  23. package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.d.ts.map +1 -1
  24. package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.js +46 -46
  25. package/src/engine/graphics/sh3/path_tracer/sampling/getBiasedNormalSample.d.ts.map +1 -1
  26. package/src/engine/graphics/sh3/path_tracer/sampling/getBiasedNormalSample.js +6 -28
  27. package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +185 -0
  28. package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts +11 -0
  29. package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts.map +1 -1
  30. package/src/engine/navigation/mesh/bt_mesh_face_find_path.js +623 -100
  31. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.d.ts.map +1 -1
  32. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.js +354 -138
  33. package/src/engine/navigation/mesh/navmesh_polyanya_find_path.d.ts +17 -0
  34. package/src/engine/navigation/mesh/navmesh_polyanya_find_path.d.ts.map +1 -0
  35. package/src/engine/navigation/mesh/navmesh_polyanya_find_path.js +613 -0
@@ -94,21 +94,20 @@ function fill_loop(mesh, cycle) {
94
94
  }
95
95
 
96
96
  /**
97
- * Fill boundary holes that are too thin for the agent to occupy.
97
+ * Fill inner boundary holes whose mean width falls below `min_width`.
98
98
  *
99
- * Erosion/clearance can leave a hair-thin sliver hole hugging an obstacle's offset edge (a strip of
100
- * cells that are actually clear but get pinched off into their own tiny loop). Such a hole is narrower
101
- * than the agent, so it can never be a real navigation obstacle - it is numerical noise that inflates
102
- * the boundary, the face count, and the loop count. This fills any inner loop whose mean width
103
- * (2*area/perimeter, the inradius of an equivalent disc) is below the agent radius. The single
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} agent_radius
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, agent_radius) {
111
- if (agent_radius <= 0) return 0;
109
+ export function bt_mesh_fill_small_holes(mesh, min_width) {
110
+ if (min_width <= 0) return 0;
112
111
 
113
112
  let cycles;
114
113
  try {
@@ -140,8 +139,8 @@ export function bt_mesh_fill_small_holes(mesh, agent_radius) {
140
139
  if (i === outer) continue;
141
140
  const { perimeter, area } = measures[i];
142
141
  if (perimeter <= 0) continue;
143
- // mean width of the hole = 2*area/perimeter; fill when the agent disc cannot fit
144
- if (2 * area / perimeter < agent_radius) {
142
+ // mean width of the hole = 2*area/perimeter; fill when it is below the requested threshold
143
+ if (2 * area / perimeter < min_width) {
145
144
  fill_loop(mesh, cycles[i]);
146
145
  filled++;
147
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,CAqEtB"}
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 { bt_face_get_neighbour_faces } from "./bt_face_get_neighbour_faces.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
- *
14
- * @type {number[][]}
15
- */
16
- const result = [];
17
-
18
- // traverse over all faced in the mesh, flood-filling to build islands
19
- const closed_set = new BitSet();
20
-
21
- const faces = mesh.faces;
22
-
23
- /**
24
- * @type {number}
25
- */
26
- const face_count = faces.size;
27
-
28
- /**
29
- *
30
- * @type {number[]}
31
- */
32
- const scratch_neighbours = [];
33
-
34
- for (let seed_face_id = 0; seed_face_id < face_count; seed_face_id++) {
35
- if (closed_set.getAndSet(seed_face_id)) {
36
- // already processed
37
- continue;
38
- }
39
-
40
- if (!faces.is_allocated(seed_face_id)) {
41
- // a hole
42
- continue;
43
- }
44
-
45
- // start a new island
46
-
47
- /**
48
- *
49
- * @type {number[]}
50
- */
51
- const island = [];
52
-
53
- result.push(island);
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,cACR,MAAM,SACN,MAAM,SACN,MAAM,SACN,MAAM,QAsChB"}
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":"AAGA;;;;;;;;;GASG;AACH,2CAPW,MAAM,EAAE,cACR,MAAM,UACN,MAAM,EAAE,iBACR,MAAM,SACN,MAAM,gBACK,MAAM,QAsD3B"}
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 { v3_length } from "../../../../../core/geom/vec3/v3_length.js";
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
- // we build orthonormal vectors with respect to the direction vector
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