s3m-three 0.4.21 → 0.4.23

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 (43) hide show
  1. package/dist/browser/s3m-three.iife.ext-three.js +35 -35
  2. package/dist/browser/s3m-three.iife.js +30 -30
  3. package/dist/render/DatasetManager.d.ts +30 -0
  4. package/dist/render/DatasetManager.d.ts.map +1 -1
  5. package/dist/render/DatasetManager.js +41 -0
  6. package/dist/render/DatasetManager.js.map +1 -1
  7. package/dist/render/GeometryBuilder.d.ts +18 -0
  8. package/dist/render/GeometryBuilder.d.ts.map +1 -1
  9. package/dist/render/GeometryBuilder.js +4 -1
  10. package/dist/render/GeometryBuilder.js.map +1 -1
  11. package/dist/render/S3MViewer.d.ts +68 -11
  12. package/dist/render/S3MViewer.d.ts.map +1 -1
  13. package/dist/render/S3MViewer.js +181 -19
  14. package/dist/render/S3MViewer.js.map +1 -1
  15. package/dist/render/StyleMaterial.d.ts +23 -2
  16. package/dist/render/StyleMaterial.d.ts.map +1 -1
  17. package/dist/render/StyleMaterial.js +35 -5
  18. package/dist/render/StyleMaterial.js.map +1 -1
  19. package/dist/render/TextureFactory.d.ts +23 -1
  20. package/dist/render/TextureFactory.d.ts.map +1 -1
  21. package/dist/render/TextureFactory.js +32 -8
  22. package/dist/render/TextureFactory.js.map +1 -1
  23. package/dist/render/viewer/LightRig.d.ts +21 -7
  24. package/dist/render/viewer/LightRig.d.ts.map +1 -1
  25. package/dist/render/viewer/LightRig.js +11 -13
  26. package/dist/render/viewer/LightRig.js.map +1 -1
  27. package/dist/render/viewer/types.d.ts +16 -2
  28. package/dist/render/viewer/types.d.ts.map +1 -1
  29. package/dist/render/viewer/types.js.map +1 -1
  30. package/dist/tileset/S3MTileset.d.ts +33 -0
  31. package/dist/tileset/S3MTileset.d.ts.map +1 -1
  32. package/dist/tileset/S3MTileset.js +27 -0
  33. package/dist/tileset/S3MTileset.js.map +1 -1
  34. package/docs/API.md +26 -5
  35. package/package.json +1 -1
  36. package/src/render/DatasetManager.ts +47 -0
  37. package/src/render/GeometryBuilder.ts +22 -1
  38. package/src/render/S3MViewer.ts +192 -19
  39. package/src/render/StyleMaterial.ts +48 -6
  40. package/src/render/TextureFactory.ts +50 -8
  41. package/src/render/viewer/LightRig.ts +23 -13
  42. package/src/render/viewer/types.ts +16 -2
  43. package/src/tileset/S3MTileset.ts +43 -0
@@ -23,8 +23,8 @@
23
23
  import * as THREE from 'three';
24
24
  import { usableGeoBounds } from '../tileset/S3MTileset.js';
25
25
  import { setSplatViewport } from './GeometryBuilder.js';
26
- import { setMaxAnisotropy, setCompressedTexturesSupported, setTextureOffThreadDecode, setMaxTextureSize as setTextureSizeCap } from './TextureFactory.js';
27
- import { getMaterialQuality, setMaterialQuality } from './StyleMaterial.js';
26
+ import { setMaxAnisotropy, setCompressedTexturesSupported, setTextureOffThreadDecode, setMaxTextureSize as setTextureSizeCap, applyAnisotropyCap } from './TextureFactory.js';
27
+ import { getMaterialQuality, normalizeMaterialQuality, getColorPipeline, normalizeColorPipeline } from './StyleMaterial.js';
28
28
  import { LightRig } from './viewer/LightRig.js';
29
29
  import { HeightIndex } from './viewer/HeightIndex.js';
30
30
  import { SplatLayer } from './viewer/SplatLayer.js';
@@ -91,6 +91,17 @@ const STYLE_SCALAR_KEYS = [
91
91
  'edgeHullWidth',
92
92
  'outlineWidth',
93
93
  ];
94
+ /**
95
+ * 标量样式键在「用户还没设过」时的动画起点。
96
+ *
97
+ * 只对 `metalness` / `roughness` 有意义:它们的 uniform 默认是 -1(不覆盖,保持数据
98
+ * 自身的值),从 -1 插值没意义 —— 取数据侧常见值当起点,过渡才不会先跳到 0。
99
+ * 其余键缺省仍是 0(与旧行为一致)。
100
+ */
101
+ const SCALAR_ANIM_START = {
102
+ metalness: 0, // S3M 数据的 metalness 恒为 0
103
+ roughness: 0.95, // shininess 映射后的常见值,见 GeometryBuilder 的 roughness
104
+ };
94
105
  /** 能逐帧插值的颜色键(在线性空间插值,与 shader 消费端一致) */
95
106
  const STYLE_COLOR_KEYS = ['colorA', 'colorB', 'colorC', 'selectColor', 'edgeColor'];
96
107
  /** 可动画的单体属性 */
@@ -129,6 +140,25 @@ export class S3MViewer {
129
140
  groundManager;
130
141
  /** 多数据集生命周期与锚点管理器 */
131
142
  _datasetManager = new DatasetManager();
143
+ /**
144
+ * 本图层的光照模型档位。
145
+ *
146
+ * `undefined` = 没指定,跟随模块级默认(`getMaterialQuality()`)—— 老代码
147
+ * `setMaterialQuality('lambert')` 一行改全场的用法靠这条继续有效。
148
+ * 传了值就只认自己,多图层同页各用各的,互不覆盖。
149
+ */
150
+ _materialQuality;
151
+ /**
152
+ * 本图层的颜色管线。
153
+ *
154
+ * `undefined` = 没指定,跟随模块级默认(`getColorPipeline()`)。
155
+ * 与 materialQuality 同理:传了值就只认自己,同页多图层互不污染 —— 这条尤其重要,
156
+ * 因为 `lightingPreset: 'supermap'` 会同时改管线、纹理解码方式与各向异性,
157
+ * 以前是三个模块级全局一起被改写。
158
+ */
159
+ _colorPipeline;
160
+ /** fade-through 编排的代次:中途再切一次,旧编排作废(与 setMode 同一套) */
161
+ _pipelineGen = 0;
132
162
  /** 单体样式、专题着色与状态纹理管理器 */
133
163
  _featureStyleManager;
134
164
  /** GPU 路径动画管理器 */
@@ -219,7 +249,11 @@ export class S3MViewer {
219
249
  this._unsupported = options.unsupported ?? 'warn';
220
250
  this._lodPixelRatioCap = options.lodPixelRatioCap ?? Infinity;
221
251
  setTextureSizeCap(options.maxTextureSize ?? Infinity);
222
- setMaterialQuality(options.materialQuality ?? 'pbr');
252
+ // 只记本层的档位,**不写模块级全局**:以前这里 setMaterialQuality(...) 会把全局
253
+ // 改掉,于是同页后创建的图层顺手把先创建的图层也打回去了(多层互相覆盖的根因)。
254
+ this._materialQuality = normalizeMaterialQuality(options.materialQuality);
255
+ // 之后 addDataset 的数据集按这个默认建材质(数据集自己指定则用自己的)
256
+ this._datasetManager.defaultMaterialQuality = this._materialQuality;
223
257
  if (options.totalMaxTiles) {
224
258
  this._datasetManager.totalMaxTiles = options.totalMaxTiles;
225
259
  }
@@ -243,7 +277,12 @@ export class S3MViewer {
243
277
  // 图层自身是否半透明:全局透明度改走 uOpacity 后,材质 opacity 不再代表它
244
278
  () => this._opacityTransparent);
245
279
  this.uniforms = createSharedUniforms();
246
- // 光源状态在这里就位:LightRig 构造时会按 lightingPreset 切模块级颜色管线,
280
+ // 图层 profile 先落位(显式传了就只认自己),再把 LightRig 挂上来 ——
281
+ // 它构造时会按 lightingPreset 覆盖一次管线,这一步走的是图层级通道,
282
+ // 不再去改模块级全局(以前三个全局一起被改写,同页多图层互相污染)。
283
+ this._colorPipeline = normalizeColorPipeline(options.colorPipeline);
284
+ this._applyRenderProfile({}, true);
285
+ // 光源状态在这里就位:LightRig 构造时会按 lightingPreset 切图层颜色管线,
247
286
  // 挂进场景要等 scene 建好(构造函数上面已经建了)。
248
287
  this._lights = new LightRig({
249
288
  scene: () => this.scene,
@@ -254,6 +293,8 @@ export class S3MViewer {
254
293
  // 走公共 triggerRepaint 而不是 _requestRepaint:宿主 / 单测会覆盖前者来观察重绘,
255
294
  // 绕过它会让「有没有触发重绘」这件事失去观测点
256
295
  repaint: () => this.triggerRepaint(),
296
+ // 构造期(initial)只记状态:那时还没有材质可重建、没有数据集可同步
297
+ applyRenderProfile: (p, initial) => this._applyRenderProfile(p, initial),
257
298
  });
258
299
  this._lights.addToScene();
259
300
  this._heights = new HeightIndex(this.uniforms);
@@ -1548,7 +1589,11 @@ export class S3MViewer {
1548
1589
  this.animator.register(`style.${key}`, {
1549
1590
  dim: 1,
1550
1591
  read: (out) => {
1551
- out[0] = Number(this.style[key] ?? 0);
1592
+ // 金属度 / 粗糙度没设过时 uniform 是 -1(= 不覆盖,保持数据自身的值),
1593
+ // 动画起点不能取 0:那会让 roughness 从 0.95 先跳到 0 再爬回去。
1594
+ // 这里给的是数据侧的常见值(S3M 的 shininess 映射后 ≈0.95、metalness 恒 0),
1595
+ // 过渡起点因此是连续的。
1596
+ out[0] = Number(this.style[key] ?? SCALAR_ANIM_START[key] ?? 0);
1552
1597
  },
1553
1598
  write: (v) => {
1554
1599
  this.style[key] = v[0];
@@ -1790,21 +1835,124 @@ export class S3MViewer {
1790
1835
  this._requestRepaint();
1791
1836
  }
1792
1837
  /**
1793
- * 运行时切换材质质感(`'pbr'` | `'lambert'`)。
1838
+ * 运行时切换**本图层**的材质质感(`'pbr'` | `'lambert'`)。
1794
1839
  *
1795
- * 档位是模块级设置(影响之后新建的材质);已加载瓦片的材质在这里**原位重建**:
1796
- * 不重拉瓦片、不重建几何,按创建时记录的参数(`userData.s3mOpts`)换一个
1797
- * 新材质实例,并保留逐材质状态(选中编号、瓦片淡入、批量状态纹理)。
1798
- * 切换后 program 需要重新编译一次(lambert / pbr 是不同着色器),这是唯一开销。
1840
+ * 档位是图层级的:只改这一层,同页其它图层不受影响(也不会去动模块级默认)。
1841
+ * 已加载瓦片的材质在这里**原位重建**:不重拉瓦片、不重建几何,按创建时记录的
1842
+ * 参数(`userData.s3mOpts`)换一个新材质实例,并保留逐材质状态(选中编号、
1843
+ * 瓦片淡入、批量状态纹理)。切换后 program 需要重新编译一次(lambert / pbr 是
1844
+ * 不同着色器),这是唯一开销。
1799
1845
  */
1800
1846
  setMaterialQuality(q) {
1801
- setMaterialQuality(q);
1847
+ this._materialQuality = normalizeMaterialQuality(q) ?? 'pbr';
1848
+ // 已建好的数据集也要跟着换,否则之后新加载的瓦片还是旧档位(和重建结果打架)
1849
+ this._datasetManager.setDefaultMaterialQuality(this._materialQuality);
1802
1850
  this._rebuildStyledMaterials();
1803
1851
  this._requestRepaint();
1804
1852
  }
1805
- /** 当前材质质感档位 */
1853
+ /** 当前材质质感档位(本图层指定了就用本图层的,否则是模块级默认) */
1806
1854
  get materialQuality() {
1807
- return getMaterialQuality();
1855
+ return this._materialQuality ?? getMaterialQuality();
1856
+ }
1857
+ /**
1858
+ * 运行时切换**本图层**的颜色管线(`'linear'` | `'direct'`)。
1859
+ *
1860
+ * `'direct'` = 官方 iClient3D 的 sRGB 直乘管线(配合纹理解码关 sRGB),
1861
+ * 见 `lightingPreset: 'supermap'`。切换牵扯三样东西:材质宏(换 program)、
1862
+ * 纹理 colorSpace(重传,**不重新下载瓦片**)、以及各向异性档位。
1863
+ *
1864
+ * 给 `duration > 0` 时走 fade-through:压暗 → 在同一帧回调里换管线 → 亮回来。
1865
+ * 逐像素混合做不到干净 —— 两条管线的差异有一部分在采样端(硬件 sRGB 解码 vs
1866
+ * 原始字节),要混合就得让贴图恒为原始字节并在片元里手动解码,那会改掉默认路径。
1867
+ */
1868
+ setColorPipeline(q, opts = {}) {
1869
+ const next = normalizeColorPipeline(q) ?? 'linear';
1870
+ if (next === this.colorPipeline)
1871
+ return Promise.resolve();
1872
+ const d = Math.max(0, opts.duration ?? 0);
1873
+ if (d <= 0) {
1874
+ this._applyRenderProfile({ colorPipeline: next });
1875
+ return Promise.resolve();
1876
+ }
1877
+ const dim = Math.min(1, Math.max(0.05, opts.dim ?? 0.35));
1878
+ const gen = ++this._pipelineGen;
1879
+ return new Promise((resolve) => {
1880
+ this.animator.to('style.fade', dim, {
1881
+ duration: d,
1882
+ easing: 'easeInOutQuad',
1883
+ onComplete: () => {
1884
+ // 中途又切了一次:这次编排作废,收尾交给新的那次
1885
+ if (gen !== this._pipelineGen) {
1886
+ resolve();
1887
+ return;
1888
+ }
1889
+ this._applyRenderProfile({ colorPipeline: next });
1890
+ this.animator.to('style.fade', 1, {
1891
+ duration: d,
1892
+ easing: 'easeInOutQuad',
1893
+ onComplete: resolve,
1894
+ });
1895
+ },
1896
+ });
1897
+ });
1898
+ }
1899
+ /** 当前颜色管线(本图层指定了就用本图层的,否则是模块级默认) */
1900
+ get colorPipeline() {
1901
+ return this._colorPipeline ?? getColorPipeline();
1902
+ }
1903
+ /** 本图层此刻该用的纹理创建参数(图层的渲染 profile 就落在这里) */
1904
+ _textureOptions() {
1905
+ return {
1906
+ // direct 管线的前提是贴图字节值不解码,两者必须一起切
1907
+ srgbDecode: this.colorPipeline !== 'direct',
1908
+ maxSize: this.options.maxTextureSize ?? Infinity,
1909
+ anisotropyCap: this.options.anisotropyCap ?? Infinity,
1910
+ };
1911
+ }
1912
+ /**
1913
+ * 应用图层渲染 profile(颜色管线 / 各向异性上限)。
1914
+ *
1915
+ * `initial` = 构造期:只记状态并推给数据集管理器,不重建材质、不重传纹理
1916
+ * (那时还没有瓦片)。
1917
+ */
1918
+ _applyRenderProfile(patch, initial = false) {
1919
+ const prev = this.colorPipeline;
1920
+ if (patch.colorPipeline !== undefined) {
1921
+ this._colorPipeline = normalizeColorPipeline(patch.colorPipeline) ?? 'linear';
1922
+ }
1923
+ if (patch.anisotropyCap !== undefined)
1924
+ this.options.anisotropyCap = patch.anisotropyCap;
1925
+ const textureOptions = this._textureOptions();
1926
+ // 之后加载的瓦片按新参数走(已建好的数据集也要跟着换)
1927
+ this._datasetManager.setRenderProfile({ colorPipeline: this.colorPipeline, textureOptions });
1928
+ if (initial)
1929
+ return;
1930
+ // 管线变了才需要换 program;只改各向异性时重建纯属浪费
1931
+ if (this.colorPipeline !== prev)
1932
+ this._rebuildStyledMaterials();
1933
+ this._syncCachedTextures(textureOptions);
1934
+ this._requestRepaint();
1935
+ }
1936
+ /**
1937
+ * 把纹理解码方式 / 各向异性同步到**已上传**的纹理上。
1938
+ *
1939
+ * 两者都不需要重新解码:`colorSpace` 改了 + `needsUpdate` 让 three 重传一次像素,
1940
+ * `anisotropy` 同理。所以切管线不用重拉瓦片,代价只有一次纹理上传。
1941
+ */
1942
+ _syncCachedTextures(opts) {
1943
+ const want = opts.srgbDecode ? THREE.SRGBColorSpace : THREE.NoColorSpace;
1944
+ for (const entry of this.datasets.values()) {
1945
+ const cache = entry.tileset?.textureCache;
1946
+ if (!cache)
1947
+ continue;
1948
+ for (const tex of cache.values()) {
1949
+ if (tex.colorSpace === want)
1950
+ continue;
1951
+ tex.colorSpace = want;
1952
+ tex.needsUpdate = true;
1953
+ }
1954
+ applyAnisotropyCap(cache.values(), opts.anisotropyCap ?? Infinity);
1955
+ }
1808
1956
  }
1809
1957
  /** 按创建参数原位重建所有 styled 材质(materialQuality 切换用) */
1810
1958
  _rebuildStyledMaterials() {
@@ -1827,7 +1975,14 @@ export class S3MViewer {
1827
1975
  if (!old?.userData?.isStyled || !old.userData?.s3mOpts)
1828
1976
  continue;
1829
1977
  const opts = old.userData.s3mOpts;
1830
- const fresh = createStyledMaterial({ ...opts, uniforms: this.uniforms });
1978
+ // 显式传本层档位与管线:s3mOpts 里记的是创建时的参数,可能是 undefined(跟随全局),
1979
+ // 重建时必须钉成本层当前值,否则「别处改了全局」会把这一层带跑。
1980
+ const fresh = createStyledMaterial({
1981
+ ...opts,
1982
+ uniforms: this.uniforms,
1983
+ materialQuality: this.materialQuality,
1984
+ colorPipeline: this.colorPipeline,
1985
+ });
1831
1986
  for (const key of PRESERVED) {
1832
1987
  const src = old.userData[key];
1833
1988
  const dst = fresh.userData[key];
@@ -2162,16 +2317,19 @@ export class S3MViewer {
2162
2317
  return this.options.anisotropyCap ?? Infinity;
2163
2318
  }
2164
2319
  /**
2165
- * 调整各向异性过滤上限(默认 Infinity = 取 GPU 最大值)。
2166
- * 只影响**之后创建**的纹理——已上传的纹理需要重新上传才会变,代价远大于收益,
2167
- * 所以切换档位请在建图层前决定,或接受「新瓦片按新档位、旧瓦片保持原样」。
2320
+ * 调整本图层的各向异性过滤上限(默认 Infinity = 取 GPU 最大值)。
2321
+ *
2322
+ * 与 0.4.22 之前不同:已上传的纹理**会立刻跟着变**(改 sampling 参数 + 重传,
2323
+ * 不需要重新解码,也不需要重载瓦片),所以切 `lightingPreset: 'supermap'` 时
2324
+ * 立面清晰度是当场变的,不再等新瓦片。
2168
2325
  */
2169
2326
  setAnisotropyCap(cap) {
2170
2327
  const next = Number.isFinite(cap) && cap > 0 ? Math.max(1, cap) : Infinity;
2171
2328
  this.options.anisotropyCap = next;
2329
+ // 同步模块级默认:直接调 createTexture / buildTileObjects 的老用法仍读它
2172
2330
  const gpuMax = this.renderer?.capabilities.getMaxAnisotropy?.() ?? 1;
2173
2331
  setMaxAnisotropy(gpuMax, next);
2174
- this._requestRepaint();
2332
+ this._applyRenderProfile({ anisotropyCap: next });
2175
2333
  }
2176
2334
  /** 当前纹理长边像素上限。Infinity = 不限制 */
2177
2335
  get maxTextureSize() {
@@ -2180,12 +2338,16 @@ export class S3MViewer {
2180
2338
  /**
2181
2339
  * 调整纹理长边像素上限(默认 Infinity = 不限制)。
2182
2340
  * 只影响之后解码的纹理;非图集的 RGBA / WebP 产物按 2× 减半逼近上限。
2183
- * 注意这是**模块级**设置(图集 / 解码 Worker 共享),多 viewer 实例会互相覆盖。
2341
+ *
2342
+ * 图层级:只改本层,同页其它图层不受影响(同时同步模块级默认,照顾直接调
2343
+ * `createTexture` 的老用法)。已解码的纹理不会重解 —— 那是另一件事。
2184
2344
  */
2185
2345
  setMaxTextureSize(n) {
2186
2346
  const next = Number.isFinite(n) && n >= 1 ? n : Infinity;
2187
2347
  this.options.maxTextureSize = next;
2188
2348
  setTextureSizeCap(next);
2349
+ // 只推给数据集,不动材质也不重传纹理(尺寸上限只影响「之后解码」的纹理)
2350
+ this._datasetManager.setRenderProfile({ textureOptions: this._textureOptions() });
2189
2351
  this._requestRepaint();
2190
2352
  }
2191
2353
  /**