s3m-three 0.3.8 → 0.3.10

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 (41) hide show
  1. package/dist/browser/s3m-three.iife.ext-three.js +322 -47
  2. package/dist/browser/s3m-three.iife.js +322 -47
  3. package/dist/core/types.d.ts +20 -0
  4. package/dist/core/types.d.ts.map +1 -1
  5. package/dist/index.d.ts +5 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/render/EdgeLines.d.ts +18 -0
  10. package/dist/render/EdgeLines.d.ts.map +1 -1
  11. package/dist/render/EdgeLines.js +95 -13
  12. package/dist/render/EdgeLines.js.map +1 -1
  13. package/dist/render/FeatureStyleManager.d.ts +78 -0
  14. package/dist/render/FeatureStyleManager.d.ts.map +1 -1
  15. package/dist/render/FeatureStyleManager.js +407 -59
  16. package/dist/render/FeatureStyleManager.js.map +1 -1
  17. package/dist/render/GPUPathManager.d.ts +95 -0
  18. package/dist/render/GPUPathManager.d.ts.map +1 -0
  19. package/dist/render/GPUPathManager.js +265 -0
  20. package/dist/render/GPUPathManager.js.map +1 -0
  21. package/dist/render/OrthoProjection.d.ts +6 -0
  22. package/dist/render/OrthoProjection.d.ts.map +1 -1
  23. package/dist/render/OrthoProjection.js +48 -0
  24. package/dist/render/OrthoProjection.js.map +1 -1
  25. package/dist/render/S3MViewer.d.ts +66 -0
  26. package/dist/render/S3MViewer.d.ts.map +1 -1
  27. package/dist/render/S3MViewer.js +218 -6
  28. package/dist/render/S3MViewer.js.map +1 -1
  29. package/dist/render/StyleMaterial.d.ts +21 -0
  30. package/dist/render/StyleMaterial.d.ts.map +1 -1
  31. package/dist/render/StyleMaterial.js +256 -21
  32. package/dist/render/StyleMaterial.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/core/types.ts +10 -0
  35. package/src/index.ts +8 -0
  36. package/src/render/EdgeLines.ts +101 -13
  37. package/src/render/FeatureStyleManager.ts +524 -64
  38. package/src/render/GPUPathManager.ts +306 -0
  39. package/src/render/OrthoProjection.ts +52 -0
  40. package/src/render/S3MViewer.ts +272 -5
  41. package/src/render/StyleMaterial.ts +264 -21
@@ -11,6 +11,7 @@
11
11
  import * as THREE from 'three';
12
12
  import type { StyleUniforms } from '../core/types.js';
13
13
  import { DEFAULT_FLOOR_HEIGHT } from '../core/FeatureInfo.js';
14
+ import { GPUPathManager } from './GPUPathManager.js';
14
15
 
15
16
  /** 着色模式,数值与着色器里的 uMode 一一对应 */
16
17
  export const STYLE = {
@@ -172,6 +173,7 @@ export interface StyledMaterial extends THREE.MeshStandardMaterial {
172
173
  uSelBatch: { value: number };
173
174
  uSelWhole: { value: number };
174
175
  uBatchTex: { value: THREE.Texture };
176
+ uBatchTex2: { value: THREE.Texture };
175
177
  uBatchTexWidth: { value: number };
176
178
  uBatchTexActive: { value: number };
177
179
  /** 单块瓦片的淡入系数(1 = 完全显示) */
@@ -181,8 +183,15 @@ export interface StyledMaterial extends THREE.MeshStandardMaterial {
181
183
  };
182
184
  }
183
185
 
184
- const DUMMY_DATA = new Float32Array([-1, -1, -1, 0, 0, 0, 0, 0]);
185
- export const DUMMY_BATCH_TEX = new THREE.DataTexture(DUMMY_DATA, 1, 2, THREE.RGBAFormat, THREE.FloatType);
186
+ const DUMMY_DATA = new Float32Array([
187
+ -1, -1, -1, 0, // Row 0: color rgb, elevation/hidden
188
+ 0, 0, 0, 0, // Row 1: translation xyz, opacity
189
+ 1, 0, 0, 0, // Row 2: rot row 0, pivot.x
190
+ 0, 1, 0, 0, // Row 3: rot row 1, pivot.y
191
+ 0, 0, 1, 0, // Row 4: rot row 2, pivot.z
192
+ -1, 0, 0, 0, // Row 5: pathId (-1=none), speed, phase, modeFlag
193
+ ]);
194
+ export const DUMMY_BATCH_TEX = new THREE.DataTexture(DUMMY_DATA, 1, 6, THREE.RGBAFormat, THREE.FloatType);
186
195
  DUMMY_BATCH_TEX.minFilter = THREE.NearestFilter;
187
196
  DUMMY_BATCH_TEX.magFilter = THREE.NearestFilter;
188
197
  DUMMY_BATCH_TEX.needsUpdate = true;
@@ -226,6 +235,16 @@ export function createSharedUniforms(): StyleUniforms {
226
235
  uTexMix: { value: 0 },
227
236
  /** 基础底色增益 / 暗部补偿(0~1,默认 0.0) */
228
237
  uAmbientGain: { value: 0 },
238
+ /** 双纹理交叉淡化插值系数(0 = 纯 uBatchTex, 1 = 纯 uBatchTex2) */
239
+ uBatchMix: { value: 0 },
240
+ /** GPU 路径数据采样纹理 */
241
+ uPathTex: { value: GPUPathManager.DUMMY_PATH_TEX },
242
+ /** GPU 路径纹理中包含的路径条数(高度) */
243
+ uPathCount: { value: 1 },
244
+ /** 是否激活 GPU 路径动画(0 = 未激活,1 = 激活) */
245
+ uPathTexActive: { value: 0 },
246
+ /** GPU 路径时间戳(秒) */
247
+ uPathTime: { value: 0 },
229
248
  /**
230
249
  * 图层整体透明度:在片元里乘进 diffuseColor.a。
231
250
  *
@@ -264,14 +283,25 @@ const VERT_PARS = /* glsl */ `
264
283
  uniform float uSelWhole;
265
284
  uniform float uSelElevation;
266
285
  uniform sampler2D uBatchTex;
286
+ uniform sampler2D uBatchTex2;
287
+ uniform float uBatchMix;
267
288
  uniform float uBatchTexWidth;
268
289
  uniform int uBatchTexActive;
290
+ uniform sampler2D uPathTex;
291
+ uniform float uPathCount;
292
+ uniform float uPathTime;
293
+ uniform int uPathTexActive;
269
294
  varying float vBatchId;
270
295
  varying vec3 vVertColor;
271
296
  varying float vWorldZ;
272
297
  varying vec3 vWorldNormal;
273
298
  varying vec3 vWorldPos;
274
299
 
300
+ // 跨 chunk 共享的顶点姿态变量(在 beginnormal_vertex 建立,begin_vertex / worldpos 使用)
301
+ vec3 s3mCustomOffset;
302
+ mat3 s3mRot;
303
+ vec3 s3mPivot;
304
+
275
305
  /** 当前图元所属单体的控制位:实例化取实例属性,其余取逐顶点属性 */
276
306
  float s3mBatchRaw() {
277
307
  #ifdef USE_INSTANCING
@@ -280,27 +310,126 @@ const VERT_PARS = /* glsl */ `
280
310
  return aBatchId;
281
311
  #endif
282
312
  }
313
+
314
+ vec4 s3mSampleBatch( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
315
+ vec4 vA = texture2D( texA, uv );
316
+ if ( mixVal <= 0.0001 ) return vA;
317
+ vec4 vB = texture2D( texB, uv );
318
+ return mix( vA, vB, mixVal );
319
+ }
320
+
321
+ vec4 s3mSampleBatchRow0( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
322
+ vec4 vA = texture2D( texA, uv );
323
+ if ( mixVal <= 0.0001 ) return vA;
324
+ vec4 vB = texture2D( texB, uv );
325
+ vec4 res = mix( vA, vB, mixVal );
326
+ if ( vA.r < 0.0 && vB.r >= 0.0 ) {
327
+ res.rgb = vB.rgb;
328
+ } else if ( vA.r >= 0.0 && vB.r < 0.0 ) {
329
+ res.rgb = vA.rgb;
330
+ }
331
+ if ( vA.a < -0.5 || vB.a < -0.5 ) {
332
+ res.a = ( mixVal >= 0.5 ) ? vB.a : vA.a;
333
+ }
334
+ return res;
335
+ }
336
+
337
+ vec4 s3mSampleBatchRow1( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
338
+ vec4 vA = texture2D( texA, uv );
339
+ if ( mixVal <= 0.0001 ) return vA;
340
+ vec4 vB = texture2D( texB, uv );
341
+ vec4 res = mix( vA, vB, mixVal );
342
+ if ( vA.a == 0.0 && vB.a > 0.0 ) {
343
+ res.a = mix( 1.0, vB.a, mixVal );
344
+ } else if ( vA.a > 0.0 && vB.a == 0.0 ) {
345
+ res.a = mix( vA.a, 1.0, mixVal );
346
+ }
347
+ return res;
348
+ }
283
349
  `;
284
350
 
285
- const VERT_ELEVATION = /* glsl */ `
286
- float s3mBatch = s3mBatchRaw();
287
- vec3 s3mCustomOffset = vec3( 0.0 );
288
- if ( uBatchTexActive > 0 && s3mBatch >= 0.0 ) {
289
- vec4 bState = texture2D( uBatchTex, vec2( ( s3mBatch + 0.5 ) / uBatchTexWidth, 0.25 ) );
290
- if ( bState.a < -0.5 ) {
291
- transformed = vec3( 0.0 );
292
- } else {
293
- vec4 bOffset = texture2D( uBatchTex, vec2( ( s3mBatch + 0.5 ) / uBatchTexWidth, 0.75 ) );
351
+ const VERT_SETUP = /* glsl */ `
352
+ s3mCustomOffset = vec3( 0.0 );
353
+ s3mRot = mat3( 1.0 );
354
+ s3mPivot = vec3( 0.0 );
355
+ float s3mBatchSetup = s3mBatchRaw();
356
+ if ( uBatchTexActive > 0 && s3mBatchSetup >= 0.0 ) {
357
+ float s3mU = ( s3mBatchSetup + 0.5 ) / uBatchTexWidth;
358
+ vec4 bState = s3mSampleBatchRow0( uBatchTex, uBatchTex2, vec2( s3mU, 0.08333333 ), uBatchMix );
359
+ if ( bState.a >= -0.5 ) {
360
+ vec4 bOffset = s3mSampleBatchRow1( uBatchTex, uBatchTex2, vec2( s3mU, 0.25 ), uBatchMix );
294
361
  s3mCustomOffset = bOffset.xyz;
295
362
  if ( s3mCustomOffset.z == 0.0 && bState.a > 0.0 ) {
296
363
  s3mCustomOffset.z = bState.a;
297
364
  }
365
+ vec4 r2 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.41666667 ), uBatchMix );
366
+ vec4 r3 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.58333333 ), uBatchMix );
367
+ vec4 r4 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.75 ), uBatchMix );
368
+ s3mRot = mat3(
369
+ vec3( r2.x, r3.x, r4.x ),
370
+ vec3( r2.y, r3.y, r4.y ),
371
+ vec3( r2.z, r3.z, r4.z )
372
+ );
373
+ s3mPivot = vec3( r2.w, r3.w, r4.w );
374
+
375
+ // Row 5: GPU 路径动画计算
376
+ if ( uPathTexActive > 0 ) {
377
+ vec4 r5 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.91666667 ), uBatchMix );
378
+ float pathId = r5.x;
379
+ float speed = r5.y;
380
+ float phase = r5.z;
381
+ float mode = r5.w;
382
+ if ( pathId >= 0.0 && mode > 0.5 && uPathCount > 0.0 ) {
383
+ float t = phase + uPathTime * speed;
384
+ bool autoHeading = true;
385
+ if ( mode >= 4.0 ) {
386
+ autoHeading = false;
387
+ mode -= 3.0;
388
+ }
389
+ if ( mode < 1.5 ) {
390
+ t = fract( t );
391
+ } else if ( mode < 2.5 ) {
392
+ t = clamp( t, 0.0, 1.0 );
393
+ } else {
394
+ t = mod( t, 2.0 );
395
+ t = ( t > 1.0 ) ? ( 2.0 - t ) : t;
396
+ }
397
+ float pathU = ( t * 255.0 + 0.5 ) / 256.0;
398
+ float pathV = ( pathId + 0.5 ) / uPathCount;
399
+ vec4 pathSample = texture2D( uPathTex, vec2( pathU, pathV ) );
400
+ vec3 pathPos = pathSample.xyz;
401
+ float pathYaw = pathSample.w;
402
+ if ( autoHeading ) {
403
+ float c = cos( pathYaw );
404
+ float s = sin( pathYaw );
405
+ mat3 rotHeading = mat3(
406
+ c, -s, 0.0,
407
+ s, c, 0.0,
408
+ 0.0, 0.0, 1.0
409
+ );
410
+ s3mRot = rotHeading * s3mRot;
411
+ }
412
+ s3mCustomOffset += pathPos;
413
+ }
414
+ }
298
415
  }
299
416
  }
417
+ objectNormal = normalize( s3mRot * objectNormal );
418
+ `;
300
419
 
301
- float s3mSelElev = max( step( 0.0, uSelBatch ) * step( abs( s3mBatch - uSelBatch ), 0.5 ), uSelWhole );
302
- // 实例化时这是局部坐标,实例矩阵只含平移 + 绕 z 旋转(白模摆位),所以偏移仍沿局部轴生效
303
- transformed += s3mCustomOffset;
420
+ const VERT_ELEVATION = /* glsl */ `
421
+ float s3mBatchElev = s3mBatchRaw();
422
+ if ( uBatchTexActive > 0 && s3mBatchElev >= 0.0 ) {
423
+ float s3mU = ( s3mBatchElev + 0.5 ) / uBatchTexWidth;
424
+ vec4 bState = s3mSampleBatchRow0( uBatchTex, uBatchTex2, vec2( s3mU, 0.08333333 ), uBatchMix );
425
+ if ( bState.a < -0.5 ) {
426
+ transformed = vec3( 0.0 );
427
+ }
428
+ }
429
+
430
+ float s3mSelElev = max( step( 0.0, uSelBatch ) * step( abs( s3mBatchElev - uSelBatch ), 0.5 ), uSelWhole );
431
+ // 刚体旋转以 s3mPivot 为中心,再叠加自定义平移与选中拔高
432
+ transformed = s3mRot * ( transformed - s3mPivot ) + s3mPivot + s3mCustomOffset;
304
433
  transformed.z += s3mSelElev * uSelElevation;
305
434
  `;
306
435
 
@@ -328,7 +457,7 @@ const VERT_WORLD = /* glsl */ `
328
457
  #else
329
458
  mat3 s3mNormMat = mat3( modelMatrix );
330
459
  #endif
331
- vWorldNormal = normalize( s3mNormMat * normal );
460
+ vWorldNormal = normalize( s3mNormMat * ( s3mRot * normal ) );
332
461
  `;
333
462
 
334
463
  const FRAG_PARS = /* glsl */ `
@@ -411,6 +540,8 @@ const FRAG_PARS = /* glsl */ `
411
540
  uniform float uSelEmissive;
412
541
  uniform float uDimFactor;
413
542
  uniform sampler2D uBatchTex;
543
+ uniform sampler2D uBatchTex2;
544
+ uniform float uBatchMix;
414
545
  uniform float uBatchTexWidth;
415
546
  uniform int uBatchTexActive;
416
547
  uniform float uFilterMinZ;
@@ -436,6 +567,35 @@ const FRAG_PARS = /* glsl */ `
436
567
  varying vec3 vWorldNormal;
437
568
  varying vec3 vWorldPos;
438
569
 
570
+ vec4 s3mSampleBatchRow0( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
571
+ vec4 vA = texture2D( texA, uv );
572
+ if ( mixVal <= 0.0001 ) return vA;
573
+ vec4 vB = texture2D( texB, uv );
574
+ vec4 res = mix( vA, vB, mixVal );
575
+ if ( vA.r < 0.0 && vB.r >= 0.0 ) {
576
+ res.rgb = vB.rgb;
577
+ } else if ( vA.r >= 0.0 && vB.r < 0.0 ) {
578
+ res.rgb = vA.rgb;
579
+ }
580
+ if ( vA.a < -0.5 || vB.a < -0.5 ) {
581
+ res.a = ( mixVal >= 0.5 ) ? vB.a : vA.a;
582
+ }
583
+ return res;
584
+ }
585
+
586
+ vec4 s3mSampleBatchRow1( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
587
+ vec4 vA = texture2D( texA, uv );
588
+ if ( mixVal <= 0.0001 ) return vA;
589
+ vec4 vB = texture2D( texB, uv );
590
+ vec4 res = mix( vA, vB, mixVal );
591
+ if ( vA.a == 0.0 && vB.a > 0.0 ) {
592
+ res.a = mix( 1.0, vB.a, mixVal );
593
+ } else if ( vA.a > 0.0 && vB.a == 0.0 ) {
594
+ res.a = mix( vA.a, 1.0, mixVal );
595
+ }
596
+ return res;
597
+ }
598
+
439
599
  float s3mHash( float p ) {
440
600
  p = fract( p * 0.1031 );
441
601
  p *= p + 33.33;
@@ -531,7 +691,8 @@ const FRAG_COLOR = /* glsl */ `
531
691
  bool hasCustomColor = false;
532
692
  if ( uBatchTexActive > 0 && s3mBid >= 0.0 ) {
533
693
  // 用取整后的 id 采样:否则逐像素的插值误差会让纹素在边界上来回跳,同样出彩色噪点
534
- vec4 bState = texture2D( uBatchTex, vec2( ( s3mBid + 0.5 ) / uBatchTexWidth, 0.25 ) );
694
+ float s3mU = ( s3mBid + 0.5 ) / uBatchTexWidth;
695
+ vec4 bState = s3mSampleBatchRow0( uBatchTex, uBatchTex2, vec2( s3mU, 0.08333333 ), uBatchMix );
535
696
  if ( bState.a < -0.5 ) {
536
697
  discard; // 属于被过滤隐藏的单体
537
698
  }
@@ -542,7 +703,7 @@ const FRAG_COLOR = /* glsl */ `
542
703
  }
543
704
  // 单体独立透明度(状态纹理第 2 行 alpha,0 = 无覆盖)。
544
705
  // 与全局 uOpacity(diffuseColor.a 初值)和图集贴图自带 alpha 逐级叠乘
545
- vec4 bOffset = texture2D( uBatchTex, vec2( ( s3mBid + 0.5 ) / uBatchTexWidth, 0.75 ) );
706
+ vec4 bOffset = s3mSampleBatchRow1( uBatchTex, uBatchTex2, vec2( s3mU, 0.25 ), uBatchMix );
546
707
  if ( bOffset.a > 0.0 ) {
547
708
  diffuseColor.a *= bOffset.a;
548
709
  }
@@ -643,6 +804,7 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
643
804
  // 没有单体化信息的几何(batchId = -1)只能整块选中
644
805
  const uSelWhole = { value: 0 };
645
806
  const uBatchTex = { value: DUMMY_BATCH_TEX };
807
+ const uBatchTex2 = { value: DUMMY_BATCH_TEX };
646
808
  const uBatchTexWidth = { value: 1.0 };
647
809
  const uBatchTexActive = { value: 0 };
648
810
  /** 单块瓦片的淡入系数:逐材质独享(每块网格一份材质),默认 1 = 完全显示 */
@@ -662,6 +824,7 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
662
824
  uSelBatch,
663
825
  uSelWhole,
664
826
  uBatchTex,
827
+ uBatchTex2,
665
828
  uBatchTexWidth,
666
829
  uBatchTexActive,
667
830
  uTileFade,
@@ -672,6 +835,7 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
672
835
 
673
836
  shader.vertexShader = shader.vertexShader
674
837
  .replace('#include <common>', `#include <common>\n${VERT_PARS}`)
838
+ .replace('#include <beginnormal_vertex>', `#include <beginnormal_vertex>\n${VERT_SETUP}`)
675
839
  .replace('#include <begin_vertex>', `#include <begin_vertex>\n${VERT_ELEVATION}`)
676
840
  .replace('#include <worldpos_vertex>', VERT_WORLD);
677
841
 
@@ -706,6 +870,7 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
706
870
  mat.userData.uSelBatch = uSelBatch;
707
871
  mat.userData.uSelWhole = uSelWhole;
708
872
  mat.userData.uBatchTex = uBatchTex;
873
+ mat.userData.uBatchTex2 = uBatchTex2;
709
874
  mat.userData.uBatchTexWidth = uBatchTexWidth;
710
875
  mat.userData.uBatchTexActive = uBatchTexActive;
711
876
  mat.userData.uTileFade = uTileFade;
@@ -722,26 +887,92 @@ const OUTLINE_VERT = /* glsl */ `
722
887
  uniform float uWidth;
723
888
  uniform float uElevation;
724
889
  uniform sampler2D uBatchTex;
890
+ uniform sampler2D uBatchTex2;
891
+ uniform float uBatchMix;
725
892
  uniform float uBatchTexWidth;
726
893
  uniform int uBatchTexActive;
894
+ uniform sampler2D uPathTex;
895
+ uniform float uPathCount;
896
+ uniform float uPathTime;
897
+ uniform int uPathTexActive;
727
898
  varying float vSel;
899
+
900
+ vec4 s3mSampleBatch( sampler2D texA, sampler2D texB, vec2 uv, float mixVal ) {
901
+ vec4 vA = texture2D( texA, uv );
902
+ if ( mixVal <= 0.0001 ) return vA;
903
+ vec4 vB = texture2D( texB, uv );
904
+ return mix( vA, vB, mixVal );
905
+ }
906
+
728
907
  void main() {
729
908
  vSel = max( step( 0.0, uSelBatch ) * step( abs( aBatchId - uSelBatch ), 0.5 ), uSelWhole );
730
909
  vec3 s3mCustomOffset = vec3( 0.0 );
910
+ mat3 s3mRot = mat3( 1.0 );
911
+ vec3 s3mPivot = vec3( 0.0 );
731
912
  if ( uBatchTexActive > 0 && aBatchId >= 0.0 ) {
732
- vec4 bState = texture2D( uBatchTex, vec2( ( aBatchId + 0.5 ) / uBatchTexWidth, 0.25 ) );
913
+ float s3mU = ( aBatchId + 0.5 ) / uBatchTexWidth;
914
+ vec4 bState = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.08333333 ), uBatchMix );
733
915
  if ( bState.a < -0.5 ) {
734
916
  vSel = 0.0; // 过滤隐藏的单体不显示描边
735
917
  } else {
736
- vec4 bOffset = texture2D( uBatchTex, vec2( ( aBatchId + 0.5 ) / uBatchTexWidth, 0.75 ) );
918
+ vec4 bOffset = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.25 ), uBatchMix );
737
919
  s3mCustomOffset = bOffset.xyz;
738
920
  if ( s3mCustomOffset.z == 0.0 && bState.a > 0.0 ) {
739
921
  s3mCustomOffset.z = bState.a;
740
922
  }
923
+ vec4 r2 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.41666667 ), uBatchMix );
924
+ vec4 r3 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.58333333 ), uBatchMix );
925
+ vec4 r4 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.75 ), uBatchMix );
926
+ s3mRot = mat3(
927
+ vec3( r2.x, r3.x, r4.x ),
928
+ vec3( r2.y, r3.y, r4.y ),
929
+ vec3( r2.z, r3.z, r4.z )
930
+ );
931
+ s3mPivot = vec3( r2.w, r3.w, r4.w );
932
+
933
+ if ( uPathTexActive > 0 ) {
934
+ vec4 r5 = s3mSampleBatch( uBatchTex, uBatchTex2, vec2( s3mU, 0.91666667 ), uBatchMix );
935
+ float pathId = r5.x;
936
+ float speed = r5.y;
937
+ float phase = r5.z;
938
+ float mode = r5.w;
939
+ if ( pathId >= 0.0 && mode > 0.5 && uPathCount > 0.0 ) {
940
+ float t = phase + uPathTime * speed;
941
+ bool autoHeading = true;
942
+ if ( mode >= 4.0 ) {
943
+ autoHeading = false;
944
+ mode -= 3.0;
945
+ }
946
+ if ( mode < 1.5 ) {
947
+ t = fract( t );
948
+ } else if ( mode < 2.5 ) {
949
+ t = clamp( t, 0.0, 1.0 );
950
+ } else {
951
+ t = mod( t, 2.0 );
952
+ t = ( t > 1.0 ) ? ( 2.0 - t ) : t;
953
+ }
954
+ float pathU = ( t * 255.0 + 0.5 ) / 256.0;
955
+ float pathV = ( pathId + 0.5 ) / uPathCount;
956
+ vec4 pathSample = texture2D( uPathTex, vec2( pathU, pathV ) );
957
+ vec3 pathPos = pathSample.xyz;
958
+ float pathYaw = pathSample.w;
959
+ if ( autoHeading ) {
960
+ float c = cos( pathYaw );
961
+ float s = sin( pathYaw );
962
+ mat3 rotHeading = mat3(
963
+ c, -s, 0.0,
964
+ s, c, 0.0,
965
+ 0.0, 0.0, 1.0
966
+ );
967
+ s3mRot = rotHeading * s3mRot;
968
+ }
969
+ s3mCustomOffset += pathPos;
970
+ }
971
+ }
741
972
  }
742
973
  }
743
- vec3 p = position + normal * uWidth * vSel;
744
- p += s3mCustomOffset;
974
+ vec3 p = position + ( s3mRot * normal ) * uWidth * vSel;
975
+ p = s3mRot * ( p - s3mPivot ) + s3mPivot + s3mCustomOffset;
745
976
  p.z += vSel * uElevation;
746
977
  gl_Position = projectionMatrix * modelViewMatrix * vec4( p, 1.0 );
747
978
  }
@@ -763,8 +994,14 @@ export interface OutlineMaterial extends THREE.ShaderMaterial {
763
994
  uWidth: { value: number };
764
995
  uElevation: { value: number };
765
996
  uBatchTex: { value: THREE.Texture };
997
+ uBatchTex2: { value: THREE.Texture };
998
+ uBatchMix: { value: number };
766
999
  uBatchTexWidth: { value: number };
767
1000
  uBatchTexActive: { value: number };
1001
+ uPathTex: { value: unknown };
1002
+ uPathCount: { value: number };
1003
+ uPathTexActive: { value: number };
1004
+ uPathTime: { value: number };
768
1005
  uColor: { value: THREE.Color };
769
1006
  };
770
1007
  }
@@ -778,8 +1015,14 @@ export function createOutlineMaterial(shared: StyleUniforms, initialWidth = 0.9)
778
1015
  uWidth: { value: initialWidth },
779
1016
  uElevation: { value: 0 },
780
1017
  uBatchTex: { value: DUMMY_BATCH_TEX },
1018
+ uBatchTex2: { value: DUMMY_BATCH_TEX },
1019
+ uBatchMix: shared.uBatchMix ?? { value: 0 },
781
1020
  uBatchTexWidth: { value: 1.0 },
782
1021
  uBatchTexActive: { value: 0 },
1022
+ uPathTex: shared.uPathTex ?? { value: GPUPathManager.DUMMY_PATH_TEX },
1023
+ uPathCount: shared.uPathCount ?? { value: 1 },
1024
+ uPathTexActive: shared.uPathTexActive ?? { value: 0 },
1025
+ uPathTime: shared.uPathTime ?? { value: 0 },
783
1026
  uColor: { value: shared.uSelColor.value.clone() },
784
1027
  },
785
1028
  vertexShader: OUTLINE_VERT,