@ifc-lite/renderer 1.49.1 → 1.50.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 (82) hide show
  1. package/dist/camera.d.ts +24 -0
  2. package/dist/camera.d.ts.map +1 -1
  3. package/dist/camera.js +72 -1
  4. package/dist/camera.js.map +1 -1
  5. package/dist/device.d.ts +11 -0
  6. package/dist/device.d.ts.map +1 -1
  7. package/dist/device.js +71 -5
  8. package/dist/device.js.map +1 -1
  9. package/dist/frame-timing-cpu.d.ts +31 -0
  10. package/dist/frame-timing-cpu.d.ts.map +1 -0
  11. package/dist/frame-timing-cpu.js +25 -0
  12. package/dist/frame-timing-cpu.js.map +1 -0
  13. package/dist/frame-timing-gpu.d.ts +144 -0
  14. package/dist/frame-timing-gpu.d.ts.map +1 -0
  15. package/dist/frame-timing-gpu.js +176 -0
  16. package/dist/frame-timing-gpu.js.map +1 -0
  17. package/dist/frame-timing-stats.d.ts +100 -0
  18. package/dist/frame-timing-stats.d.ts.map +1 -0
  19. package/dist/frame-timing-stats.js +116 -0
  20. package/dist/frame-timing-stats.js.map +1 -0
  21. package/dist/frame-timing.d.ts +102 -0
  22. package/dist/frame-timing.d.ts.map +1 -0
  23. package/dist/frame-timing.js +112 -0
  24. package/dist/frame-timing.js.map +1 -0
  25. package/dist/index.d.ts +35 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +229 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/lod-simplify.d.ts +0 -9
  30. package/dist/lod-simplify.d.ts.map +1 -1
  31. package/dist/lod-simplify.js +45 -7
  32. package/dist/lod-simplify.js.map +1 -1
  33. package/dist/pipeline.d.ts +16 -0
  34. package/dist/pipeline.d.ts.map +1 -1
  35. package/dist/pipeline.js +89 -3
  36. package/dist/pipeline.js.map +1 -1
  37. package/dist/pointcloud/point-cloud-spatial-index.d.ts +81 -9
  38. package/dist/pointcloud/point-cloud-spatial-index.d.ts.map +1 -1
  39. package/dist/pointcloud/point-cloud-spatial-index.js +140 -29
  40. package/dist/pointcloud/point-cloud-spatial-index.js.map +1 -1
  41. package/dist/raycast-engine.d.ts.map +1 -1
  42. package/dist/raycast-engine.js +10 -3
  43. package/dist/raycast-engine.js.map +1 -1
  44. package/dist/scene.d.ts.map +1 -1
  45. package/dist/scene.js +42 -5
  46. package/dist/scene.js.map +1 -1
  47. package/dist/section-2d-overlay.d.ts.map +1 -1
  48. package/dist/section-2d-overlay.js +6 -3
  49. package/dist/section-2d-overlay.js.map +1 -1
  50. package/dist/section-plane.d.ts +11 -0
  51. package/dist/section-plane.d.ts.map +1 -1
  52. package/dist/section-plane.js.map +1 -1
  53. package/dist/shaders/main.wgsl.d.ts +1 -1
  54. package/dist/shaders/main.wgsl.d.ts.map +1 -1
  55. package/dist/shaders/main.wgsl.js +87 -4
  56. package/dist/shaders/main.wgsl.js.map +1 -1
  57. package/dist/shaders/shadow.wgsl.d.ts +31 -0
  58. package/dist/shaders/shadow.wgsl.d.ts.map +1 -0
  59. package/dist/shaders/shadow.wgsl.js +141 -0
  60. package/dist/shaders/shadow.wgsl.js.map +1 -0
  61. package/dist/shadow-light-matrix.d.ts +106 -0
  62. package/dist/shadow-light-matrix.d.ts.map +1 -0
  63. package/dist/shadow-light-matrix.js +185 -0
  64. package/dist/shadow-light-matrix.js.map +1 -0
  65. package/dist/shadow-occluders.d.ts +106 -0
  66. package/dist/shadow-occluders.d.ts.map +1 -0
  67. package/dist/shadow-occluders.js +156 -0
  68. package/dist/shadow-occluders.js.map +1 -0
  69. package/dist/shadow-pass.d.ts +132 -0
  70. package/dist/shadow-pass.d.ts.map +1 -0
  71. package/dist/shadow-pass.js +342 -0
  72. package/dist/shadow-pass.js.map +1 -0
  73. package/dist/snap-detector.d.ts +10 -0
  74. package/dist/snap-detector.d.ts.map +1 -1
  75. package/dist/snap-detector.js.map +1 -1
  76. package/dist/symbolic-overlay-pipelines.d.ts +35 -0
  77. package/dist/symbolic-overlay-pipelines.d.ts.map +1 -1
  78. package/dist/symbolic-overlay-pipelines.js +31 -15
  79. package/dist/symbolic-overlay-pipelines.js.map +1 -1
  80. package/dist/types.d.ts +40 -0
  81. package/dist/types.d.ts.map +1 -1
  82. package/package.json +3 -3
@@ -0,0 +1,141 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ /**
5
+ * Sun shadow-map depth pre-pass shaders (issue #2670, Phase 2).
6
+ *
7
+ * Depth-only: each entry point transforms a vertex to the sun's light clip
8
+ * space and lets the depth test record the closest occluder. There is ONE
9
+ * entry point per geometry path, and each MUST reproduce the world-space
10
+ * position its main-shader counterpart computes, or that path silently stops
11
+ * casting (the divergence class #2670's acceptance test guards):
12
+ *
13
+ * • vs_shadow_flat ← vs_main worldPos = model * position
14
+ * • vs_shadow_quantized ← vs_main_quantized worldPos = model * dequant(q)
15
+ * • vs_shadow_instanced ← vs_instanced worldPos = instMat * position
16
+ * • vs_shadow_textured ← textured vs_main worldPos = model * position
17
+ *
18
+ * `lightViewProj` replaces the camera `viewProj`; `model`/`quantParams` come
19
+ * per-draw via a dynamic-offset uniform (unused by the instanced path, whose
20
+ * per-occurrence matrix arrives on vertex slot 1). The anti-z-fighting depth
21
+ * nudge from the main shaders is deliberately omitted — it perturbs camera
22
+ * clip depth for coplanar-face separation and has no meaning in light space.
23
+ *
24
+ * CLIPPING. Every entry point also passes world position to `fs_shadow_clip`,
25
+ * which re-applies the section plane and clip box exactly as `fs_main` does,
26
+ * so geometry a section cut removed from view stops casting too (otherwise the
27
+ * cut-away roof keeps shadowing the floor it no longer covers). The fragment
28
+ * stage is only attached when a clip is actually active — with no clipping the
29
+ * pipelines stay fragment-less, keeping the double-speed depth-only path — and
30
+ * the unused `worldPos` output on those pipelines costs an interpolator, not a
31
+ * shader stage.
32
+ */
33
+ export const shadowShaderSource = `
34
+ struct Light {
35
+ lightViewProj: mat4x4<f32>,
36
+ }
37
+ @binding(0) @group(0) var<uniform> light: Light;
38
+
39
+ struct Draw {
40
+ model: mat4x4<f32>,
41
+ // xyz = lattice-aligned quantMin (batch-origin-relative), w = step.
42
+ // Read only by vs_shadow_quantized; zero for the other paths.
43
+ quantParams: vec4<f32>,
44
+ }
45
+ @binding(1) @group(0) var<uniform> draw: Draw;
46
+
47
+ struct Clip {
48
+ // xyz = plane normal, w = plane distance (world space).
49
+ sectionPlane: vec4<f32>,
50
+ clipBoxMin: vec4<f32>,
51
+ clipBoxMax: vec4<f32>,
52
+ // x packs: bit 0 = section enabled, bit 1 = section flipped,
53
+ // bit 2 = clip box enabled. Mirrors main.wgsl's flags.y.
54
+ flags: vec4<u32>,
55
+ }
56
+ @binding(2) @group(0) var<uniform> clip: Clip;
57
+
58
+ struct FlatIn {
59
+ @location(0) position: vec3<f32>,
60
+ }
61
+
62
+ struct QuantIn {
63
+ @location(0) q: vec4<u32>, // uint16x4: lattice x, y, z, packedOct
64
+ }
65
+
66
+ struct InstanceIn {
67
+ @location(3) m0: vec4<f32>,
68
+ @location(4) m1: vec4<f32>,
69
+ @location(5) m2: vec4<f32>,
70
+ @location(6) m3: vec4<f32>,
71
+ // Per-occurrence flags lane (bit 1 = hidden), same instance buffer the
72
+ // colour pass reads. Locations 7 (entityId) and 8 (rgba) are unused by
73
+ // the depth pass and left unbound.
74
+ @location(9) flags: u32,
75
+ }
76
+
77
+ struct ShadowOut {
78
+ @builtin(position) position: vec4<f32>,
79
+ @location(0) worldPos: vec3<f32>,
80
+ }
81
+
82
+ fn emit(worldPos: vec4<f32>) -> ShadowOut {
83
+ var out: ShadowOut;
84
+ out.position = light.lightViewProj * worldPos;
85
+ out.worldPos = worldPos.xyz;
86
+ return out;
87
+ }
88
+
89
+ @vertex
90
+ fn vs_shadow_flat(input: FlatIn) -> ShadowOut {
91
+ return emit(draw.model * vec4<f32>(input.position, 1.0));
92
+ }
93
+
94
+ @vertex
95
+ fn vs_shadow_quantized(input: QuantIn) -> ShadowOut {
96
+ let p = draw.quantParams.xyz
97
+ + vec3<f32>(f32(input.q.x), f32(input.q.y), f32(input.q.z)) * draw.quantParams.w;
98
+ return emit(draw.model * vec4<f32>(p, 1.0));
99
+ }
100
+
101
+ @vertex
102
+ fn vs_shadow_instanced(input: FlatIn, inst: InstanceIn) -> ShadowOut {
103
+ // A hidden / isolation-excluded occurrence (flags bit 1) must not cast:
104
+ // the colour pass discards it in fs_main, so collapse it to a zero-area
105
+ // triangle here (every vertex to one clip point) — the rasterizer drops
106
+ // it and it writes no depth. Mirrors main.wgsl's instSelected & 2u check.
107
+ if ((inst.flags & 2u) != 0u) {
108
+ var out: ShadowOut;
109
+ out.position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
110
+ out.worldPos = vec3<f32>(0.0, 0.0, 0.0);
111
+ return out;
112
+ }
113
+ let instMat = mat4x4<f32>(inst.m0, inst.m1, inst.m2, inst.m3);
114
+ return emit(instMat * vec4<f32>(input.position, 1.0));
115
+ }
116
+
117
+ @vertex
118
+ fn vs_shadow_textured(input: FlatIn) -> ShadowOut {
119
+ return emit(draw.model * vec4<f32>(input.position, 1.0));
120
+ }
121
+
122
+ // Clipped variant: discard before the depth write, so a clipped-away
123
+ // occluder leaves the shadow map untouched. Kept byte-for-byte in step
124
+ // with the fs_main section/clip-box branches.
125
+ @fragment
126
+ fn fs_shadow_clip(@location(0) worldPos: vec3<f32>) {
127
+ if ((clip.flags.x & 1u) == 1u) {
128
+ let side = select(1.0, -1.0, (clip.flags.x & 2u) == 2u);
129
+ let distToPlane = (dot(worldPos, clip.sectionPlane.xyz) - clip.sectionPlane.w) * side;
130
+ if (distToPlane > 0.0) {
131
+ discard;
132
+ }
133
+ }
134
+ if ((clip.flags.x & 4u) != 0u) {
135
+ if (any(worldPos < clip.clipBoxMin.xyz) || any(worldPos > clip.clipBoxMax.xyz)) {
136
+ discard;
137
+ }
138
+ }
139
+ }
140
+ `;
141
+ //# sourceMappingURL=shadow.wgsl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadow.wgsl.js","sourceRoot":"","sources":["../../src/shaders/shadow.wgsl.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2GjC,CAAC"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Sun shadow-map light matrix (issue #2670, Phase 2).
3
+ *
4
+ * Builds the orthographic light-view-projection the sun shadow depth pass
5
+ * renders occluders through. A directional sun is an orthographic camera:
6
+ * the frustum is a box whose axis is the (negated) sun direction, and whose
7
+ * cross-section must cover everything that can cast into the view.
8
+ *
9
+ * Fitting policy: by default the lateral (x/y) extent is fitted to the whole
10
+ * model AABB — a single, well-conditioned cascade that is correct at
11
+ * building scale. A caller MAY instead pass `focusCorners` (e.g. the camera
12
+ * frustum clipped to the model, for a site-scale view-fitted map, maintainer
13
+ * constraint #1 on #2670) and the lateral fit tightens to those; that path is
14
+ * a documented follow-up and needs a proper cascade to be robust against a
15
+ * reverse-Z / infinite-far camera, so the live renderer uses the bounds fit.
16
+ *
17
+ * The DEPTH range is deliberately NOT fitted to the focus alone: an occluder
18
+ * BEHIND the visible region along the sun axis (a roof over the room you are
19
+ * looking at) still has to be in the map to cast into it, so the near plane is
20
+ * pushed back to enclose the whole model along the light direction while the
21
+ * lateral (x/y) extent stays fitted to the focus.
22
+ *
23
+ * Everything here is pure and framework-free so it can be unit-tested without
24
+ * a GPU; the depth pass (`shadow-pass.ts`) consumes {@link SunLightFit}.
25
+ */
26
+ import type { Mat4, Vec3 } from './types.js';
27
+ export interface SunLightFitParams {
28
+ /**
29
+ * Unit vector TOWARD the sun in viewer/world space (Y-up), matching the
30
+ * shading path's `env.sunDirection` convention. Normalized defensively; a
31
+ * non-finite or zero vector falls back to straight-down sun (Y-up).
32
+ */
33
+ sunDirection: readonly [number, number, number];
34
+ /** World-space model AABB min corner. */
35
+ boundsMin: readonly [number, number, number];
36
+ /** World-space model AABB max corner. */
37
+ boundsMax: readonly [number, number, number];
38
+ /**
39
+ * Eight corners of the camera frustum clipped to the model bounds, world
40
+ * space. When provided the lateral fit uses these (tight, view-fitted map);
41
+ * when omitted or shorter than 4 the model AABB corners are used instead.
42
+ */
43
+ focusCorners?: readonly Vec3[];
44
+ /**
45
+ * Fractional slack added around the fitted lateral extent so a PCF/normal
46
+ * -offset kernel near the map edge still has neighbours to sample. Default
47
+ * 0.05 (5%). Clamped to [0, 1].
48
+ */
49
+ lateralPadding?: number;
50
+ }
51
+ export interface SunLightFit {
52
+ /** Column-major light view-projection (`ortho * lightView`), reverse-Z. */
53
+ lightViewProj: Mat4;
54
+ /** Light view matrix on its own (world → light view space). */
55
+ lightView: Mat4;
56
+ /** Fitted ortho half-width in world units (light-space x). */
57
+ orthoHalfWidth: number;
58
+ /** Fitted ortho half-height in world units (light-space y). */
59
+ orthoHalfHeight: number;
60
+ /** Near→far depth span along the sun axis, world units. */
61
+ depthRange: number;
62
+ }
63
+ /**
64
+ * Fit the sun's orthographic shadow frustum. Pure; see the file header for the
65
+ * fitting policy. The returned matrix maps world space straight to the shadow
66
+ * map's clip space (reverse-Z, depth 1 at near → 0 at far), so the depth pass
67
+ * uses it exactly where the main pass uses the camera `viewProj`.
68
+ */
69
+ export declare function fitSunLightMatrix(params: SunLightFitParams): SunLightFit;
70
+ export interface CameraFrustumParams {
71
+ /** Camera eye (world). */
72
+ eye: Vec3;
73
+ /** Unit view direction (eye → target). */
74
+ forward: Vec3;
75
+ /** Unit screen-right. */
76
+ right: Vec3;
77
+ /** Unit screen-up. */
78
+ up: Vec3;
79
+ /** Vertical FOV in radians (perspective). */
80
+ fovY: number;
81
+ /** Viewport width / height. */
82
+ aspect: number;
83
+ /** Orthographic projection? Then `orthoHalfHeight` sizes the frustum. */
84
+ ortho: boolean;
85
+ /** Half-height of the ortho view volume, world units (ortho only). */
86
+ orthoHalfHeight: number;
87
+ boundsMin: readonly [number, number, number];
88
+ boundsMax: readonly [number, number, number];
89
+ /** Lateral expansion so a caster just off-screen still reaches the map. */
90
+ margin?: number;
91
+ }
92
+ /**
93
+ * Eight corners of the camera frustum, clipped to the model AABB — the region
94
+ * the camera can actually see (maintainer constraint #1 on #2670). Passing
95
+ * these to {@link fitSunLightMatrix} as `focusCorners` concentrates the shadow
96
+ * map on visible geometry instead of a site-scale model's whole footprint,
97
+ * which is what makes distant terrain steal all the resolution.
98
+ *
99
+ * Unlike unprojecting the clip cube, this builds the frustum from the camera
100
+ * BASIS at finite near/far distances derived from where the model sits along
101
+ * the view axis, so it never depends on a reverse-Z / infinite-far plane.
102
+ * Returns `null` when the model is entirely behind the camera (no usable
103
+ * focus — the caller falls back to a whole-bounds fit).
104
+ */
105
+ export declare function cameraFrustumFocusCorners(p: CameraFrustumParams): Vec3[] | null;
106
+ //# sourceMappingURL=shadow-light-matrix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadow-light-matrix.d.ts","sourceRoot":"","sources":["../src/shadow-light-matrix.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,YAAY,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,yCAAyC;IACzC,SAAS,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,yCAAyC;IACzC,SAAS,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC;IAC/B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,2EAA2E;IAC3E,aAAa,EAAE,IAAI,CAAC;IACpB,+DAA+D;IAC/D,SAAS,EAAE,IAAI,CAAC;IAChB,8DAA8D;IAC9D,cAAc,EAAE,MAAM,CAAC;IACvB,+DAA+D;IAC/D,eAAe,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;CACpB;AAwCD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,WAAW,CA0FxE;AAED,MAAM,WAAW,mBAAmB;IAClC,0BAA0B;IAC1B,GAAG,EAAE,IAAI,CAAC;IACV,0CAA0C;IAC1C,OAAO,EAAE,IAAI,CAAC;IACd,yBAAyB;IACzB,KAAK,EAAE,IAAI,CAAC;IACZ,sBAAsB;IACtB,EAAE,EAAE,IAAI,CAAC;IACT,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,KAAK,EAAE,OAAO,CAAC;IACf,sEAAsE;IACtE,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,SAAS,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,mBAAmB,GAAG,IAAI,EAAE,GAAG,IAAI,CAoC/E"}
@@ -0,0 +1,185 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ import { MathUtils } from './math.js';
5
+ const DEFAULT_SUN = { x: 0, y: 1, z: 0 };
6
+ /** Unit sun direction, falling back to straight down-up for unusable input. */
7
+ function resolveSun(dir) {
8
+ const x = dir[0], y = dir[1], z = dir[2];
9
+ const lenSq = x * x + y * y + z * z;
10
+ // Finiteness matters as much as length (#2489): an Infinity component makes
11
+ // the length Infinity and the divide below NaN, so guard both ends.
12
+ if (!(lenSq > 1e-12 && lenSq < Infinity))
13
+ return DEFAULT_SUN;
14
+ const inv = 1 / Math.sqrt(lenSq);
15
+ return { x: x * inv, y: y * inv, z: z * inv };
16
+ }
17
+ /** The eight corners of an axis-aligned box. */
18
+ function aabbCorners(min, max) {
19
+ const corners = [];
20
+ for (let i = 0; i < 8; i++) {
21
+ corners.push({
22
+ x: (i & 1) ? max[0] : min[0],
23
+ y: (i & 2) ? max[1] : min[1],
24
+ z: (i & 4) ? max[2] : min[2],
25
+ });
26
+ }
27
+ return corners;
28
+ }
29
+ /** Transform a world point by a column-major Mat4 (w = 1), returning xyz. */
30
+ function transformPoint(m, p) {
31
+ return {
32
+ x: m[0] * p.x + m[4] * p.y + m[8] * p.z + m[12],
33
+ y: m[1] * p.x + m[5] * p.y + m[9] * p.z + m[13],
34
+ z: m[2] * p.x + m[6] * p.y + m[10] * p.z + m[14],
35
+ };
36
+ }
37
+ /**
38
+ * Fit the sun's orthographic shadow frustum. Pure; see the file header for the
39
+ * fitting policy. The returned matrix maps world space straight to the shadow
40
+ * map's clip space (reverse-Z, depth 1 at near → 0 at far), so the depth pass
41
+ * uses it exactly where the main pass uses the camera `viewProj`.
42
+ */
43
+ export function fitSunLightMatrix(params) {
44
+ const sun = resolveSun(params.sunDirection);
45
+ const modelCorners = aabbCorners(params.boundsMin, params.boundsMax);
46
+ // Lateral fit target: the view-clipped frustum corners when usable, else the
47
+ // whole model. Depth always encloses the whole model (see header).
48
+ const lateralPts = params.focusCorners && params.focusCorners.length >= 4
49
+ ? params.focusCorners
50
+ : modelCorners;
51
+ // Centre the light view on the lateral focus so the fitted x/y box is
52
+ // symmetric around the region of interest.
53
+ let cx = 0, cy = 0, cz = 0;
54
+ for (const p of lateralPts) {
55
+ cx += p.x;
56
+ cy += p.y;
57
+ cz += p.z;
58
+ }
59
+ const n = lateralPts.length;
60
+ const center = { x: cx / n, y: cy / n, z: cz / n };
61
+ // Light view: eye one unit up-sun of the centre, looking down the sun axis
62
+ // (forward = -sun). The eye offset only shifts light-space z uniformly; near
63
+ // /far below are derived from the transformed points, so its magnitude is
64
+ // immaterial. `up` comes from viewBasis's degeneracy handling for the
65
+ // straight-overhead sun where any fixed up would be parallel.
66
+ const eye = {
67
+ x: center.x + sun.x,
68
+ y: center.y + sun.y,
69
+ z: center.z + sun.z,
70
+ };
71
+ // A world-Y up hint, swapped to world-Z when the sun is near-vertical, keeps
72
+ // the basis well-conditioned; viewBasis substitutes its own hint if this is
73
+ // still parallel, so the result is always finite.
74
+ const upHint = Math.abs(sun.y) > 0.99 ? { x: 0, y: 0, z: 1 } : { x: 0, y: 1, z: 0 };
75
+ const lightView = MathUtils.lookAt(eye, center, upHint);
76
+ const lv = lightView.m;
77
+ // Lateral (x/y) extent from the focus points.
78
+ let minX = Infinity, maxX = -Infinity;
79
+ let minY = Infinity, maxY = -Infinity;
80
+ for (const p of lateralPts) {
81
+ const v = transformPoint(lv, p);
82
+ if (v.x < minX)
83
+ minX = v.x;
84
+ if (v.x > maxX)
85
+ maxX = v.x;
86
+ if (v.y < minY)
87
+ minY = v.y;
88
+ if (v.y > maxY)
89
+ maxY = v.y;
90
+ }
91
+ // Depth (z) extent from the WHOLE model, so occluders behind the focus still
92
+ // cast into it. In lookAt view space the camera looks down -Z, so scene
93
+ // points have negative z and their distance from the eye is -z.
94
+ let minZ = Infinity, maxZ = -Infinity;
95
+ for (const p of modelCorners) {
96
+ const vz = lv[2] * p.x + lv[6] * p.y + lv[10] * p.z + lv[14];
97
+ if (vz < minZ)
98
+ minZ = vz;
99
+ if (vz > maxZ)
100
+ maxZ = vz;
101
+ }
102
+ // Pad the lateral box so an edge texel's kernel still has neighbours.
103
+ const pad = Math.min(Math.max(params.lateralPadding ?? 0.05, 0), 1);
104
+ const halfW = Math.max((maxX - minX) * 0.5, 1e-4);
105
+ const halfH = Math.max((maxY - minY) * 0.5, 1e-4);
106
+ const midX = (minX + maxX) * 0.5;
107
+ const midY = (minY + maxY) * 0.5;
108
+ const padW = halfW * (1 + pad);
109
+ const padH = halfH * (1 + pad);
110
+ const left = midX - padW;
111
+ const right = midX + padW;
112
+ const bottom = midY - padH;
113
+ const top = midY + padH;
114
+ // Distances along the view direction. In lookAt view space scene points have
115
+ // negative z, so distance = -z: the nearest point (largest z) sets `near`,
116
+ // the farthest (smallest z) sets `far`. `near` is NOT clamped to 0 — the eye
117
+ // sits just up-sun of the FOCUS centre, so model geometry further up-sun is
118
+ // behind it (positive z ⇒ negative near), and clamping would push those
119
+ // occluders out of the depth range so they stopped casting. A small epsilon
120
+ // pads both ends against precision and a zero-thickness (flat) frustum.
121
+ const zEps = Math.max((maxZ - minZ) * 1e-3, 1e-3);
122
+ const near = -maxZ - zEps;
123
+ const far = -minZ + zEps;
124
+ const ortho = MathUtils.orthographicReverseZ(left, right, bottom, top, near, far);
125
+ const lightViewProj = MathUtils.multiply(ortho, lightView);
126
+ return {
127
+ lightViewProj,
128
+ lightView,
129
+ orthoHalfWidth: padW,
130
+ orthoHalfHeight: padH,
131
+ depthRange: far - near,
132
+ };
133
+ }
134
+ /**
135
+ * Eight corners of the camera frustum, clipped to the model AABB — the region
136
+ * the camera can actually see (maintainer constraint #1 on #2670). Passing
137
+ * these to {@link fitSunLightMatrix} as `focusCorners` concentrates the shadow
138
+ * map on visible geometry instead of a site-scale model's whole footprint,
139
+ * which is what makes distant terrain steal all the resolution.
140
+ *
141
+ * Unlike unprojecting the clip cube, this builds the frustum from the camera
142
+ * BASIS at finite near/far distances derived from where the model sits along
143
+ * the view axis, so it never depends on a reverse-Z / infinite-far plane.
144
+ * Returns `null` when the model is entirely behind the camera (no usable
145
+ * focus — the caller falls back to a whole-bounds fit).
146
+ */
147
+ export function cameraFrustumFocusCorners(p) {
148
+ const corners = aabbCorners(p.boundsMin, p.boundsMax);
149
+ // Depth span of the model along the view axis, relative to the eye.
150
+ let nearD = Infinity;
151
+ let farD = -Infinity;
152
+ for (const c of corners) {
153
+ const d = (c.x - p.eye.x) * p.forward.x + (c.y - p.eye.y) * p.forward.y + (c.z - p.eye.z) * p.forward.z;
154
+ if (d < nearD)
155
+ nearD = d;
156
+ if (d > farD)
157
+ farD = d;
158
+ }
159
+ if (!(farD > 1e-3))
160
+ return null; // model behind the camera
161
+ nearD = Math.max(nearD, 0.01);
162
+ const margin = Math.min(Math.max(p.margin ?? 0.15, 0), 2);
163
+ const half = (d) => {
164
+ const h = (p.ortho ? p.orthoHalfHeight : d * Math.tan(p.fovY / 2)) * (1 + margin);
165
+ return { w: h * p.aspect, h };
166
+ };
167
+ const out = [];
168
+ for (const d of [nearD, farD]) {
169
+ const { w, h } = half(d);
170
+ for (const sx of [-1, 1]) {
171
+ for (const sy of [-1, 1]) {
172
+ const x = p.eye.x + p.forward.x * d + p.right.x * (sx * w) + p.up.x * (sy * h);
173
+ const y = p.eye.y + p.forward.y * d + p.right.y * (sx * w) + p.up.y * (sy * h);
174
+ const z = p.eye.z + p.forward.z * d + p.right.z * (sx * w) + p.up.z * (sy * h);
175
+ out.push({
176
+ x: Math.min(Math.max(x, p.boundsMin[0]), p.boundsMax[0]),
177
+ y: Math.min(Math.max(y, p.boundsMin[1]), p.boundsMax[1]),
178
+ z: Math.min(Math.max(z, p.boundsMin[2]), p.boundsMax[2]),
179
+ });
180
+ }
181
+ }
182
+ }
183
+ return out;
184
+ }
185
+ //# sourceMappingURL=shadow-light-matrix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadow-light-matrix.js","sourceRoot":"","sources":["../src/shadow-light-matrix.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Occluder collection for the sun shadow depth pass (issue #2670, Phase 2a).
3
+ *
4
+ * Turns the scene's three GPU draw lists into a flat {@link ShadowOccluderDraw}
5
+ * array the {@link ShadowPass} rasterises. The whole point of this module is
6
+ * the maintainer's acceptance criterion: EVERY geometry path must cast, or part
7
+ * of the model silently stops shadowing. So each of the four paths —
8
+ *
9
+ * • flat colour batches → kind 'flat'
10
+ * • lattice-quantized batches → kind 'quantized'
11
+ * • GPU-instanced templates → kind 'instanced'
12
+ * • surface-textured meshes → kind 'textured'
13
+ *
14
+ * is handled here, and `occluder-collection.test.ts` asserts a scene carrying
15
+ * all four emits one occluder per path.
16
+ *
17
+ * Kept pure (no `Scene`, no GPU calls beyond passing buffer handles through) so
18
+ * it is unit-testable with plain fakes.
19
+ */
20
+ import type { BatchedMesh, Mesh } from './types.js';
21
+ import type { InstancedTemplateGPU, TexturedMesh } from './scene.js';
22
+ import type { ShadowOccluderDraw } from './shadow-pass.js';
23
+ /** The draw lists a `Scene` exposes, as the collector needs them. */
24
+ export interface ShadowOccluderSources {
25
+ batches: readonly BatchedMesh[];
26
+ instanced: readonly InstancedTemplateGPU[];
27
+ textured: readonly TexturedMesh[];
28
+ /**
29
+ * Individual meshes (the `Renderer.addMesh()` / no-batch fallback path).
30
+ * Optional — the batched viewer never populates it. Hydrated meshes (lazy
31
+ * selection-highlight copies that duplicate batch geometry) are skipped so
32
+ * they don't double-cast.
33
+ */
34
+ meshes?: readonly Mesh[];
35
+ }
36
+ /** Hide/isolate state, matching `RenderOptions`. */
37
+ export interface ShadowVisibility {
38
+ hiddenIds?: ReadonlySet<number>;
39
+ isolatedIds?: ReadonlySet<number>;
40
+ }
41
+ /** Tuning for {@link collectShadowOccluders}. */
42
+ export interface ShadowOccluderOptions {
43
+ /**
44
+ * Material-alpha floor for casting: geometry whose base colour alpha is below
45
+ * this does NOT cast a shadow, so light passes through it (#2670). This is
46
+ * how glass windows let daylight through their opening — the wall mesh keeps
47
+ * the void, the transparent glass filling it simply stops occluding — and it
48
+ * also spares the virtual transparent volumes (IfcSpace, IfcOpeningElement)
49
+ * from throwing solid shadows. Uses the MATERIAL alpha (`color[3]`), not the
50
+ * X-ray/ghost view overrides, so ghosting the model does not delete its
51
+ * shadows. Default {@link DEFAULT_MIN_CAST_ALPHA}.
52
+ */
53
+ minCastAlpha?: number;
54
+ }
55
+ /**
56
+ * Below this material alpha a surface is treated as glass-like and stops
57
+ * casting. Solid materials are 1.0; IFC glass / spaces / openings render well
58
+ * under it, while a lightly tinted-but-solid material (>= 0.9) still casts.
59
+ */
60
+ export declare const DEFAULT_MIN_CAST_ALPHA = 0.9;
61
+ /**
62
+ * Column-major model matrix for a per-batch/per-mesh local frame: identity
63
+ * rotation with the origin in the translation column, matching how the main
64
+ * pass reconstructs `world = origin + position` (the vertex buffers store
65
+ * origin-relative positions). Reused into `out` when provided to avoid a
66
+ * per-draw allocation on the hot path.
67
+ */
68
+ export declare function originModelMatrix(origin: readonly [number, number, number] | undefined, out?: Float32Array): Float32Array;
69
+ /**
70
+ * How a shared batch stands under the current hide/isolate state (#2670, Phase
71
+ * 2b), so the renderer can feed the shadow collector the SAME visible subset the
72
+ * colour pass draws:
73
+ * - `all` — no filtering, or every element visible → cast the batch's own buffers.
74
+ * - `none` — every element hidden/excluded → the batch does not cast at all.
75
+ * - `partial` — a mix → cast only `visibleIds` (via the colour pass's cached
76
+ * partial sub-batch), so an individually-hidden element stops casting instead
77
+ * of throwing a phantom shadow.
78
+ *
79
+ * Uses {@link isEntityVisible}, so the empty-isolate-hides-everything and
80
+ * hiding-wins-over-isolation rules match every other visibility surface exactly.
81
+ */
82
+ export type BatchVisibilityClass = {
83
+ kind: 'all';
84
+ } | {
85
+ kind: 'none';
86
+ } | {
87
+ kind: 'partial';
88
+ visibleIds: Set<number>;
89
+ };
90
+ export declare function classifyBatchVisibility(expressIds: readonly number[], hiddenIds: ReadonlySet<number> | null | undefined, isolatedIds: ReadonlySet<number> | null | undefined): BatchVisibilityClass;
91
+ /**
92
+ * Collect every occluder draw for the shadow depth pass.
93
+ *
94
+ * A batch casts if it is GPU-resident, opaque enough (material alpha >=
95
+ * `minCastAlpha` — transparent glass lets light through, see
96
+ * {@link ShadowOccluderOptions.minCastAlpha}) and has at least one visible
97
+ * element. Per-element hide/isolate within a shared batch is applied UPSTREAM:
98
+ * the renderer feeds this collector the same visible subset it draws (a
99
+ * fully-hidden batch is dropped, a partially-hidden one arrives as its visible
100
+ * sub-batch), so `sources.batches` is already visibility-correct. Instanced
101
+ * templates carry a per-occurrence HIDDEN flag the depth shader discards on, so
102
+ * a hidden/isolated occurrence stops casting. Textured meshes are filtered per
103
+ * element like the main textured sub-pass.
104
+ */
105
+ export declare function collectShadowOccluders(sources: ShadowOccluderSources, visibility?: ShadowVisibility, options?: ShadowOccluderOptions): ShadowOccluderDraw[];
106
+ //# sourceMappingURL=shadow-occluders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadow-occluders.d.ts","sourceRoot":"","sources":["../src/shadow-occluders.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAG3D,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IAChC,SAAS,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAC3C,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAClC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC;CAC1B;AAED,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAChC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACnC;AAED,iDAAiD;AACjD,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EACrD,GAAG,GAAE,YAAmC,GACvC,YAAY,CASd;AAgBD;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,GACf;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC;AAEjD,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,SAAS,EACjD,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,SAAS,GAClD,oBAAoB,CAYtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,qBAAqB,EAC9B,UAAU,CAAC,EAAE,gBAAgB,EAC7B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,kBAAkB,EAAE,CAgEtB"}