s3m-three 0.2.0 → 0.3.1
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/README.md +2 -2
- package/dist/browser/s3m-three.iife.ext-three.js +585 -0
- package/dist/browser/s3m-three.iife.js +4419 -0
- package/dist/browser.d.ts +3 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +21 -0
- package/dist/browser.js.map +1 -0
- package/dist/core/AtlasAlpha.d.ts +31 -0
- package/dist/core/AtlasAlpha.d.ts.map +1 -0
- package/dist/core/AtlasAlpha.js +82 -0
- package/dist/core/AtlasAlpha.js.map +1 -0
- package/dist/core/AtlasPack.d.ts +57 -0
- package/dist/core/AtlasPack.d.ts.map +1 -0
- package/dist/core/AtlasPack.js +81 -0
- package/dist/core/AtlasPack.js.map +1 -0
- package/dist/core/AtlasUpgrade.d.ts +68 -0
- package/dist/core/AtlasUpgrade.d.ts.map +1 -0
- package/dist/core/AtlasUpgrade.js +154 -0
- package/dist/core/AtlasUpgrade.js.map +1 -0
- package/dist/core/DXTTextureDecode.d.ts.map +1 -1
- package/dist/core/DXTTextureDecode.js +28 -6
- package/dist/core/DXTTextureDecode.js.map +1 -1
- package/dist/core/Draco.d.ts +52 -0
- package/dist/core/Draco.d.ts.map +1 -0
- package/dist/core/Draco.js +323 -0
- package/dist/core/Draco.js.map +1 -0
- package/dist/core/MeshOpt.d.ts +14 -3
- package/dist/core/MeshOpt.d.ts.map +1 -1
- package/dist/core/MeshOpt.js +101 -18
- package/dist/core/MeshOpt.js.map +1 -1
- package/dist/core/S3M1Parser.d.ts +16 -0
- package/dist/core/S3M1Parser.d.ts.map +1 -0
- package/dist/core/S3M1Parser.js +367 -0
- package/dist/core/S3M1Parser.js.map +1 -0
- package/dist/core/S3MBParser.d.ts +43 -1
- package/dist/core/S3MBParser.d.ts.map +1 -1
- package/dist/core/S3MBParser.js +101 -23
- package/dist/core/S3MBParser.js.map +1 -1
- package/dist/core/S3MBlockParser.d.ts +209 -0
- package/dist/core/S3MBlockParser.d.ts.map +1 -0
- package/dist/core/S3MBlockParser.js +789 -0
- package/dist/core/S3MBlockParser.js.map +1 -0
- package/dist/core/TextureFormat.d.ts +0 -1
- package/dist/core/TextureFormat.d.ts.map +1 -1
- package/dist/core/TextureFormat.js +19 -2
- package/dist/core/TextureFormat.js.map +1 -1
- package/dist/core/types.d.ts +65 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/render/DatasetManager.d.ts.map +1 -1
- package/dist/render/DatasetManager.js +5 -3
- package/dist/render/DatasetManager.js.map +1 -1
- package/dist/render/GeometryBuilder.d.ts +2 -0
- package/dist/render/GeometryBuilder.d.ts.map +1 -1
- package/dist/render/GeometryBuilder.js +185 -16
- package/dist/render/GeometryBuilder.js.map +1 -1
- package/dist/render/GroundManager.d.ts +57 -1
- package/dist/render/GroundManager.d.ts.map +1 -1
- package/dist/render/GroundManager.js +149 -21
- package/dist/render/GroundManager.js.map +1 -1
- package/dist/render/Picker.d.ts.map +1 -1
- package/dist/render/Picker.js +12 -4
- package/dist/render/Picker.js.map +1 -1
- package/dist/render/PointCloudMaterial.d.ts +36 -0
- package/dist/render/PointCloudMaterial.d.ts.map +1 -0
- package/dist/render/PointCloudMaterial.js +159 -0
- package/dist/render/PointCloudMaterial.js.map +1 -0
- package/dist/render/S3MViewer.d.ts +19 -0
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +143 -22
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/StyleMaterial.d.ts +6 -0
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +125 -2
- package/dist/render/StyleMaterial.js.map +1 -1
- package/dist/render/TextureFactory.d.ts.map +1 -1
- package/dist/render/TextureFactory.js +64 -10
- package/dist/render/TextureFactory.js.map +1 -1
- package/dist/tileset/S3MTileset.d.ts +152 -1
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +672 -54
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/dist/vendor/crunch_decoder.module.js +12 -18
- package/dist/vendor/draco_decoder.module.js +3178 -0
- package/docs/API.md +112 -11
- package/package.json +5 -3
- package/src/browser.ts +21 -0
- package/src/core/AtlasAlpha.ts +82 -0
- package/src/core/AtlasPack.ts +127 -0
- package/src/core/AtlasUpgrade.ts +182 -0
- package/src/core/DXTTextureDecode.ts +28 -6
- package/src/core/Draco.ts +409 -0
- package/src/core/MeshOpt.ts +124 -18
- package/src/core/S3M1Parser.ts +430 -0
- package/src/core/S3MBParser.ts +112 -21
- package/src/core/S3MBlockParser.ts +953 -0
- package/src/core/TextureFormat.ts +20 -2
- package/src/core/types.ts +63 -0
- package/src/index.ts +2 -1
- package/src/render/DatasetManager.ts +5 -5
- package/src/render/GeometryBuilder.ts +207 -15
- package/src/render/GroundManager.ts +174 -20
- package/src/render/Picker.ts +13 -4
- package/src/render/PointCloudMaterial.ts +174 -0
- package/src/render/S3MViewer.ts +140 -24
- package/src/render/StyleMaterial.ts +135 -2
- package/src/render/TextureFactory.ts +67 -11
- package/src/tileset/S3MTileset.ts +704 -59
- package/src/vendor/crunch_decoder.module.js +12 -18
- package/src/vendor/draco_decoder.module.d.ts +128 -0
- package/src/vendor/draco_decoder.module.js +3178 -0
package/src/render/S3MViewer.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* MapLibre / Mapbox 的胶水层在 s3m-maplibre 包的 S3MLayer.ts。
|
|
22
22
|
*/
|
|
23
23
|
import * as THREE from 'three';
|
|
24
|
-
import { S3MTileset } from '../tileset/S3MTileset.js';
|
|
24
|
+
import { S3MTileset, usableGeoBounds } from '../tileset/S3MTileset.js';
|
|
25
25
|
import type { BuildTileFn, TileNode } from '../tileset/S3MTileset.js';
|
|
26
26
|
import { setMaxAnisotropy } from './TextureFactory.js';
|
|
27
27
|
import {
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
createOutlineMaterial,
|
|
34
34
|
} from './StyleMaterial.js';
|
|
35
35
|
import type { OutlineMaterial, StyledMaterial, StyleMode, SelectMode, S3MStyle } from './StyleMaterial.js';
|
|
36
|
+
import { applyPointCloudStyle, isOwnedPointCloudMaterial } from './PointCloudMaterial.js';
|
|
36
37
|
import { Picker } from './Picker.js';
|
|
37
38
|
import {
|
|
38
39
|
buildFeatureEdgeSegments,
|
|
@@ -191,6 +192,8 @@ interface MeshUserData {
|
|
|
191
192
|
geoRef?: GeoPackageEntry;
|
|
192
193
|
tileRef?: ParsedTile;
|
|
193
194
|
primitiveType?: number;
|
|
195
|
+
/** 点云对象(THREE.Points):不吃面着色,也没有棱可描 */
|
|
196
|
+
isPointCloud?: boolean;
|
|
194
197
|
/** CPU 端现算的棱线对象(等宽线 Mesh) */
|
|
195
198
|
edges?: THREE.Object3D | null;
|
|
196
199
|
/** 数据自带棱线包(此时不再用 EdgesGeometry 现算) */
|
|
@@ -211,6 +214,11 @@ function styledMaterialOf(mesh: THREE.Mesh): StyledMaterial | null {
|
|
|
211
214
|
return styled?.userData?.isStyled ? styled : null;
|
|
212
215
|
}
|
|
213
216
|
|
|
217
|
+
/** 点云是 THREE.Points 不是 Mesh,样式 / 拾取 / 卸载链路都靠这个判定放行 */
|
|
218
|
+
function isPointCloudObject(o: THREE.Object3D): o is THREE.Points {
|
|
219
|
+
return (o as unknown as THREE.Points).isPoints === true;
|
|
220
|
+
}
|
|
221
|
+
|
|
214
222
|
/* ------------------------------------------------------------------ 图层 */
|
|
215
223
|
|
|
216
224
|
export class S3MViewer {
|
|
@@ -272,7 +280,7 @@ export class S3MViewer {
|
|
|
272
280
|
private _heightMax = -Infinity;
|
|
273
281
|
private readonly _boxScratch = new THREE.Box3();
|
|
274
282
|
private _selection: PickResult | null = null;
|
|
275
|
-
private _selectedMeshes: THREE.
|
|
283
|
+
private _selectedMeshes: THREE.Object3D[] = [];
|
|
276
284
|
private _outlineMesh: THREE.Mesh | null = null;
|
|
277
285
|
private _outlineMeshes: THREE.Mesh[] = [];
|
|
278
286
|
private _selectionEdges: THREE.Mesh | null = null;
|
|
@@ -540,8 +548,11 @@ export class S3MViewer {
|
|
|
540
548
|
/**
|
|
541
549
|
* 物理地表网格的高度跟随「主数据集的应用层抬升量」,
|
|
542
550
|
* 保证贴地后地表覆盖 / 地下透视的物理关系与被抬升的模型一致。
|
|
551
|
+
* `ground.trackHeight = false` 时不跟随:地表钉在当前高度,
|
|
552
|
+
* 高程调整只动模型不动切割面。
|
|
543
553
|
*/
|
|
544
554
|
private _syncGroundHeight(): void {
|
|
555
|
+
if (!this.groundManager.trackHeight) return;
|
|
545
556
|
const primaryId = this.datasets.keys().next().value;
|
|
546
557
|
if (!primaryId) return;
|
|
547
558
|
const userOffset = this._datasetManager.getDatasetHeight(primaryId);
|
|
@@ -704,6 +715,15 @@ export class S3MViewer {
|
|
|
704
715
|
this._requestRepaint();
|
|
705
716
|
}
|
|
706
717
|
|
|
718
|
+
/**
|
|
719
|
+
* 开关「物理地表作为深度遮挡源」:开启后地表以下的几何被真正切掉。
|
|
720
|
+
* 不依赖宿主是否开启 MapLibre terrain——深度缓冲是共用的,谁更近谁遮挡。
|
|
721
|
+
*/
|
|
722
|
+
setGroundOcclude(enabled: boolean): void {
|
|
723
|
+
this.groundManager.setOcclude(enabled);
|
|
724
|
+
this._requestRepaint();
|
|
725
|
+
}
|
|
726
|
+
|
|
707
727
|
/* ---------------------------------------------------------------- 状态纹理同步 */
|
|
708
728
|
|
|
709
729
|
private _isFeatureHidden(smid: number | null, _batchId: number): boolean {
|
|
@@ -773,6 +793,7 @@ export class S3MViewer {
|
|
|
773
793
|
mesh.geometry?.dispose?.();
|
|
774
794
|
const styled = styledMaterialOf(mesh);
|
|
775
795
|
if (styled) styled.dispose();
|
|
796
|
+
if (isOwnedPointCloudMaterial(mesh.material)) (mesh.material as THREE.Material).dispose();
|
|
776
797
|
this._featureStyleManager.disposeMesh(mesh);
|
|
777
798
|
});
|
|
778
799
|
});
|
|
@@ -808,8 +829,10 @@ export class S3MViewer {
|
|
|
808
829
|
continue;
|
|
809
830
|
}
|
|
810
831
|
|
|
832
|
+
// 点云是 THREE.Points 不是 Mesh,但同样要进拾取表、吃不透明度、跟着着色模式走
|
|
833
|
+
const isPoints = isPointCloudObject(child);
|
|
811
834
|
const mesh = child as THREE.Mesh;
|
|
812
|
-
if (!mesh.isMesh) continue;
|
|
835
|
+
if (!mesh.isMesh && !isPoints) continue;
|
|
813
836
|
const ud = mesh.userData as MeshUserData;
|
|
814
837
|
ud.pickId = this._allocPickId();
|
|
815
838
|
ud.datasetId = entry.id;
|
|
@@ -822,29 +845,88 @@ export class S3MViewer {
|
|
|
822
845
|
});
|
|
823
846
|
this._indexMeshSmids(mesh);
|
|
824
847
|
this._addMeshHeight(mesh);
|
|
825
|
-
|
|
848
|
+
// 点云没有「棱」:给一堆孤立点建 EdgesGeometry 又慢又没意义
|
|
849
|
+
if (this._edgesEnabled && !hasNativeEdges && !isPoints) this._ensureEdges(mesh);
|
|
826
850
|
|
|
827
851
|
// 新瓦片要继承当前的不透明度与着色方式
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
mat
|
|
832
|
-
mat
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
852
|
+
if (isPoints) {
|
|
853
|
+
this._applyPointCloudStyle(child as THREE.Points);
|
|
854
|
+
} else {
|
|
855
|
+
const mat = styledMaterialOf(mesh);
|
|
856
|
+
if (mat) {
|
|
857
|
+
const o = Math.min(1, Math.max(0.05, this.style.opacity ?? 1));
|
|
858
|
+
mat.opacity = o;
|
|
859
|
+
mat.transparent = o < 0.999;
|
|
860
|
+
mat.depthWrite = o >= 0.999;
|
|
861
|
+
mat.flatShading = this.style.shading !== 'smooth';
|
|
862
|
+
mat.needsUpdate = true;
|
|
863
|
+
this._featureStyleManager.applyOverridesToMesh(mesh);
|
|
864
|
+
}
|
|
837
865
|
}
|
|
866
|
+
|
|
867
|
+
// 选中状态也要跟着继承:LOD 细化 / 瓦片重载会换掉 mesh,
|
|
868
|
+
// 不重放就出现「点中的那栋楼一拉近就没高亮了」
|
|
869
|
+
this._replaySelectionOnto(mesh);
|
|
838
870
|
}
|
|
839
871
|
}
|
|
840
872
|
|
|
873
|
+
/**
|
|
874
|
+
* 把「当前选中」重放到一块新登记的 mesh 上。
|
|
875
|
+
*
|
|
876
|
+
* 为什么需要:选中高亮是直接写在各 mesh 材质的 uSelBatch / uSelWhole 上的,
|
|
877
|
+
* 而 LOD 细化、瓦片重载、淘汰回补都会换掉 mesh —— 不重放的话,用户点中的那栋楼
|
|
878
|
+
* 一细化就失去高亮(状态还在 _selection.smid 里,只是没人往新 mesh 上刷)。
|
|
879
|
+
*/
|
|
880
|
+
private _replaySelectionOnto(mesh: THREE.Mesh): void {
|
|
881
|
+
const sel = this._selection;
|
|
882
|
+
if (!sel) return;
|
|
883
|
+
const mat = styledMaterialOf(mesh);
|
|
884
|
+
if (!mat) return;
|
|
885
|
+
|
|
886
|
+
const ud = mesh.userData as MeshUserData;
|
|
887
|
+
let batchId = -1;
|
|
888
|
+
if (sel.smid != null) {
|
|
889
|
+
const pickInfo = ud.geoRef?.pickInfo as Record<string | number, unknown> | undefined;
|
|
890
|
+
const entry = pickInfo?.[sel.smid] ?? pickInfo?.[String(sel.smid)];
|
|
891
|
+
batchId = entry ? pickEntryId(entry as any) : -1;
|
|
892
|
+
}
|
|
893
|
+
// 拾取命中的那块 mesh 用原始 batchId;同一 smid 散在别的几何里时按各自的区间算
|
|
894
|
+
if (mesh === sel.mesh) batchId = sel.batchId;
|
|
895
|
+
// 这块几何里根本没有这个单体 → 不动它(否则会把无关瓦片一起点亮)
|
|
896
|
+
if (batchId < 0 && mesh !== sel.mesh) return;
|
|
897
|
+
|
|
898
|
+
mat.userData.uSelBatch.value = batchId;
|
|
899
|
+
mat.userData.uSelWhole.value = batchId < 0 ? 1 : 0;
|
|
900
|
+
if (!this._selectedMeshes.includes(mesh)) this._selectedMeshes.push(mesh);
|
|
901
|
+
if (isPointCloudObject(mesh)) this._applyPointCloudStyle(mesh, true);
|
|
902
|
+
this.uniforms.uSelMix.value = 1;
|
|
903
|
+
this.uniforms.uDim.value = this.style.dimOthers ? 1 : 0;
|
|
904
|
+
}
|
|
905
|
+
|
|
841
906
|
/** 瓦片被淘汰:从拾取表和场景里摘掉,并释放它自己的 GPU 资源 */
|
|
842
907
|
private _unregisterTile(node: TileNode, entry: S3MDatasetEntry): void {
|
|
843
908
|
for (const child of [...node.group.children]) {
|
|
844
909
|
const mesh = child as THREE.Mesh;
|
|
845
910
|
if (!mesh.isMesh) continue;
|
|
846
911
|
const ud = mesh.userData as MeshUserData;
|
|
847
|
-
|
|
912
|
+
// 「这块 mesh 没了」≠「用户不再选中」:LOD 细化 / 淘汰回补都会换掉 mesh。
|
|
913
|
+
// 以前这里一律 select(null),于是点中的那栋楼一拉近,选中状态就整个被清掉。
|
|
914
|
+
const wasSelected = this._selectedMeshes.includes(mesh);
|
|
915
|
+
if (wasSelected) {
|
|
916
|
+
this._selectedMeshes = this._selectedMeshes.filter((m) => m !== mesh);
|
|
917
|
+
}
|
|
918
|
+
if (this._selection?.mesh === mesh) {
|
|
919
|
+
const smid = this._selection.smid;
|
|
920
|
+
const carrier =
|
|
921
|
+
smid != null ? [...(this._smidMeshes.get(smid) ?? [])].find((m) => m !== mesh && m.parent) : undefined;
|
|
922
|
+
if (carrier) {
|
|
923
|
+
// 选中身份交给承载同一 smid 的另一块 mesh(通常是接替显示的子瓦)
|
|
924
|
+
this._selection = { ...this._selection, mesh: carrier };
|
|
925
|
+
this._replaySelectionOnto(carrier);
|
|
926
|
+
} else {
|
|
927
|
+
this.select(null);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
848
930
|
if (ud.pickId != null) {
|
|
849
931
|
this._pickables.delete(ud.pickId);
|
|
850
932
|
this._releasePickId(ud.pickId);
|
|
@@ -855,6 +937,8 @@ export class S3MViewer {
|
|
|
855
937
|
this._featureStyleManager.disposeMesh(mesh);
|
|
856
938
|
const styled = styledMaterialOf(mesh);
|
|
857
939
|
styled?.dispose?.();
|
|
940
|
+
// 点云材质是本模块按模式建的,不走 styled 那条链
|
|
941
|
+
if (isOwnedPointCloudMaterial(mesh.material)) (mesh.material as THREE.Material).dispose();
|
|
858
942
|
mesh.geometry?.dispose?.();
|
|
859
943
|
}
|
|
860
944
|
entry.root.remove(node.group);
|
|
@@ -890,6 +974,8 @@ export class S3MViewer {
|
|
|
890
974
|
this.style = { ...this.style, ...patch };
|
|
891
975
|
this._applyStyleUniforms();
|
|
892
976
|
if ('opacity' in patch || 'depthWrite' in patch) this._applyOpacity();
|
|
977
|
+
// 点云不走 StyleMaterial 的 uMode,模式切换要单独同步它的材质
|
|
978
|
+
if ('mode' in patch) this._applyPointCloudStyles();
|
|
893
979
|
if ('shading' in patch) this._applyShading();
|
|
894
980
|
if ('edges' in patch) this.setEdges(!!this.style.edges);
|
|
895
981
|
else if ('edgeColor' in patch || 'edgeWidth' in patch || 'opacity' in patch || 'edgeOpacity' in patch) {
|
|
@@ -959,6 +1045,10 @@ export class S3MViewer {
|
|
|
959
1045
|
// 关键:若 depthWrite 开启(默认开启),即使半透明也保持深度写入,完美遮挡背面棱线,防止背面线条透出
|
|
960
1046
|
const keepDepth = this.style.depthWrite !== false;
|
|
961
1047
|
for (const p of this._pickables.values()) {
|
|
1048
|
+
if (isPointCloudObject(p.mesh)) {
|
|
1049
|
+
this._applyPointCloudStyle(p.mesh);
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
962
1052
|
const m = styledMaterialOf(p.mesh);
|
|
963
1053
|
if (!m) continue;
|
|
964
1054
|
m.opacity = o;
|
|
@@ -968,6 +1058,19 @@ export class S3MViewer {
|
|
|
968
1058
|
this._requestRepaint();
|
|
969
1059
|
}
|
|
970
1060
|
|
|
1061
|
+
/** 单个点云对象同步当前样式(着色模式 + 不透明度 + 选中高亮) */
|
|
1062
|
+
private _applyPointCloudStyle(points: THREE.Points, selected = this._selectedMeshes.includes(points)): void {
|
|
1063
|
+
applyPointCloudStyle(points, this.style, this.uniforms, selected);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/** 全部点云刷新:着色模式切换时用(uniform 是共享引用,改色不用走这里) */
|
|
1067
|
+
private _applyPointCloudStyles(): void {
|
|
1068
|
+
for (const p of this._pickables.values()) {
|
|
1069
|
+
if (isPointCloudObject(p.mesh)) this._applyPointCloudStyle(p.mesh);
|
|
1070
|
+
}
|
|
1071
|
+
this._requestRepaint();
|
|
1072
|
+
}
|
|
1073
|
+
|
|
971
1074
|
/** 平面/平滑着色:FLAT_SHADED 是编译期宏,改它必须重编译 */
|
|
972
1075
|
private _applyShading(): void {
|
|
973
1076
|
const flat = this.style.shading !== 'smooth';
|
|
@@ -1104,7 +1207,8 @@ export class S3MViewer {
|
|
|
1104
1207
|
.map((p) => p.mesh)
|
|
1105
1208
|
.filter((m) => {
|
|
1106
1209
|
const ud = m.userData as MeshUserData;
|
|
1107
|
-
|
|
1210
|
+
// 点云没有棱可描
|
|
1211
|
+
return !ud.edges && !ud.nativeEdges && !isPointCloudObject(m);
|
|
1108
1212
|
});
|
|
1109
1213
|
let i = 0;
|
|
1110
1214
|
const step = (): void => {
|
|
@@ -1614,8 +1718,13 @@ export class S3MViewer {
|
|
|
1614
1718
|
select(result: PickResult | null, { silent = false }: { silent?: boolean } = {}): void {
|
|
1615
1719
|
// 清掉上一次
|
|
1616
1720
|
if (this._selection) {
|
|
1617
|
-
for (const
|
|
1618
|
-
|
|
1721
|
+
for (const obj of this._selectedMeshes) {
|
|
1722
|
+
// 点云:恢复成当前着色模式(去掉选中混色)
|
|
1723
|
+
if (isPointCloudObject(obj)) {
|
|
1724
|
+
this._applyPointCloudStyle(obj, false);
|
|
1725
|
+
continue;
|
|
1726
|
+
}
|
|
1727
|
+
const mat = styledMaterialOf(obj as THREE.Mesh);
|
|
1619
1728
|
if (mat) {
|
|
1620
1729
|
mat.userData.uSelBatch.value = -1;
|
|
1621
1730
|
mat.userData.uSelWhole.value = 0;
|
|
@@ -1666,14 +1775,22 @@ export class S3MViewer {
|
|
|
1666
1775
|
mat.userData.uSelWhole.value = batchId < 0 ? 1 : 0;
|
|
1667
1776
|
this._selectedMeshes.push(mesh);
|
|
1668
1777
|
}
|
|
1778
|
+
// 点云没有单体粒度:整块混成 selectColor,让选中至少看得见
|
|
1779
|
+
for (const mesh of this._selectedMeshes) {
|
|
1780
|
+
if (isPointCloudObject(mesh)) this._applyPointCloudStyle(mesh, true);
|
|
1781
|
+
}
|
|
1669
1782
|
this.uniforms.uSelMix.value = 1;
|
|
1670
1783
|
this.uniforms.uDim.value = this.style.dimOthers ? 1 : 0;
|
|
1671
1784
|
|
|
1672
1785
|
if (this.style.outline) {
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
this.
|
|
1786
|
+
// 棱线 / 外扩轮廓都是按「面」设计的,点云画不出来也不该画
|
|
1787
|
+
const decoMeshes = [...targetMeshes].filter((m) => !isPointCloudObject(m));
|
|
1788
|
+
if (decoMeshes.length) {
|
|
1789
|
+
if ((this.style.outlineStyle ?? 'edges') === 'edges') {
|
|
1790
|
+
this._addSelectionEdges(result, decoMeshes);
|
|
1791
|
+
} else {
|
|
1792
|
+
this._addSelectionHull(result, decoMeshes);
|
|
1793
|
+
}
|
|
1677
1794
|
}
|
|
1678
1795
|
}
|
|
1679
1796
|
this._requestRepaint();
|
|
@@ -1973,9 +2090,8 @@ export class S3MViewer {
|
|
|
1973
2090
|
let maxLon = -Infinity;
|
|
1974
2091
|
let maxLat = -Infinity;
|
|
1975
2092
|
for (const entry of this.datasets.values()) {
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
| undefined;
|
|
2093
|
+
// 同 DatasetManager.getBounds:退化 / 越界的 geoBounds 不参与,避免视角甩到 (0,0)
|
|
2094
|
+
const b = usableGeoBounds(entry.tileset?.config?.geoBounds);
|
|
1979
2095
|
if (!b) continue;
|
|
1980
2096
|
minLon = Math.min(minLon, b.left);
|
|
1981
2097
|
maxLon = Math.max(maxLon, b.right);
|
|
@@ -208,6 +208,16 @@ export function createSharedUniforms(): StyleUniforms {
|
|
|
208
208
|
const VERT_PARS = /* glsl */ `
|
|
209
209
|
attribute float aBatchId;
|
|
210
210
|
attribute vec3 aColor;
|
|
211
|
+
#ifdef S3M_ATLAS
|
|
212
|
+
attribute vec4 aAtlasRect;
|
|
213
|
+
#ifdef S3M_ATLAS_SEC
|
|
214
|
+
attribute vec4 aAtlasRect1;
|
|
215
|
+
attribute vec2 aSecUv;
|
|
216
|
+
varying vec2 vSecUv;
|
|
217
|
+
varying vec4 vSecRect;
|
|
218
|
+
#endif
|
|
219
|
+
varying vec4 vAtlasRect;
|
|
220
|
+
#endif
|
|
211
221
|
// 实例化几何没有逐顶点 batchId:顶点是 base mesh 的,单体粒度落在实例上
|
|
212
222
|
#ifdef USE_INSTANCING
|
|
213
223
|
attribute float aInstanceBatchId;
|
|
@@ -267,6 +277,14 @@ const VERT_WORLD = /* glsl */ `
|
|
|
267
277
|
vBatchId = s3mBatchRaw();
|
|
268
278
|
vVertColor = aColor;
|
|
269
279
|
|
|
280
|
+
#ifdef S3M_ATLAS
|
|
281
|
+
vAtlasRect = aAtlasRect;
|
|
282
|
+
#ifdef S3M_ATLAS_SEC
|
|
283
|
+
vSecUv = aSecUv;
|
|
284
|
+
vSecRect = aAtlasRect1;
|
|
285
|
+
#endif
|
|
286
|
+
#endif
|
|
287
|
+
|
|
270
288
|
#ifdef USE_INSTANCING
|
|
271
289
|
mat3 s3mNormMat = mat3( modelMatrix ) * mat3( instanceMatrix );
|
|
272
290
|
#else
|
|
@@ -276,6 +294,75 @@ const VERT_WORLD = /* glsl */ `
|
|
|
276
294
|
`;
|
|
277
295
|
|
|
278
296
|
const FRAG_PARS = /* glsl */ `
|
|
297
|
+
#ifdef S3M_ATLAS
|
|
298
|
+
varying vec4 vAtlasRect;
|
|
299
|
+
uniform vec2 uAtlasDim;
|
|
300
|
+
#ifdef S3M_ATLAS_SEC
|
|
301
|
+
uniform sampler2D uSecMap;
|
|
302
|
+
uniform vec2 uAtlasDim1;
|
|
303
|
+
varying vec2 vSecUv;
|
|
304
|
+
varying vec4 vSecRect;
|
|
305
|
+
#endif
|
|
306
|
+
/**
|
|
307
|
+
* 官方 CalculateMipLevel(vec2 版),逐字照抄 iClient3D 12.1.1。
|
|
308
|
+
* 用**未 fract** 的 uv 求屏幕空间导数 —— 先 fract 再求导会在平铺接缝处炸出
|
|
309
|
+
* 一个巨大导数,GPU 直接把整块砖采成最糊的 mip,表现为每块砖边缘一条暗线。
|
|
310
|
+
*/
|
|
311
|
+
void s3mMipLevel( vec2 uv, vec2 texSize, float maxMip, out float mip ) {
|
|
312
|
+
vec2 dx = dFdx( uv * texSize.x );
|
|
313
|
+
vec2 dy = dFdy( uv * texSize.y );
|
|
314
|
+
float dotX = dot( dx, dx );
|
|
315
|
+
float dotY = dot( dy, dy );
|
|
316
|
+
float dMax = max( dotX, dotY );
|
|
317
|
+
float dMin = min( dotX, dotY );
|
|
318
|
+
float off = clamp( ( dMax - dMin ) / max( dMax + dMin, 1e-8 ), 0.0, 1.0 );
|
|
319
|
+
float d = dMax * ( 1.0 - off ) + dMin * off;
|
|
320
|
+
mip = clamp( 0.5 * log2( max( d, 1e-8 ) ), 0.0, maxMip - 1.62 );
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 按指定 mip 档采样子区域(官方 czm_getTexColorForS3M 的内层循环体)。
|
|
325
|
+
*
|
|
326
|
+
* ★ 子区域 rect 的**右 1/3 不是贴图内容,是内联 mip 金字塔**:
|
|
327
|
+
* mip0 只占左 2/3,右 1/3 从上往下依次堆 1/2、1/4、1/8 缩略图,总高恰好 = rect 高。
|
|
328
|
+
* 官方那句 width *= 2.0/3.0 就是为这个,不是「经验收缩系数」。
|
|
329
|
+
* 数值实证:14 个块 691 条子纹理里 96.7% 满足「右侧 1/3 ≈ 左 2/3 的 1/2 下采样」
|
|
330
|
+
* (平均误差 6.8 vs 27.3)。
|
|
331
|
+
* 所以 mip > 0 时采样点必须右移到 x + 2/3·w,并下移到 (2^(m-1)-1)/2^(m-1)。
|
|
332
|
+
*/
|
|
333
|
+
vec4 s3mAtlasLod( vec2 uv, vec2 translate, vec2 scale, vec2 texSize, sampler2D tex, float mip ) {
|
|
334
|
+
vec2 t = translate;
|
|
335
|
+
if ( mip > 0.0 ) {
|
|
336
|
+
t.x = translate.x + scale.x;
|
|
337
|
+
float temp = pow( 2.0, mip - 1.0 );
|
|
338
|
+
t.y = translate.y + scale.y * ( temp - 1.0 ) / temp;
|
|
339
|
+
}
|
|
340
|
+
float inv = 1.0 / pow( 2.0, mip );
|
|
341
|
+
vec2 o = fract( uv );
|
|
342
|
+
// 半纹素内缩,避免双线性把邻格内容混进来
|
|
343
|
+
vec2 guard = vec2( pow( 2.0, mip ) / texSize.x, pow( 2.0, mip ) / texSize.y );
|
|
344
|
+
o = clamp( o, guard, vec2( 1.0 ) - guard );
|
|
345
|
+
return texture2D( tex, o * inv * scale + t );
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* 图集采样入口:层级由 ceil/floor 两档各采一次再平均(官方同样做法),
|
|
350
|
+
* 因为内联金字塔只有 2 的整数次幂,中间档必须靠混合。
|
|
351
|
+
*/
|
|
352
|
+
vec4 s3mAtlasUnit( vec2 uv, vec4 rect, vec2 atlasDim, sampler2D tex ) {
|
|
353
|
+
// 退化矩形 = 官方 batchId < 0:该纹理单元不参与合成
|
|
354
|
+
if ( rect.z <= 0.0 || rect.w <= 0.0 ) return vec4( 1.0 );
|
|
355
|
+
vec2 dimPx = vec2( rect.z * atlasDim.x, rect.w * atlasDim.y );
|
|
356
|
+
vec2 texSize = vec2( dimPx.x * ( 2.0 / 3.0 ), dimPx.y );
|
|
357
|
+
float maxMip = log2( max( min( texSize.x, texSize.y ), 1.0 ) );
|
|
358
|
+
vec2 scale = texSize / atlasDim;
|
|
359
|
+
float mip = 0.0;
|
|
360
|
+
s3mMipLevel( uv, texSize, maxMip, mip );
|
|
361
|
+
vec4 hi = s3mAtlasLod( uv, rect.xy, scale, texSize, tex, ceil( mip ) );
|
|
362
|
+
vec4 lo = s3mAtlasLod( uv, rect.xy, scale, texSize, tex, floor( mip ) );
|
|
363
|
+
return hi * 0.5 + lo * 0.5;
|
|
364
|
+
}
|
|
365
|
+
#endif
|
|
279
366
|
uniform int uMode;
|
|
280
367
|
uniform vec3 uColorA;
|
|
281
368
|
uniform vec3 uColorB;
|
|
@@ -445,6 +532,14 @@ const FRAG_EMISSIVE = /* glsl */ `
|
|
|
445
532
|
}
|
|
446
533
|
`;
|
|
447
534
|
|
|
535
|
+
const FRAG_BAKE = /* glsl */ `
|
|
536
|
+
#ifdef S3M_ATLAS_SEC
|
|
537
|
+
// 官方 getTextureColorForS3M = firstColor(base) * secColor(bake),
|
|
538
|
+
// 两套独立的 rect / 尺寸(实测 base 11 条、bake 1 条,条数并不相同)
|
|
539
|
+
diffuseColor *= s3mAtlasUnit( vSecUv, vSecRect, uAtlasDim1, uSecMap );
|
|
540
|
+
#endif
|
|
541
|
+
`;
|
|
542
|
+
|
|
448
543
|
export interface StyledMaterialOptions {
|
|
449
544
|
uniforms: StyleUniforms;
|
|
450
545
|
map?: THREE.Texture | null;
|
|
@@ -454,6 +549,12 @@ export interface StyledMaterialOptions {
|
|
|
454
549
|
opacity?: number;
|
|
455
550
|
roughness?: number;
|
|
456
551
|
flatShading?: boolean;
|
|
552
|
+
/** >0 时开启 alphaTest:镂空贴图(S3MBLOCK 图集)的透明片元直接丢弃,避免渲染成黑面 */
|
|
553
|
+
alphaTest?: number;
|
|
554
|
+
/** 贴图为 S3MBLOCK 图集:几何需带 aAtlasRect,平铺改由片元按子区域做 */
|
|
555
|
+
atlas?: boolean;
|
|
556
|
+
/** 图集材质的第二套纹理(官方 BakeTex,光照烘焙),与 base 相乘 */
|
|
557
|
+
secMap?: THREE.Texture | null;
|
|
457
558
|
}
|
|
458
559
|
|
|
459
560
|
/**
|
|
@@ -476,6 +577,7 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
|
|
|
476
577
|
flatShading: opts.flatShading !== false,
|
|
477
578
|
// 顶点色、贴图全部在自定义 chunk 里处理,不让 three 插手
|
|
478
579
|
vertexColors: false,
|
|
580
|
+
alphaTest: opts.alphaTest ?? 0,
|
|
479
581
|
}) as StyledMaterial;
|
|
480
582
|
|
|
481
583
|
// 选中单体编号:每个 Mesh 独立,默认 -1 表示无选中
|
|
@@ -486,6 +588,15 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
|
|
|
486
588
|
const uBatchTexWidth = { value: 1.0 };
|
|
487
589
|
const uBatchTexActive = { value: 0 };
|
|
488
590
|
|
|
591
|
+
// 图集材质:只有几何确实带了 aAtlasRect 才开这个宏,否则缺属性会采到 (0,0) 的默认值
|
|
592
|
+
const atlas = !!opts.atlas && !!opts.map;
|
|
593
|
+
const secMap = atlas ? (opts.secMap ?? null) : null;
|
|
594
|
+
if (atlas) mat.defines = { ...(mat.defines as Record<string, string>), S3M_ATLAS: '' };
|
|
595
|
+
if (secMap) mat.defines = { ...mat.defines, S3M_ATLAS_SEC: '' };
|
|
596
|
+
// 官方 uTextureDim = (base宽, base高, bake宽, bake高),取实际绑定纹理的像素尺寸
|
|
597
|
+
const uAtlasDim = { value: new THREE.Vector2(opts.map?.image?.width || 1, opts.map?.image?.height || 1) };
|
|
598
|
+
const uAtlasDim1 = { value: new THREE.Vector2(secMap?.image?.width || 1, secMap?.image?.height || 1) };
|
|
599
|
+
|
|
489
600
|
mat.onBeforeCompile = (shader) => {
|
|
490
601
|
Object.assign(shader.uniforms, uniforms, {
|
|
491
602
|
uSelBatch,
|
|
@@ -493,6 +604,9 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
|
|
|
493
604
|
uBatchTex,
|
|
494
605
|
uBatchTexWidth,
|
|
495
606
|
uBatchTexActive,
|
|
607
|
+
uSecMap: { value: secMap },
|
|
608
|
+
uAtlasDim,
|
|
609
|
+
uAtlasDim1,
|
|
496
610
|
});
|
|
497
611
|
|
|
498
612
|
shader.vertexShader = shader.vertexShader
|
|
@@ -504,10 +618,29 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
|
|
|
504
618
|
.replace('#include <common>', `#include <common>\n${FRAG_PARS}`)
|
|
505
619
|
.replace('#include <color_fragment>', `#include <color_fragment>\n${FRAG_COLOR}`)
|
|
506
620
|
.replace('#include <emissivemap_fragment>', `#include <emissivemap_fragment>\n${FRAG_EMISSIVE}`);
|
|
621
|
+
|
|
622
|
+
if (atlas) {
|
|
623
|
+
// onBeforeCompile 收到的还是未展开的 `#include <map_fragment>`,不是 include 里的
|
|
624
|
+
// `texture2D( map, vMapUv )`。只替换后者会静默 no-op,最终仍按整张图集普通采样。
|
|
625
|
+
const atlasMapFragment = /* glsl */ `
|
|
626
|
+
#ifdef USE_MAP
|
|
627
|
+
vec4 sampledDiffuseColor = s3mAtlasUnit( vMapUv, vAtlasRect, uAtlasDim, map );
|
|
628
|
+
diffuseColor *= sampledDiffuseColor;
|
|
629
|
+
#endif
|
|
630
|
+
`;
|
|
631
|
+
shader.fragmentShader = shader.fragmentShader.replace(
|
|
632
|
+
'#include <map_fragment>',
|
|
633
|
+
`${atlasMapFragment}${secMap ? `\n${FRAG_BAKE}` : ''}`
|
|
634
|
+
);
|
|
635
|
+
} else if (secMap) {
|
|
636
|
+
shader.fragmentShader = shader.fragmentShader.replace('#include <map_fragment>', `#include <map_fragment>\n${FRAG_BAKE}`);
|
|
637
|
+
}
|
|
507
638
|
};
|
|
508
639
|
|
|
509
|
-
// 不同材质共用同一段注入代码,必须让 three 认为它们的 program
|
|
510
|
-
|
|
640
|
+
// 不同材质共用同一段注入代码,必须让 three 认为它们的 program 可复用;
|
|
641
|
+
// 但图集 / 烘焙两套宏会改注入结果,必须进 cacheKey,否则会复用错 program。
|
|
642
|
+
mat.customProgramCacheKey = () =>
|
|
643
|
+
`s3m-styled${atlas ? '-atlas' : ''}${secMap ? '-bake' : ''}`;
|
|
511
644
|
|
|
512
645
|
mat.userData.uSelBatch = uSelBatch;
|
|
513
646
|
mat.userData.uSelWhole = uSelWhole;
|
|
@@ -47,7 +47,7 @@ function configure<T extends THREE.Texture>(tex: T, srgb = true): T {
|
|
|
47
47
|
tex.magFilter = THREE.LinearFilter;
|
|
48
48
|
tex.minFilter = THREE.LinearMipmapLinearFilter;
|
|
49
49
|
tex.generateMipmaps = true;
|
|
50
|
-
//
|
|
50
|
+
// 各向异性过滤对斜视角立面清晰度的提升比提高分辨率更直接
|
|
51
51
|
tex.anisotropy = maxAnisotropy;
|
|
52
52
|
if (srgb) tex.colorSpace = THREE.SRGBColorSpace;
|
|
53
53
|
tex.flipY = false;
|
|
@@ -55,13 +55,61 @@ function configure<T extends THREE.Texture>(tex: T, srgb = true): T {
|
|
|
55
55
|
return tex;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* S3MBLOCK 图集专用:关掉 GPU mip。
|
|
60
|
+
*
|
|
61
|
+
* 官方 `sampleLod` 是显式 `textureLod(tex, uv, 0.0)`,层级靠「把子区域 rect 平移 +
|
|
62
|
+
* 内缩」自己算(见 StyleMaterial 的 s3mAtlasUnit)。若让 GPU 对整张图集生成 mip,
|
|
63
|
+
* 自动选的层级会把相邻子区域混进来,远处就是一糊串色 —— 实测照抄公式但留着 GPU mip
|
|
64
|
+
* 时,画面反而比不改更糟,就是因为这两个变量叠在一起了。
|
|
65
|
+
*/
|
|
66
|
+
function configureAtlas<T extends THREE.Texture>(tex: T): T {
|
|
67
|
+
configure(tex);
|
|
68
|
+
tex.wrapS = THREE.ClampToEdgeWrapping;
|
|
69
|
+
tex.wrapT = THREE.ClampToEdgeWrapping;
|
|
70
|
+
tex.minFilter = THREE.LinearFilter;
|
|
71
|
+
tex.generateMipmaps = false;
|
|
72
|
+
tex.anisotropy = 1;
|
|
73
|
+
tex.needsUpdate = true;
|
|
74
|
+
return tex;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** 行序翻转(原地)。S3MBLOCK 图集按 GL 方向存储(v=0 在最后一行),解码后要翻一次行 */
|
|
78
|
+
function flipRows(rgba: Uint8Array, width: number, height: number): void {
|
|
79
|
+
const row = width * 4;
|
|
80
|
+
const tmp = new Uint8Array(row);
|
|
81
|
+
for (let y = 0; y < height >> 1; y++) {
|
|
82
|
+
const a = y * row;
|
|
83
|
+
const b = (height - 1 - y) * row;
|
|
84
|
+
tmp.set(rgba.subarray(a, a + row));
|
|
85
|
+
rgba.copyWithin(a, b, b + row);
|
|
86
|
+
rgba.set(tmp, b);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 该像素格式自带 alpha 通道(DXT1 的 1bit alpha 不算,历史上没开过 alphaTest) */
|
|
91
|
+
function hasAlphaChannel(pixelFormat: number): boolean {
|
|
92
|
+
return (
|
|
93
|
+
pixelFormat === PixelFormat.DXT3 ||
|
|
94
|
+
pixelFormat === PixelFormat.DXT4 ||
|
|
95
|
+
pixelFormat === PixelFormat.DXT5 ||
|
|
96
|
+
pixelFormat === PixelFormat.ARGB ||
|
|
97
|
+
pixelFormat === PixelFormat.ABGR ||
|
|
98
|
+
pixelFormat === PixelFormat.BGRA ||
|
|
99
|
+
pixelFormat === PixelFormat.RGBA
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
58
103
|
/** 解 DXT 到 RGBA8。DXT5 是 1 字节/像素,DXT1 是 0.5 字节/像素。 */
|
|
59
104
|
/** DXT 解码只需要尺寸 / 格式 / 字节,CRN 转码出来的结果也走这条路 */
|
|
60
|
-
function decodeDXT(tex: Pick<S3MTexture, 'width' | 'height' | 'pixelFormat' | 'data'>): THREE.DataTexture {
|
|
105
|
+
function decodeDXT(tex: Pick<S3MTexture, 'width' | 'height' | 'pixelFormat' | 'data' | 'flipY'>): THREE.DataTexture {
|
|
61
106
|
const { width, height, pixelFormat, data } = tex;
|
|
62
107
|
const out = new Uint8Array(width * height * 4);
|
|
63
108
|
DXTTextureDecode.decode(out, width, height, data, pixelFormat);
|
|
64
|
-
|
|
109
|
+
if (tex.flipY) flipRows(out, width, height);
|
|
110
|
+
const dt = new THREE.DataTexture(out, width, height, THREE.RGBAFormat, THREE.UnsignedByteType);
|
|
111
|
+
if (hasAlphaChannel(pixelFormat)) dt.userData.s3mHasAlpha = true;
|
|
112
|
+
return dt;
|
|
65
113
|
}
|
|
66
114
|
|
|
67
115
|
function fromRawRGB(tex: S3MTexture): THREE.DataTexture {
|
|
@@ -83,12 +131,20 @@ function fromRawRGB(tex: S3MTexture): THREE.DataTexture {
|
|
|
83
131
|
out[d + 2] = swapRB ? data[p] : data[p + 2];
|
|
84
132
|
out[d + 3] = hasAlpha ? data[alphaFirst ? s : s + 3] : 255;
|
|
85
133
|
}
|
|
86
|
-
|
|
134
|
+
if (tex.flipY) flipRows(out, width, height);
|
|
135
|
+
const dt = new THREE.DataTexture(out, width, height, THREE.RGBAFormat, THREE.UnsignedByteType);
|
|
136
|
+
if (hasAlphaChannel(pixelFormat)) dt.userData.s3mHasAlpha = true;
|
|
137
|
+
return dt;
|
|
87
138
|
}
|
|
88
139
|
|
|
89
140
|
export async function createTexture(tex: S3MTexture | null | undefined): Promise<DecodedTexture> {
|
|
90
141
|
if (!tex || !tex.width || !tex.height) return { texture: null, decoded: 'empty' };
|
|
91
142
|
|
|
143
|
+
// 图集纹理(flipY 的原始 DXT 图集 / AtlasPack 拼出来的 RGBA 大图 / leaf 组合画布)都必须
|
|
144
|
+
// 关 GPU mip,层级由 StyleMaterial.s3mAtlasUnit 的内联金字塔自己算。
|
|
145
|
+
const isAtlas = tex.packed === true || tex.flipY === true || tex.composed === true;
|
|
146
|
+
const asAtlas = <T extends THREE.Texture>(t: T): T => (isAtlas ? configureAtlas(t) : configure(t));
|
|
147
|
+
|
|
92
148
|
// 判定只能看 compressType:v3.00 的 CRN 纹理 pixelFormat 是 13(ABGR)、
|
|
93
149
|
// 行标的 CRN 是 32856(GL_RGBA8),都不是 26 / 27(见 core/TextureFormat.ts)
|
|
94
150
|
const kind = resolveTextureKind(tex.compressType ?? 0, tex.pixelFormat);
|
|
@@ -102,7 +158,7 @@ export async function createTexture(tex: S3MTexture | null | undefined): Promise
|
|
|
102
158
|
pixelFormat: crn.dxtFormat === 1 ? 17 : 21,
|
|
103
159
|
data: crn.data,
|
|
104
160
|
});
|
|
105
|
-
return { texture:
|
|
161
|
+
return { texture: asAtlas(decoded), decoded: `crn->dxt${crn.dxtFormat}` };
|
|
106
162
|
} catch (e) {
|
|
107
163
|
// 一张纹理坏了不该带崩整个瓦片,如实上报并降级成无贴图
|
|
108
164
|
console.warn('[s3m] CRN 转码失败:', e);
|
|
@@ -111,10 +167,9 @@ export async function createTexture(tex: S3MTexture | null | undefined): Promise
|
|
|
111
167
|
}
|
|
112
168
|
|
|
113
169
|
if (kind === 'dxt1' || kind === 'dxt3' || kind === 'dxt4' || kind === 'dxt5') {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
};
|
|
170
|
+
const decoded = decodeDXT({ ...tex, pixelFormat: dxtPixelFormatOf(kind) });
|
|
171
|
+
// flipY 为真即 S3MBLOCK 图集(见 core/types.ts),必须无 mip
|
|
172
|
+
return { texture: asAtlas(decoded), decoded: kind };
|
|
118
173
|
}
|
|
119
174
|
|
|
120
175
|
if (kind === 'webp') {
|
|
@@ -122,11 +177,12 @@ export async function createTexture(tex: S3MTexture | null | undefined): Promise
|
|
|
122
177
|
const bmp = await createImageBitmap(blob);
|
|
123
178
|
const t = new THREE.Texture(bmp);
|
|
124
179
|
t.needsUpdate = true;
|
|
125
|
-
return { texture:
|
|
180
|
+
return { texture: asAtlas(t), decoded: 'webp' };
|
|
126
181
|
}
|
|
127
182
|
|
|
128
183
|
if (kind === 'raw') {
|
|
129
|
-
|
|
184
|
+
const decoded = fromRawRGB(tex);
|
|
185
|
+
return { texture: asAtlas(decoded), decoded: 'raw' };
|
|
130
186
|
}
|
|
131
187
|
|
|
132
188
|
// KTX2 / ETC 需要额外解码器,先如实上报而不是静默出错
|