@navaramap/three-csm 0.1.1 → 0.2.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.
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2026 Navara contributors
1
+ Copyright (c) 2026 MapLibre contributors
2
2
 
3
3
  This project is dual-licensed under either of
4
4
 
package/dist/index.d.ts CHANGED
@@ -29,9 +29,14 @@ import { WebGLRenderer } from 'three';
29
29
  * left untouched, as are shaders compiled for the shadow-map depth pass
30
30
  * (`USE_SHADOWMAP_DEPTH` — they never sample shadows and would upload the
31
31
  * uniform before the shadow maps for the frame are rendered).
32
+ *
33
+ * The fragment shader gets `NVR_VIEW_SPACE_SHADOW` defined, so code that
34
+ * computes shadow coordinates there (`chunks/ground_shadow_coord_fragment`)
35
+ * takes the same path.
32
36
  */
33
37
  export declare function applyViewSpaceShadowReceive(shader: {
34
38
  vertexShader: string;
39
+ fragmentShader: string;
35
40
  defines?: Record<string, unknown>;
36
41
  uniforms: Record<string, {
37
42
  value: unknown;
@@ -248,29 +253,13 @@ declare class MaterialStates {
248
253
 
249
254
  export declare function overrideFragmentShaderForCSM(fragmentShader: string): string;
250
255
 
251
- /**
252
- * Uniform holding per-cascade directional shadow matrices composed against
253
- * the render camera's world matrix on the CPU: `light.shadow.matrix *
254
- * camera.matrixWorld` (f64).
255
- *
256
- * Sampling shadow maps with `directionalShadowMatrix * worldPosition` loses
257
- * precision on a globe: the absolute ECEF world position (~6.4e6 m) and the
258
- * shadow matrix translation cancel in f32 on the GPU, and because the CSM
259
- * cascade matrices follow the camera the rounding error changes every frame —
260
- * received shadows jitter while the geometry itself stays put.
261
- *
262
- * Folding the camera's world matrix in instead lets the shader use the
263
- * view-space position `mvPosition`, which is precise by construction for
264
- * every positioning mode: RTE meshes compute it from camera-relative
265
- * coordinates, RTC meshes get it from the CPU-composed f64 modelViewMatrix,
266
- * and instancing folds instanceMatrix into it. Shadow sampling then reuses
267
- * the exact quantity that renders stably, so it can never jitter relative to
268
- * the geometry — and a single patch covers every mesh type.
269
- */
270
256
  export declare type ShadowMatricesViewUniform = {
271
257
  value: Matrix4[];
272
258
  };
273
259
 
274
260
  export declare function splitFrustum(mode: FrustumSplitMode, count: number, near: number, far: number, lambda?: number, result?: number[]): number[];
275
261
 
262
+ /** Defined in the fragment shader of a material patched for view-space shadows. */
263
+ export declare const VIEW_SPACE_SHADOW_DEFINE = "NVR_VIEW_SPACE_SHADOW";
264
+
276
265
  export { }
package/dist/index.js CHANGED
@@ -138,24 +138,24 @@ function M(e) {
138
138
  }
139
139
  //#endregion
140
140
  //#region src/viewSpaceShadowReceive.ts
141
- var N = "\n#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n// Per-cascade shadow matrices composed with the render camera's world matrix\n// on the CPU in f64 (shadow.matrix * camera.matrixWorld). Multiplied with the\n// view-space position instead of the absolute world position to avoid f32\n// cancellation at ECEF magnitudes.\n//\n// Only the CSM cascade lights (the first CSM_CASCADE_COUNT shadow indices,\n// matching the ordering assumption of the CSM fragment shader) get these\n// matrices; additional shadow-casting directional lights keep the stock\n// directionalShadowMatrix path. Outside a CSM material the define is absent —\n// treat every directional shadow as camera-composed then.\n#ifndef CSM_CASCADE_COUNT\n#define CSM_CASCADE_COUNT NUM_DIR_LIGHT_SHADOWS\n#endif\nuniform mat4 nvrCsmShadowMatrixView[ CSM_CASCADE_COUNT ];\n#endif\n";
142
- function P(e, t, n) {
141
+ var N = "NVR_VIEW_SPACE_SHADOW", P = "\n#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n// Per-cascade shadow matrices composed with the render camera's world matrix\n// on the CPU in f64 (shadow.matrix * camera.matrixWorld). Multiplied with the\n// view-space position instead of the absolute world position to avoid f32\n// cancellation at ECEF magnitudes.\n//\n// Only the CSM cascade lights (the first CSM_CASCADE_COUNT shadow indices,\n// matching the ordering assumption of the CSM fragment shader) get these\n// matrices; additional shadow-casting directional lights keep the stock\n// directionalShadowMatrix path. Outside a CSM material the define is absent —\n// treat every directional shadow as camera-composed then.\n#ifndef CSM_CASCADE_COUNT\n#define CSM_CASCADE_COUNT NUM_DIR_LIGHT_SHADOWS\n#endif\nuniform mat4 nvrCsmShadowMatrixView[ CSM_CASCADE_COUNT ];\n#endif\n";
142
+ function F(e, t, n) {
143
143
  if (!e.includes(t)) throw Error(`Failed to replace "${t}" in shader code. The pattern was not found.`);
144
144
  return e.replace(t, n);
145
145
  }
146
- function F(e, t) {
146
+ function I(e, t) {
147
147
  if (e.defines?.USE_SHADOWMAP_DEPTH || !e.vertexShader.includes("#include <shadowmap_vertex>")) return;
148
- e.uniforms.nvrCsmShadowMatrixView = t;
149
- let n = P(h.shadowmap_vertex, "shadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );", "#if UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT\n shadowWorldPosition = vec4( mvPosition.xyz + ( viewMatrix * vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0.0 ) ).xyz, 1.0 );\n #else\n shadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n #endif");
150
- n = P(n, "vDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;", "#if UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT\n vDirectionalShadowCoord[ i ] = nvrCsmShadowMatrixView[ i ] * shadowWorldPosition;\n #else\n vDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n #endif"), e.vertexShader = P(P(e.vertexShader, "#include <shadowmap_pars_vertex>", `#include <shadowmap_pars_vertex>\n${N}`), "#include <shadowmap_vertex>", n);
148
+ e.uniforms.nvrCsmShadowMatrixView = t, e.fragmentShader = `#define ${N}\n${e.fragmentShader}`;
149
+ let n = F(h.shadowmap_vertex, "shadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );", "#if UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT\n shadowWorldPosition = vec4( mvPosition.xyz + ( viewMatrix * vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0.0 ) ).xyz, 1.0 );\n #else\n shadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n #endif");
150
+ n = F(n, "vDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;", "#if UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT\n vDirectionalShadowCoord[ i ] = nvrCsmShadowMatrixView[ i ] * shadowWorldPosition;\n #else\n vDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n #endif"), e.vertexShader = F(F(e.vertexShader, "#include <shadowmap_pars_vertex>", `#include <shadowmap_pars_vertex>\n${P}`), "#include <shadowmap_vertex>", n);
151
151
  }
152
- function I(e, t, n) {
152
+ function L(e, t, n) {
153
153
  n.length = e.length;
154
154
  for (let r = 0; r < e.length; r++) (n[r] ?? (n[r] = new u())).multiplyMatrices(e[r].shadow.matrix, t);
155
155
  }
156
156
  //#endregion
157
157
  //#region src/MaterialStates.ts
158
- var L = class {
158
+ var R = class {
159
159
  map = /* @__PURE__ */ new Map();
160
160
  setup(e, t) {
161
161
  if (this.map.has(e)) return e;
@@ -165,7 +165,7 @@ var L = class {
165
165
  let r = (e, r) => {
166
166
  n.parameters = e, t.captureRenderer(r);
167
167
  let i = t.cascades, a = t.mainCamera.near, o = Math.min(t.mainCamera.far, t.far), s = e.uniforms;
168
- s.csmCascades = { value: i }, s.csmNear = { value: a }, s.csmFar = { value: o }, e.fragmentShader = M(e.fragmentShader), F(e, t.shadowMatricesViewUniform);
168
+ s.csmCascades = { value: i }, s.csmNear = { value: a }, s.csmFar = { value: o }, e.fragmentShader = M(e.fragmentShader), I(e, t.shadowMatricesViewUniform);
169
169
  }, i = e.onBeforeCompile.bind(e), a = e.customProgramCacheKey.bind(e);
170
170
  Object.prototype.hasOwnProperty.call(e, "hasOwnProperty") && (n.originalHandler = i), e.customProgramCacheKey = () => i.toString() + a(), e.onBeforeCompile = (...t) => {
171
171
  let [n] = t;
@@ -201,12 +201,12 @@ var L = class {
201
201
  }
202
202
  };
203
203
  Math.PI / 180, 180 / Math.PI;
204
- function R(e, t, n) {
204
+ function z(e, t, n) {
205
205
  return (1 - n) * e + n * t;
206
206
  }
207
207
  //#endregion
208
208
  //#region src/splitFrustum.ts
209
- var z = {
209
+ var B = {
210
210
  uniform: (e, t, n, r, i = []) => {
211
211
  for (let r = 0; r < e; ++r) i[r] = (t + (n - t) * (r + 1) / e) / n;
212
212
  return i.length = e, i;
@@ -218,17 +218,17 @@ var z = {
218
218
  practical: (e, t, n, r = .5, i = []) => {
219
219
  for (let a = 0; a < e; ++a) {
220
220
  let o = (t + (n - t) * (a + 1) / e) / n, s = t * (n / t) ** ((a + 1) / e) / n;
221
- i[a] = R(o, s, r);
221
+ i[a] = z(o, s, r);
222
222
  }
223
223
  return i.length = e, i;
224
224
  }
225
- }, B = z;
226
- function V(e, t, n, r, i, a = []) {
227
- return z[e](t, n, r, i, a);
225
+ }, V = B;
226
+ function H(e, t, n, r, i, a = []) {
227
+ return B[e](t, n, r, i, a);
228
228
  }
229
229
  //#endregion
230
230
  //#region src/CascadedShadowMaps.ts
231
- var H = /*#__PURE__*/ new _(), U = /*#__PURE__*/ new _(), W = /*#__PURE__*/ new u(), G = /*#__PURE__*/ new u(), K = /*#__PURE__*/ new S(), q = /*#__PURE__*/ new e(), J = {
231
+ var U = /*#__PURE__*/ new _(), W = /*#__PURE__*/ new _(), G = /*#__PURE__*/ new u(), K = /*#__PURE__*/ new u(), q = /*#__PURE__*/ new S(), J = /*#__PURE__*/ new e(), Y = {
232
232
  cascadeCount: 4,
233
233
  mapSize: 2048,
234
234
  far: 5e3,
@@ -237,11 +237,11 @@ var H = /*#__PURE__*/ new _(), U = /*#__PURE__*/ new _(), W = /*#__PURE__*/ new
237
237
  margin: 200,
238
238
  fade: !0,
239
239
  disableLastCascadeCutoff: !1
240
- }, Y = class {
240
+ }, X = class {
241
241
  mainCamera;
242
242
  needsUpdateFrusta = !0;
243
243
  directionalLights = new y();
244
- materialStates = new L();
244
+ materialStates = new R();
245
245
  mainFrustum = new S();
246
246
  cascadedFrusta = [];
247
247
  splits = [];
@@ -263,7 +263,7 @@ var H = /*#__PURE__*/ new _(), U = /*#__PURE__*/ new _(), W = /*#__PURE__*/ new
263
263
  return n();
264
264
  } };
265
265
  let { cascadeCount: r, mapSize: i, far: a, mode: o, lambda: s, margin: c, fade: l, disableLastCascadeCutoff: u } = {
266
- ...J,
266
+ ...Y,
267
267
  ...t
268
268
  };
269
269
  this._far = a, this._mode = o, this._lambda = s, this._margin = c, this._fade = l, this._disableLastCascadeCutoff = u, this.cascadeCount = r, this.mapSize = i;
@@ -279,7 +279,7 @@ var H = /*#__PURE__*/ new _(), U = /*#__PURE__*/ new _(), W = /*#__PURE__*/ new
279
279
  }
280
280
  refreshShadowMatricesView() {
281
281
  let e = this._renderer?.info.render.frame ?? -1;
282
- (e === -1 || e !== this._shadowMatricesViewFrame) && (this._shadowMatricesViewFrame = e, I(this.directionalLights.cascadedLights, this.mainCamera.matrixWorld, this._shadowMatricesView));
282
+ (e === -1 || e !== this._shadowMatricesViewFrame) && (this._shadowMatricesViewFrame = e, L(this.directionalLights.cascadedLights, this.mainCamera.matrixWorld, this._shadowMatricesView));
283
283
  }
284
284
  rollbackMaterial(e) {
285
285
  return this.materialStates.rollback(e);
@@ -292,7 +292,7 @@ var H = /*#__PURE__*/ new _(), U = /*#__PURE__*/ new _(), W = /*#__PURE__*/ new
292
292
  }
293
293
  updateCascades() {
294
294
  let e = this.cascadeCount, t = this.splits;
295
- V(this.mode, e, this.mainCamera.near, Math.min(this.mainCamera.far, this.far), this.lambda, t), this.mainFrustum.setFromCamera(this.mainCamera, this.far), this.mainFrustum.split(t, this.cascadedFrusta);
295
+ H(this.mode, e, this.mainCamera.near, Math.min(this.mainCamera.far, this.far), this.lambda, t), this.mainFrustum.setFromCamera(this.mainCamera, this.far), this.mainFrustum.split(t, this.cascadedFrusta);
296
296
  let n = this.cascades;
297
297
  for (let r = 0; r < e; ++r) (n[r] ?? (n[r] = new g())).set(t[r - 1] ?? 0, t[r] ?? 0);
298
298
  this.disableLastCascadeCutoff && (n[e - 1].y = Infinity), n.length = e;
@@ -318,11 +318,11 @@ var H = /*#__PURE__*/ new _(), U = /*#__PURE__*/ new _(), W = /*#__PURE__*/ new
318
318
  }
319
319
  update() {
320
320
  this.needsUpdateFrusta && (this.needsUpdateFrusta = !1, this.updateFrusta());
321
- let e = this.directionalLights, t = H.copy(e.direction).normalize(), n = W.lookAt(new _(), t, p.DEFAULT_UP), r = G.multiplyMatrices(G.copy(n).invert(), this.mainCamera.matrixWorld), i = this.margin, a = this.mapSize, o = this.cascadedFrusta, s = e.cascadedLights;
321
+ let e = this.directionalLights, t = U.copy(e.direction).normalize(), n = G.lookAt(new _(), t, p.DEFAULT_UP), r = K.multiplyMatrices(K.copy(n).invert(), this.mainCamera.matrixWorld), i = this.margin, a = this.mapSize, o = this.cascadedFrusta, s = e.cascadedLights;
322
322
  for (let e = 0; e < o.length; ++e) {
323
- let { near: c, far: l } = K.copy(o[e]).applyMatrix4(r), u = q.makeEmpty();
323
+ let { near: c, far: l } = q.copy(o[e]).applyMatrix4(r), u = J.makeEmpty();
324
324
  for (let e = 0; e < 4; e++) u.expandByPoint(c[e]), u.expandByPoint(l[e]);
325
- let d = u.getCenter(U);
325
+ let d = u.getCenter(W);
326
326
  d.z = u.max.z + i;
327
327
  let f = s[e], { left: p, right: m, top: h, bottom: g } = f.shadow.camera, _ = (m - p) / a, v = (h - g) / a;
328
328
  d.x = Math.round(d.x / _) * _, d.y = Math.round(d.y / v) * v, d.applyMatrix4(n), f.position.copy(d), f.target.position.copy(d), f.target.position.add(t);
@@ -442,7 +442,7 @@ var H = /*#__PURE__*/ new _(), U = /*#__PURE__*/ new _(), W = /*#__PURE__*/ new
442
442
  for (let n = 0; n < t.length; ++n) t[n].shadow.blurSamples = e;
443
443
  }
444
444
  }
445
- }, X = class extends s {
445
+ }, Z = class extends s {
446
446
  csm;
447
447
  displayFrustum = !0;
448
448
  displayPlanes = !0;
@@ -515,4 +515,4 @@ var H = /*#__PURE__*/ new _(), U = /*#__PURE__*/ new _(), W = /*#__PURE__*/ new
515
515
  }
516
516
  };
517
517
  //#endregion
518
- export { X as CSMHelper, y as CascadedDirectionalLights, Y as CascadedShadowMaps, F as applyViewSpaceShadowReceive, J as cascadedShadowMapsOptionsDefaults, I as composeViewSpaceShadowMatrices, B as frustumSplitFunctions, M as overrideFragmentShaderForCSM, V as splitFrustum };
518
+ export { Z as CSMHelper, y as CascadedDirectionalLights, X as CascadedShadowMaps, N as VIEW_SPACE_SHADOW_DEFINE, I as applyViewSpaceShadowReceive, Y as cascadedShadowMapsOptionsDefaults, L as composeViewSpaceShadowMatrices, V as frustumSplitFunctions, M as overrideFragmentShaderForCSM, H as splitFrustum };
@@ -331,7 +331,7 @@ void defaultLights() {
331
331
  ${p(u)}
332
332
  #endif // CSM
333
333
  #endif // NUM_DIR_LIGHTS > 0 && defined(RE_Direct)
334
- `;function h({source:e,startLine:t,endLine:n,replacement:r}){let i=`\n${t}\n`,a=e.indexOf(i);if(a<0)return e;let o=`\n${n}\n`,s=e.indexOf(o,a+i.length);return s<0?e:e.slice(0,a+1)+r+e.slice(s+o.length-1)}function g(e){return e.replace(`#include <lights_pars_begin>`,f).replace(`#include <lights_fragment_begin>`,h({source:t.ShaderChunk.lights_fragment_begin,startLine:`#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )`,endLine:`#endif`,replacement:m}))}var _=`
334
+ `;function h({source:e,startLine:t,endLine:n,replacement:r}){let i=`\n${t}\n`,a=e.indexOf(i);if(a<0)return e;let o=`\n${n}\n`,s=e.indexOf(o,a+i.length);return s<0?e:e.slice(0,a+1)+r+e.slice(s+o.length-1)}function g(e){return e.replace(`#include <lights_pars_begin>`,f).replace(`#include <lights_fragment_begin>`,h({source:t.ShaderChunk.lights_fragment_begin,startLine:`#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )`,endLine:`#endif`,replacement:m}))}var _=`NVR_VIEW_SPACE_SHADOW`,v=`
335
335
  #if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0
336
336
  // Per-cascade shadow matrices composed with the render camera's world matrix
337
337
  // on the CPU in f64 (shadow.matrix * camera.matrixWorld). Multiplied with the
@@ -348,12 +348,12 @@ void defaultLights() {
348
348
  #endif
349
349
  uniform mat4 nvrCsmShadowMatrixView[ CSM_CASCADE_COUNT ];
350
350
  #endif
351
- `;function v(e,t,n){if(!e.includes(t))throw Error(`Failed to replace "${t}" in shader code. The pattern was not found.`);return e.replace(t,n)}function y(e,n){if(e.defines?.USE_SHADOWMAP_DEPTH||!e.vertexShader.includes(`#include <shadowmap_vertex>`))return;e.uniforms.nvrCsmShadowMatrixView=n;let r=v(t.ShaderChunk.shadowmap_vertex,`shadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );`,`#if UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT
351
+ `;function y(e,t,n){if(!e.includes(t))throw Error(`Failed to replace "${t}" in shader code. The pattern was not found.`);return e.replace(t,n)}function b(e,n){if(e.defines?.USE_SHADOWMAP_DEPTH||!e.vertexShader.includes(`#include <shadowmap_vertex>`))return;e.uniforms.nvrCsmShadowMatrixView=n,e.fragmentShader=`#define ${_}\n${e.fragmentShader}`;let r=y(t.ShaderChunk.shadowmap_vertex,`shadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );`,`#if UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT
352
352
  shadowWorldPosition = vec4( mvPosition.xyz + ( viewMatrix * vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0.0 ) ).xyz, 1.0 );
353
353
  #else
354
354
  shadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );
355
- #endif`);r=v(r,`vDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;`,`#if UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT
355
+ #endif`);r=y(r,`vDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;`,`#if UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT
356
356
  vDirectionalShadowCoord[ i ] = nvrCsmShadowMatrixView[ i ] * shadowWorldPosition;
357
357
  #else
358
358
  vDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;
359
- #endif`),e.vertexShader=v(v(e.vertexShader,`#include <shadowmap_pars_vertex>`,`#include <shadowmap_pars_vertex>\n${_}`),`#include <shadowmap_vertex>`,r)}function b(e,n,r){r.length=e.length;for(let i=0;i<e.length;i++)(r[i]??(r[i]=new t.Matrix4)).multiplyMatrices(e[i].shadow.matrix,n)}var x=class{map=new Map;setup(e,t){if(this.map.has(e))return e;e.userData.defines??={},e.userData.defines.CSM=1,e.userData.defines.CSM_CASCADE_COUNT=t.cascadeCount,t.fade&&(e.userData.defines.CSM_FADE=1);let n={};this.map.set(e,n);let r=(e,r)=>{n.parameters=e,t.captureRenderer(r);let i=t.cascades,a=t.mainCamera.near,o=Math.min(t.mainCamera.far,t.far),s=e.uniforms;s.csmCascades={value:i},s.csmNear={value:a},s.csmFar={value:o},e.fragmentShader=g(e.fragmentShader),y(e,t.shadowMatricesViewUniform)},i=e.onBeforeCompile.bind(e),a=e.customProgramCacheKey.bind(e);Object.prototype.hasOwnProperty.call(e,`hasOwnProperty`)&&(n.originalHandler=i),e.customProgramCacheKey=()=>i.toString()+a(),e.onBeforeCompile=(...t)=>{let[n]=t;n.defines??={},Object.assign(n.defines,e.userData.defines),i.apply(e,t),r(...t)};let o=()=>{this.map.delete(e),e.removeEventListener(`dispose`,o)};return e.addEventListener(`dispose`,o),e}update(e){let t=e.cascadeCount,n=e.fade,r=e.cascades,i=e.mainCamera.near,a=Math.min(e.mainCamera.far,e.far);for(let[e,o]of this.map.entries()){let s=e.userData.defines;if(s!=null){let r=!1;s.CSM_FADE!=null!==n&&(n?s.CSM_FADE=1:delete s.CSM_FADE,r=!0),s.CSM_CASCADE_COUNT!==t&&(s.CSM_CASCADE_COUNT=t,r=!0),r&&(e.needsUpdate=!0)}let c=o.parameters?.uniforms;c!=null&&(c.csmCascades.value=r,c.csmNear.value=i,c.csmFar.value=a)}}rollback(e){let t=this.map.get(e);return t==null?e:this.rollbackToState(e,t)}rollbackToState(e,{parameters:t,originalHandler:n}){return t!=null&&(delete t.uniforms.csmCascades,delete t.uniforms.csmNear,delete t.uniforms.csmFar,delete t.uniforms.nvrCsmShadowMatrixView),n==null?delete e.onBeforeCompile:e.onBeforeCompile=n,e.userData.defines!=null&&(delete e.userData.defines.CSM,delete e.userData.defines.CSM_CASCADE_COUNT,delete e.userData.defines.CSM_FADE),e.needsUpdate=!0,this.map.delete(e),e}dispose(){for(let[e,t]of this.map)this.rollbackToState(e,t);this.map.clear()}};Math.PI/180,180/Math.PI;function S(e,t,n){return(1-n)*e+n*t}var C={uniform:(e,t,n,r,i=[])=>{for(let r=0;r<e;++r)i[r]=(t+(n-t)*(r+1)/e)/n;return i.length=e,i},logarithmic:(e,t,n,r,i=[])=>{for(let r=0;r<e;++r)i[r]=t*(n/t)**((r+1)/e)/n;return i.length=e,i},practical:(e,t,n,r=.5,i=[])=>{for(let a=0;a<e;++a){let o=(t+(n-t)*(a+1)/e)/n,s=t*(n/t)**((a+1)/e)/n;i[a]=S(o,s,r)}return i.length=e,i}},w=C;function T(e,t,n,r,i,a=[]){return C[e](t,n,r,i,a)}var E=new t.Vector3,D=new t.Vector3,O=new t.Matrix4,k=new t.Matrix4,A=new o,j=new t.Box3,M={cascadeCount:4,mapSize:2048,far:5e3,mode:`practical`,lambda:.5,margin:200,fade:!0,disableLastCascadeCutoff:!1},N=class{mainCamera;needsUpdateFrusta=!0;directionalLights=new r;materialStates=new x;mainFrustum=new o;cascadedFrusta=[];splits=[];cascades=[];_far;_mode;_lambda;_margin;_fade;_disableLastCascadeCutoff;_shadowMatricesView=[];_shadowMatricesViewFrame=-1;_renderer=null;shadowMatricesViewUniform;constructor(e,t){this.mainCamera=e;let n=()=>(this.refreshShadowMatricesView(),this._shadowMatricesView);this.shadowMatricesViewUniform={get value(){return n()}};let{cascadeCount:r,mapSize:i,far:a,mode:o,lambda:s,margin:c,fade:l,disableLastCascadeCutoff:u}={...M,...t};this._far=a,this._mode=o,this._lambda=s,this._margin=c,this._fade=l,this._disableLastCascadeCutoff=u,this.cascadeCount=r,this.mapSize=i}dispose(){this.materialStates.dispose(),this.directionalLights.dispose()}setupMaterial(e){return this.materialStates.setup(e,this)}captureRenderer(e){this._renderer??=e}refreshShadowMatricesView(){let e=this._renderer?.info.render.frame??-1;(e===-1||e!==this._shadowMatricesViewFrame)&&(this._shadowMatricesViewFrame=e,b(this.directionalLights.cascadedLights,this.mainCamera.matrixWorld,this._shadowMatricesView))}rollbackMaterial(e){return this.materialStates.rollback(e)}setupMaterials(e){for(let t=0;t<e.length;++t)this.materialStates.setup(e[t],this);return()=>{for(let t=0;t<e.length;++t)this.materialStates.rollback(e[t])}}updateCascades(){let e=this.cascadeCount,n=this.splits;T(this.mode,e,this.mainCamera.near,Math.min(this.mainCamera.far,this.far),this.lambda,n),this.mainFrustum.setFromCamera(this.mainCamera,this.far),this.mainFrustum.split(n,this.cascadedFrusta);let r=this.cascades;for(let i=0;i<e;++i)(r[i]??(r[i]=new t.Vector2)).set(n[i-1]??0,n[i]??0);this.disableLastCascadeCutoff&&(r[e-1].y=1/0),r.length=e}getFrustumRadius(e){let t=e.near,n=e.far,r=Math.max(n[0].distanceTo(n[2]),n[0].distanceTo(t[2]));if(this.fade){let e=this.mainCamera,t=e.near,i=Math.min(e.far,this.far),a=n[0].z/(i-t);r+=.25*a**2*(i-t)}return r*.5}updateShadowBounds(){let e=this.cascadedFrusta,t=this.directionalLights.cascadedLights;a(e.length===t.length);for(let n=0;n<e.length;++n){let e=this.getFrustumRadius(this.cascadedFrusta[n]),r=t[n].shadow.camera;r.left=-e,r.right=e,r.top=e,r.bottom=-e,r.near=0,r.far=e*2+this.margin,r.updateProjectionMatrix()}}updateFrusta(){this.updateCascades(),this.updateShadowBounds(),this.materialStates.update(this)}update(){this.needsUpdateFrusta&&(this.needsUpdateFrusta=!1,this.updateFrusta());let e=this.directionalLights,n=E.copy(e.direction).normalize(),r=O.lookAt(new t.Vector3,n,t.Object3D.DEFAULT_UP),i=k.multiplyMatrices(k.copy(r).invert(),this.mainCamera.matrixWorld),a=this.margin,o=this.mapSize,s=this.cascadedFrusta,c=e.cascadedLights;for(let e=0;e<s.length;++e){let{near:t,far:l}=A.copy(s[e]).applyMatrix4(i),u=j.makeEmpty();for(let e=0;e<4;e++)u.expandByPoint(t[e]),u.expandByPoint(l[e]);let d=u.getCenter(D);d.z=u.max.z+a;let f=c[e],{left:p,right:m,top:h,bottom:g}=f.shadow.camera,_=(m-p)/o,v=(h-g)/o;d.x=Math.round(d.x/_)*_,d.y=Math.round(d.y/v)*v,d.applyMatrix4(r),f.position.copy(d),f.target.position.copy(d),f.target.position.add(n)}}get cascadeCount(){return this.directionalLights.cascadedLights.length}set cascadeCount(e){e!==this.cascadeCount&&(this.directionalLights.setCount(e),this.needsUpdateFrusta=!0)}get mapSize(){return this.directionalLights.mainLight.shadow.mapSize.width}set mapSize(e){if(e!==this.mapSize){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n){let r=t[n].shadow;r.mapSize.width=e,r.mapSize.height=e,r.map!=null&&(r.map.dispose(),r.map=null)}}}get far(){return this._far}set far(e){e!==this._far&&(this._far=e,this.needsUpdateFrusta=!0)}get mode(){return this._mode}set mode(e){e!==this._mode&&(this._mode=e,this.needsUpdateFrusta=!0)}get lambda(){return this._lambda}set lambda(e){e!==this._lambda&&(this._lambda=e,this.needsUpdateFrusta=!0)}get margin(){return this._margin}set margin(e){e!==this._margin&&(this._margin=e,this.needsUpdateFrusta=!0)}get fade(){return this._fade}set fade(e){e!==this._fade&&(this._fade=e,this.needsUpdateFrusta=!0)}get disableLastCascadeCutoff(){return this._disableLastCascadeCutoff}set disableLastCascadeCutoff(e){e!==this._disableLastCascadeCutoff&&(this._disableLastCascadeCutoff=e,this.needsUpdateFrusta=!0)}get intensity(){return this.directionalLights.mainLight.intensity}set intensity(e){if(e!==this.intensity){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].intensity=e}}get shadowIntensity(){return this.directionalLights.mainLight.shadow.intensity}set shadowIntensity(e){if(e!==this.shadowIntensity){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.intensity=e}}get color(){return this.directionalLights.mainLight.color}set color(e){e!==this.color&&this.directionalLights.mainLight.color.copy(e)}get bias(){return this.directionalLights.mainLight.shadow.bias}set bias(e){if(e!==this.bias){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.bias=e}}get normalBias(){return this.directionalLights.mainLight.shadow.normalBias}set normalBias(e){if(e!==this.normalBias){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.normalBias=e}}get radius(){return this.directionalLights.mainLight.shadow.radius}set radius(e){if(e!==this.radius){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.radius=e}}get blurSamples(){return this.directionalLights.mainLight.shadow.blurSamples}set blurSamples(e){if(e!==this.blurSamples){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.blurSamples=e}}};e.CSMHelper=class extends t.Group{csm;displayFrustum=!0;displayPlanes=!0;displayShadowBounds=!0;frustumLines;cascadeLines=[];cascadePlanes=[];shadowLines=[];constructor(e){super(),this.csm=e;let n=new Uint16Array([0,1,1,2,2,3,3,0,4,5,5,6,6,7,7,4,0,4,1,5,2,6,3,7]),r=new Float32Array(24),i=new t.BufferGeometry;i.setIndex(new t.BufferAttribute(n,1)),i.setAttribute(`position`,new t.BufferAttribute(r,3,!1));let a=new t.LineSegments(i,new t.LineBasicMaterial);this.add(a),this.frustumLines=a}updateVisibility(){let e=this.displayFrustum,t=this.displayPlanes,n=this.displayShadowBounds,r=this.frustumLines,i=this.cascadeLines,a=this.cascadePlanes,o=this.shadowLines;for(let r=0,s=i.length;r<s;r++){let s=i[r],c=a[r],l=o[r];s.visible=e,c.visible=e&&t,l.visible=n}r.visible=e}update(){let e=this.csm,n=e.mainCamera,r=e.cascadeCount,i=e.mainFrustum,a=e.cascadedFrusta,o=e.directionalLights.cascadedLights,s=this.frustumLines.geometry.getAttribute(`position`),c=this.cascadeLines,l=this.cascadePlanes,u=this.shadowLines;for(this.position.copy(n.position),this.quaternion.copy(n.quaternion),this.scale.copy(n.scale),this.updateMatrixWorld(!0);c.length>r;){let e=c.pop(),t=l.pop(),n=u.pop();e&&this.remove(e),t&&this.remove(t),n&&this.remove(n)}for(;c.length<r;){let e=new t.Box3Helper(new t.Box3,new t.Color(16777215)),n=new t.MeshBasicMaterial({transparent:!0,opacity:.1,depthWrite:!1,side:t.DoubleSide}),r=new t.Mesh(new t.PlaneGeometry,n),i=new t.Group,a=new t.Box3Helper(new t.Box3,new t.Color(16776960));i.add(a),this.add(e),this.add(r),this.add(i),c.push(e),l.push(r),u.push(i)}for(let e=0;e<r;e++){let t=a[e],n=o[e].shadow.camera,r=t.far,i=c[e],s=l[e],d=u[e],f=d.children[0];i.box.min.copy(r[2]),i.box.max.copy(r[0]),i.box.max.z+=1e-4,s.position.addVectors(r[0],r[2]),s.position.multiplyScalar(.5),s.scale.subVectors(r[0],r[2]),s.scale.z=1e-4,this.remove(d),d.position.copy(n.position),d.quaternion.copy(n.quaternion),d.scale.copy(n.scale),d.updateMatrixWorld(!0),this.attach(d),f.box.min.set(n.bottom,n.left,-n.far),f.box.max.set(n.top,n.right,-n.near)}let d=i.near,f=i.far;s.setXYZ(0,f[0].x,f[0].y,f[0].z),s.setXYZ(1,f[3].x,f[3].y,f[3].z),s.setXYZ(2,f[2].x,f[2].y,f[2].z),s.setXYZ(3,f[1].x,f[1].y,f[1].z),s.setXYZ(4,d[0].x,d[0].y,d[0].z),s.setXYZ(5,d[3].x,d[3].y,d[3].z),s.setXYZ(6,d[2].x,d[2].y,d[2].z),s.setXYZ(7,d[1].x,d[1].y,d[1].z),s.needsUpdate=!0}},e.CascadedDirectionalLights=r,e.CascadedShadowMaps=N,e.applyViewSpaceShadowReceive=y,e.cascadedShadowMapsOptionsDefaults=M,e.composeViewSpaceShadowMatrices=b,e.frustumSplitFunctions=w,e.overrideFragmentShaderForCSM=g,e.splitFrustum=T});
359
+ #endif`),e.vertexShader=y(y(e.vertexShader,`#include <shadowmap_pars_vertex>`,`#include <shadowmap_pars_vertex>\n${v}`),`#include <shadowmap_vertex>`,r)}function x(e,n,r){r.length=e.length;for(let i=0;i<e.length;i++)(r[i]??(r[i]=new t.Matrix4)).multiplyMatrices(e[i].shadow.matrix,n)}var S=class{map=new Map;setup(e,t){if(this.map.has(e))return e;e.userData.defines??={},e.userData.defines.CSM=1,e.userData.defines.CSM_CASCADE_COUNT=t.cascadeCount,t.fade&&(e.userData.defines.CSM_FADE=1);let n={};this.map.set(e,n);let r=(e,r)=>{n.parameters=e,t.captureRenderer(r);let i=t.cascades,a=t.mainCamera.near,o=Math.min(t.mainCamera.far,t.far),s=e.uniforms;s.csmCascades={value:i},s.csmNear={value:a},s.csmFar={value:o},e.fragmentShader=g(e.fragmentShader),b(e,t.shadowMatricesViewUniform)},i=e.onBeforeCompile.bind(e),a=e.customProgramCacheKey.bind(e);Object.prototype.hasOwnProperty.call(e,`hasOwnProperty`)&&(n.originalHandler=i),e.customProgramCacheKey=()=>i.toString()+a(),e.onBeforeCompile=(...t)=>{let[n]=t;n.defines??={},Object.assign(n.defines,e.userData.defines),i.apply(e,t),r(...t)};let o=()=>{this.map.delete(e),e.removeEventListener(`dispose`,o)};return e.addEventListener(`dispose`,o),e}update(e){let t=e.cascadeCount,n=e.fade,r=e.cascades,i=e.mainCamera.near,a=Math.min(e.mainCamera.far,e.far);for(let[e,o]of this.map.entries()){let s=e.userData.defines;if(s!=null){let r=!1;s.CSM_FADE!=null!==n&&(n?s.CSM_FADE=1:delete s.CSM_FADE,r=!0),s.CSM_CASCADE_COUNT!==t&&(s.CSM_CASCADE_COUNT=t,r=!0),r&&(e.needsUpdate=!0)}let c=o.parameters?.uniforms;c!=null&&(c.csmCascades.value=r,c.csmNear.value=i,c.csmFar.value=a)}}rollback(e){let t=this.map.get(e);return t==null?e:this.rollbackToState(e,t)}rollbackToState(e,{parameters:t,originalHandler:n}){return t!=null&&(delete t.uniforms.csmCascades,delete t.uniforms.csmNear,delete t.uniforms.csmFar,delete t.uniforms.nvrCsmShadowMatrixView),n==null?delete e.onBeforeCompile:e.onBeforeCompile=n,e.userData.defines!=null&&(delete e.userData.defines.CSM,delete e.userData.defines.CSM_CASCADE_COUNT,delete e.userData.defines.CSM_FADE),e.needsUpdate=!0,this.map.delete(e),e}dispose(){for(let[e,t]of this.map)this.rollbackToState(e,t);this.map.clear()}};Math.PI/180,180/Math.PI;function C(e,t,n){return(1-n)*e+n*t}var w={uniform:(e,t,n,r,i=[])=>{for(let r=0;r<e;++r)i[r]=(t+(n-t)*(r+1)/e)/n;return i.length=e,i},logarithmic:(e,t,n,r,i=[])=>{for(let r=0;r<e;++r)i[r]=t*(n/t)**((r+1)/e)/n;return i.length=e,i},practical:(e,t,n,r=.5,i=[])=>{for(let a=0;a<e;++a){let o=(t+(n-t)*(a+1)/e)/n,s=t*(n/t)**((a+1)/e)/n;i[a]=C(o,s,r)}return i.length=e,i}},T=w;function E(e,t,n,r,i,a=[]){return w[e](t,n,r,i,a)}var D=new t.Vector3,O=new t.Vector3,k=new t.Matrix4,A=new t.Matrix4,j=new o,M=new t.Box3,N={cascadeCount:4,mapSize:2048,far:5e3,mode:`practical`,lambda:.5,margin:200,fade:!0,disableLastCascadeCutoff:!1},P=class{mainCamera;needsUpdateFrusta=!0;directionalLights=new r;materialStates=new S;mainFrustum=new o;cascadedFrusta=[];splits=[];cascades=[];_far;_mode;_lambda;_margin;_fade;_disableLastCascadeCutoff;_shadowMatricesView=[];_shadowMatricesViewFrame=-1;_renderer=null;shadowMatricesViewUniform;constructor(e,t){this.mainCamera=e;let n=()=>(this.refreshShadowMatricesView(),this._shadowMatricesView);this.shadowMatricesViewUniform={get value(){return n()}};let{cascadeCount:r,mapSize:i,far:a,mode:o,lambda:s,margin:c,fade:l,disableLastCascadeCutoff:u}={...N,...t};this._far=a,this._mode=o,this._lambda=s,this._margin=c,this._fade=l,this._disableLastCascadeCutoff=u,this.cascadeCount=r,this.mapSize=i}dispose(){this.materialStates.dispose(),this.directionalLights.dispose()}setupMaterial(e){return this.materialStates.setup(e,this)}captureRenderer(e){this._renderer??=e}refreshShadowMatricesView(){let e=this._renderer?.info.render.frame??-1;(e===-1||e!==this._shadowMatricesViewFrame)&&(this._shadowMatricesViewFrame=e,x(this.directionalLights.cascadedLights,this.mainCamera.matrixWorld,this._shadowMatricesView))}rollbackMaterial(e){return this.materialStates.rollback(e)}setupMaterials(e){for(let t=0;t<e.length;++t)this.materialStates.setup(e[t],this);return()=>{for(let t=0;t<e.length;++t)this.materialStates.rollback(e[t])}}updateCascades(){let e=this.cascadeCount,n=this.splits;E(this.mode,e,this.mainCamera.near,Math.min(this.mainCamera.far,this.far),this.lambda,n),this.mainFrustum.setFromCamera(this.mainCamera,this.far),this.mainFrustum.split(n,this.cascadedFrusta);let r=this.cascades;for(let i=0;i<e;++i)(r[i]??(r[i]=new t.Vector2)).set(n[i-1]??0,n[i]??0);this.disableLastCascadeCutoff&&(r[e-1].y=1/0),r.length=e}getFrustumRadius(e){let t=e.near,n=e.far,r=Math.max(n[0].distanceTo(n[2]),n[0].distanceTo(t[2]));if(this.fade){let e=this.mainCamera,t=e.near,i=Math.min(e.far,this.far),a=n[0].z/(i-t);r+=.25*a**2*(i-t)}return r*.5}updateShadowBounds(){let e=this.cascadedFrusta,t=this.directionalLights.cascadedLights;a(e.length===t.length);for(let n=0;n<e.length;++n){let e=this.getFrustumRadius(this.cascadedFrusta[n]),r=t[n].shadow.camera;r.left=-e,r.right=e,r.top=e,r.bottom=-e,r.near=0,r.far=e*2+this.margin,r.updateProjectionMatrix()}}updateFrusta(){this.updateCascades(),this.updateShadowBounds(),this.materialStates.update(this)}update(){this.needsUpdateFrusta&&(this.needsUpdateFrusta=!1,this.updateFrusta());let e=this.directionalLights,n=D.copy(e.direction).normalize(),r=k.lookAt(new t.Vector3,n,t.Object3D.DEFAULT_UP),i=A.multiplyMatrices(A.copy(r).invert(),this.mainCamera.matrixWorld),a=this.margin,o=this.mapSize,s=this.cascadedFrusta,c=e.cascadedLights;for(let e=0;e<s.length;++e){let{near:t,far:l}=j.copy(s[e]).applyMatrix4(i),u=M.makeEmpty();for(let e=0;e<4;e++)u.expandByPoint(t[e]),u.expandByPoint(l[e]);let d=u.getCenter(O);d.z=u.max.z+a;let f=c[e],{left:p,right:m,top:h,bottom:g}=f.shadow.camera,_=(m-p)/o,v=(h-g)/o;d.x=Math.round(d.x/_)*_,d.y=Math.round(d.y/v)*v,d.applyMatrix4(r),f.position.copy(d),f.target.position.copy(d),f.target.position.add(n)}}get cascadeCount(){return this.directionalLights.cascadedLights.length}set cascadeCount(e){e!==this.cascadeCount&&(this.directionalLights.setCount(e),this.needsUpdateFrusta=!0)}get mapSize(){return this.directionalLights.mainLight.shadow.mapSize.width}set mapSize(e){if(e!==this.mapSize){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n){let r=t[n].shadow;r.mapSize.width=e,r.mapSize.height=e,r.map!=null&&(r.map.dispose(),r.map=null)}}}get far(){return this._far}set far(e){e!==this._far&&(this._far=e,this.needsUpdateFrusta=!0)}get mode(){return this._mode}set mode(e){e!==this._mode&&(this._mode=e,this.needsUpdateFrusta=!0)}get lambda(){return this._lambda}set lambda(e){e!==this._lambda&&(this._lambda=e,this.needsUpdateFrusta=!0)}get margin(){return this._margin}set margin(e){e!==this._margin&&(this._margin=e,this.needsUpdateFrusta=!0)}get fade(){return this._fade}set fade(e){e!==this._fade&&(this._fade=e,this.needsUpdateFrusta=!0)}get disableLastCascadeCutoff(){return this._disableLastCascadeCutoff}set disableLastCascadeCutoff(e){e!==this._disableLastCascadeCutoff&&(this._disableLastCascadeCutoff=e,this.needsUpdateFrusta=!0)}get intensity(){return this.directionalLights.mainLight.intensity}set intensity(e){if(e!==this.intensity){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].intensity=e}}get shadowIntensity(){return this.directionalLights.mainLight.shadow.intensity}set shadowIntensity(e){if(e!==this.shadowIntensity){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.intensity=e}}get color(){return this.directionalLights.mainLight.color}set color(e){e!==this.color&&this.directionalLights.mainLight.color.copy(e)}get bias(){return this.directionalLights.mainLight.shadow.bias}set bias(e){if(e!==this.bias){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.bias=e}}get normalBias(){return this.directionalLights.mainLight.shadow.normalBias}set normalBias(e){if(e!==this.normalBias){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.normalBias=e}}get radius(){return this.directionalLights.mainLight.shadow.radius}set radius(e){if(e!==this.radius){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.radius=e}}get blurSamples(){return this.directionalLights.mainLight.shadow.blurSamples}set blurSamples(e){if(e!==this.blurSamples){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.blurSamples=e}}};e.CSMHelper=class extends t.Group{csm;displayFrustum=!0;displayPlanes=!0;displayShadowBounds=!0;frustumLines;cascadeLines=[];cascadePlanes=[];shadowLines=[];constructor(e){super(),this.csm=e;let n=new Uint16Array([0,1,1,2,2,3,3,0,4,5,5,6,6,7,7,4,0,4,1,5,2,6,3,7]),r=new Float32Array(24),i=new t.BufferGeometry;i.setIndex(new t.BufferAttribute(n,1)),i.setAttribute(`position`,new t.BufferAttribute(r,3,!1));let a=new t.LineSegments(i,new t.LineBasicMaterial);this.add(a),this.frustumLines=a}updateVisibility(){let e=this.displayFrustum,t=this.displayPlanes,n=this.displayShadowBounds,r=this.frustumLines,i=this.cascadeLines,a=this.cascadePlanes,o=this.shadowLines;for(let r=0,s=i.length;r<s;r++){let s=i[r],c=a[r],l=o[r];s.visible=e,c.visible=e&&t,l.visible=n}r.visible=e}update(){let e=this.csm,n=e.mainCamera,r=e.cascadeCount,i=e.mainFrustum,a=e.cascadedFrusta,o=e.directionalLights.cascadedLights,s=this.frustumLines.geometry.getAttribute(`position`),c=this.cascadeLines,l=this.cascadePlanes,u=this.shadowLines;for(this.position.copy(n.position),this.quaternion.copy(n.quaternion),this.scale.copy(n.scale),this.updateMatrixWorld(!0);c.length>r;){let e=c.pop(),t=l.pop(),n=u.pop();e&&this.remove(e),t&&this.remove(t),n&&this.remove(n)}for(;c.length<r;){let e=new t.Box3Helper(new t.Box3,new t.Color(16777215)),n=new t.MeshBasicMaterial({transparent:!0,opacity:.1,depthWrite:!1,side:t.DoubleSide}),r=new t.Mesh(new t.PlaneGeometry,n),i=new t.Group,a=new t.Box3Helper(new t.Box3,new t.Color(16776960));i.add(a),this.add(e),this.add(r),this.add(i),c.push(e),l.push(r),u.push(i)}for(let e=0;e<r;e++){let t=a[e],n=o[e].shadow.camera,r=t.far,i=c[e],s=l[e],d=u[e],f=d.children[0];i.box.min.copy(r[2]),i.box.max.copy(r[0]),i.box.max.z+=1e-4,s.position.addVectors(r[0],r[2]),s.position.multiplyScalar(.5),s.scale.subVectors(r[0],r[2]),s.scale.z=1e-4,this.remove(d),d.position.copy(n.position),d.quaternion.copy(n.quaternion),d.scale.copy(n.scale),d.updateMatrixWorld(!0),this.attach(d),f.box.min.set(n.bottom,n.left,-n.far),f.box.max.set(n.top,n.right,-n.near)}let d=i.near,f=i.far;s.setXYZ(0,f[0].x,f[0].y,f[0].z),s.setXYZ(1,f[3].x,f[3].y,f[3].z),s.setXYZ(2,f[2].x,f[2].y,f[2].z),s.setXYZ(3,f[1].x,f[1].y,f[1].z),s.setXYZ(4,d[0].x,d[0].y,d[0].z),s.setXYZ(5,d[3].x,d[3].y,d[3].z),s.setXYZ(6,d[2].x,d[2].y,d[2].z),s.setXYZ(7,d[1].x,d[1].y,d[1].z),s.needsUpdate=!0}},e.CascadedDirectionalLights=r,e.CascadedShadowMaps=P,e.VIEW_SPACE_SHADOW_DEFINE=_,e.applyViewSpaceShadowReceive=b,e.cascadedShadowMapsOptionsDefaults=N,e.composeViewSpaceShadowMatrices=x,e.frustumSplitFunctions=T,e.overrideFragmentShaderForCSM=g,e.splitFrustum=E});
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@navaramap/three-csm",
3
3
  "type": "module",
4
- "version": "0.1.1",
4
+ "version": "0.2.0",
5
5
  "main": "dist/index.umd.cjs",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -12,7 +12,7 @@
12
12
  "license": "MIT OR Apache-2.0",
13
13
  "repository": {
14
14
  "type": "git",
15
- "url": "git+https://github.com/reearth/navara.git",
15
+ "url": "git+https://github.com/maplibre/navara.git",
16
16
  "directory": "web/navara_three_csm"
17
17
  },
18
18
  "publishConfig": {
@@ -22,7 +22,7 @@
22
22
  "three": ">=0.183.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@types/three": "0.185.1",
25
+ "@types/three": "0.185.4",
26
26
  "three": "0.185.1"
27
27
  },
28
28
  "scripts": {
@@ -12,6 +12,7 @@ import { describe, expect, it } from "vitest";
12
12
  import {
13
13
  applyViewSpaceShadowReceive,
14
14
  composeViewSpaceShadowMatrices,
15
+ VIEW_SPACE_SHADOW_DEFINE,
15
16
  } from "./viewSpaceShadowReceive";
16
17
 
17
18
  function makeLightWithShadowMatrix(matrix: Matrix4): DirectionalLight {
@@ -100,12 +101,16 @@ describe("applyViewSpaceShadowReceive", () => {
100
101
  it("rewrites the directional shadow loop against the view-space matrices", () => {
101
102
  const shader = {
102
103
  vertexShader: litVertexShader,
104
+ fragmentShader: "",
103
105
  uniforms: {} as Record<string, { value: unknown }>,
104
106
  };
105
107
 
106
108
  applyViewSpaceShadowReceive(shader, uniform);
107
109
 
108
110
  expect(shader.uniforms.nvrCsmShadowMatrixView).toBe(uniform);
111
+ expect(shader.fragmentShader).toContain(
112
+ `#define ${VIEW_SPACE_SHADOW_DEFINE}`,
113
+ );
109
114
  // Sized to the cascade count: additional shadow-casting directional
110
115
  // lights beyond the cascades are not part of the composed array.
111
116
  expect(shader.vertexShader).toContain(
@@ -127,6 +132,7 @@ describe("applyViewSpaceShadowReceive", () => {
127
132
  it("keeps the stock path for extra directional lights and point shadows", () => {
128
133
  const shader = {
129
134
  vertexShader: litVertexShader,
135
+ fragmentShader: "",
130
136
  uniforms: {} as Record<string, { value: unknown }>,
131
137
  };
132
138
 
@@ -158,6 +164,7 @@ describe("applyViewSpaceShadowReceive", () => {
158
164
  it("skips shadow-map depth pass variants", () => {
159
165
  const shader = {
160
166
  vertexShader: litVertexShader,
167
+ fragmentShader: "",
161
168
  defines: { USE_SHADOWMAP_DEPTH: 1 } as Record<string, unknown>,
162
169
  uniforms: {} as Record<string, { value: unknown }>,
163
170
  };
@@ -172,12 +179,14 @@ describe("applyViewSpaceShadowReceive", () => {
172
179
  const source = "#include <common>\nvoid main() {}";
173
180
  const shader = {
174
181
  vertexShader: source,
182
+ fragmentShader: "",
175
183
  uniforms: {} as Record<string, { value: unknown }>,
176
184
  };
177
185
 
178
186
  applyViewSpaceShadowReceive(shader, uniform);
179
187
 
180
188
  expect(shader.vertexShader).toBe(source);
189
+ expect(shader.fragmentShader).toBe("");
181
190
  expect(shader.uniforms.nvrCsmShadowMatrixView).toBeUndefined();
182
191
  });
183
192
 
@@ -208,6 +217,7 @@ describe("applyViewSpaceShadowReceive", () => {
208
217
  (name) => {
209
218
  const shader = {
210
219
  vertexShader: ShaderLib[name].vertexShader,
220
+ fragmentShader: ShaderLib[name].fragmentShader,
211
221
  uniforms: {} as Record<string, { value: unknown }>,
212
222
  };
213
223
 
@@ -233,6 +243,7 @@ describe("applyViewSpaceShadowReceive", () => {
233
243
  const source = ShaderLib[name as keyof typeof ShaderLib].vertexShader;
234
244
  const shader = {
235
245
  vertexShader: source,
246
+ fragmentShader: "",
236
247
  uniforms: {} as Record<string, { value: unknown }>,
237
248
  };
238
249
 
@@ -19,6 +19,9 @@ import { Matrix4, ShaderChunk, type DirectionalLight } from "three";
19
19
  * the exact quantity that renders stably, so it can never jitter relative to
20
20
  * the geometry — and a single patch covers every mesh type.
21
21
  */
22
+ /** Defined in the fragment shader of a material patched for view-space shadows. */
23
+ export const VIEW_SPACE_SHADOW_DEFINE = "NVR_VIEW_SPACE_SHADOW";
24
+
22
25
  export type ShadowMatricesViewUniform = { value: Matrix4[] };
23
26
 
24
27
  const SHADOW_MATRIX_VIEW_PARS_VERTEX = /* glsl */ `
@@ -72,10 +75,15 @@ function replaceOrThrow(
72
75
  * left untouched, as are shaders compiled for the shadow-map depth pass
73
76
  * (`USE_SHADOWMAP_DEPTH` — they never sample shadows and would upload the
74
77
  * uniform before the shadow maps for the frame are rendered).
78
+ *
79
+ * The fragment shader gets `NVR_VIEW_SPACE_SHADOW` defined, so code that
80
+ * computes shadow coordinates there (`chunks/ground_shadow_coord_fragment`)
81
+ * takes the same path.
75
82
  */
76
83
  export function applyViewSpaceShadowReceive(
77
84
  shader: {
78
85
  vertexShader: string;
86
+ fragmentShader: string;
79
87
  defines?: Record<string, unknown>;
80
88
  uniforms: Record<string, { value: unknown }>;
81
89
  },
@@ -85,6 +93,7 @@ export function applyViewSpaceShadowReceive(
85
93
  if (!shader.vertexShader.includes("#include <shadowmap_vertex>")) return;
86
94
 
87
95
  shader.uniforms.nvrCsmShadowMatrixView = uniform;
96
+ shader.fragmentShader = `#define ${VIEW_SPACE_SHADOW_DEFINE}\n${shader.fragmentShader}`;
88
97
 
89
98
  // Rewrite only the directional-light loop of three's shadowmap_vertex
90
99
  // chunk, preserving the unroll pragmas. Each line becomes a per-index