s3m-three 0.3.7 → 0.3.9

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 (67) hide show
  1. package/dist/anim/Animator.d.ts +111 -0
  2. package/dist/anim/Animator.d.ts.map +1 -0
  3. package/dist/anim/Animator.js +382 -0
  4. package/dist/anim/Animator.js.map +1 -0
  5. package/dist/anim/Path.d.ts +30 -0
  6. package/dist/anim/Path.d.ts.map +1 -0
  7. package/dist/anim/Path.js +100 -0
  8. package/dist/anim/Path.js.map +1 -0
  9. package/dist/anim/easing.d.ts +70 -0
  10. package/dist/anim/easing.d.ts.map +1 -0
  11. package/dist/anim/easing.js +170 -0
  12. package/dist/anim/easing.js.map +1 -0
  13. package/dist/anim/index.d.ts +6 -0
  14. package/dist/anim/index.d.ts.map +1 -0
  15. package/dist/anim/index.js +4 -0
  16. package/dist/anim/index.js.map +1 -0
  17. package/dist/browser/s3m-three.iife.ext-three.js +218 -39
  18. package/dist/browser/s3m-three.iife.js +218 -39
  19. package/dist/core/types.d.ts +20 -0
  20. package/dist/core/types.d.ts.map +1 -1
  21. package/dist/index.d.ts +6 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/render/EdgeLines.d.ts +68 -14
  26. package/dist/render/EdgeLines.d.ts.map +1 -1
  27. package/dist/render/EdgeLines.js +125 -8
  28. package/dist/render/EdgeLines.js.map +1 -1
  29. package/dist/render/FeatureStyleManager.d.ts +62 -1
  30. package/dist/render/FeatureStyleManager.d.ts.map +1 -1
  31. package/dist/render/FeatureStyleManager.js +331 -60
  32. package/dist/render/FeatureStyleManager.js.map +1 -1
  33. package/dist/render/MergeVertices.d.ts +7 -0
  34. package/dist/render/MergeVertices.d.ts.map +1 -1
  35. package/dist/render/MergeVertices.js +10 -1
  36. package/dist/render/MergeVertices.js.map +1 -1
  37. package/dist/render/OrthoProjection.d.ts +6 -0
  38. package/dist/render/OrthoProjection.d.ts.map +1 -1
  39. package/dist/render/OrthoProjection.js +48 -0
  40. package/dist/render/OrthoProjection.js.map +1 -1
  41. package/dist/render/S3MViewer.d.ts +235 -9
  42. package/dist/render/S3MViewer.d.ts.map +1 -1
  43. package/dist/render/S3MViewer.js +880 -68
  44. package/dist/render/S3MViewer.js.map +1 -1
  45. package/dist/render/StyleMaterial.d.ts +28 -0
  46. package/dist/render/StyleMaterial.d.ts.map +1 -1
  47. package/dist/render/StyleMaterial.js +190 -21
  48. package/dist/render/StyleMaterial.js.map +1 -1
  49. package/dist/tileset/S3MTileset.d.ts.map +1 -1
  50. package/dist/tileset/S3MTileset.js +3 -1
  51. package/dist/tileset/S3MTileset.js.map +1 -1
  52. package/docs/ANIMATION.md +534 -0
  53. package/docs/API.md +2 -0
  54. package/package.json +1 -1
  55. package/src/anim/Animator.ts +460 -0
  56. package/src/anim/Path.ts +107 -0
  57. package/src/anim/easing.ts +195 -0
  58. package/src/anim/index.ts +10 -0
  59. package/src/core/types.ts +10 -0
  60. package/src/index.ts +16 -0
  61. package/src/render/EdgeLines.ts +161 -15
  62. package/src/render/FeatureStyleManager.ts +414 -65
  63. package/src/render/MergeVertices.ts +11 -1
  64. package/src/render/OrthoProjection.ts +52 -0
  65. package/src/render/S3MViewer.ts +1012 -64
  66. package/src/render/StyleMaterial.ts +211 -22
  67. package/src/tileset/S3MTileset.ts +3 -1
@@ -115,8 +115,23 @@ export interface S3MStyle {
115
115
  roofThreshold?: number;
116
116
  /** 是否与模型原色贴图叠加混合(0 = 纯色覆盖,1 = 叠乘原色贴图) */
117
117
  texMix?: number;
118
+ /**
119
+ * 基础漫反射底色增益 / 直通自发光微调(0~1,默认 0.0,建议开启 TOP_SIDE 时可设 0.15~0.3)。
120
+ * 绕过光照夹角直接补偿表面暗部底色,让 TOP_SIDE / 自定义等模式下的背光阴影面保持通透、不至于过黑。
121
+ */
122
+ ambientGain?: number;
118
123
  /** 半透明时是否保持深度写入(默认 true,保持写入可彻底遮挡背面与内部棱线,避免背面透出) */
119
124
  depthWrite?: boolean;
125
+ /**
126
+ * 新瓦片首次出现时的淡入时长(毫秒,0 = 关闭)。
127
+ *
128
+ * 只影响「刚加载进来」的瓦片,已经显示的不会被重播。LOD 细化换瓦片时也会淡入,
129
+ * 视觉上比「啪」一下换掉柔和得多。
130
+ *
131
+ * 注意代价:淡入期间该瓦片会进透明渲染队列(混合 + 前后排序),首次加载
132
+ * 几百块瓦片同时淡入时会有一小段额外开销,所以默认关闭、按需开启。
133
+ */
134
+ fadeIn?: number;
120
135
  }
121
136
 
122
137
  export const DEFAULT_STYLE: S3MStyle = {
@@ -147,7 +162,9 @@ export const DEFAULT_STYLE: S3MStyle = {
147
162
  edgeThreshold: 55,
148
163
  roofThreshold: 0.65,
149
164
  texMix: 0,
165
+ ambientGain: 0,
150
166
  depthWrite: true,
167
+ fadeIn: 0,
151
168
  };
152
169
 
153
170
  export interface StyledMaterial extends THREE.MeshStandardMaterial {
@@ -155,15 +172,24 @@ export interface StyledMaterial extends THREE.MeshStandardMaterial {
155
172
  uSelBatch: { value: number };
156
173
  uSelWhole: { value: number };
157
174
  uBatchTex: { value: THREE.Texture };
175
+ uBatchTex2: { value: THREE.Texture };
158
176
  uBatchTexWidth: { value: number };
159
177
  uBatchTexActive: { value: number };
178
+ /** 单块瓦片的淡入系数(1 = 完全显示) */
179
+ uTileFade: { value: number };
160
180
  isStyled: true;
161
181
  [key: string]: unknown;
162
182
  };
163
183
  }
164
184
 
165
- const DUMMY_DATA = new Float32Array([-1, -1, -1, 0, 0, 0, 0, 0]);
166
- export const DUMMY_BATCH_TEX = new THREE.DataTexture(DUMMY_DATA, 1, 2, THREE.RGBAFormat, THREE.FloatType);
185
+ const DUMMY_DATA = new Float32Array([
186
+ -1, -1, -1, 0, // Row 0: color rgb, elevation/hidden
187
+ 0, 0, 0, 0, // Row 1: translation xyz, opacity
188
+ 1, 0, 0, 0, // Row 2: rot row 0, pivot.x
189
+ 0, 1, 0, 0, // Row 3: rot row 1, pivot.y
190
+ 0, 0, 1, 0, // Row 4: rot row 2, pivot.z
191
+ ]);
192
+ export const DUMMY_BATCH_TEX = new THREE.DataTexture(DUMMY_DATA, 1, 5, THREE.RGBAFormat, THREE.FloatType);
167
193
  DUMMY_BATCH_TEX.minFilter = THREE.NearestFilter;
168
194
  DUMMY_BATCH_TEX.magFilter = THREE.NearestFilter;
169
195
  DUMMY_BATCH_TEX.needsUpdate = true;
@@ -205,9 +231,27 @@ export function createSharedUniforms(): StyleUniforms {
205
231
  uRoofThreshold: { value: 0.65 },
206
232
  /** 与模型原色贴图混合强度:0 = 纯色覆盖,1 = 叠乘原色贴图 */
207
233
  uTexMix: { value: 0 },
234
+ /** 基础底色增益 / 暗部补偿(0~1,默认 0.0) */
235
+ uAmbientGain: { value: 0 },
236
+ /** 双纹理交叉淡化插值系数(0 = 纯 uBatchTex, 1 = 纯 uBatchTex2) */
237
+ uBatchMix: { value: 0 },
238
+ /**
239
+ * 图层整体透明度:在片元里乘进 diffuseColor.a。
240
+ *
241
+ * 为什么不做成 `material.opacity`:那是逐材质写入,几百块瓦片就是 O(meshes),
242
+ * 而且跨 0.999 改 `material.transparent` 会换 program(`opaque` 参与缓存键)。
243
+ * 放到共享 uniform 里,透明度动画就退化成一次 float 赋值。
244
+ */
245
+ uOpacity: { value: 1 },
246
+ /** 棱线淡入淡出乘子:setEdges 过渡期间 0→1,新插入的瓦片自动继承 */
247
+ uEdgeFade: { value: 1 },
248
+ /**
249
+ * 整体压暗乘子:`mode` 这类不可插值的切换用它做 fade-through
250
+ * (压暗 → 换模式 → 亮回来),比硬切自然得多。
251
+ */
252
+ uFade: { value: 1 },
208
253
  };
209
254
  }
210
-
211
255
  const VERT_PARS = /* glsl */ `
212
256
  attribute float aBatchId;
213
257
  attribute vec3 aColor;
@@ -229,6 +273,8 @@ const VERT_PARS = /* glsl */ `
229
273
  uniform float uSelWhole;
230
274
  uniform float uSelElevation;
231
275
  uniform sampler2D uBatchTex;
276
+ uniform sampler2D uBatchTex2;
277
+ uniform float uBatchMix;
232
278
  uniform float uBatchTexWidth;
233
279
  uniform int uBatchTexActive;
234
280
  varying float vBatchId;
@@ -237,6 +283,11 @@ const VERT_PARS = /* glsl */ `
237
283
  varying vec3 vWorldNormal;
238
284
  varying vec3 vWorldPos;
239
285
 
286
+ // 跨 chunk 共享的顶点姿态变量(在 beginnormal_vertex 建立,begin_vertex / worldpos 使用)
287
+ vec3 s3mCustomOffset;
288
+ mat3 s3mRot;
289
+ vec3 s3mPivot;
290
+
240
291
  /** 当前图元所属单体的控制位:实例化取实例属性,其余取逐顶点属性 */
241
292
  float s3mBatchRaw() {
242
293
  #ifdef USE_INSTANCING
@@ -245,27 +296,85 @@ const VERT_PARS = /* glsl */ `
245
296
  return aBatchId;
246
297
  #endif
247
298
  }
299
+
300
+ vec4 s3mSampleBatch( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
301
+ vec4 vA = texture2D( texA, uv );
302
+ if ( mixVal <= 0.0001 ) return vA;
303
+ vec4 vB = texture2D( texB, uv );
304
+ return mix( vA, vB, mixVal );
305
+ }
306
+
307
+ vec4 s3mSampleBatchRow0( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
308
+ vec4 vA = texture2D( texA, uv );
309
+ if ( mixVal <= 0.0001 ) return vA;
310
+ vec4 vB = texture2D( texB, uv );
311
+ vec4 res = mix( vA, vB, mixVal );
312
+ if ( vA.r < 0.0 && vB.r >= 0.0 ) {
313
+ res.rgb = vB.rgb;
314
+ } else if ( vA.r >= 0.0 && vB.r < 0.0 ) {
315
+ res.rgb = vA.rgb;
316
+ }
317
+ if ( vA.a < -0.5 || vB.a < -0.5 ) {
318
+ res.a = ( mixVal >= 0.5 ) ? vB.a : vA.a;
319
+ }
320
+ return res;
321
+ }
322
+
323
+ vec4 s3mSampleBatchRow1( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
324
+ vec4 vA = texture2D( texA, uv );
325
+ if ( mixVal <= 0.0001 ) return vA;
326
+ vec4 vB = texture2D( texB, uv );
327
+ vec4 res = mix( vA, vB, mixVal );
328
+ if ( vA.a == 0.0 && vB.a > 0.0 ) {
329
+ res.a = mix( 1.0, vB.a, mixVal );
330
+ } else if ( vA.a > 0.0 && vB.a == 0.0 ) {
331
+ res.a = mix( vA.a, 1.0, mixVal );
332
+ }
333
+ return res;
334
+ }
248
335
  `;
249
336
 
250
- const VERT_ELEVATION = /* glsl */ `
251
- float s3mBatch = s3mBatchRaw();
252
- vec3 s3mCustomOffset = vec3( 0.0 );
253
- if ( uBatchTexActive > 0 && s3mBatch >= 0.0 ) {
254
- vec4 bState = texture2D( uBatchTex, vec2( ( s3mBatch + 0.5 ) / uBatchTexWidth, 0.25 ) );
255
- if ( bState.a < -0.5 ) {
256
- transformed = vec3( 0.0 );
257
- } else {
258
- vec4 bOffset = texture2D( uBatchTex, vec2( ( s3mBatch + 0.5 ) / uBatchTexWidth, 0.75 ) );
337
+ const VERT_SETUP = /* glsl */ `
338
+ s3mCustomOffset = vec3( 0.0 );
339
+ s3mRot = mat3( 1.0 );
340
+ s3mPivot = vec3( 0.0 );
341
+ float s3mBatchSetup = s3mBatchRaw();
342
+ if ( uBatchTexActive > 0 && s3mBatchSetup >= 0.0 ) {
343
+ float s3mU = ( s3mBatchSetup + 0.5 ) / uBatchTexWidth;
344
+ vec4 bState = s3mSampleBatchRow0( uBatchTex, uBatchTex2, vec2( s3mU, 0.1 ), uBatchMix );
345
+ if ( bState.a >= -0.5 ) {
346
+ vec4 bOffset = s3mSampleBatchRow1( uBatchTex, uBatchTex2, vec2( s3mU, 0.3 ), uBatchMix );
259
347
  s3mCustomOffset = bOffset.xyz;
260
348
  if ( s3mCustomOffset.z == 0.0 && bState.a > 0.0 ) {
261
349
  s3mCustomOffset.z = bState.a;
262
350
  }
351
+ vec4 r2 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.5 ), uBatchMix );
352
+ vec4 r3 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.7 ), uBatchMix );
353
+ vec4 r4 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.9 ), uBatchMix );
354
+ s3mRot = mat3(
355
+ vec3( r2.x, r3.x, r4.x ),
356
+ vec3( r2.y, r3.y, r4.y ),
357
+ vec3( r2.z, r3.z, r4.z )
358
+ );
359
+ s3mPivot = vec3( r2.w, r3.w, r4.w );
360
+ }
361
+ }
362
+ objectNormal = normalize( s3mRot * objectNormal );
363
+ `;
364
+
365
+ const VERT_ELEVATION = /* glsl */ `
366
+ float s3mBatchElev = s3mBatchRaw();
367
+ if ( uBatchTexActive > 0 && s3mBatchElev >= 0.0 ) {
368
+ float s3mU = ( s3mBatchElev + 0.5 ) / uBatchTexWidth;
369
+ vec4 bState = s3mSampleBatchRow0( uBatchTex, uBatchTex2, vec2( s3mU, 0.1 ), uBatchMix );
370
+ if ( bState.a < -0.5 ) {
371
+ transformed = vec3( 0.0 );
263
372
  }
264
373
  }
265
374
 
266
- float s3mSelElev = max( step( 0.0, uSelBatch ) * step( abs( s3mBatch - uSelBatch ), 0.5 ), uSelWhole );
267
- // 实例化时这是局部坐标,实例矩阵只含平移 + 绕 z 旋转(白模摆位),所以偏移仍沿局部轴生效
268
- transformed += s3mCustomOffset;
375
+ float s3mSelElev = max( step( 0.0, uSelBatch ) * step( abs( s3mBatchElev - uSelBatch ), 0.5 ), uSelWhole );
376
+ // 刚体旋转以 s3mPivot 为中心,再叠加自定义平移与选中拔高
377
+ transformed = s3mRot * ( transformed - s3mPivot ) + s3mPivot + s3mCustomOffset;
269
378
  transformed.z += s3mSelElev * uSelElevation;
270
379
  `;
271
380
 
@@ -293,7 +402,7 @@ const VERT_WORLD = /* glsl */ `
293
402
  #else
294
403
  mat3 s3mNormMat = mat3( modelMatrix );
295
404
  #endif
296
- vWorldNormal = normalize( s3mNormMat * normal );
405
+ vWorldNormal = normalize( s3mNormMat * ( s3mRot * normal ) );
297
406
  `;
298
407
 
299
408
  const FRAG_PARS = /* glsl */ `
@@ -376,6 +485,8 @@ const FRAG_PARS = /* glsl */ `
376
485
  uniform float uSelEmissive;
377
486
  uniform float uDimFactor;
378
487
  uniform sampler2D uBatchTex;
488
+ uniform sampler2D uBatchTex2;
489
+ uniform float uBatchMix;
379
490
  uniform float uBatchTexWidth;
380
491
  uniform int uBatchTexActive;
381
492
  uniform float uFilterMinZ;
@@ -391,12 +502,45 @@ const FRAG_PARS = /* glsl */ `
391
502
  uniform float uSelWhole;
392
503
  uniform float uRoofThreshold;
393
504
  uniform float uTexMix;
505
+ uniform float uAmbientGain;
506
+ uniform float uOpacity;
507
+ uniform float uTileFade;
508
+ uniform float uFade;
394
509
  varying float vBatchId;
395
510
  varying vec3 vVertColor;
396
511
  varying float vWorldZ;
397
512
  varying vec3 vWorldNormal;
398
513
  varying vec3 vWorldPos;
399
514
 
515
+ vec4 s3mSampleBatchRow0( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
516
+ vec4 vA = texture2D( texA, uv );
517
+ if ( mixVal <= 0.0001 ) return vA;
518
+ vec4 vB = texture2D( texB, uv );
519
+ vec4 res = mix( vA, vB, mixVal );
520
+ if ( vA.r < 0.0 && vB.r >= 0.0 ) {
521
+ res.rgb = vB.rgb;
522
+ } else if ( vA.r >= 0.0 && vB.r < 0.0 ) {
523
+ res.rgb = vA.rgb;
524
+ }
525
+ if ( vA.a < -0.5 || vB.a < -0.5 ) {
526
+ res.a = ( mixVal >= 0.5 ) ? vB.a : vA.a;
527
+ }
528
+ return res;
529
+ }
530
+
531
+ vec4 s3mSampleBatchRow1( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
532
+ vec4 vA = texture2D( texA, uv );
533
+ if ( mixVal <= 0.0001 ) return vA;
534
+ vec4 vB = texture2D( texB, uv );
535
+ vec4 res = mix( vA, vB, mixVal );
536
+ if ( vA.a == 0.0 && vB.a > 0.0 ) {
537
+ res.a = mix( 1.0, vB.a, mixVal );
538
+ } else if ( vA.a > 0.0 && vB.a == 0.0 ) {
539
+ res.a = mix( vA.a, 1.0, mixVal );
540
+ }
541
+ return res;
542
+ }
543
+
400
544
  float s3mHash( float p ) {
401
545
  p = fract( p * 0.1031 );
402
546
  p *= p + 33.33;
@@ -430,6 +574,8 @@ const FRAG_PARS = /* glsl */ `
430
574
  const FRAG_COLOR = /* glsl */ `
431
575
  float s3mH = clamp( ( vWorldZ - uHeightMin ) / max( uHeightMax - uHeightMin, 0.001 ), 0.0, 1.0 );
432
576
  vec3 s3mBase = diffuseColor.rgb; // 已含 diffuse × map
577
+ // 图层整体透明度(共享 uniform)+ 单瓦片淡入系数(逐材质),逐帧改都不产生重编译
578
+ diffuseColor.a *= uOpacity * uTileFade;
433
579
  // 所有按单体取值(配色 / 状态纹理 / 选中判定)都走取整后的 id,避免插值噪声
434
580
  float s3mBid = s3mBatchId();
435
581
 
@@ -490,7 +636,8 @@ const FRAG_COLOR = /* glsl */ `
490
636
  bool hasCustomColor = false;
491
637
  if ( uBatchTexActive > 0 && s3mBid >= 0.0 ) {
492
638
  // 用取整后的 id 采样:否则逐像素的插值误差会让纹素在边界上来回跳,同样出彩色噪点
493
- vec4 bState = texture2D( uBatchTex, vec2( ( s3mBid + 0.5 ) / uBatchTexWidth, 0.25 ) );
639
+ float s3mU = ( s3mBid + 0.5 ) / uBatchTexWidth;
640
+ vec4 bState = s3mSampleBatchRow0( uBatchTex, uBatchTex2, vec2( s3mU, 0.1 ), uBatchMix );
494
641
  if ( bState.a < -0.5 ) {
495
642
  discard; // 属于被过滤隐藏的单体
496
643
  }
@@ -501,7 +648,7 @@ const FRAG_COLOR = /* glsl */ `
501
648
  }
502
649
  // 单体独立透明度(状态纹理第 2 行 alpha,0 = 无覆盖)。
503
650
  // 与全局 uOpacity(diffuseColor.a 初值)和图集贴图自带 alpha 逐级叠乘
504
- vec4 bOffset = texture2D( uBatchTex, vec2( ( s3mBid + 0.5 ) / uBatchTexWidth, 0.75 ) );
651
+ vec4 bOffset = s3mSampleBatchRow1( uBatchTex, uBatchTex2, vec2( s3mU, 0.3 ), uBatchMix );
505
652
  if ( bOffset.a > 0.0 ) {
506
653
  diffuseColor.a *= bOffset.a;
507
654
  }
@@ -531,6 +678,10 @@ const FRAG_COLOR = /* glsl */ `
531
678
  } else {
532
679
  diffuseColor.rgb *= mix( 1.0, uDimFactor, uDim );
533
680
  }
681
+
682
+ // 压暗乘子放在最后:mode 分支会整体覆写 diffuseColor.rgb,
683
+ // 放在前面会被覆盖掉。setMode 的 fade-through 就靠它。
684
+ diffuseColor.rgb *= uFade;
534
685
  `;
535
686
 
536
687
  const FRAG_EMISSIVE = /* glsl */ `
@@ -539,6 +690,10 @@ const FRAG_EMISSIVE = /* glsl */ `
539
690
  if ( uSelMode != 3 ) {
540
691
  totalEmissiveRadiance += uSelColor * s3mSelE * uSelMix * ( uSelEmissive * 0.4 );
541
692
  }
693
+ // 直通自发光微调 / 环境光底色提升:补偿背光阴影面(尤其 TOP_SIDE 立面),避免光照阴影面过暗
694
+ if ( uAmbientGain > 0.001 ) {
695
+ totalEmissiveRadiance += diffuseColor.rgb * uAmbientGain;
696
+ }
542
697
  `;
543
698
 
544
699
  const FRAG_BAKE = /* glsl */ `
@@ -594,8 +749,11 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
594
749
  // 没有单体化信息的几何(batchId = -1)只能整块选中
595
750
  const uSelWhole = { value: 0 };
596
751
  const uBatchTex = { value: DUMMY_BATCH_TEX };
752
+ const uBatchTex2 = { value: DUMMY_BATCH_TEX };
597
753
  const uBatchTexWidth = { value: 1.0 };
598
754
  const uBatchTexActive = { value: 0 };
755
+ /** 单块瓦片的淡入系数:逐材质独享(每块网格一份材质),默认 1 = 完全显示 */
756
+ const uTileFade = { value: 1 };
599
757
 
600
758
  // 图集材质:只有几何确实带了 aAtlasRect 才开这个宏,否则缺属性会采到 (0,0) 的默认值
601
759
  const atlas = !!opts.atlas && !!opts.map;
@@ -611,8 +769,10 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
611
769
  uSelBatch,
612
770
  uSelWhole,
613
771
  uBatchTex,
772
+ uBatchTex2,
614
773
  uBatchTexWidth,
615
774
  uBatchTexActive,
775
+ uTileFade,
616
776
  uSecMap: { value: secMap },
617
777
  uAtlasDim,
618
778
  uAtlasDim1,
@@ -620,6 +780,7 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
620
780
 
621
781
  shader.vertexShader = shader.vertexShader
622
782
  .replace('#include <common>', `#include <common>\n${VERT_PARS}`)
783
+ .replace('#include <beginnormal_vertex>', `#include <beginnormal_vertex>\n${VERT_SETUP}`)
623
784
  .replace('#include <begin_vertex>', `#include <begin_vertex>\n${VERT_ELEVATION}`)
624
785
  .replace('#include <worldpos_vertex>', VERT_WORLD);
625
786
 
@@ -654,8 +815,10 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
654
815
  mat.userData.uSelBatch = uSelBatch;
655
816
  mat.userData.uSelWhole = uSelWhole;
656
817
  mat.userData.uBatchTex = uBatchTex;
818
+ mat.userData.uBatchTex2 = uBatchTex2;
657
819
  mat.userData.uBatchTexWidth = uBatchTexWidth;
658
820
  mat.userData.uBatchTexActive = uBatchTexActive;
821
+ mat.userData.uTileFade = uTileFade;
659
822
  mat.userData.isStyled = true;
660
823
  return mat;
661
824
  }
@@ -669,26 +832,48 @@ const OUTLINE_VERT = /* glsl */ `
669
832
  uniform float uWidth;
670
833
  uniform float uElevation;
671
834
  uniform sampler2D uBatchTex;
835
+ uniform sampler2D uBatchTex2;
836
+ uniform float uBatchMix;
672
837
  uniform float uBatchTexWidth;
673
838
  uniform int uBatchTexActive;
674
839
  varying float vSel;
840
+
841
+ vec4 s3mSampleBatch( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
842
+ vec4 vA = texture2D( texA, uv );
843
+ if ( mixVal <= 0.0001 ) return vA;
844
+ vec4 vB = texture2D( texB, uv );
845
+ return mix( vA, vB, mixVal );
846
+ }
847
+
675
848
  void main() {
676
849
  vSel = max( step( 0.0, uSelBatch ) * step( abs( aBatchId - uSelBatch ), 0.5 ), uSelWhole );
677
850
  vec3 s3mCustomOffset = vec3( 0.0 );
851
+ mat3 s3mRot = mat3( 1.0 );
852
+ vec3 s3mPivot = vec3( 0.0 );
678
853
  if ( uBatchTexActive > 0 && aBatchId >= 0.0 ) {
679
- vec4 bState = texture2D( uBatchTex, vec2( ( aBatchId + 0.5 ) / uBatchTexWidth, 0.25 ) );
854
+ float s3mU = ( aBatchId + 0.5 ) / uBatchTexWidth;
855
+ vec4 bState = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.1 ), uBatchMix );
680
856
  if ( bState.a < -0.5 ) {
681
857
  vSel = 0.0; // 过滤隐藏的单体不显示描边
682
858
  } else {
683
- vec4 bOffset = texture2D( uBatchTex, vec2( ( aBatchId + 0.5 ) / uBatchTexWidth, 0.75 ) );
859
+ vec4 bOffset = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.3 ), uBatchMix );
684
860
  s3mCustomOffset = bOffset.xyz;
685
861
  if ( s3mCustomOffset.z == 0.0 && bState.a > 0.0 ) {
686
862
  s3mCustomOffset.z = bState.a;
687
863
  }
864
+ vec4 r2 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.5 ), uBatchMix );
865
+ vec4 r3 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.7 ), uBatchMix );
866
+ vec4 r4 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.9 ), uBatchMix );
867
+ s3mRot = mat3(
868
+ vec3( r2.x, r3.x, r4.x ),
869
+ vec3( r2.y, r3.y, r4.y ),
870
+ vec3( r2.z, r3.z, r4.z )
871
+ );
872
+ s3mPivot = vec3( r2.w, r3.w, r4.w );
688
873
  }
689
874
  }
690
- vec3 p = position + normal * uWidth * vSel;
691
- p += s3mCustomOffset;
875
+ vec3 p = position + ( s3mRot * normal ) * uWidth * vSel;
876
+ p = s3mRot * ( p - s3mPivot ) + s3mPivot + s3mCustomOffset;
692
877
  p.z += vSel * uElevation;
693
878
  gl_Position = projectionMatrix * modelViewMatrix * vec4( p, 1.0 );
694
879
  }
@@ -710,6 +895,8 @@ export interface OutlineMaterial extends THREE.ShaderMaterial {
710
895
  uWidth: { value: number };
711
896
  uElevation: { value: number };
712
897
  uBatchTex: { value: THREE.Texture };
898
+ uBatchTex2: { value: THREE.Texture };
899
+ uBatchMix: { value: number };
713
900
  uBatchTexWidth: { value: number };
714
901
  uBatchTexActive: { value: number };
715
902
  uColor: { value: THREE.Color };
@@ -725,6 +912,8 @@ export function createOutlineMaterial(shared: StyleUniforms, initialWidth = 0.9)
725
912
  uWidth: { value: initialWidth },
726
913
  uElevation: { value: 0 },
727
914
  uBatchTex: { value: DUMMY_BATCH_TEX },
915
+ uBatchTex2: { value: DUMMY_BATCH_TEX },
916
+ uBatchMix: shared.uBatchMix ?? { value: 0 },
728
917
  uBatchTexWidth: { value: 1.0 },
729
918
  uBatchTexActive: { value: 0 },
730
919
  uColor: { value: shared.uSelColor.value.clone() },
@@ -1178,7 +1178,9 @@ export class S3MTileset {
1178
1178
  if (inRange) node.lastSeen = this._frame;
1179
1179
 
1180
1180
  const childUrls = node.childUrls ?? (node.childUrl ? [node.childUrl] : []);
1181
- const needRefine = childUrls.length > 0 && (byDistance ? metric < threshold : metric > threshold);
1181
+ // 加入 8% 迟滞区间(Hysteresis Band),防止相机在 LOD 临界距离处发生父子瓦片逐帧来回横跳闪烁
1182
+ const hFactor = node.needRefine ? (byDistance ? 1.08 : 0.92) : 1.0;
1183
+ const needRefine = childUrls.length > 0 && (byDistance ? metric < threshold * hFactor : metric > threshold * hFactor);
1182
1184
  node.needRefine = needRefine;
1183
1185
 
1184
1186
  const dbgSeen = ((globalThis as { __S3M_LOD_SEEN?: Set<string> }).__S3M_LOD_SEEN ??= new Set<string>());