@woosh/meep-engine 3.2.0 → 3.3.0

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 (50) hide show
  1. package/package.json +1 -1
  2. package/src/avif/encoder_worker.js +5 -18
  3. package/src/core/color/PQ/PQ_constants.d.ts +43 -0
  4. package/src/core/color/PQ/PQ_constants.d.ts.map +1 -0
  5. package/src/core/color/PQ/PQ_constants.js +50 -0
  6. package/src/core/color/PQ/PQ_to_linear.d.ts +24 -0
  7. package/src/core/color/PQ/PQ_to_linear.d.ts.map +1 -0
  8. package/src/core/color/PQ/PQ_to_linear.js +35 -0
  9. package/src/core/color/PQ/linear_to_PQ.d.ts +27 -0
  10. package/src/core/color/PQ/linear_to_PQ.d.ts.map +1 -0
  11. package/src/core/color/PQ/linear_to_PQ.js +32 -0
  12. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_other_face.d.ts +32 -0
  13. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_other_face.d.ts.map +1 -0
  14. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_other_face.js +59 -0
  15. package/src/core/geom/3d/topology/struct/binary/query/bt_face_third_vertex.d.ts +26 -0
  16. package/src/core/geom/3d/topology/struct/binary/query/bt_face_third_vertex.d.ts.map +1 -0
  17. package/src/core/geom/3d/topology/struct/binary/query/bt_face_third_vertex.js +55 -0
  18. package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
  19. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +269 -265
  20. package/src/engine/graphics3/VolumetricLightMap.d.ts +90 -0
  21. package/src/engine/graphics3/VolumetricLightMap.d.ts.map +1 -0
  22. package/src/engine/graphics3/VolumetricLightMap.js +160 -0
  23. package/src/engine/graphics3/VolumetricLightMapSerializationAdapter.d.ts +32 -0
  24. package/src/engine/graphics3/VolumetricLightMapSerializationAdapter.d.ts.map +1 -0
  25. package/src/engine/graphics3/VolumetricLightMapSerializationAdapter.js +67 -0
  26. package/src/engine/graphics3/VolumetricLightMapSystem3.d.ts +98 -0
  27. package/src/engine/graphics3/VolumetricLightMapSystem3.d.ts.map +1 -0
  28. package/src/engine/graphics3/VolumetricLightMapSystem3.js +261 -0
  29. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  30. package/src/shade/renderer/global_illumination/brick4/cpu/brick4_empty_gpu_structure.d.ts +23 -0
  31. package/src/shade/renderer/global_illumination/brick4/cpu/brick4_empty_gpu_structure.d.ts.map +1 -0
  32. package/src/shade/renderer/global_illumination/brick4/cpu/brick4_empty_gpu_structure.js +34 -0
  33. package/src/shade/renderer/scene/optimization/geometry/internal/anchor_grow_topological.d.ts.map +1 -1
  34. package/src/shade/renderer/scene/optimization/geometry/internal/anchor_grow_topological.js +12 -3
  35. package/src/shade/renderer/scene/optimization/geometry/internal/match_validate_topology.js +3 -3
  36. package/src/shade/renderer/scene/optimization/geometry/internal/seed_correspondence_from_window.js +3 -3
  37. package/src/shade/renderer/scene/serialization/deserialize_scene.d.ts.map +1 -1
  38. package/src/shade/renderer/scene/serialization/deserialize_scene.js +0 -2
  39. package/src/shade/renderer/scene/optimization/geometry/internal/bt_face_loop_helpers.d.ts +0 -32
  40. package/src/shade/renderer/scene/optimization/geometry/internal/bt_face_loop_helpers.d.ts.map +0 -1
  41. package/src/shade/renderer/scene/optimization/geometry/internal/bt_face_loop_helpers.js +0 -77
  42. package/src/shade/renderer/scene/optimization/geometry/internal/bt_find_edge_between.d.ts +0 -12
  43. package/src/shade/renderer/scene/optimization/geometry/internal/bt_find_edge_between.d.ts.map +0 -1
  44. package/src/shade/renderer/scene/optimization/geometry/internal/bt_find_edge_between.js +0 -35
  45. package/src/shade/renderer/scene/serialization/linear_to_pq.d.ts +0 -2
  46. package/src/shade/renderer/scene/serialization/linear_to_pq.d.ts.map +0 -1
  47. package/src/shade/renderer/scene/serialization/linear_to_pq.js +0 -14
  48. package/src/shade/renderer/scene/serialization/pq_to_linear.d.ts +0 -9
  49. package/src/shade/renderer/scene/serialization/pq_to_linear.d.ts.map +0 -1
  50. package/src/shade/renderer/scene/serialization/pq_to_linear.js +0 -30
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "description": "Pure JavaScript game engine. Fully featured and production ready.",
7
7
  "type": "module",
8
8
  "author": "Alexander Goldring",
9
- "version": "3.2.0",
9
+ "version": "3.3.0",
10
10
  "exports": {
11
11
  "./build/*": "./build/*",
12
12
  "./src/*": "./src/*",
@@ -8,21 +8,11 @@ import avifEncoderFactory from './codec/enc/avif_enc.js';
8
8
  import { initEmscriptenModule } from './utils.js';
9
9
  import { defaultOptions } from './meta.js';
10
10
  import { to_half_float_uint16 } from '../core/binary/to_half_float_uint16.js';
11
+ import { linear_to_PQ } from '../core/color/PQ/linear_to_PQ.js';
12
+ import { PQ_SDR_WHITE_LINEAR } from '../core/color/PQ/PQ_constants.js';
11
13
 
12
14
  const AVIF_DEFAULT_SPEED = 5;
13
15
 
14
- // PQ OETF (SMPTE ST 2084). Input: linear light normalized so 1.0 = 10000 nits. Output: PQ signal in [0,1].
15
- const PQ_M1 = 0.1593017578125;
16
- const PQ_M2 = 78.84375;
17
- const PQ_C1 = 0.8359375;
18
- const PQ_C2 = 18.8515625;
19
- const PQ_C3 = 18.6875;
20
-
21
- function linear_to_pq(x) {
22
- const xp = Math.pow(Math.max(x, 0), PQ_M1);
23
- return Math.pow((PQ_C1 + PQ_C2 * xp) / (1.0 + PQ_C3 * xp), PQ_M2);
24
- }
25
-
26
16
  const AvifSubsample = {
27
17
  YUV400: 0,
28
18
  YUV420: 1,
@@ -30,10 +20,6 @@ const AvifSubsample = {
30
20
  YUV444: 3,
31
21
  };
32
22
 
33
- // PQ tone mapping: 1.0 linear = SDR reference white = ~203 nits per BT.2408
34
- const SDR_WHITE_NITS = 203;
35
- const PQ_MAX_NITS = 10000;
36
-
37
23
  let encoder_promise = null;
38
24
 
39
25
  function get_encoder() {
@@ -103,8 +89,9 @@ function rgba_float16_to_pq_uint16(f16_view, width, height) {
103
89
  for (let i = 0; i < f16_view.length; i += 4) {
104
90
  for (let c = 0; c < 3; c++) {
105
91
  const linear = Math.max(f16_view[i + c], 0);
106
- const normalized = linear * SDR_WHITE_NITS / PQ_MAX_NITS;
107
- const pq = linear_to_pq(normalized);
92
+ // Engine linear is normalized to SDR reference white; PQ's is normalized to its own
93
+ // 10000-nit peak, so cross over before encoding.
94
+ const pq = linear_to_PQ(linear * PQ_SDR_WHITE_LINEAR);
108
95
  out[i + c] = Math.round(pq * 4095);
109
96
  }
110
97
  // alpha: linear 0..1 mapped directly to 0..4095
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Coefficients of the SMPTE ST 2084 (a.k.a. "PQ", Perceptual Quantizer) transfer function.
3
+ *
4
+ * Every value is exact in binary floating point -- each is a small integer over a power of two, as the
5
+ * standard specifies them -- so the OETF and the EOTF built from these are exact inverses to within the
6
+ * rounding of `Math.pow` alone.
7
+ *
8
+ * @author Alex Goldring
9
+ * @copyright Company Named Limited (c) 2026
10
+ */
11
+ /** @type {number} 2610 / 16384 */
12
+ export const PQ_M1: number;
13
+ /** @type {number} 2523 / 32 */
14
+ export const PQ_M2: number;
15
+ /** @type {number} 3424 / 4096 */
16
+ export const PQ_C1: number;
17
+ /** @type {number} 2413 / 128 */
18
+ export const PQ_C2: number;
19
+ /** @type {number} 2392 / 128 */
20
+ export const PQ_C3: number;
21
+ /**
22
+ * Peak luminance of the PQ signal range, in nits. A linear value of `1.0` in PQ's own normalization is
23
+ * this bright -- which is why {@link linear_to_PQ} and {@link PQ_to_linear} work in that normalization
24
+ * and not in any renderer's.
25
+ * @type {number}
26
+ */
27
+ export const PQ_MAX_NITS: number;
28
+ /**
29
+ * Luminance of SDR reference white, in nits, per ITU-R BT.2408. This is the engine's `1.0`, and it is
30
+ * NOT PQ's `1.0`: converting between the two scales is the caller's job and needs
31
+ * {@link PQ_SDR_WHITE_LINEAR} in one direction or its reciprocal in the other.
32
+ * @type {number}
33
+ */
34
+ export const SDR_WHITE_NITS: number;
35
+ /**
36
+ * SDR reference white expressed in PQ's linear normalization, i.e. `SDR_WHITE_NITS / PQ_MAX_NITS`.
37
+ *
38
+ * Multiply an engine-normalized linear value by this before {@link linear_to_PQ}; divide by it after
39
+ * {@link PQ_to_linear} to come back.
40
+ * @type {number}
41
+ */
42
+ export const PQ_SDR_WHITE_LINEAR: number;
43
+ //# sourceMappingURL=PQ_constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PQ_constants.d.ts","sourceRoot":"","sources":["../../../../../src/core/color/PQ/PQ_constants.js"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,kCAAkC;AAClC,oBADW,MAAM,CACoB;AAErC,+BAA+B;AAC/B,oBADW,MAAM,CACa;AAE9B,iCAAiC;AACjC,oBADW,MAAM,CACc;AAE/B,gCAAgC;AAChC,oBADW,MAAM,CACe;AAEhC,gCAAgC;AAChC,oBADW,MAAM,CACY;AAE7B;;;;;GAKG;AACH,0BAFU,MAAM,CAEiB;AAEjC;;;;;GAKG;AACH,6BAFU,MAAM,CAEkB;AAElC;;;;;;GAMG;AACH,kCAFU,MAAM,CAEgD"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Coefficients of the SMPTE ST 2084 (a.k.a. "PQ", Perceptual Quantizer) transfer function.
3
+ *
4
+ * Every value is exact in binary floating point -- each is a small integer over a power of two, as the
5
+ * standard specifies them -- so the OETF and the EOTF built from these are exact inverses to within the
6
+ * rounding of `Math.pow` alone.
7
+ *
8
+ * @author Alex Goldring
9
+ * @copyright Company Named Limited (c) 2026
10
+ */
11
+
12
+ /** @type {number} 2610 / 16384 */
13
+ export const PQ_M1 = 0.1593017578125;
14
+
15
+ /** @type {number} 2523 / 32 */
16
+ export const PQ_M2 = 78.84375;
17
+
18
+ /** @type {number} 3424 / 4096 */
19
+ export const PQ_C1 = 0.8359375;
20
+
21
+ /** @type {number} 2413 / 128 */
22
+ export const PQ_C2 = 18.8515625;
23
+
24
+ /** @type {number} 2392 / 128 */
25
+ export const PQ_C3 = 18.6875;
26
+
27
+ /**
28
+ * Peak luminance of the PQ signal range, in nits. A linear value of `1.0` in PQ's own normalization is
29
+ * this bright -- which is why {@link linear_to_PQ} and {@link PQ_to_linear} work in that normalization
30
+ * and not in any renderer's.
31
+ * @type {number}
32
+ */
33
+ export const PQ_MAX_NITS = 10000;
34
+
35
+ /**
36
+ * Luminance of SDR reference white, in nits, per ITU-R BT.2408. This is the engine's `1.0`, and it is
37
+ * NOT PQ's `1.0`: converting between the two scales is the caller's job and needs
38
+ * {@link PQ_SDR_WHITE_LINEAR} in one direction or its reciprocal in the other.
39
+ * @type {number}
40
+ */
41
+ export const SDR_WHITE_NITS = 203;
42
+
43
+ /**
44
+ * SDR reference white expressed in PQ's linear normalization, i.e. `SDR_WHITE_NITS / PQ_MAX_NITS`.
45
+ *
46
+ * Multiply an engine-normalized linear value by this before {@link linear_to_PQ}; divide by it after
47
+ * {@link PQ_to_linear} to come back.
48
+ * @type {number}
49
+ */
50
+ export const PQ_SDR_WHITE_LINEAR = SDR_WHITE_NITS / PQ_MAX_NITS;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * SMPTE ST 2084 EOTF: decode a PQ signal back to linear light. Exact inverse of {@link linear_to_PQ}.
3
+ *
4
+ * NORMALIZATION: the result is in PQ's own scale, where `1.0` is {@link PQ_MAX_NITS} (10000 nits) --
5
+ * not the engine's, where `1.0` is {@link SDR_WHITE_NITS} (203 nits). Divide by
6
+ * {@link PQ_SDR_WHITE_LINEAR} to land back in engine units:
7
+ *
8
+ * ```js
9
+ * const engine_linear = PQ_to_linear(pq) / PQ_SDR_WHITE_LINEAR;
10
+ * ```
11
+ *
12
+ * The numerator is clamped at `0` so that signals below `linear_to_PQ(0)` -- which the standard's
13
+ * curve never produces, but quantization and interpolation both do -- decode to black instead of
14
+ * `NaN` from a negative base under a fractional exponent.
15
+ *
16
+ * @param {number} pq PQ signal, in `[0, 1]`
17
+ * @returns {number} linear light, `1.0` = 10000 nits
18
+ * @see linear_to_PQ
19
+ *
20
+ * @author Alex Goldring
21
+ * @copyright Company Named Limited (c) 2026
22
+ */
23
+ export function PQ_to_linear(pq: number): number;
24
+ //# sourceMappingURL=PQ_to_linear.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PQ_to_linear.d.ts","sourceRoot":"","sources":["../../../../../src/core/color/PQ/PQ_to_linear.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,iCAPW,MAAM,GACJ,MAAM,CAalB"}
@@ -0,0 +1,35 @@
1
+ import { PQ_C1, PQ_C2, PQ_C3, PQ_M1, PQ_M2 } from "./PQ_constants.js";
2
+
3
+ const PQ_M1_INV = 1 / PQ_M1;
4
+ const PQ_M2_INV = 1 / PQ_M2;
5
+
6
+ /**
7
+ * SMPTE ST 2084 EOTF: decode a PQ signal back to linear light. Exact inverse of {@link linear_to_PQ}.
8
+ *
9
+ * NORMALIZATION: the result is in PQ's own scale, where `1.0` is {@link PQ_MAX_NITS} (10000 nits) --
10
+ * not the engine's, where `1.0` is {@link SDR_WHITE_NITS} (203 nits). Divide by
11
+ * {@link PQ_SDR_WHITE_LINEAR} to land back in engine units:
12
+ *
13
+ * ```js
14
+ * const engine_linear = PQ_to_linear(pq) / PQ_SDR_WHITE_LINEAR;
15
+ * ```
16
+ *
17
+ * The numerator is clamped at `0` so that signals below `linear_to_PQ(0)` -- which the standard's
18
+ * curve never produces, but quantization and interpolation both do -- decode to black instead of
19
+ * `NaN` from a negative base under a fractional exponent.
20
+ *
21
+ * @param {number} pq PQ signal, in `[0, 1]`
22
+ * @returns {number} linear light, `1.0` = 10000 nits
23
+ * @see linear_to_PQ
24
+ *
25
+ * @author Alex Goldring
26
+ * @copyright Company Named Limited (c) 2026
27
+ */
28
+ export function PQ_to_linear(pq) {
29
+ const y = Math.pow(pq, PQ_M2_INV);
30
+
31
+ const numerator = Math.max(y - PQ_C1, 0);
32
+ const denominator = PQ_C2 - PQ_C3 * y;
33
+
34
+ return Math.pow(numerator / denominator, PQ_M1_INV);
35
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * SMPTE ST 2084 OETF: encode a linear light value as a PQ signal.
3
+ *
4
+ * NORMALIZATION: the input is in PQ's own scale, where `1.0` is {@link PQ_MAX_NITS} (10000 nits) --
5
+ * not the engine's, where `1.0` is {@link SDR_WHITE_NITS} (203 nits). Scale an engine-normalized
6
+ * value by {@link PQ_SDR_WHITE_LINEAR} first:
7
+ *
8
+ * ```js
9
+ * const pq = linear_to_PQ(engine_linear * PQ_SDR_WHITE_LINEAR);
10
+ * ```
11
+ *
12
+ * Keeping the transfer function free of any white-point convention is what makes it and
13
+ * {@link PQ_to_linear} an exact inverse pair; a scale folded into one side but not the other is the
14
+ * classic way this round-trip ends up off by a factor of ~49.
15
+ *
16
+ * Negative input is clamped to `0`, since `Math.pow` of a negative base to a fractional exponent is
17
+ * `NaN` and a linear light value below zero has no PQ encoding to give.
18
+ *
19
+ * @param {number} linear linear light, `1.0` = 10000 nits
20
+ * @returns {number} PQ signal, in `[0, 1]`
21
+ * @see PQ_to_linear
22
+ *
23
+ * @author Alex Goldring
24
+ * @copyright Company Named Limited (c) 2026
25
+ */
26
+ export function linear_to_PQ(linear: number): number;
27
+ //# sourceMappingURL=linear_to_PQ.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear_to_PQ.d.ts","sourceRoot":"","sources":["../../../../../src/core/color/PQ/linear_to_PQ.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qCAPW,MAAM,GACJ,MAAM,CAUlB"}
@@ -0,0 +1,32 @@
1
+ import { PQ_C1, PQ_C2, PQ_C3, PQ_M1, PQ_M2 } from "./PQ_constants.js";
2
+
3
+ /**
4
+ * SMPTE ST 2084 OETF: encode a linear light value as a PQ signal.
5
+ *
6
+ * NORMALIZATION: the input is in PQ's own scale, where `1.0` is {@link PQ_MAX_NITS} (10000 nits) --
7
+ * not the engine's, where `1.0` is {@link SDR_WHITE_NITS} (203 nits). Scale an engine-normalized
8
+ * value by {@link PQ_SDR_WHITE_LINEAR} first:
9
+ *
10
+ * ```js
11
+ * const pq = linear_to_PQ(engine_linear * PQ_SDR_WHITE_LINEAR);
12
+ * ```
13
+ *
14
+ * Keeping the transfer function free of any white-point convention is what makes it and
15
+ * {@link PQ_to_linear} an exact inverse pair; a scale folded into one side but not the other is the
16
+ * classic way this round-trip ends up off by a factor of ~49.
17
+ *
18
+ * Negative input is clamped to `0`, since `Math.pow` of a negative base to a fractional exponent is
19
+ * `NaN` and a linear light value below zero has no PQ encoding to give.
20
+ *
21
+ * @param {number} linear linear light, `1.0` = 10000 nits
22
+ * @returns {number} PQ signal, in `[0, 1]`
23
+ * @see PQ_to_linear
24
+ *
25
+ * @author Alex Goldring
26
+ * @copyright Company Named Limited (c) 2026
27
+ */
28
+ export function linear_to_PQ(linear) {
29
+ const y = Math.pow(Math.max(linear, 0), PQ_M1);
30
+
31
+ return Math.pow((PQ_C1 + PQ_C2 * y) / (1 + PQ_C3 * y), PQ_M2);
32
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The first face on `edge_id`'s radial cycle that is not `face_id`, or {@link NULL_POINTER} when the
3
+ * edge has no such face.
4
+ *
5
+ * On a manifold interior edge -- two faces -- that is unambiguously "the face on the other side", which
6
+ * is what callers walking across a mesh want. The other configurations are worth being explicit about,
7
+ * because the answer is only interesting in the first case:
8
+ *
9
+ * - a wire edge (no faces) or a boundary edge whose one face IS `face_id`: {@link NULL_POINTER};
10
+ * - a non-manifold edge (three faces or more): whichever of the others the radial base pointer
11
+ * happens to reach first, which a caller cannot predict. Use {@link bt_edge_count_faces} first if
12
+ * that distinction matters;
13
+ * - an edge that `face_id` uses twice: the walk skips both of its loops and reports a genuine
14
+ * neighbour, rather than reporting `face_id` back.
15
+ *
16
+ * Passing a `face_id` that is not on the edge at all is not detected -- it simply reports the edge's
17
+ * first face. That matches {@link bt_edge_other_vertex}, whose contract is the same shape.
18
+ *
19
+ * The walk is bounded by the loop pool's size, so a corrupted radial cycle cannot hang the caller; a
20
+ * cycle that long is broken, and {@link bt_mesh_validate} will say where.
21
+ *
22
+ * @param {BinaryTopology} mesh
23
+ * @param {number} edge_id
24
+ * @param {number} face_id the face already known to sit on this edge
25
+ * @returns {number} face id, or {@link NULL_POINTER}
26
+ * @see bt_edge_other_vertex
27
+ *
28
+ * @author Alex Goldring
29
+ * @copyright Company Named Limited (c) 2026
30
+ */
31
+ export function bt_edge_other_face(mesh: BinaryTopology, edge_id: number, face_id: number): number;
32
+ //# sourceMappingURL=bt_edge_other_face.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bt_edge_other_face.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/geom/3d/topology/struct/binary/query/bt_edge_other_face.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,kEARW,MAAM,WACN,MAAM,GACJ,MAAM,CAgClB"}
@@ -0,0 +1,59 @@
1
+ import { NULL_POINTER } from "../BinaryTopology.js";
2
+
3
+ /**
4
+ * The first face on `edge_id`'s radial cycle that is not `face_id`, or {@link NULL_POINTER} when the
5
+ * edge has no such face.
6
+ *
7
+ * On a manifold interior edge -- two faces -- that is unambiguously "the face on the other side", which
8
+ * is what callers walking across a mesh want. The other configurations are worth being explicit about,
9
+ * because the answer is only interesting in the first case:
10
+ *
11
+ * - a wire edge (no faces) or a boundary edge whose one face IS `face_id`: {@link NULL_POINTER};
12
+ * - a non-manifold edge (three faces or more): whichever of the others the radial base pointer
13
+ * happens to reach first, which a caller cannot predict. Use {@link bt_edge_count_faces} first if
14
+ * that distinction matters;
15
+ * - an edge that `face_id` uses twice: the walk skips both of its loops and reports a genuine
16
+ * neighbour, rather than reporting `face_id` back.
17
+ *
18
+ * Passing a `face_id` that is not on the edge at all is not detected -- it simply reports the edge's
19
+ * first face. That matches {@link bt_edge_other_vertex}, whose contract is the same shape.
20
+ *
21
+ * The walk is bounded by the loop pool's size, so a corrupted radial cycle cannot hang the caller; a
22
+ * cycle that long is broken, and {@link bt_mesh_validate} will say where.
23
+ *
24
+ * @param {BinaryTopology} mesh
25
+ * @param {number} edge_id
26
+ * @param {number} face_id the face already known to sit on this edge
27
+ * @returns {number} face id, or {@link NULL_POINTER}
28
+ * @see bt_edge_other_vertex
29
+ *
30
+ * @author Alex Goldring
31
+ * @copyright Company Named Limited (c) 2026
32
+ */
33
+ export function bt_edge_other_face(mesh, edge_id, face_id) {
34
+ const start = mesh.edge_read_loop(edge_id);
35
+
36
+ if (start === NULL_POINTER) {
37
+ // wire edge, no faces at all
38
+ return NULL_POINTER;
39
+ }
40
+
41
+ const limit = mesh.loops.size;
42
+
43
+ let loop = start;
44
+ let visited = 0;
45
+
46
+ do {
47
+ const face = mesh.loop_read_face(loop);
48
+
49
+ if (face !== face_id) {
50
+ return face;
51
+ }
52
+
53
+ visited++;
54
+
55
+ loop = mesh.loop_read_radial_next(loop);
56
+ } while (loop !== start && loop !== NULL_POINTER && visited <= limit);
57
+
58
+ return NULL_POINTER;
59
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The corner of `face_id` that lies on neither end of `edge_id` -- the apex of a triangle over that
3
+ * edge -- or {@link NULL_POINTER} when the face has no such corner.
4
+ *
5
+ * The edge is read for its two endpoints only; it is never required to be one of the face's own edges.
6
+ * A caller that hands over an unrelated edge therefore gets the face's first corner rather than an
7
+ * error, the same contract {@link bt_edge_other_vertex} carries.
8
+ *
9
+ * "Third" names the intended use, not a precondition: the face loop is walked to its end, so a polygon
10
+ * with more corners reports the first one clear of the edge. {@link NULL_POINTER} comes back for an
11
+ * face with no loop, and for a face all of whose corners sit on the edge -- a degenerate triangle with a
12
+ * repeated vertex, which {@link bt_mesh_validate} counts as a defect.
13
+ *
14
+ * The walk is bounded by the loop pool's size, so a corrupted face cycle cannot hang the caller.
15
+ *
16
+ * @param {BinaryTopology} mesh
17
+ * @param {number} face_id
18
+ * @param {number} edge_id
19
+ * @returns {number} vertex id, or {@link NULL_POINTER}
20
+ * @see bt_edge_read_diamond for both apexes of an interior edge, with winding respected
21
+ *
22
+ * @author Alex Goldring
23
+ * @copyright Company Named Limited (c) 2026
24
+ */
25
+ export function bt_face_third_vertex(mesh: BinaryTopology, face_id: number, edge_id: number): number;
26
+ //# sourceMappingURL=bt_face_third_vertex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bt_face_third_vertex.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/geom/3d/topology/struct/binary/query/bt_face_third_vertex.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,oEARW,MAAM,WACN,MAAM,GACJ,MAAM,CAkClB"}
@@ -0,0 +1,55 @@
1
+ import { NULL_POINTER } from "../BinaryTopology.js";
2
+
3
+ /**
4
+ * The corner of `face_id` that lies on neither end of `edge_id` -- the apex of a triangle over that
5
+ * edge -- or {@link NULL_POINTER} when the face has no such corner.
6
+ *
7
+ * The edge is read for its two endpoints only; it is never required to be one of the face's own edges.
8
+ * A caller that hands over an unrelated edge therefore gets the face's first corner rather than an
9
+ * error, the same contract {@link bt_edge_other_vertex} carries.
10
+ *
11
+ * "Third" names the intended use, not a precondition: the face loop is walked to its end, so a polygon
12
+ * with more corners reports the first one clear of the edge. {@link NULL_POINTER} comes back for an
13
+ * face with no loop, and for a face all of whose corners sit on the edge -- a degenerate triangle with a
14
+ * repeated vertex, which {@link bt_mesh_validate} counts as a defect.
15
+ *
16
+ * The walk is bounded by the loop pool's size, so a corrupted face cycle cannot hang the caller.
17
+ *
18
+ * @param {BinaryTopology} mesh
19
+ * @param {number} face_id
20
+ * @param {number} edge_id
21
+ * @returns {number} vertex id, or {@link NULL_POINTER}
22
+ * @see bt_edge_read_diamond for both apexes of an interior edge, with winding respected
23
+ *
24
+ * @author Alex Goldring
25
+ * @copyright Company Named Limited (c) 2026
26
+ */
27
+ export function bt_face_third_vertex(mesh, face_id, edge_id) {
28
+ const start = mesh.face_read_loop(face_id);
29
+
30
+ if (start === NULL_POINTER) {
31
+ return NULL_POINTER;
32
+ }
33
+
34
+ const edge_v1 = mesh.edge_read_vertex1(edge_id);
35
+ const edge_v2 = mesh.edge_read_vertex2(edge_id);
36
+
37
+ const limit = mesh.loops.size;
38
+
39
+ let loop = start;
40
+ let visited = 0;
41
+
42
+ do {
43
+ const vertex = mesh.loop_read_vertex(loop);
44
+
45
+ if (vertex !== edge_v1 && vertex !== edge_v2) {
46
+ return vertex;
47
+ }
48
+
49
+ visited++;
50
+
51
+ loop = mesh.loop_read_next(loop);
52
+ } while (loop !== start && loop !== NULL_POINTER && visited <= limit);
53
+
54
+ return NULL_POINTER;
55
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"populateEngineSerializationRegistry.d.ts","sourceRoot":"","sources":["../../../../../src/engine/ecs/storage/populateEngineSerializationRegistry.js"],"names":[],"mappings":"AAkKA;;;;;;;;GAQG;AACH,iGA6FC"}
1
+ {"version":3,"file":"populateEngineSerializationRegistry.d.ts","sourceRoot":"","sources":["../../../../../src/engine/ecs/storage/populateEngineSerializationRegistry.js"],"names":[],"mappings":"AAqKA;;;;;;;;GAQG;AACH,iGA8FC"}