iflow-engine-base 4.0.16 → 4.0.19
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/dist/bim-engine-sdk.es.js +29 -40
- package/dist/bim-engine-sdk.umd.js +2754 -2392
- package/dist/chunks/{EngineKernel-BLJsCrxR.js → EngineKernel-CZX0Omjs.js} +2676 -2402
- package/dist/chunks/{engine-2d-DneQvSOi.js → engine-2d-CA5-uNuf.js} +5 -5
- package/dist/chunks/{engine-3d-B0D38zLu.js → engine-3d-BsWcD-bl.js} +7215 -6460
- package/dist/chunks/{engine-720-OdgX2p-d.js → engine-720-DiTJEtdN.js} +8 -8
- package/dist/chunks/{engine-gaussian-CQdrGBYh.js → engine-gaussian-BHUGuaw_.js} +227 -227
- package/dist/chunks/gis-tiles-vendor-DIbUOKJM.js +11781 -0
- package/dist/chunks/index-D7NwsYhB.js +2640 -0
- package/dist/chunks/{sdk-assets-Dh7PGW73.js → sdk-assets-DtcFvuth.js} +2 -2
- package/dist/chunks/{sdk-runtime-KslKRuwI.js → sdk-runtime-DN15bwdD.js} +761 -762
- package/dist/chunks/{sdk-vendor-CVc1LiJj.js → sdk-vendor-6wYtfmXR.js} +31044 -31044
- package/dist/chunks/{three-vendor-Ds9cgGKb.js → three-vendor-CGmeNMm5.js} +7963 -7954
- package/dist/gis-fixtures/terrain/0/0/0.terrain +0 -0
- package/dist/gis-fixtures/terrain/0/1/0.terrain +0 -0
- package/dist/gis-fixtures/terrain/1/2/0.terrain +0 -0
- package/dist/gis-fixtures/terrain/1/2/1.terrain +0 -0
- package/dist/gis-fixtures/terrain/1/3/0.terrain +0 -0
- package/dist/gis-fixtures/terrain/1/3/1.terrain +0 -0
- package/dist/gis-fixtures/terrain/10/1694/650.terrain +0 -0
- package/dist/gis-fixtures/terrain/10/1694/651.terrain +0 -0
- package/dist/gis-fixtures/terrain/10/1694/652.terrain +0 -0
- package/dist/gis-fixtures/terrain/10/1695/650.terrain +0 -0
- package/dist/gis-fixtures/terrain/10/1695/651.terrain +0 -0
- package/dist/gis-fixtures/terrain/10/1695/652.terrain +0 -0
- package/dist/gis-fixtures/terrain/10/1696/650.terrain +0 -0
- package/dist/gis-fixtures/terrain/10/1696/651.terrain +0 -0
- package/dist/gis-fixtures/terrain/10/1696/652.terrain +0 -0
- package/dist/gis-fixtures/terrain/11/3390/1301.terrain +0 -0
- package/dist/gis-fixtures/terrain/11/3390/1302.terrain +0 -0
- package/dist/gis-fixtures/terrain/11/3390/1303.terrain +0 -0
- package/dist/gis-fixtures/terrain/11/3391/1301.terrain +0 -0
- package/dist/gis-fixtures/terrain/11/3391/1302.terrain +0 -0
- package/dist/gis-fixtures/terrain/11/3391/1303.terrain +0 -0
- package/dist/gis-fixtures/terrain/11/3392/1301.terrain +0 -0
- package/dist/gis-fixtures/terrain/11/3392/1302.terrain +0 -0
- package/dist/gis-fixtures/terrain/11/3392/1303.terrain +0 -0
- package/dist/gis-fixtures/terrain/12/6782/2604.terrain +0 -0
- package/dist/gis-fixtures/terrain/12/6782/2605.terrain +0 -0
- package/dist/gis-fixtures/terrain/12/6782/2606.terrain +0 -0
- package/dist/gis-fixtures/terrain/12/6783/2604.terrain +0 -0
- package/dist/gis-fixtures/terrain/12/6783/2605.terrain +0 -0
- package/dist/gis-fixtures/terrain/12/6783/2606.terrain +0 -0
- package/dist/gis-fixtures/terrain/12/6784/2604.terrain +0 -0
- package/dist/gis-fixtures/terrain/12/6784/2605.terrain +0 -0
- package/dist/gis-fixtures/terrain/12/6784/2606.terrain +0 -0
- package/dist/gis-fixtures/terrain/13/13565/5209.terrain +0 -0
- package/dist/gis-fixtures/terrain/13/13565/5210.terrain +0 -0
- package/dist/gis-fixtures/terrain/13/13565/5211.terrain +0 -0
- package/dist/gis-fixtures/terrain/13/13566/5209.terrain +0 -0
- package/dist/gis-fixtures/terrain/13/13566/5210.terrain +0 -0
- package/dist/gis-fixtures/terrain/13/13566/5211.terrain +0 -0
- package/dist/gis-fixtures/terrain/13/13567/5209.terrain +0 -0
- package/dist/gis-fixtures/terrain/13/13567/5210.terrain +0 -0
- package/dist/gis-fixtures/terrain/13/13567/5211.terrain +0 -0
- package/dist/gis-fixtures/terrain/14/27131/10419.terrain +0 -0
- package/dist/gis-fixtures/terrain/14/27131/10420.terrain +0 -0
- package/dist/gis-fixtures/terrain/14/27131/10421.terrain +0 -0
- package/dist/gis-fixtures/terrain/14/27132/10419.terrain +0 -0
- package/dist/gis-fixtures/terrain/14/27132/10420.terrain +0 -0
- package/dist/gis-fixtures/terrain/14/27132/10421.terrain +0 -0
- package/dist/gis-fixtures/terrain/14/27133/10419.terrain +0 -0
- package/dist/gis-fixtures/terrain/14/27133/10420.terrain +0 -0
- package/dist/gis-fixtures/terrain/14/27133/10421.terrain +0 -0
- package/dist/gis-fixtures/terrain/2/5/1.terrain +0 -0
- package/dist/gis-fixtures/terrain/2/5/2.terrain +0 -0
- package/dist/gis-fixtures/terrain/2/5/3.terrain +0 -0
- package/dist/gis-fixtures/terrain/2/6/1.terrain +0 -0
- package/dist/gis-fixtures/terrain/2/6/2.terrain +0 -0
- package/dist/gis-fixtures/terrain/2/6/3.terrain +0 -0
- package/dist/gis-fixtures/terrain/2/7/1.terrain +0 -0
- package/dist/gis-fixtures/terrain/2/7/2.terrain +0 -0
- package/dist/gis-fixtures/terrain/2/7/3.terrain +0 -0
- package/dist/gis-fixtures/terrain/3/12/4.terrain +0 -0
- package/dist/gis-fixtures/terrain/3/12/5.terrain +0 -0
- package/dist/gis-fixtures/terrain/3/12/6.terrain +0 -0
- package/dist/gis-fixtures/terrain/3/13/4.terrain +0 -0
- package/dist/gis-fixtures/terrain/3/13/5.terrain +0 -0
- package/dist/gis-fixtures/terrain/3/13/6.terrain +0 -0
- package/dist/gis-fixtures/terrain/3/14/4.terrain +0 -0
- package/dist/gis-fixtures/terrain/3/14/5.terrain +0 -0
- package/dist/gis-fixtures/terrain/3/14/6.terrain +0 -0
- package/dist/gis-fixtures/terrain/4/25/10.terrain +0 -0
- package/dist/gis-fixtures/terrain/4/25/11.terrain +0 -0
- package/dist/gis-fixtures/terrain/4/25/9.terrain +0 -0
- package/dist/gis-fixtures/terrain/4/26/10.terrain +0 -0
- package/dist/gis-fixtures/terrain/4/26/11.terrain +0 -0
- package/dist/gis-fixtures/terrain/4/26/9.terrain +0 -0
- package/dist/gis-fixtures/terrain/4/27/10.terrain +0 -0
- package/dist/gis-fixtures/terrain/4/27/11.terrain +0 -0
- package/dist/gis-fixtures/terrain/4/27/9.terrain +0 -0
- package/dist/gis-fixtures/terrain/5/51/19.terrain +0 -0
- package/dist/gis-fixtures/terrain/5/51/20.terrain +0 -0
- package/dist/gis-fixtures/terrain/5/51/21.terrain +0 -0
- package/dist/gis-fixtures/terrain/5/52/19.terrain +0 -0
- package/dist/gis-fixtures/terrain/5/52/20.terrain +0 -0
- package/dist/gis-fixtures/terrain/5/52/21.terrain +0 -0
- package/dist/gis-fixtures/terrain/5/53/19.terrain +0 -0
- package/dist/gis-fixtures/terrain/5/53/20.terrain +0 -0
- package/dist/gis-fixtures/terrain/5/53/21.terrain +0 -0
- package/dist/gis-fixtures/terrain/6/104/39.terrain +0 -0
- package/dist/gis-fixtures/terrain/6/104/40.terrain +0 -0
- package/dist/gis-fixtures/terrain/6/104/41.terrain +0 -0
- package/dist/gis-fixtures/terrain/6/105/39.terrain +0 -0
- package/dist/gis-fixtures/terrain/6/105/40.terrain +0 -0
- package/dist/gis-fixtures/terrain/6/105/41.terrain +0 -0
- package/dist/gis-fixtures/terrain/6/106/39.terrain +0 -0
- package/dist/gis-fixtures/terrain/6/106/40.terrain +0 -0
- package/dist/gis-fixtures/terrain/6/106/41.terrain +0 -0
- package/dist/gis-fixtures/terrain/7/210/80.terrain +0 -0
- package/dist/gis-fixtures/terrain/7/210/81.terrain +0 -0
- package/dist/gis-fixtures/terrain/7/210/82.terrain +0 -0
- package/dist/gis-fixtures/terrain/7/211/80.terrain +0 -0
- package/dist/gis-fixtures/terrain/7/211/81.terrain +0 -0
- package/dist/gis-fixtures/terrain/7/211/82.terrain +0 -0
- package/dist/gis-fixtures/terrain/7/212/80.terrain +0 -0
- package/dist/gis-fixtures/terrain/7/212/81.terrain +0 -0
- package/dist/gis-fixtures/terrain/7/212/82.terrain +0 -0
- package/dist/gis-fixtures/terrain/8/422/161.terrain +0 -0
- package/dist/gis-fixtures/terrain/8/422/162.terrain +0 -0
- package/dist/gis-fixtures/terrain/8/422/163.terrain +0 -0
- package/dist/gis-fixtures/terrain/8/423/161.terrain +0 -0
- package/dist/gis-fixtures/terrain/8/423/162.terrain +0 -0
- package/dist/gis-fixtures/terrain/8/423/163.terrain +0 -0
- package/dist/gis-fixtures/terrain/8/424/161.terrain +0 -0
- package/dist/gis-fixtures/terrain/8/424/162.terrain +0 -0
- package/dist/gis-fixtures/terrain/8/424/163.terrain +0 -0
- package/dist/gis-fixtures/terrain/9/846/324.terrain +0 -0
- package/dist/gis-fixtures/terrain/9/846/325.terrain +0 -0
- package/dist/gis-fixtures/terrain/9/846/326.terrain +0 -0
- package/dist/gis-fixtures/terrain/9/847/324.terrain +0 -0
- package/dist/gis-fixtures/terrain/9/847/325.terrain +0 -0
- package/dist/gis-fixtures/terrain/9/847/326.terrain +0 -0
- package/dist/gis-fixtures/terrain/9/848/324.terrain +0 -0
- package/dist/gis-fixtures/terrain/9/848/325.terrain +0 -0
- package/dist/gis-fixtures/terrain/9/848/326.terrain +0 -0
- package/dist/gis-fixtures/terrain/layer.json +141 -0
- package/dist/iflow-engine-base.css +1 -1
- package/dist/index.d.ts +928 -1711
- package/package.json +10 -21
- package/dist/chunks/gis-tiles-vendor-Dr7piDQM.js +0 -11386
- package/dist/chunks/index-DjNAYhvI.js +0 -5252
|
@@ -0,0 +1,2640 @@
|
|
|
1
|
+
import { r as te, s as F, a as re, b as ie } from "./sdk-assets-DtcFvuth.js";
|
|
2
|
+
import "./engine-3d-BsWcD-bl.js";
|
|
3
|
+
import { u as z, R as de, a as f, bG as ge, G as O, b as y, aX as G, k as S, bH as me, S as pe, a_ as fe, a4 as H, at as J, aU as ye, aZ as Me, au as be, ai as we, bI as ve, C as Ee } from "./three-vendor-CGmeNMm5.js";
|
|
4
|
+
import { W as Se, a as Ce, r as Ie } from "./sdk-runtime-DN15bwdD.js";
|
|
5
|
+
class xe {
|
|
6
|
+
constructor(e) {
|
|
7
|
+
this.engine = e;
|
|
8
|
+
}
|
|
9
|
+
engine;
|
|
10
|
+
layers = /* @__PURE__ */ new Map();
|
|
11
|
+
layerSequence = 0;
|
|
12
|
+
/** 登记图层;不同对象不得复用同一个 id,避免产生无法查询的孤立图层。 */
|
|
13
|
+
registerLayer(e) {
|
|
14
|
+
if (!e?.object?.isObject3D)
|
|
15
|
+
throw new TypeError("GIS 图层必须提供有效的 Three.js Object3D。");
|
|
16
|
+
const t = e.id?.trim() || `${e.type}-${++this.layerSequence}`, r = e.name?.trim();
|
|
17
|
+
if (!r) throw new TypeError("GIS 图层名称不能为空。");
|
|
18
|
+
const i = this.layers.get(t);
|
|
19
|
+
if (i && i.object !== e.object)
|
|
20
|
+
throw new RangeError(`GIS 图层 id 已存在:${t}`);
|
|
21
|
+
const s = {
|
|
22
|
+
id: t,
|
|
23
|
+
name: r,
|
|
24
|
+
type: e.type,
|
|
25
|
+
source: e.source,
|
|
26
|
+
object: e.object,
|
|
27
|
+
remove: e.remove
|
|
28
|
+
};
|
|
29
|
+
return this.layers.set(t, s), this.toInfo(s);
|
|
30
|
+
}
|
|
31
|
+
/** 查询全部 GIS 图层,返回快照以避免外部修改内部登记信息。 */
|
|
32
|
+
getLayers() {
|
|
33
|
+
return [...this.layers.values()].map((e) => this.toInfo(e));
|
|
34
|
+
}
|
|
35
|
+
/** 按 id 查询单个 GIS 图层。 */
|
|
36
|
+
getLayer(e) {
|
|
37
|
+
const t = this.layers.get(e);
|
|
38
|
+
return t ? this.toInfo(t) : null;
|
|
39
|
+
}
|
|
40
|
+
/** 仅合并当前可见的业务模型,避免底图、地形和标注放大回家视角。 */
|
|
41
|
+
getModelBoundingBox() {
|
|
42
|
+
const e = new z(), t = /* @__PURE__ */ new Set();
|
|
43
|
+
let r = !1;
|
|
44
|
+
return this.layers.forEach((i) => {
|
|
45
|
+
if (i.type !== "tileset" && i.type !== "model" || !i.object.visible || t.has(i.object))
|
|
46
|
+
return;
|
|
47
|
+
t.add(i.object), i.object.updateWorldMatrix(!0, !0);
|
|
48
|
+
const s = new z().setFromObject(i.object);
|
|
49
|
+
s.isEmpty() || (e.union(s), r = !0);
|
|
50
|
+
}), r ? e : null;
|
|
51
|
+
}
|
|
52
|
+
/** 设置图层显隐;图层不存在时返回 false。 */
|
|
53
|
+
setLayerVisible(e, t) {
|
|
54
|
+
const r = this.layers.get(e);
|
|
55
|
+
return r ? (r.object.visible !== t && (r.object.visible = t, this.engine.requestRender()), !0) : !1;
|
|
56
|
+
}
|
|
57
|
+
/** 开启指定图层。 */
|
|
58
|
+
showLayer(e) {
|
|
59
|
+
return this.setLayerVisible(e, !0);
|
|
60
|
+
}
|
|
61
|
+
/** 关闭指定图层,但保留已加载资源。 */
|
|
62
|
+
hideLayer(e) {
|
|
63
|
+
return this.setLayerVisible(e, !1);
|
|
64
|
+
}
|
|
65
|
+
/** 切换指定图层,返回切换后的显隐状态;图层不存在时返回 null。 */
|
|
66
|
+
toggleLayer(e) {
|
|
67
|
+
const t = this.layers.get(e);
|
|
68
|
+
if (!t) return null;
|
|
69
|
+
const r = !t.object.visible;
|
|
70
|
+
return this.setLayerVisible(e, r), r;
|
|
71
|
+
}
|
|
72
|
+
/** 注销图层登记,不移除或销毁场景资源。 */
|
|
73
|
+
unregisterLayer(e) {
|
|
74
|
+
return this.layers.delete(e);
|
|
75
|
+
}
|
|
76
|
+
/** 调用资源所有者的释放回调并移除图层;无释放回调时返回 false。 */
|
|
77
|
+
removeLayer(e) {
|
|
78
|
+
const t = this.layers.get(e);
|
|
79
|
+
return !t?.remove || t.remove() === !1 ? !1 : (this.layers.delete(e), this.engine.requestRender(), !0);
|
|
80
|
+
}
|
|
81
|
+
/** 清空登记信息;实际资源仍由各自加载器负责释放。 */
|
|
82
|
+
dispose() {
|
|
83
|
+
this.layers.clear();
|
|
84
|
+
}
|
|
85
|
+
toInfo(e) {
|
|
86
|
+
return {
|
|
87
|
+
id: e.id,
|
|
88
|
+
name: e.name,
|
|
89
|
+
type: e.type,
|
|
90
|
+
source: e.source,
|
|
91
|
+
object: e.object,
|
|
92
|
+
visible: e.object.visible
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
class Le {
|
|
97
|
+
constructor(e, t, r) {
|
|
98
|
+
this.engine = e, this.coordinateModule = t, this.layerManager = r, this.worldToScreen = new Se(
|
|
99
|
+
e.camera,
|
|
100
|
+
e.renderer,
|
|
101
|
+
e.scene
|
|
102
|
+
), this.manager = new Ce({
|
|
103
|
+
container: e.container,
|
|
104
|
+
className: "bim-engine-label-container bim-engine-label-container-gis",
|
|
105
|
+
projectCoordinate: (i) => this.projectCoordinate(i),
|
|
106
|
+
pickCoordinate: (i) => this.pickCoordinate(i),
|
|
107
|
+
emitEvent: (i, s) => e.events.trigger(i, s)
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
engine;
|
|
111
|
+
coordinateModule;
|
|
112
|
+
layerManager;
|
|
113
|
+
manager;
|
|
114
|
+
worldToScreen;
|
|
115
|
+
raycaster = new de();
|
|
116
|
+
/** 进入一次选点模式,结果同时包含 WGS84 和 Three.js 世界坐标。 */
|
|
117
|
+
pickPoint() {
|
|
118
|
+
return this.manager.pickPoint();
|
|
119
|
+
}
|
|
120
|
+
/** 兼容旧版回调式选点。 */
|
|
121
|
+
startPickPoint(e) {
|
|
122
|
+
return this.manager.startPickPoint(e);
|
|
123
|
+
}
|
|
124
|
+
/** 创建 GIS 标签;经纬度按 WGS84 解释。 */
|
|
125
|
+
createLabel(e) {
|
|
126
|
+
return this.manager.createLabel(e);
|
|
127
|
+
}
|
|
128
|
+
/** 更新指定 GIS 标签。 */
|
|
129
|
+
updateLabel(e, t) {
|
|
130
|
+
return this.manager.updateLabel(e, t);
|
|
131
|
+
}
|
|
132
|
+
/** 删除指定 GIS 标签。 */
|
|
133
|
+
deleteLabel(e) {
|
|
134
|
+
return this.manager.deleteLabel(e);
|
|
135
|
+
}
|
|
136
|
+
/** deleteLabel 的兼容别名。 */
|
|
137
|
+
removeLabel(e) {
|
|
138
|
+
return this.manager.removeLabel(e);
|
|
139
|
+
}
|
|
140
|
+
/** 清空全部 GIS 标签。 */
|
|
141
|
+
clearLabels() {
|
|
142
|
+
this.manager.clearLabels();
|
|
143
|
+
}
|
|
144
|
+
/** 查询单个 GIS 标签。 */
|
|
145
|
+
getLabel(e) {
|
|
146
|
+
return this.manager.getLabel(e);
|
|
147
|
+
}
|
|
148
|
+
/** 查询全部 GIS 标签。 */
|
|
149
|
+
getLabels() {
|
|
150
|
+
return this.manager.getLabels();
|
|
151
|
+
}
|
|
152
|
+
/** 监听标签点击,并返回取消监听函数。 */
|
|
153
|
+
onLabelClick(e) {
|
|
154
|
+
return this.manager.onLabelClick(e);
|
|
155
|
+
}
|
|
156
|
+
/** 取消指定标签点击监听。 */
|
|
157
|
+
offLabelClick(e) {
|
|
158
|
+
this.manager.offLabelClick(e);
|
|
159
|
+
}
|
|
160
|
+
/** 由 GIS 共享帧循环更新标签屏幕位置。 */
|
|
161
|
+
update() {
|
|
162
|
+
this.worldToScreen.updateCamera(this.engine.camera), this.manager.update();
|
|
163
|
+
}
|
|
164
|
+
/** 释放 DOM、选点监听和标签数据。 */
|
|
165
|
+
dispose() {
|
|
166
|
+
this.manager.dispose();
|
|
167
|
+
}
|
|
168
|
+
projectCoordinate(e) {
|
|
169
|
+
const t = this.resolveWorldCoordinate(e);
|
|
170
|
+
if (!t) return null;
|
|
171
|
+
const r = this.worldToScreen.worldToScreenIfVisible(t);
|
|
172
|
+
return r ? { x: r.x, y: r.y, visible: !0 } : null;
|
|
173
|
+
}
|
|
174
|
+
resolveWorldCoordinate(e) {
|
|
175
|
+
return Number.isFinite(e.longitude) && Number.isFinite(e.latitude) ? this.coordinateModule.toWorld({
|
|
176
|
+
longitude: e.longitude,
|
|
177
|
+
latitude: e.latitude,
|
|
178
|
+
height: e.height ?? 0
|
|
179
|
+
}) : [e.x, e.y, e.z].every(Number.isFinite) ? new f(e.x, e.y, e.z) : null;
|
|
180
|
+
}
|
|
181
|
+
pickCoordinate(e) {
|
|
182
|
+
const t = this.engine.renderer.domElement.getBoundingClientRect(), r = this.worldToScreen.screenToNormalized(
|
|
183
|
+
e.clientX - t.left,
|
|
184
|
+
e.clientY - t.top
|
|
185
|
+
);
|
|
186
|
+
this.raycaster.setFromCamera(r, this.engine.camera);
|
|
187
|
+
const i = this.layerManager.getLayers().filter((c) => c.visible).map((c) => c.object), s = this.raycaster.intersectObjects(this.uniqueObjects(i), !0), a = this.engine.octreeBox?.rayInterationModel?.(
|
|
188
|
+
this.raycaster,
|
|
189
|
+
!0,
|
|
190
|
+
!1
|
|
191
|
+
), o = [...s, ...a ?? []].filter((c) => c?.point instanceof f).sort((c, m) => c.distance - m.distance)[0];
|
|
192
|
+
if (!o) return null;
|
|
193
|
+
const l = this.coordinateModule.toWgs84(o.point);
|
|
194
|
+
return {
|
|
195
|
+
x: o.point.x,
|
|
196
|
+
y: o.point.y,
|
|
197
|
+
z: o.point.z,
|
|
198
|
+
...l
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
uniqueObjects(e) {
|
|
202
|
+
const t = new Set(e);
|
|
203
|
+
return e.filter((r) => {
|
|
204
|
+
let i = r.parent;
|
|
205
|
+
for (; i; ) {
|
|
206
|
+
if (t.has(i)) return !1;
|
|
207
|
+
i = i.parent;
|
|
208
|
+
}
|
|
209
|
+
return !0;
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
class Re {
|
|
214
|
+
constructor(e, t) {
|
|
215
|
+
this.cameraModule = e, this.initialView = t;
|
|
216
|
+
}
|
|
217
|
+
cameraModule;
|
|
218
|
+
initialView;
|
|
219
|
+
mainView = null;
|
|
220
|
+
/** 切换底图重建相机模块后更新引用,同时保留已经保存的主视角。 */
|
|
221
|
+
setCameraModule(e) {
|
|
222
|
+
this.cameraModule = e;
|
|
223
|
+
}
|
|
224
|
+
/** 保存用户当前实际相机姿态,而不是上一次 API 写入的缓存。 */
|
|
225
|
+
saveMainViewPort() {
|
|
226
|
+
return this.mainView = this.cameraModule.getView(), { ...this.mainView };
|
|
227
|
+
}
|
|
228
|
+
/** 设置主视角并立即应用;null 表示清除已保存视角。 */
|
|
229
|
+
setMainViewPort(e) {
|
|
230
|
+
if (!e) {
|
|
231
|
+
this.mainView = null;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const t = this.cameraModule.getView();
|
|
235
|
+
this.mainView = {
|
|
236
|
+
...t,
|
|
237
|
+
...e
|
|
238
|
+
}, this.cameraModule.setView(this.mainView);
|
|
239
|
+
}
|
|
240
|
+
/** 平滑飞行到已保存的主视角;未保存时保持当前视图。 */
|
|
241
|
+
async goToMainPort(e) {
|
|
242
|
+
this.mainView && await this.cameraModule.flyTo(this.mainView, e);
|
|
243
|
+
}
|
|
244
|
+
/** 清除主视角并回到初始化视图。 */
|
|
245
|
+
resetMainViewPort() {
|
|
246
|
+
this.mainView = null, this.cameraModule.setView(this.initialView);
|
|
247
|
+
}
|
|
248
|
+
/** 查询已保存主视角的独立快照。 */
|
|
249
|
+
getMainViewPort() {
|
|
250
|
+
return this.mainView ? { ...this.mainView } : null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function Pe() {
|
|
254
|
+
return se(new ge());
|
|
255
|
+
}
|
|
256
|
+
function se(n, e = []) {
|
|
257
|
+
const t = n;
|
|
258
|
+
return t.isClippingGroup !== !0 && Object.defineProperty(t, "isClippingGroup", {
|
|
259
|
+
value: !0,
|
|
260
|
+
enumerable: !0
|
|
261
|
+
}), t.clippingPlanes = e, t.enabled = !0, t.clipIntersection = !1, t.clipShadows = !1, t;
|
|
262
|
+
}
|
|
263
|
+
const U = "__isModelUrlGroup", D = "__modelDepthBias", ne = 4, Te = 5e5;
|
|
264
|
+
function ae(n) {
|
|
265
|
+
return n.__modelGroupMap instanceof Map || (n.__modelGroupMap = /* @__PURE__ */ new Map()), n.__modelGroupMap;
|
|
266
|
+
}
|
|
267
|
+
function oe(n) {
|
|
268
|
+
return n.__modelObjectIndexMap instanceof Map || (n.__modelObjectIndexMap = /* @__PURE__ */ new Map()), n.__modelObjectIndexMap;
|
|
269
|
+
}
|
|
270
|
+
function N(n, e) {
|
|
271
|
+
n.url = e, n.name = e, n.userData = n.userData ?? {}, n.userData[U] = !0;
|
|
272
|
+
}
|
|
273
|
+
function _(n, e) {
|
|
274
|
+
return n instanceof O ? e == null ? n.userData?.[U] === !0 : n.userData?.[U] === !0 && n.url === e : !1;
|
|
275
|
+
}
|
|
276
|
+
function q(n, e) {
|
|
277
|
+
return n?.models?.find?.((t) => t?.url === e);
|
|
278
|
+
}
|
|
279
|
+
function nt(n, e, t) {
|
|
280
|
+
if (typeof t != "string" || t.length === 0)
|
|
281
|
+
return;
|
|
282
|
+
const r = q(n, e);
|
|
283
|
+
r != null && (Array.isArray(r.urls) || (r.urls = []), r.urls.includes(t) || r.urls.push(t));
|
|
284
|
+
}
|
|
285
|
+
function b(n, e) {
|
|
286
|
+
const t = ae(n), r = t.get(e);
|
|
287
|
+
if (r && r.parent != null)
|
|
288
|
+
return r;
|
|
289
|
+
const i = q(n, e);
|
|
290
|
+
if (i?.group instanceof O && i.group.parent != null)
|
|
291
|
+
return N(i.group, e), t.set(e, i.group), i.group;
|
|
292
|
+
const s = n?.scene?.children?.find?.((a) => a instanceof O ? _(a, e) ? !0 : a.name === e && a.url === e : !1);
|
|
293
|
+
return s != null ? (N(s, e), t.set(e, s), i != null && (i.group = s), s) : null;
|
|
294
|
+
}
|
|
295
|
+
function at(n, e) {
|
|
296
|
+
const t = b(n, e);
|
|
297
|
+
return t == null ? new y() : (t.updateWorldMatrix(!0, !1), t.matrixWorld.clone());
|
|
298
|
+
}
|
|
299
|
+
function ot(n, e) {
|
|
300
|
+
const t = typeof n.__getActiveWebGPUClippingPlanes == "function" ? n.__getActiveWebGPUClippingPlanes() : n.sectionPlane, r = b(n, e);
|
|
301
|
+
if (r != null) {
|
|
302
|
+
const o = se(r, t);
|
|
303
|
+
return o.clipShadows = !0, o;
|
|
304
|
+
}
|
|
305
|
+
const i = Pe();
|
|
306
|
+
i.clippingPlanes = Array.isArray(t) ? t : [], i.clipIntersection = !1, i.clipShadows = !0, N(i, e), n.scene.add(i), ae(n).set(e, i);
|
|
307
|
+
const a = q(n, e);
|
|
308
|
+
return a != null && (a.group = i), i;
|
|
309
|
+
}
|
|
310
|
+
function ke(n) {
|
|
311
|
+
const e = [], t = /* @__PURE__ */ new Set();
|
|
312
|
+
return (Array.isArray(n?.models) ? n.models : []).forEach((i) => {
|
|
313
|
+
if (i?.url == null)
|
|
314
|
+
return;
|
|
315
|
+
const s = b(n, i.url);
|
|
316
|
+
s == null || t.has(s.uuid) || (t.add(s.uuid), e.push(s));
|
|
317
|
+
}), n?.scene?.children?.forEach?.((i) => {
|
|
318
|
+
!_(i) || t.has(i.uuid) || (t.add(i.uuid), e.push(i));
|
|
319
|
+
}), e;
|
|
320
|
+
}
|
|
321
|
+
function le(n, e) {
|
|
322
|
+
const t = [], r = /* @__PURE__ */ new Set();
|
|
323
|
+
return ke(n).forEach((s) => {
|
|
324
|
+
s.traverse((a) => {
|
|
325
|
+
a === s || r.has(a.uuid) || e(a) && (r.add(a.uuid), t.push(a));
|
|
326
|
+
});
|
|
327
|
+
}), n?.scene?.children?.forEach?.((s) => {
|
|
328
|
+
if (s.name == "highlightModel")
|
|
329
|
+
r.add(s.uuid), t.push(s);
|
|
330
|
+
else {
|
|
331
|
+
if (r.has(s.uuid) || _(s) || s.url == null)
|
|
332
|
+
return;
|
|
333
|
+
e(s) && (r.add(s.uuid), t.push(s));
|
|
334
|
+
}
|
|
335
|
+
}), t;
|
|
336
|
+
}
|
|
337
|
+
function Ae(n) {
|
|
338
|
+
if (typeof n.__modelObjectIndexSeed == "number")
|
|
339
|
+
return;
|
|
340
|
+
let e = -1;
|
|
341
|
+
n?.scene?.traverse?.((t) => {
|
|
342
|
+
typeof t?.index == "number" && Number.isFinite(t.index) && (e = Math.max(e, t.index));
|
|
343
|
+
}), n.__modelObjectIndexSeed = e + 1;
|
|
344
|
+
}
|
|
345
|
+
function Be(n) {
|
|
346
|
+
Ae(n);
|
|
347
|
+
const e = n.__modelObjectIndexSeed;
|
|
348
|
+
return n.__modelObjectIndexSeed += 1, e;
|
|
349
|
+
}
|
|
350
|
+
function lt(n, e, t) {
|
|
351
|
+
const r = typeof t == "number" ? t : Be(n);
|
|
352
|
+
return e.index = r, oe(n).set(r, e), r;
|
|
353
|
+
}
|
|
354
|
+
function ut(n) {
|
|
355
|
+
n.updateMatrix(), n.matrixAutoUpdate = !1;
|
|
356
|
+
}
|
|
357
|
+
function Ge(n) {
|
|
358
|
+
return Number.isFinite(n) && n >= 3;
|
|
359
|
+
}
|
|
360
|
+
function ht(n, e) {
|
|
361
|
+
return e <= Te && Ge(n);
|
|
362
|
+
}
|
|
363
|
+
function ue(n) {
|
|
364
|
+
return Array.isArray(n) ? n : n != null ? [n] : [];
|
|
365
|
+
}
|
|
366
|
+
function Fe(n) {
|
|
367
|
+
return 1 + (Number.isFinite(n) ? Math.abs(Math.floor(n)) : 0) % 16 * 0.125;
|
|
368
|
+
}
|
|
369
|
+
function ct(n, e, t = !1) {
|
|
370
|
+
const r = typeof e == "number" ? e : n?.index, i = Fe(r), s = t ? -1 : 1;
|
|
371
|
+
ue(n?.material).forEach((a) => {
|
|
372
|
+
a.userData = a.userData ?? {};
|
|
373
|
+
const o = a.userData[D] ?? {};
|
|
374
|
+
a.userData[D] = {
|
|
375
|
+
...o,
|
|
376
|
+
enabled: !0,
|
|
377
|
+
factor: 1,
|
|
378
|
+
units: i
|
|
379
|
+
}, a.polygonOffset = !0, a.polygonOffsetFactor = s, a.polygonOffsetUnits = s * (i + (o.edgeActive ? ne : 0)), a.needsUpdate = !0;
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
function dt(n, e, t = !1) {
|
|
383
|
+
const r = t ? -1 : 1;
|
|
384
|
+
ue(n?.material).forEach((i) => {
|
|
385
|
+
i.userData = i.userData ?? {};
|
|
386
|
+
const s = i.userData[D] ?? {}, a = s.enabled ? s.units : i.polygonOffsetUnits ?? 0, o = s.enabled ? s.factor : i.polygonOffsetFactor ?? 1;
|
|
387
|
+
i.userData[D] = {
|
|
388
|
+
...s,
|
|
389
|
+
edgeActive: e
|
|
390
|
+
}, s.enabled || e ? (i.polygonOffset = !0, i.polygonOffsetFactor = r * Math.abs(o), i.polygonOffsetUnits = r * (Math.abs(a) + (e ? ne : 0))) : i.polygonOffset = !1, i.needsUpdate = !0;
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
function gt(n, e) {
|
|
394
|
+
if (!Number.isFinite(e))
|
|
395
|
+
return;
|
|
396
|
+
const t = oe(n), r = t.get(e);
|
|
397
|
+
if (r && r.parent != null)
|
|
398
|
+
return r;
|
|
399
|
+
r && r.parent == null && t.delete(e);
|
|
400
|
+
const i = le(n, (s) => s.index === e)[0];
|
|
401
|
+
return i != null ? (t.set(e, i), i) : n?.scene?.children?.find?.((s) => s?.index === e);
|
|
402
|
+
}
|
|
403
|
+
class Oe {
|
|
404
|
+
constructor(e, t) {
|
|
405
|
+
this.engine = e, this.coordinateModule = t;
|
|
406
|
+
}
|
|
407
|
+
engine;
|
|
408
|
+
coordinateModule;
|
|
409
|
+
records = /* @__PURE__ */ new Map();
|
|
410
|
+
/** 登记附加 3D Tiles;根节点就绪后再冻结加载完成矩阵作为校准基准。 */
|
|
411
|
+
registerTileset(e, t, r, i, s = !0) {
|
|
412
|
+
const a = this.createRecord(e, t, r, "3dtiles", i.group);
|
|
413
|
+
return a.tiles = i, this.records.set(e, a), s && this.captureBaseState(a), this.cloneItem(a.item);
|
|
414
|
+
}
|
|
415
|
+
/** 高度偏移和根瓦片变换完成后,记录最终加载矩阵。 */
|
|
416
|
+
markTilesetReady(e) {
|
|
417
|
+
const t = [...this.records.values()].find((r) => r.tiles === e);
|
|
418
|
+
return t ? (this.captureBaseState(t), !0) : !1;
|
|
419
|
+
}
|
|
420
|
+
/** 登记普通 BIM 模型;模型 URL 默认同时作为校准 id。 */
|
|
421
|
+
registerBimModel(e, t = {}) {
|
|
422
|
+
const r = b(this.engine, e);
|
|
423
|
+
if (!r) throw new Error(`BIM 模型尚未加载,无法登记校准:${e}`);
|
|
424
|
+
const i = t.id?.trim() || e, s = this.createRecord(i, t.name?.trim() || e, e, "bim", r);
|
|
425
|
+
return s.bimUrl = e, this.records.set(i, s), this.captureBaseState(s), this.cloneItem(s.item);
|
|
426
|
+
}
|
|
427
|
+
/** 查询全部模型校准状态快照。 */
|
|
428
|
+
getItems() {
|
|
429
|
+
return [...this.records.values()].map((e) => this.cloneItem(e.item));
|
|
430
|
+
}
|
|
431
|
+
/** 查询单个模型校准状态。 */
|
|
432
|
+
getItem(e) {
|
|
433
|
+
const t = this.records.get(e);
|
|
434
|
+
return t ? this.cloneItem(t.item) : null;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* 从基准矩阵应用 ENU 偏移与旋转。
|
|
438
|
+
* 每次都从 baseMatrix 重算,避免滑块连续调整时累计产生水平漂移。
|
|
439
|
+
*/
|
|
440
|
+
setTransform(e) {
|
|
441
|
+
let t = this.records.get(e.id);
|
|
442
|
+
if (!t && b(this.engine, e.id) && (this.registerBimModel(e.id), t = this.records.get(e.id)), !t) return !1;
|
|
443
|
+
if (!t.item.ready || !t.baseMatrix || !t.anchorWorld)
|
|
444
|
+
throw new Error(`模型校准基准尚未就绪:${e.id}`);
|
|
445
|
+
const r = this.resolveOffset(e.offset, t.item.offset), i = this.resolveRotation(e.rotation, t.item.rotation), s = this.buildCalibratedLocalMatrix(t, r, i);
|
|
446
|
+
return this.applyMatrix(t, s) ? (t.item.offset = r, t.item.rotation = i, this.emitChanged(t.item, "apply"), !0) : !1;
|
|
447
|
+
}
|
|
448
|
+
/** 恢复模型登记时的矩阵。 */
|
|
449
|
+
reset(e) {
|
|
450
|
+
const t = this.records.get(e);
|
|
451
|
+
return !t?.baseMatrix || !this.applyMatrix(t, t.baseMatrix) ? !1 : (t.item.offset = this.zeroOffset(), t.item.rotation = this.zeroRotation(), this.emitChanged(t.item, "reset"), !0);
|
|
452
|
+
}
|
|
453
|
+
/** 移除模型的校准记录,不改变模型当前矩阵。 */
|
|
454
|
+
unregister(e) {
|
|
455
|
+
return this.records.delete(e);
|
|
456
|
+
}
|
|
457
|
+
/** 释放内部快照,模型资源仍由各自加载器管理。 */
|
|
458
|
+
dispose() {
|
|
459
|
+
this.records.clear();
|
|
460
|
+
}
|
|
461
|
+
createRecord(e, t, r, i, s) {
|
|
462
|
+
return {
|
|
463
|
+
item: {
|
|
464
|
+
id: e,
|
|
465
|
+
name: t,
|
|
466
|
+
source: r,
|
|
467
|
+
type: i,
|
|
468
|
+
offset: this.zeroOffset(),
|
|
469
|
+
rotation: this.zeroRotation(),
|
|
470
|
+
baseMatrix: new y().toArray(),
|
|
471
|
+
ready: !1
|
|
472
|
+
},
|
|
473
|
+
object: s,
|
|
474
|
+
baseMatrix: null,
|
|
475
|
+
anchorWorld: null
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
captureBaseState(e) {
|
|
479
|
+
e.object.matrixAutoUpdate && e.object.updateMatrix(), e.object.updateWorldMatrix(!0, !0), e.baseMatrix = e.object.matrix.clone(), e.anchorWorld = this.readAnchorWorld(e), e.item.baseMatrix = e.baseMatrix.toArray(), e.item.ready = !0, e.item.offset = this.zeroOffset(), e.item.rotation = this.zeroRotation();
|
|
480
|
+
}
|
|
481
|
+
readAnchorWorld(e) {
|
|
482
|
+
if (e.tiles) {
|
|
483
|
+
const r = new G();
|
|
484
|
+
if (e.tiles.getBoundingSphere(r))
|
|
485
|
+
return r.center.applyMatrix4(e.object.matrixWorld);
|
|
486
|
+
}
|
|
487
|
+
const t = new z().setFromObject(e.object);
|
|
488
|
+
return t.isEmpty() ? e.object.getWorldPosition(new f()) : t.getCenter(new f());
|
|
489
|
+
}
|
|
490
|
+
buildCalibratedLocalMatrix(e, t, r) {
|
|
491
|
+
const i = e.anchorWorld, s = this.coordinateModule.toWgs84(i), { east: a, north: o, up: l } = this.coordinateModule.getEastNorthUpAxes(s), c = a.clone().multiplyScalar(t.east).addScaledVector(o, t.north).addScaledVector(l, t.up), m = new y().multiply(new y().makeRotationAxis(l, S.degToRad(r.heading))).multiply(new y().makeRotationAxis(a, S.degToRad(r.pitch))).multiply(new y().makeRotationAxis(o, S.degToRad(r.roll))), u = new y().makeTranslation(...c.toArray()).multiply(new y().makeTranslation(...i.toArray())).multiply(m).multiply(new y().makeTranslation(...i.clone().negate().toArray()));
|
|
492
|
+
e.object.parent?.updateWorldMatrix(!0, !1);
|
|
493
|
+
const g = e.object.parent?.matrixWorld.clone() ?? new y(), h = g.clone().multiply(e.baseMatrix);
|
|
494
|
+
return g.invert().multiply(u).multiply(h);
|
|
495
|
+
}
|
|
496
|
+
applyMatrix(e, t) {
|
|
497
|
+
return e.item.type === "bim" ? this.engine.calibration.apply({
|
|
498
|
+
url: e.bimUrl,
|
|
499
|
+
matrix: t
|
|
500
|
+
}) : (e.object.matrixAutoUpdate = !1, e.object.matrix.copy(t), e.object.matrixWorldNeedsUpdate = !0, e.object.updateMatrixWorld(!0), this.engine.requestRender(), !0);
|
|
501
|
+
}
|
|
502
|
+
resolveOffset(e, t) {
|
|
503
|
+
return {
|
|
504
|
+
east: this.numberOrCurrent(e?.east, t.east),
|
|
505
|
+
north: this.numberOrCurrent(e?.north, t.north),
|
|
506
|
+
up: this.numberOrCurrent(e?.up, t.up)
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
resolveRotation(e, t) {
|
|
510
|
+
return {
|
|
511
|
+
heading: this.numberOrCurrent(e?.heading, t.heading),
|
|
512
|
+
pitch: this.numberOrCurrent(e?.pitch, t.pitch),
|
|
513
|
+
roll: this.numberOrCurrent(e?.roll, t.roll)
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
numberOrCurrent(e, t) {
|
|
517
|
+
if (e === void 0) return t;
|
|
518
|
+
if (!Number.isFinite(e)) throw new RangeError("模型校准参数必须是有限数值。");
|
|
519
|
+
return Math.abs(e) < 1e-10 ? 0 : e;
|
|
520
|
+
}
|
|
521
|
+
emitChanged(e, t) {
|
|
522
|
+
this.engine.events.trigger("gis:calibration:changed", {
|
|
523
|
+
...this.cloneItem(e),
|
|
524
|
+
action: t,
|
|
525
|
+
timestamp: Date.now()
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
cloneItem(e) {
|
|
529
|
+
return {
|
|
530
|
+
...e,
|
|
531
|
+
offset: { ...e.offset },
|
|
532
|
+
rotation: { ...e.rotation },
|
|
533
|
+
baseMatrix: [...e.baseMatrix]
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
zeroOffset() {
|
|
537
|
+
return { east: 0, north: 0, up: 0 };
|
|
538
|
+
}
|
|
539
|
+
zeroRotation() {
|
|
540
|
+
return { heading: 0, pitch: 0, roll: 0 };
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
const I = Math.PI / 180;
|
|
544
|
+
class De {
|
|
545
|
+
constructor(e, t, r, i) {
|
|
546
|
+
this.engine = e, this.tiles = t, this.layerManager = r, this.calibrationManager = i;
|
|
547
|
+
}
|
|
548
|
+
engine;
|
|
549
|
+
tiles;
|
|
550
|
+
layerManager;
|
|
551
|
+
calibrationManager;
|
|
552
|
+
records = /* @__PURE__ */ new Map();
|
|
553
|
+
inverseParentMatrix = new y();
|
|
554
|
+
/** 切换底图后更新地理参考,已有 BIM 模型的世界定位保持不变。 */
|
|
555
|
+
setTiles(e) {
|
|
556
|
+
this.tiles = e;
|
|
557
|
+
}
|
|
558
|
+
/** 定位已加载模型;模型本地 +Y 为上方,heading 从正北向东顺时针增加。 */
|
|
559
|
+
setPosition(e, t) {
|
|
560
|
+
this.assertUrl(e);
|
|
561
|
+
const r = this.resolvePosition(t), i = this.requireModelGroup(e), s = this.records.get(e), a = s?.groupUuid === i.uuid ? s.originalMatrix : i.matrix.clone(), o = this.tiles.ellipsoid.getObjectFrame(
|
|
562
|
+
r.latitude * I,
|
|
563
|
+
r.longitude * I,
|
|
564
|
+
r.height,
|
|
565
|
+
r.heading * I,
|
|
566
|
+
r.pitch * I,
|
|
567
|
+
r.roll * I,
|
|
568
|
+
new y()
|
|
569
|
+
);
|
|
570
|
+
if (this.tiles.group.updateWorldMatrix(!0, !1), o.premultiply(this.tiles.group.matrixWorld), i.parent && (i.parent.updateWorldMatrix(!0, !1), this.inverseParentMatrix.copy(i.parent.matrixWorld).invert(), o.premultiply(this.inverseParentMatrix)), !this.engine.calibration.apply({ url: e, matrix: o }))
|
|
571
|
+
throw new Error(`模型定位失败:${e}`);
|
|
572
|
+
this.records.set(e, {
|
|
573
|
+
groupUuid: i.uuid,
|
|
574
|
+
originalMatrix: a.clone(),
|
|
575
|
+
position: { ...r }
|
|
576
|
+
});
|
|
577
|
+
const l = this.getLayerId(e);
|
|
578
|
+
return this.layerManager?.unregisterLayer(l), this.layerManager?.registerLayer({
|
|
579
|
+
id: l,
|
|
580
|
+
name: e,
|
|
581
|
+
type: "model",
|
|
582
|
+
source: e,
|
|
583
|
+
object: i
|
|
584
|
+
}), this.calibrationManager?.registerBimModel(e, { name: e }), { ...r };
|
|
585
|
+
}
|
|
586
|
+
/** 模型未加载时先通过 v3 Loader 加载,再应用地理定位。 */
|
|
587
|
+
async loadAndPosition(e, t, r) {
|
|
588
|
+
return this.assertUrl(e), b(this.engine, e) || await this.engine.loaderModule.loadModels([e], r), this.setPosition(e, t);
|
|
589
|
+
}
|
|
590
|
+
/** 获取指定模型最近一次由本管理器应用的定位信息。 */
|
|
591
|
+
getPosition(e) {
|
|
592
|
+
const t = this.records.get(e), r = b(this.engine, e);
|
|
593
|
+
return !t || r?.uuid !== t.groupUuid ? null : { ...t.position };
|
|
594
|
+
}
|
|
595
|
+
/** 恢复模型定位前的根分组矩阵。 */
|
|
596
|
+
resetPosition(e) {
|
|
597
|
+
const t = this.records.get(e), r = b(this.engine, e);
|
|
598
|
+
return !t || !r || r.uuid !== t.groupUuid ? (this.records.delete(e), this.layerManager?.unregisterLayer(this.getLayerId(e)), !1) : this.engine.calibration.apply({ url: e, matrix: t.originalMatrix }) ? (this.records.delete(e), this.layerManager?.unregisterLayer(this.getLayerId(e)), this.calibrationManager?.unregister(e), !0) : !1;
|
|
599
|
+
}
|
|
600
|
+
/** 释放内部记录;模型资源由引擎统一管理。 */
|
|
601
|
+
dispose() {
|
|
602
|
+
this.records.forEach((e, t) => {
|
|
603
|
+
this.layerManager?.unregisterLayer(this.getLayerId(t)), this.calibrationManager?.unregister(t);
|
|
604
|
+
}), this.records.clear();
|
|
605
|
+
}
|
|
606
|
+
requireModelGroup(e) {
|
|
607
|
+
const t = b(this.engine, e);
|
|
608
|
+
if (!t) throw new Error(`模型尚未加载,无法定位:${e}`);
|
|
609
|
+
return t;
|
|
610
|
+
}
|
|
611
|
+
resolvePosition(e) {
|
|
612
|
+
if (!e || !Number.isFinite(e.latitude) || e.latitude < -90 || e.latitude > 90)
|
|
613
|
+
throw new RangeError("纬度必须是 -90 到 90 之间的有限数值。");
|
|
614
|
+
if (!Number.isFinite(e.longitude))
|
|
615
|
+
throw new RangeError("经度必须是有限数值。");
|
|
616
|
+
const t = e.height ?? 0, r = e.heading ?? 0, i = e.pitch ?? 0, s = e.roll ?? 0;
|
|
617
|
+
if (![t, r, i, s].every(Number.isFinite))
|
|
618
|
+
throw new RangeError("高度和模型朝向必须是有限数值。");
|
|
619
|
+
if (i < -90 || i > 90)
|
|
620
|
+
throw new RangeError("俯仰角必须是 -90 到 90 之间的有限数值。");
|
|
621
|
+
return {
|
|
622
|
+
longitude: this.normalizeAngle(e.longitude),
|
|
623
|
+
latitude: e.latitude,
|
|
624
|
+
height: t,
|
|
625
|
+
heading: this.normalizeAngle(r),
|
|
626
|
+
pitch: i,
|
|
627
|
+
roll: this.normalizeAngle(s)
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
assertUrl(e) {
|
|
631
|
+
if (typeof e != "string" || e.trim().length === 0)
|
|
632
|
+
throw new TypeError("模型 URL 不能为空。");
|
|
633
|
+
}
|
|
634
|
+
normalizeAngle(e) {
|
|
635
|
+
return ((e + 180) % 360 + 360) % 360 - 180;
|
|
636
|
+
}
|
|
637
|
+
getLayerId(e) {
|
|
638
|
+
return `model:${e}`;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
const T = {
|
|
642
|
+
preset: "noon_clear",
|
|
643
|
+
params: {
|
|
644
|
+
cloudSpeed: 0,
|
|
645
|
+
cloudCoverage: 0,
|
|
646
|
+
cloudDensity: 0
|
|
647
|
+
},
|
|
648
|
+
intensity: 0.2
|
|
649
|
+
}, Ve = "4";
|
|
650
|
+
class je {
|
|
651
|
+
constructor(e) {
|
|
652
|
+
this.engine = e;
|
|
653
|
+
}
|
|
654
|
+
engine;
|
|
655
|
+
cubeTextureLoader = new me();
|
|
656
|
+
skyBoxTexture = null;
|
|
657
|
+
savedSceneState = null;
|
|
658
|
+
skyBoxLoadVersion = 0;
|
|
659
|
+
skyBoxType = null;
|
|
660
|
+
/** 应用天空配置;传 false 时关闭并保留资源,便于后续快速恢复。 */
|
|
661
|
+
setSky(e = !0) {
|
|
662
|
+
if (this.clearSkyBox(), e === !1) {
|
|
663
|
+
this.hide();
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
const t = e === !0 ? {} : e, r = {
|
|
667
|
+
preset: t.preset ?? T.preset,
|
|
668
|
+
params: { ...T.params, ...t.params },
|
|
669
|
+
intensity: t.intensity ?? T.intensity
|
|
670
|
+
};
|
|
671
|
+
r.preset && this.engine.skyManager.setPreset(r.preset), r.params && this.engine.skyManager.setParams(r.params), r.intensity !== void 0 && this.setIntensity(r.intensity), this.engine.skyManager.isActive() || this.engine.skyManager.activate();
|
|
672
|
+
}
|
|
673
|
+
/** 显示天空,并可同时覆盖预设、参数和环境光强度。 */
|
|
674
|
+
show(e = {}) {
|
|
675
|
+
this.setSky(e);
|
|
676
|
+
}
|
|
677
|
+
/** 设置内置 HDR 或六面图天空盒,未传配置时使用内置晴空。 */
|
|
678
|
+
async setSkyBox(e = {}) {
|
|
679
|
+
this.validateSkyBoxOptions(e);
|
|
680
|
+
const t = ++this.skyBoxLoadVersion;
|
|
681
|
+
if (!("urls" in e)) {
|
|
682
|
+
this.releaseCubeSkyBox(), this.engine.skyManager.close(!1);
|
|
683
|
+
const o = e.id ?? Ve;
|
|
684
|
+
if (await this.engine.hdr.setHdr(o), t !== this.skyBoxLoadVersion) {
|
|
685
|
+
this.skyBoxType !== "hdr" && this.engine.hdr.getCurrentHdrId() === o && await this.engine.hdr.setHdr("0");
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
this.engine.hdr.setIntensity(e.intensity ?? T.intensity), this.engine.hdr.setBackgroundIntensity(e.backgroundIntensity ?? 1), this.engine.hdr.setVisible(!0), this.skyBoxType = "hdr";
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
const r = await this.cubeTextureLoader.loadAsync([...e.urls]);
|
|
692
|
+
if (t !== this.skyBoxLoadVersion) {
|
|
693
|
+
r.dispose();
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
r.colorSpace = pe, this.skyBoxType === "hdr" && this.engine.hdr.setHdr("0"), this.engine.skyManager.close(!1), this.captureSceneState(), this.skyBoxTexture?.dispose(), this.skyBoxTexture = r, this.skyBoxType = "cube";
|
|
697
|
+
const i = this.engine.scene, s = e.intensity ?? 1, a = S.degToRad(e.rotation ?? 0);
|
|
698
|
+
i.background = r, i.environment = r, i.backgroundIntensity = s, i.environmentIntensity = s, i.backgroundBlurriness = e.blurriness ?? 0, i.backgroundRotation.set(0, a, 0), i.environmentRotation.set(0, a, 0), this.refreshScene();
|
|
699
|
+
}
|
|
700
|
+
/** 清除天空盒并恢复设置前的场景背景和环境光。 */
|
|
701
|
+
clearSkyBox() {
|
|
702
|
+
this.skyBoxLoadVersion += 1, this.skyBoxType === "hdr" && (this.engine.hdr.setHdr("0"), this.skyBoxType = null), this.releaseCubeSkyBox();
|
|
703
|
+
}
|
|
704
|
+
releaseCubeSkyBox() {
|
|
705
|
+
if (!this.savedSceneState && !this.skyBoxTexture) return;
|
|
706
|
+
const e = this.engine.scene;
|
|
707
|
+
this.savedSceneState && (e.background = this.savedSceneState.background, e.environment = this.savedSceneState.environment, e.backgroundIntensity = this.savedSceneState.backgroundIntensity, e.environmentIntensity = this.savedSceneState.environmentIntensity, e.backgroundBlurriness = this.savedSceneState.backgroundBlurriness, e.backgroundRotation.copy(this.savedSceneState.backgroundRotation), e.environmentRotation.copy(this.savedSceneState.environmentRotation)), this.savedSceneState = null, this.skyBoxTexture?.dispose(), this.skyBoxTexture = null, this.skyBoxType = null, this.refreshScene();
|
|
708
|
+
}
|
|
709
|
+
/** 返回 HDR 或六面图天空盒是否已经应用到场景。 */
|
|
710
|
+
isSkyBoxActive() {
|
|
711
|
+
return this.skyBoxType === "hdr" ? this.engine.hdr.getVisible() && this.engine.hdr.getCurrentHdrId() !== "0" : this.skyBoxTexture !== null && this.engine.scene.background === this.skyBoxTexture;
|
|
712
|
+
}
|
|
713
|
+
/** 隐藏当前天空;天空盒纹理会立即释放,物理天空可按需保留资源。 */
|
|
714
|
+
hide(e = !1) {
|
|
715
|
+
if (this.skyBoxType !== null) {
|
|
716
|
+
this.clearSkyBox();
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
this.engine.skyManager.close(e);
|
|
720
|
+
}
|
|
721
|
+
/** 切换 v3 引擎内置天空预设。 */
|
|
722
|
+
setPreset(e) {
|
|
723
|
+
this.clearSkyBox(), this.engine.skyManager.setPreset(e), this.engine.skyManager.isActive() || this.engine.skyManager.activate();
|
|
724
|
+
}
|
|
725
|
+
/** 增量修改太阳、云层和大气参数。 */
|
|
726
|
+
setParams(e) {
|
|
727
|
+
this.clearSkyBox(), this.engine.skyManager.setParams(e), this.engine.skyManager.isActive() || this.engine.skyManager.activate();
|
|
728
|
+
}
|
|
729
|
+
/** 设置天空生成的环境光强度。 */
|
|
730
|
+
setIntensity(e) {
|
|
731
|
+
if (!Number.isFinite(e) || e < 0)
|
|
732
|
+
throw new RangeError("天空环境光强度必须是大于或等于 0 的有限数值。");
|
|
733
|
+
if (this.skyBoxType === "hdr") {
|
|
734
|
+
this.engine.hdr.setIntensity(e), this.engine.hdr.setBackgroundIntensity(e);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (this.skyBoxType === "cube") {
|
|
738
|
+
this.engine.scene.backgroundIntensity = e, this.engine.scene.environmentIntensity = e, this.refreshScene();
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
this.engine.skyManager.setIntensity(e);
|
|
742
|
+
}
|
|
743
|
+
/** 返回当前天空是否可见。 */
|
|
744
|
+
isActive() {
|
|
745
|
+
return this.isSkyBoxActive() || this.engine.skyManager.isActive();
|
|
746
|
+
}
|
|
747
|
+
/** 释放 GIS 天空资源,v3 引擎销毁时仍会执行兜底清理。 */
|
|
748
|
+
dispose() {
|
|
749
|
+
this.clearSkyBox(), this.engine.skyManager.close(!0);
|
|
750
|
+
}
|
|
751
|
+
captureSceneState() {
|
|
752
|
+
if (this.savedSceneState) return;
|
|
753
|
+
const e = this.engine.scene;
|
|
754
|
+
this.savedSceneState = {
|
|
755
|
+
background: e.background,
|
|
756
|
+
environment: e.environment,
|
|
757
|
+
backgroundIntensity: e.backgroundIntensity,
|
|
758
|
+
environmentIntensity: e.environmentIntensity,
|
|
759
|
+
backgroundBlurriness: e.backgroundBlurriness,
|
|
760
|
+
backgroundRotation: e.backgroundRotation.clone(),
|
|
761
|
+
environmentRotation: e.environmentRotation.clone()
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
validateSkyBoxOptions(e) {
|
|
765
|
+
if ("urls" in e && (e.urls.length !== 6 || e.urls.some((t) => !t.trim())))
|
|
766
|
+
throw new TypeError("GIS 天空盒必须提供右、左、上、下、前、后共 6 张图片。");
|
|
767
|
+
if (e.intensity !== void 0 && (!Number.isFinite(e.intensity) || e.intensity < 0))
|
|
768
|
+
throw new RangeError("天空盒亮度必须是大于或等于 0 的有限数值。");
|
|
769
|
+
if ("blurriness" in e && e.blurriness !== void 0 && (!Number.isFinite(e.blurriness) || e.blurriness < 0 || e.blurriness > 1))
|
|
770
|
+
throw new RangeError("天空盒模糊度必须是 0 到 1 之间的有限数值。");
|
|
771
|
+
if ("rotation" in e && e.rotation !== void 0 && !Number.isFinite(e.rotation))
|
|
772
|
+
throw new RangeError("天空盒旋转角度必须是有限数值。");
|
|
773
|
+
if ("backgroundIntensity" in e && e.backgroundIntensity !== void 0 && (!Number.isFinite(e.backgroundIntensity) || e.backgroundIntensity < 0))
|
|
774
|
+
throw new RangeError("天空盒背景亮度必须是大于或等于 0 的有限数值。");
|
|
775
|
+
}
|
|
776
|
+
refreshScene() {
|
|
777
|
+
this.engine.composerModule?.rebuild?.(), this.engine.requestComposerRender?.();
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
const V = {
|
|
781
|
+
latitude: 35.6812,
|
|
782
|
+
longitude: 139.8,
|
|
783
|
+
height: 500,
|
|
784
|
+
heading: -90,
|
|
785
|
+
pitch: -10,
|
|
786
|
+
roll: 0
|
|
787
|
+
}, x = Math.PI / 180;
|
|
788
|
+
class Y {
|
|
789
|
+
constructor(e, t, r, i, s = V, a) {
|
|
790
|
+
this.camera = e, this.tiles = t, this.cameraFrame = r, this.requestRender = i, this.coordinateModule = a, this.camera.near = 0.1, this.camera.far = 1e7, this.camera.updateProjectionMatrix(), this.currentView = this.resolveView(s), this.applyView(this.currentView);
|
|
791
|
+
}
|
|
792
|
+
camera;
|
|
793
|
+
tiles;
|
|
794
|
+
cameraFrame;
|
|
795
|
+
requestRender;
|
|
796
|
+
coordinateModule;
|
|
797
|
+
controls = null;
|
|
798
|
+
currentView = { ...V };
|
|
799
|
+
inverseParentMatrix = new y();
|
|
800
|
+
flight = null;
|
|
801
|
+
controlsEnabledBeforeFlight = !0;
|
|
802
|
+
disposed = !1;
|
|
803
|
+
/** 绑定地球控制器,使跳转期间禁用输入并在结束后同步控制状态。 */
|
|
804
|
+
setControls(e) {
|
|
805
|
+
this.controls?.removeEventListener("change", this.handleControlsChange), this.controls = e, e.addEventListener("change", this.handleControlsChange), e.resetState();
|
|
806
|
+
}
|
|
807
|
+
/** 立即跳转到指定经纬度。 */
|
|
808
|
+
setView(e) {
|
|
809
|
+
this.cancelFlight("新的视图跳转已替代当前飞行。"), this.currentView = this.resolveView(e), this.applyView(this.currentView), this.controls?.resetState();
|
|
810
|
+
}
|
|
811
|
+
/** 平滑飞行到指定经纬度,默认动画时长为 1.5 秒。 */
|
|
812
|
+
flyTo(e, t = {}) {
|
|
813
|
+
if (this.disposed) return Promise.reject(new Error("GIS 相机模块已销毁。"));
|
|
814
|
+
if (this.cancelFlight("新的视图跳转已替代当前飞行。"), t.signal?.aborted)
|
|
815
|
+
return Promise.reject(new DOMException("GIS 相机飞行已取消。", "AbortError"));
|
|
816
|
+
this.syncViewFromCamera();
|
|
817
|
+
const r = this.resolveView(e), i = Math.max(0, Math.min(6e4, t.durationMs ?? 1500));
|
|
818
|
+
return i === 0 ? (this.setView(r), Promise.resolve()) : (this.controlsEnabledBeforeFlight = this.controls?.enabled ?? !0, this.controls && (this.controls.enabled = !1), new Promise((s, a) => {
|
|
819
|
+
const o = {
|
|
820
|
+
start: { ...this.currentView },
|
|
821
|
+
end: r,
|
|
822
|
+
startedAt: performance.now(),
|
|
823
|
+
durationMs: i,
|
|
824
|
+
resolve: s,
|
|
825
|
+
reject: a,
|
|
826
|
+
signal: t.signal
|
|
827
|
+
};
|
|
828
|
+
t.signal && (o.abortHandler = () => this.cancelFlight("GIS 相机飞行已取消。", !0), t.signal.addEventListener("abort", o.abortHandler, { once: !0 })), this.flight = o, this.requestRender();
|
|
829
|
+
}));
|
|
830
|
+
}
|
|
831
|
+
/** 返回当前已经应用到相机的 WGS84 视图。 */
|
|
832
|
+
getView() {
|
|
833
|
+
return this.syncViewFromCamera(), { ...this.currentView };
|
|
834
|
+
}
|
|
835
|
+
/** 取消当前飞行,并恢复跳转前的控制器状态。 */
|
|
836
|
+
cancelFlight(e = "GIS 相机飞行已取消。", t = !1) {
|
|
837
|
+
const r = this.flight;
|
|
838
|
+
return r ? (this.flight = null, this.cleanupFlight(r), r.reject(t ? new DOMException(e, "AbortError") : new Error(e)), !0) : !1;
|
|
839
|
+
}
|
|
840
|
+
/** 由 GIS 共享渲染循环调用,推进当前相机飞行动画。 */
|
|
841
|
+
update(e) {
|
|
842
|
+
const t = this.flight;
|
|
843
|
+
if (!t) return;
|
|
844
|
+
const r = Math.min(1, Math.max(0, (e - t.startedAt) / t.durationMs)), i = r < 0.5 ? 4 * r * r * r : 1 - Math.pow(-2 * r + 2, 3) / 2;
|
|
845
|
+
this.currentView = {
|
|
846
|
+
latitude: this.lerp(t.start.latitude, t.end.latitude, i),
|
|
847
|
+
longitude: this.lerpAngle(t.start.longitude, t.end.longitude, i),
|
|
848
|
+
height: this.lerp(t.start.height, t.end.height, i),
|
|
849
|
+
heading: this.lerpAngle(t.start.heading, t.end.heading, i),
|
|
850
|
+
pitch: this.lerp(t.start.pitch, t.end.pitch, i),
|
|
851
|
+
roll: this.lerpAngle(t.start.roll, t.end.roll, i)
|
|
852
|
+
}, this.applyView(this.currentView), r === 1 && (this.currentView = { ...t.end }, this.flight = null, this.cleanupFlight(t), this.controls?.resetState(), t.resolve());
|
|
853
|
+
}
|
|
854
|
+
/** 释放飞行任务持有的事件和控制器状态。 */
|
|
855
|
+
dispose() {
|
|
856
|
+
this.disposed || (this.disposed = !0, this.cancelFlight("GIS 相机模块已销毁。"), this.controls?.removeEventListener("change", this.handleControlsChange), this.controls = null);
|
|
857
|
+
}
|
|
858
|
+
/** 用户通过 GlobeControls 改变相机后,同步真实地理视图供保存和后续飞行使用。 */
|
|
859
|
+
handleControlsChange = () => {
|
|
860
|
+
this.flight || this.syncViewFromCamera();
|
|
861
|
+
};
|
|
862
|
+
syncViewFromCamera() {
|
|
863
|
+
!this.coordinateModule || this.flight || (this.currentView = this.coordinateModule.getCameraView(this.camera));
|
|
864
|
+
}
|
|
865
|
+
resolveView(e) {
|
|
866
|
+
if (!Number.isFinite(e.latitude) || e.latitude < -90 || e.latitude > 90)
|
|
867
|
+
throw new RangeError("纬度必须是 -90 到 90 之间的有限数值。");
|
|
868
|
+
if (!Number.isFinite(e.longitude))
|
|
869
|
+
throw new RangeError("经度必须是有限数值。");
|
|
870
|
+
const t = e.height ?? this.currentView.height, r = e.heading ?? this.currentView.heading, i = e.pitch ?? this.currentView.pitch, s = e.roll ?? this.currentView.roll;
|
|
871
|
+
if (![t, r, i, s].every(Number.isFinite))
|
|
872
|
+
throw new RangeError("高度和相机朝向必须是有限数值。");
|
|
873
|
+
return {
|
|
874
|
+
latitude: e.latitude,
|
|
875
|
+
longitude: this.normalizeAngle(e.longitude),
|
|
876
|
+
height: Math.max(1, t),
|
|
877
|
+
heading: this.normalizeAngle(r),
|
|
878
|
+
pitch: Math.max(-90, Math.min(90, i)),
|
|
879
|
+
roll: this.normalizeAngle(s)
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
applyView(e) {
|
|
883
|
+
this.tiles.ellipsoid.getObjectFrame(
|
|
884
|
+
e.latitude * x,
|
|
885
|
+
e.longitude * x,
|
|
886
|
+
e.height,
|
|
887
|
+
e.heading * x,
|
|
888
|
+
e.pitch * x,
|
|
889
|
+
e.roll * x,
|
|
890
|
+
this.camera.matrix,
|
|
891
|
+
this.cameraFrame
|
|
892
|
+
), this.tiles.group.updateWorldMatrix(!0, !1), this.camera.matrix.premultiply(this.tiles.group.matrixWorld), this.camera.parent && (this.camera.parent.updateWorldMatrix(!0, !1), this.inverseParentMatrix.copy(this.camera.parent.matrixWorld).invert(), this.camera.matrix.premultiply(this.inverseParentMatrix)), this.camera.matrix.decompose(this.camera.position, this.camera.quaternion, this.camera.scale), this.camera.updateMatrixWorld(!0), this.requestRender();
|
|
893
|
+
}
|
|
894
|
+
cleanupFlight(e) {
|
|
895
|
+
e.signal && e.abortHandler && e.signal.removeEventListener("abort", e.abortHandler), this.controls && (this.controls.enabled = this.controlsEnabledBeforeFlight);
|
|
896
|
+
}
|
|
897
|
+
lerp(e, t, r) {
|
|
898
|
+
return e + (t - e) * r;
|
|
899
|
+
}
|
|
900
|
+
lerpAngle(e, t, r) {
|
|
901
|
+
const i = this.normalizeAngle(t - e);
|
|
902
|
+
return this.normalizeAngle(e + i * r);
|
|
903
|
+
}
|
|
904
|
+
normalizeAngle(e) {
|
|
905
|
+
return ((e + 180) % 360 + 360) % 360 - 180;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
const k = Math.PI / 180, E = 180 / Math.PI;
|
|
909
|
+
class ze {
|
|
910
|
+
constructor(e, t) {
|
|
911
|
+
this.tiles = e, this.cameraFrame = t;
|
|
912
|
+
}
|
|
913
|
+
tiles;
|
|
914
|
+
cameraFrame;
|
|
915
|
+
inverseTilesMatrix = new y();
|
|
916
|
+
/** 切换底图后更新坐标参考,但保持依赖此模块的业务管理器不变。 */
|
|
917
|
+
setTiles(e, t) {
|
|
918
|
+
this.tiles = e, this.cameraFrame = t;
|
|
919
|
+
}
|
|
920
|
+
/** 将 WGS84 经纬高转换为 Three.js 世界坐标。 */
|
|
921
|
+
toWorld(e, t = new f()) {
|
|
922
|
+
const r = this.normalizeCoordinate(e);
|
|
923
|
+
return this.tiles.ellipsoid.getCartographicToPosition(
|
|
924
|
+
r.latitude * k,
|
|
925
|
+
r.longitude * k,
|
|
926
|
+
r.height,
|
|
927
|
+
t
|
|
928
|
+
), this.tiles.group.updateWorldMatrix(!0, !1), t.applyMatrix4(this.tiles.group.matrixWorld);
|
|
929
|
+
}
|
|
930
|
+
/** 将 Three.js 世界坐标反算为 WGS84 经纬高。 */
|
|
931
|
+
toWgs84(e) {
|
|
932
|
+
this.tiles.group.updateWorldMatrix(!0, !1), this.inverseTilesMatrix.copy(this.tiles.group.matrixWorld).invert();
|
|
933
|
+
const t = e instanceof f ? e.clone() : new f(e.x, e.y, e.z);
|
|
934
|
+
t.applyMatrix4(this.inverseTilesMatrix);
|
|
935
|
+
const r = this.tiles.ellipsoid.getPositionToCartographic(t, {});
|
|
936
|
+
return {
|
|
937
|
+
longitude: r.lon * E,
|
|
938
|
+
latitude: r.lat * E,
|
|
939
|
+
height: r.height
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
/** 从相机当前真实世界矩阵读取 WGS84 位置和姿态。 */
|
|
943
|
+
getCameraView(e) {
|
|
944
|
+
e.updateWorldMatrix(!0, !1), this.tiles.group.updateWorldMatrix(!0, !1);
|
|
945
|
+
const t = this.inverseTilesMatrix.copy(this.tiles.group.matrixWorld).invert().multiply(e.matrixWorld), r = this.tiles.ellipsoid.getCartographicFromObjectFrame(
|
|
946
|
+
t,
|
|
947
|
+
{},
|
|
948
|
+
this.cameraFrame
|
|
949
|
+
);
|
|
950
|
+
return {
|
|
951
|
+
longitude: this.normalizeAngle(r.lon * E),
|
|
952
|
+
latitude: r.lat * E,
|
|
953
|
+
height: r.height,
|
|
954
|
+
heading: this.normalizeAngle(r.azimuth * E),
|
|
955
|
+
pitch: r.elevation * E,
|
|
956
|
+
roll: this.normalizeAngle(r.roll * E)
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
/** 获取指定 WGS84 位置在 Three.js 世界空间中的东、北、上单位轴。 */
|
|
960
|
+
getEastNorthUpAxes(e, t = new f(), r = new f(), i = new f()) {
|
|
961
|
+
const s = this.normalizeCoordinate(e);
|
|
962
|
+
return this.tiles.ellipsoid.getEastNorthUpAxes(
|
|
963
|
+
s.latitude * k,
|
|
964
|
+
s.longitude * k,
|
|
965
|
+
t,
|
|
966
|
+
r,
|
|
967
|
+
i
|
|
968
|
+
), this.tiles.group.updateWorldMatrix(!0, !1), t.transformDirection(this.tiles.group.matrixWorld).normalize(), r.transformDirection(this.tiles.group.matrixWorld).normalize(), i.transformDirection(this.tiles.group.matrixWorld).normalize(), { east: t, north: r, up: i };
|
|
969
|
+
}
|
|
970
|
+
normalizeCoordinate(e) {
|
|
971
|
+
if (!e || !Number.isFinite(e.latitude) || e.latitude < -90 || e.latitude > 90)
|
|
972
|
+
throw new RangeError("纬度必须是 -90 到 90 之间的有限数值。");
|
|
973
|
+
if (!Number.isFinite(e.longitude))
|
|
974
|
+
throw new RangeError("经度必须是有限数值。");
|
|
975
|
+
const t = e.height ?? 0;
|
|
976
|
+
if (!Number.isFinite(t))
|
|
977
|
+
throw new RangeError("高度必须是有限数值。");
|
|
978
|
+
return {
|
|
979
|
+
longitude: this.normalizeAngle(e.longitude),
|
|
980
|
+
latitude: e.latitude,
|
|
981
|
+
height: t
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
normalizeAngle(e) {
|
|
985
|
+
return ((e + 180) % 360 + 360) % 360 - 180;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
const v = {
|
|
989
|
+
GIS: "gis",
|
|
990
|
+
Default: "default",
|
|
991
|
+
FirstPerson: "first-person",
|
|
992
|
+
ThirdPerson: "third-person"
|
|
993
|
+
};
|
|
994
|
+
class X {
|
|
995
|
+
constructor(e, t, r, i) {
|
|
996
|
+
this.engine = e, this.gisControls = t, this.requestRender = r, this.layerManager = i, this.ensureViewCubeTargetCompatibility(), this.removeThirdPersonPlacementResolver = this.engine.controlModule.setThirdPersonPlacementResolver?.(
|
|
997
|
+
this.resolveGISPlacement
|
|
998
|
+
) ?? null, this.removeOrbitCenterResolver = this.engine.controlModule.setOrbitCenterResolver?.(
|
|
999
|
+
this.resolveGISPlacement
|
|
1000
|
+
) ?? null;
|
|
1001
|
+
}
|
|
1002
|
+
engine;
|
|
1003
|
+
gisControls;
|
|
1004
|
+
requestRender;
|
|
1005
|
+
layerManager;
|
|
1006
|
+
currentMode = null;
|
|
1007
|
+
disposed = !1;
|
|
1008
|
+
removeThirdPersonPlacementResolver = null;
|
|
1009
|
+
removeOrbitCenterResolver = null;
|
|
1010
|
+
/** 切换控制器模式,并把业务配置原样委托给 v3 控制模块。 */
|
|
1011
|
+
switchController(e, t = {}) {
|
|
1012
|
+
if (this.disposed) throw new Error("GIS 控制器模块已销毁。");
|
|
1013
|
+
const r = this.captureCameraRange();
|
|
1014
|
+
try {
|
|
1015
|
+
switch (this.gisControls.enabled = !1, e) {
|
|
1016
|
+
case v.GIS:
|
|
1017
|
+
this.currentMode !== null && this.currentMode !== v.GIS && this.engine.controlModule.switchDefaultMode({ preserveCameraPose: !0 }), this.engine.controlModule.disActive(), this.gisControls.enabled = !0, this.gisControls.resetState(), this.engine.controls = this.gisControls, this.engine.bindControlEvents(this.gisControls);
|
|
1018
|
+
break;
|
|
1019
|
+
case v.Default:
|
|
1020
|
+
this.engine.controlModule.switchDefaultMode({
|
|
1021
|
+
preserveCameraPose: t.preserveCameraPose
|
|
1022
|
+
}), this.engine.controlModule.active(this.engine.controlModule.orbitControls);
|
|
1023
|
+
break;
|
|
1024
|
+
case v.FirstPerson:
|
|
1025
|
+
this.leavePendingThirdPersonMode(), this.engine.controlModule.switchFirstPersonMode(), this.engine.controlModule.active(this.engine.controlModule.firstPersonControls);
|
|
1026
|
+
break;
|
|
1027
|
+
case v.ThirdPerson:
|
|
1028
|
+
this.leavePendingThirdPersonMode(), this.engine.controlModule.switchThirdPersonMode(t.thirdPersonOptions), this.engine.controlModule.active(this.engine.controlModule.orbitControls);
|
|
1029
|
+
break;
|
|
1030
|
+
default:
|
|
1031
|
+
throw new RangeError(`不支持的 GIS 控制器模式:${String(e)}`);
|
|
1032
|
+
}
|
|
1033
|
+
} finally {
|
|
1034
|
+
this.restoreCameraRange(r);
|
|
1035
|
+
}
|
|
1036
|
+
this.currentMode = e, this.requestRender();
|
|
1037
|
+
}
|
|
1038
|
+
/** 获取当前已请求的控制器模式。 */
|
|
1039
|
+
getMode() {
|
|
1040
|
+
return this.currentMode ?? v.GIS;
|
|
1041
|
+
}
|
|
1042
|
+
/** 切换到 3d-tiles-renderer 原生 GIS 控制器。 */
|
|
1043
|
+
switchToGIS() {
|
|
1044
|
+
this.switchController(v.GIS);
|
|
1045
|
+
}
|
|
1046
|
+
/** 切换到 v3 默认轨道控制器。 */
|
|
1047
|
+
switchToDefault(e) {
|
|
1048
|
+
this.switchController(v.Default, e);
|
|
1049
|
+
}
|
|
1050
|
+
/** 切换到 v3 第一人称控制器。 */
|
|
1051
|
+
switchToFirstPerson() {
|
|
1052
|
+
this.switchController(v.FirstPerson);
|
|
1053
|
+
}
|
|
1054
|
+
/** 进入 v3 第三人称控制器已有的落点选择流程。 */
|
|
1055
|
+
switchToThirdPerson(e) {
|
|
1056
|
+
this.switchController(v.ThirdPerson, { thirdPersonOptions: e });
|
|
1057
|
+
}
|
|
1058
|
+
/** 停止当前控制输入,具体控制器资源仍由各自所属模块释放。 */
|
|
1059
|
+
dispose() {
|
|
1060
|
+
this.disposed || (this.disposed = !0, this.gisControls.enabled = !1, this.removeThirdPersonPlacementResolver?.(), this.removeThirdPersonPlacementResolver = null, this.removeOrbitCenterResolver?.(), this.removeOrbitCenterResolver = null, this.engine.controlModule.disActive());
|
|
1061
|
+
}
|
|
1062
|
+
/** 重入或离开第三人称前,借助 v3 默认入口清理等待落点状态。 */
|
|
1063
|
+
leavePendingThirdPersonMode() {
|
|
1064
|
+
this.currentMode === v.ThirdPerson && this.engine.controlModule.switchDefaultMode({ preserveCameraPose: !0 });
|
|
1065
|
+
}
|
|
1066
|
+
/** 将 GlobeControls 的旋转支点适配为 v3 ViewCube 使用的 OrbitControls target。 */
|
|
1067
|
+
ensureViewCubeTargetCompatibility() {
|
|
1068
|
+
const e = this.gisControls;
|
|
1069
|
+
e.target?.clone || !e.pivotPoint || Object.defineProperty(e, "target", {
|
|
1070
|
+
configurable: !0,
|
|
1071
|
+
get: () => e.pivotPoint,
|
|
1072
|
+
set: (t) => e.pivotPoint?.copy(t)
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
/** 记录 GIS 当前近远裁剪面,避免底层控制器切换改写地图可见范围。 */
|
|
1076
|
+
captureCameraRange() {
|
|
1077
|
+
const e = this.engine.camera;
|
|
1078
|
+
return typeof e?.near != "number" || typeof e.far != "number" ? null : { near: e.near, far: e.far };
|
|
1079
|
+
}
|
|
1080
|
+
/** 将切换前的裁剪范围应用到切换后的活动相机。 */
|
|
1081
|
+
restoreCameraRange(e) {
|
|
1082
|
+
if (!e) return;
|
|
1083
|
+
const t = this.engine.camera;
|
|
1084
|
+
t && (t.near === e.near && t.far === e.far || (t.near = e.near, t.far = e.far, t.updateProjectionMatrix?.()));
|
|
1085
|
+
}
|
|
1086
|
+
/** 从当前可见 GIS 图层中拾取地图、地形或模型表面。 */
|
|
1087
|
+
resolveGISPlacement = (e) => {
|
|
1088
|
+
const t = this.layerManager?.getLayers().filter((o) => o.visible && o.object.visible).map((o) => o.object) ?? [];
|
|
1089
|
+
if (t.length === 0) return null;
|
|
1090
|
+
const i = e.intersectObjects([...new Set(t)], !0).find((o) => o.object.isMesh === !0 && t.some((l) => this.isVisibleWithinLayer(o.object, l)));
|
|
1091
|
+
if (!i) return null;
|
|
1092
|
+
let s;
|
|
1093
|
+
i.normal instanceof f ? s = i.normal.clone().transformDirection(i.object.matrixWorld) : i.face?.normal instanceof f ? s = i.face.normal.clone().transformDirection(i.object.matrixWorld) : s = e.ray.direction.clone().negate();
|
|
1094
|
+
const a = e.ray.origin.clone().sub(i.point);
|
|
1095
|
+
return s.dot(a) < 0 && s.negate(), {
|
|
1096
|
+
point: i.point.clone(),
|
|
1097
|
+
normal: s.normalize(),
|
|
1098
|
+
distance: i.distance
|
|
1099
|
+
};
|
|
1100
|
+
};
|
|
1101
|
+
isVisibleWithinLayer(e, t) {
|
|
1102
|
+
let r = e;
|
|
1103
|
+
for (; r; ) {
|
|
1104
|
+
if (r.visible === !1) return !1;
|
|
1105
|
+
if (r === t) return !0;
|
|
1106
|
+
r = r.parent;
|
|
1107
|
+
}
|
|
1108
|
+
return !1;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
const R = Math.PI / 180, A = 180 / Math.PI, Z = 6378245, Q = 0.006693421622965943, B = 1e-5, Ue = 1e-9, Ne = 8;
|
|
1112
|
+
function he(n, e) {
|
|
1113
|
+
return n < 72.004 || n > 137.8347 || e < 0.8293 || e > 55.8271;
|
|
1114
|
+
}
|
|
1115
|
+
function We(n, e) {
|
|
1116
|
+
let t = -100 + 2 * n + 3 * e + 0.2 * e * e + 0.1 * n * e + 0.2 * Math.sqrt(Math.abs(n));
|
|
1117
|
+
return t += (20 * Math.sin(6 * n * Math.PI) + 20 * Math.sin(2 * n * Math.PI)) * 2 / 3, t += (20 * Math.sin(e * Math.PI) + 40 * Math.sin(e / 3 * Math.PI)) * 2 / 3, t += (160 * Math.sin(e / 12 * Math.PI) + 320 * Math.sin(e * Math.PI / 30)) * 2 / 3, t;
|
|
1118
|
+
}
|
|
1119
|
+
function _e(n, e) {
|
|
1120
|
+
let t = 300 + n + 2 * e + 0.1 * n * n + 0.1 * n * e + 0.1 * Math.sqrt(Math.abs(n));
|
|
1121
|
+
return t += (20 * Math.sin(6 * n * Math.PI) + 20 * Math.sin(2 * n * Math.PI)) * 2 / 3, t += (20 * Math.sin(n * Math.PI) + 40 * Math.sin(n / 3 * Math.PI)) * 2 / 3, t += (150 * Math.sin(n / 12 * Math.PI) + 300 * Math.sin(n / 30 * Math.PI)) * 2 / 3, t;
|
|
1122
|
+
}
|
|
1123
|
+
function ce(n, e, t = [0, 0]) {
|
|
1124
|
+
if (he(n, e))
|
|
1125
|
+
return t[0] = n, t[1] = e, t;
|
|
1126
|
+
const r = e * R, i = Math.sin(r), s = 1 - Q * i * i, a = Math.sqrt(s), o = We(n - 105, e - 35) * 180 / (Z * (1 - Q) / (s * a) * Math.PI), l = _e(n - 105, e - 35) * 180 / (Z / a * Math.cos(r) * Math.PI);
|
|
1127
|
+
return t[0] = n + l, t[1] = e + o, t;
|
|
1128
|
+
}
|
|
1129
|
+
function qe(n, e, t = [0, 0]) {
|
|
1130
|
+
if (he(n, e))
|
|
1131
|
+
return t[0] = n, t[1] = e, t;
|
|
1132
|
+
let r = n, i = e;
|
|
1133
|
+
const s = [0, 0];
|
|
1134
|
+
for (let a = 0; a < Ne; a += 1) {
|
|
1135
|
+
ce(r, i, s);
|
|
1136
|
+
const o = s[0] - n, l = s[1] - e;
|
|
1137
|
+
if (r -= o, i -= l, Math.max(Math.abs(o), Math.abs(l)) < Ue) break;
|
|
1138
|
+
}
|
|
1139
|
+
return t[0] = r, t[1] = i, t;
|
|
1140
|
+
}
|
|
1141
|
+
class $e {
|
|
1142
|
+
constructor(e) {
|
|
1143
|
+
this.base = e, this.scheme = e.scheme, this.tileCountX = e.tileCountX, this.tileCountY = e.tileCountY;
|
|
1144
|
+
}
|
|
1145
|
+
base;
|
|
1146
|
+
scheme;
|
|
1147
|
+
tileCountX;
|
|
1148
|
+
tileCountY;
|
|
1149
|
+
get isMercator() {
|
|
1150
|
+
return this.base.isMercator;
|
|
1151
|
+
}
|
|
1152
|
+
get isCartographic() {
|
|
1153
|
+
return this.base.isCartographic;
|
|
1154
|
+
}
|
|
1155
|
+
getDerivativeAtNormalizedPoint(e, t, r = [0, 0]) {
|
|
1156
|
+
const i = Math.max(e - B, 0), s = Math.min(e + B, 1), a = Math.max(t - B, 0), o = Math.min(t + B, 1), l = [0, 0], c = this.fromNormalizedToCartographic(i, t, l)[0], m = this.fromNormalizedToCartographic(s, t, l)[0];
|
|
1157
|
+
r[0] = Math.abs(m - c) / (s - i);
|
|
1158
|
+
const u = this.fromNormalizedToCartographic(e, a, l)[1], g = this.fromNormalizedToCartographic(e, o, l)[1];
|
|
1159
|
+
return r[1] = Math.abs(g - u) / (o - a), r;
|
|
1160
|
+
}
|
|
1161
|
+
getBounds() {
|
|
1162
|
+
return this.base.getBounds();
|
|
1163
|
+
}
|
|
1164
|
+
fromCartographicToNormalized(e, t, r = [0, 0]) {
|
|
1165
|
+
return ce(e * A, t * A, r), this.base.fromCartographicToNormalized(
|
|
1166
|
+
r[0] * R,
|
|
1167
|
+
r[1] * R,
|
|
1168
|
+
r
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
fromCartographicToNormalizedRange(e) {
|
|
1172
|
+
return this.convertRange(e, (t, r, i) => this.fromCartographicToNormalized(t, r, i));
|
|
1173
|
+
}
|
|
1174
|
+
fromNormalizedToCartographic(e, t, r = [0, 0]) {
|
|
1175
|
+
return this.base.fromNormalizedToCartographic(e, t, r), qe(r[0] * A, r[1] * A, r), r[0] *= R, r[1] *= R, r;
|
|
1176
|
+
}
|
|
1177
|
+
fromNormalizedToCartographicRange(e) {
|
|
1178
|
+
return this.convertRange(e, (t, r, i) => this.fromNormalizedToCartographic(t, r, i));
|
|
1179
|
+
}
|
|
1180
|
+
getProjectedExtents() {
|
|
1181
|
+
return this.base.getProjectedExtents();
|
|
1182
|
+
}
|
|
1183
|
+
clampToBounds(e, t = !1) {
|
|
1184
|
+
return this.base.clampToBounds(e, t);
|
|
1185
|
+
}
|
|
1186
|
+
convertRange(e, t) {
|
|
1187
|
+
const r = [0, 0], i = [1 / 0, 1 / 0, -1 / 0, -1 / 0];
|
|
1188
|
+
return [
|
|
1189
|
+
[e[0], e[1]],
|
|
1190
|
+
[e[0], e[3]],
|
|
1191
|
+
[e[2], e[1]],
|
|
1192
|
+
[e[2], e[3]]
|
|
1193
|
+
].forEach(([a, o]) => {
|
|
1194
|
+
t(a, o, r), i[0] = Math.min(i[0], r[0]), i[1] = Math.min(i[1], r[1]), i[2] = Math.max(i[2], r[0]), i[3] = Math.max(i[3], r[1]);
|
|
1195
|
+
}), i;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
const L = Math.PI / 180, K = 2, He = 35;
|
|
1199
|
+
class Je {
|
|
1200
|
+
name = "GIS_XYZ_WEBGPU_UV";
|
|
1201
|
+
processTileModel(e) {
|
|
1202
|
+
e.traverse((t) => {
|
|
1203
|
+
const i = t.geometry?.attributes?.uv;
|
|
1204
|
+
if (!(!i || t.userData.gisWebGpuUvCorrected)) {
|
|
1205
|
+
for (let s = 0; s < i.count; s += 1)
|
|
1206
|
+
i.setY(s, 1 - i.getY(s));
|
|
1207
|
+
i.needsUpdate = !0, t.userData.gisWebGpuUvCorrected = !0;
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
class Ye {
|
|
1213
|
+
constructor(e, t = {}, r = !0, i) {
|
|
1214
|
+
this.engine = e, this.options = t, this.isWebGpuRenderer = r, this.layerManager = i;
|
|
1215
|
+
}
|
|
1216
|
+
engine;
|
|
1217
|
+
options;
|
|
1218
|
+
isWebGpuRenderer;
|
|
1219
|
+
layerManager;
|
|
1220
|
+
managedTilesets = /* @__PURE__ */ new Map();
|
|
1221
|
+
lifecycle = null;
|
|
1222
|
+
disposed = !1;
|
|
1223
|
+
imageConfigs = [
|
|
1224
|
+
{
|
|
1225
|
+
id: "cesium",
|
|
1226
|
+
name: "Cesium",
|
|
1227
|
+
type: "cesium-ion",
|
|
1228
|
+
// CesiumJS 1.145 官方评估 Token,官方声明于 2026-11-01 到期。
|
|
1229
|
+
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6ImwwendUTkl3UVk5Uk5XdEkiLCJqdGkiOiJjZGFkZDZlNC0wMDI5LTQ5OTMtYWIwMC01ZWY4ZDVlNTBhMzYiLCJpZCI6MjU5LCJzdWIiOiJDZXNpdW1KUyIsImlzcyI6Imh0dHBzOi8vYXBpLmNlc2l1bS5jb20iLCJhdWQiOiIxLjE0NSBSZWxlYXNlIC0gRGVsZXRlIG9uIE5vdmVtYmVyIDEsIDIwMjYiLCJpYXQiOjE3ODc2MzQ3NzV9.3SPieoGxEZ22pnWLETAIFZveR0xXTzvOh8G4NYTxDSo",
|
|
1230
|
+
assetId: 2275207
|
|
1231
|
+
},
|
|
1232
|
+
{
|
|
1233
|
+
id: "gd",
|
|
1234
|
+
name: "高德地图",
|
|
1235
|
+
type: "xyz",
|
|
1236
|
+
url: "https://webrd01.is.autonavi.com/appmaptile?style=7&lang=zh_cn&size=1&scale=1&x={x}&y={y}&z={z}",
|
|
1237
|
+
levels: 19,
|
|
1238
|
+
coordinateSystem: "gcj02"
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
id: "gd2",
|
|
1242
|
+
name: "高德卫星地图",
|
|
1243
|
+
type: "xyz",
|
|
1244
|
+
url: "https://webst01.is.autonavi.com/appmaptile?style=6&size=1&scl=1&x={x}&y={y}&z={z}",
|
|
1245
|
+
levels: 19,
|
|
1246
|
+
coordinateSystem: "gcj02"
|
|
1247
|
+
}
|
|
1248
|
+
];
|
|
1249
|
+
/** 注册附加 Tileset 生命周期回调,业务管理器通过此入口维护校准等状态。 */
|
|
1250
|
+
setTilesetLifecycle(e) {
|
|
1251
|
+
this.lifecycle = e;
|
|
1252
|
+
}
|
|
1253
|
+
/** 加载初始化配置指定的影像,未指定时默认使用 Cesium。 */
|
|
1254
|
+
loadImage() {
|
|
1255
|
+
return this.options.source ? this.load3dtiles(this.options.source) : this.loadImageById(this.options.imageId ?? "cesium");
|
|
1256
|
+
}
|
|
1257
|
+
/** 按内置配置标识加载影像。 */
|
|
1258
|
+
loadImageById(e) {
|
|
1259
|
+
const t = this.imageConfigs.find((r) => r.id === e);
|
|
1260
|
+
return t ? t.type === "cesium-ion" ? this.load3dtiles({ type: "cesium-ion", assetId: t.assetId }) : this.load3dtiles({
|
|
1261
|
+
type: "xyz",
|
|
1262
|
+
url: t.url,
|
|
1263
|
+
levels: t.levels,
|
|
1264
|
+
coordinateSystem: t.coordinateSystem
|
|
1265
|
+
}) : Promise.reject(new RangeError(`未知的影像配置 id: ${e}`));
|
|
1266
|
+
}
|
|
1267
|
+
/** 按 URL 加载自定义 3D Tiles 影像。 */
|
|
1268
|
+
loadImageCustomize(e) {
|
|
1269
|
+
return this.load3dtiles({ type: "url", url: e });
|
|
1270
|
+
}
|
|
1271
|
+
/** 当前地形由 3D Tiles 影像一并提供。 */
|
|
1272
|
+
loadTerrain() {
|
|
1273
|
+
return this.loadImage();
|
|
1274
|
+
}
|
|
1275
|
+
/** 在当前 GIS 底图上叠加通用 3D Tiles,并接入统一渲染生命周期。 */
|
|
1276
|
+
async loadTileset(e, t = {}) {
|
|
1277
|
+
if (this.assertUsable(), typeof e != "string" || e.trim().length === 0)
|
|
1278
|
+
throw new TypeError("3D Tiles URL 不能为空。");
|
|
1279
|
+
const r = t.heightOffset ?? 0;
|
|
1280
|
+
if (!Number.isFinite(r))
|
|
1281
|
+
throw new RangeError("3D Tiles 高度偏移必须是有限数值。");
|
|
1282
|
+
const { tiles: i } = await this.load3dtiles({ type: "url", url: e.trim() });
|
|
1283
|
+
if (this.disposed)
|
|
1284
|
+
throw i.dispose(), new Error("GIS LoaderModule 已销毁。");
|
|
1285
|
+
t.fetchData && i.registerPlugin({
|
|
1286
|
+
name: `GIS_LOCAL_TILESET_${t.id ?? i.group.uuid}`,
|
|
1287
|
+
fetchData: t.fetchData
|
|
1288
|
+
});
|
|
1289
|
+
const s = {
|
|
1290
|
+
onNeedsUpdate: () => this.engine.requestRender(),
|
|
1291
|
+
onLoadError: (o) => {
|
|
1292
|
+
const l = o.error instanceof Error ? o.error : new Error(`3D Tiles 加载失败:${e}`);
|
|
1293
|
+
this.options.onError?.(l);
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
let a;
|
|
1297
|
+
try {
|
|
1298
|
+
a = this.layerManager?.registerLayer({
|
|
1299
|
+
id: t.id,
|
|
1300
|
+
name: t.name?.trim() || e.trim(),
|
|
1301
|
+
type: "tileset",
|
|
1302
|
+
source: e.trim(),
|
|
1303
|
+
object: i.group,
|
|
1304
|
+
remove: () => this.removeTileset(i)
|
|
1305
|
+
});
|
|
1306
|
+
} catch (o) {
|
|
1307
|
+
throw i.dispose(), o;
|
|
1308
|
+
}
|
|
1309
|
+
return s.layerId = a?.id, i.group.name = a?.id ?? `gisTileset:${e}`, i.setCamera(this.engine.camera), i.setResolutionFromRenderer(this.engine.camera, this.engine.renderer), i.addEventListener("needs-update", s.onNeedsUpdate), i.addEventListener("load-error", s.onLoadError), this.engine.scene.add(i.group), this.managedTilesets.set(i, s), this.lifecycle?.onTilesetAdded?.(i, {
|
|
1310
|
+
id: s.layerId ?? i.group.name,
|
|
1311
|
+
name: a?.name ?? (t.name?.trim() || e.trim()),
|
|
1312
|
+
source: e.trim()
|
|
1313
|
+
}, !1), r === 0 ? this.registerTilesetReady(i, s) : this.registerHeightOffset(i, s, r), this.engine.requestRender(), i;
|
|
1314
|
+
}
|
|
1315
|
+
/** 移除附加的 3D Tiles,并释放其缓存与 GPU 资源。 */
|
|
1316
|
+
removeTileset(e) {
|
|
1317
|
+
const t = this.managedTilesets.get(e);
|
|
1318
|
+
return t ? (e.removeEventListener("needs-update", t.onNeedsUpdate), e.removeEventListener("load-error", t.onLoadError), t.onRootLoaded && e.removeEventListener("load-root-tileset", t.onRootLoaded), e.deleteCamera(this.engine.camera), this.engine.scene.remove(e.group), this.lifecycle?.onTilesetRemoved?.(e, t.layerId), e.dispose(), t.layerId && this.layerManager?.unregisterLayer(t.layerId), this.managedTilesets.delete(e), this.engine.requestRender(), !0) : !1;
|
|
1319
|
+
}
|
|
1320
|
+
/** 等待附加 Tileset 就绪,并把相机调整到可完整观察模型的位置。 */
|
|
1321
|
+
async flyToTileset(e, t = {}) {
|
|
1322
|
+
if (this.assertUsable(), !this.managedTilesets.has(e))
|
|
1323
|
+
throw new Error("只能跳转到当前 LoaderModule 管理的 3D Tiles。");
|
|
1324
|
+
const r = await this.waitForBoundingSphere(e, t.timeoutMs ?? 15e3), i = Math.max(1, t.padding ?? 1.25), s = this.engine.camera, a = r.center, o = this.engine.controls, l = new f(0, 1, 0);
|
|
1325
|
+
o?.getUpDirection?.(a, l), l.lengthSq() < 1e-8 && l.set(0, 1, 0), l.normalize();
|
|
1326
|
+
const c = s.position.clone().sub(a);
|
|
1327
|
+
if (c.addScaledVector(l, -c.dot(l)), c.lengthSq() < 1e-8 && (s.getWorldDirection(c).negate(), c.addScaledVector(l, -c.dot(l))), c.lengthSq() < 1e-8) {
|
|
1328
|
+
const w = Math.abs(l.y) < 0.9 ? new f(0, 1, 0) : new f(1, 0, 0);
|
|
1329
|
+
c.crossVectors(l, w);
|
|
1330
|
+
}
|
|
1331
|
+
c.normalize();
|
|
1332
|
+
const m = S.degToRad(S.clamp(
|
|
1333
|
+
t.elevation ?? He,
|
|
1334
|
+
5,
|
|
1335
|
+
85
|
|
1336
|
+
)), u = c.multiplyScalar(Math.cos(m)).addScaledVector(l, Math.sin(m)).normalize(), g = S.degToRad(s.fov), h = 2 * Math.atan(Math.tan(g / 2) * s.aspect), p = Math.max(1e-4, Math.min(g, h)), d = r.radius * i / Math.sin(p / 2);
|
|
1337
|
+
s.position.copy(a).addScaledVector(u, d), s.up.copy(l), s.lookAt(a), s.updateMatrix(), s.updateMatrixWorld(!0), (o?.target ?? o?.pivotPoint)?.copy(a), o?.resetState?.(), o?.update?.(0), this.engine.requestRender();
|
|
1338
|
+
}
|
|
1339
|
+
/** 更新全部附加瓦片的 LOD 调度。 */
|
|
1340
|
+
update() {
|
|
1341
|
+
this.managedTilesets.forEach((e, t) => {
|
|
1342
|
+
t.group.visible && t.update();
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
/** 同步全部附加瓦片使用的屏幕分辨率。 */
|
|
1346
|
+
updateResolution() {
|
|
1347
|
+
this.managedTilesets.forEach((e, t) => {
|
|
1348
|
+
t.setResolutionFromRenderer(this.engine.camera, this.engine.renderer);
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
/** 释放 LoaderModule 管理的附加瓦片;主底图仍由 GIS 实例释放。 */
|
|
1352
|
+
dispose() {
|
|
1353
|
+
this.disposed || ([...this.managedTilesets.keys()].forEach((e) => this.removeTileset(e)), this.disposed = !0);
|
|
1354
|
+
}
|
|
1355
|
+
/** 创建 TilesRenderer,并注册官方示例使用的扩展插件。 */
|
|
1356
|
+
async load3dtiles(e = { type: "cesium-ion" }) {
|
|
1357
|
+
this.assertUsable();
|
|
1358
|
+
const [t, r] = await Promise.all([
|
|
1359
|
+
import("./gis-tiles-vendor-DIbUOKJM.js").then((h) => h.i),
|
|
1360
|
+
import("./gis-tiles-vendor-DIbUOKJM.js").then((h) => h.a)
|
|
1361
|
+
]), i = new t.TilesRenderer(e.type === "url" ? e.url : void 0), s = this.resolveLocalOrigin();
|
|
1362
|
+
if (s) {
|
|
1363
|
+
const h = new r.ReorientationPlugin({
|
|
1364
|
+
lat: s.latitude * L,
|
|
1365
|
+
lon: s.longitude * L,
|
|
1366
|
+
height: s.height,
|
|
1367
|
+
recenter: !0
|
|
1368
|
+
});
|
|
1369
|
+
i.registerPlugin(h), h.transformLatLonHeightToOrigin(
|
|
1370
|
+
s.latitude * L,
|
|
1371
|
+
s.longitude * L,
|
|
1372
|
+
s.height
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
if (e.type === "xyz") {
|
|
1376
|
+
const h = e.levels ?? 19, p = new r.XYZTilesOverlay({
|
|
1377
|
+
url: e.url,
|
|
1378
|
+
levels: h,
|
|
1379
|
+
tileDimension: e.tileDimension ?? 256,
|
|
1380
|
+
projection: "EPSG:3857"
|
|
1381
|
+
});
|
|
1382
|
+
e.coordinateSystem === "gcj02" && await this.correctGcj02Overlay(p);
|
|
1383
|
+
const d = r.GeneratedSurfacePlugin;
|
|
1384
|
+
i.registerPlugin(new d({
|
|
1385
|
+
overlay: p,
|
|
1386
|
+
projection: "ellipsoid",
|
|
1387
|
+
applyOverlayTexture: !0
|
|
1388
|
+
})), this.isWebGpuRenderer && i.registerPlugin(new Je()), i.registerPlugin(new r.UpdateOnChangePlugin()), i.errorTarget = this.options.errorTarget ?? K, i.loadSiblings = this.options.loadSiblings ?? !0, i.loadAncestors = !1;
|
|
1389
|
+
const { CAMERA_FRAME: w } = t;
|
|
1390
|
+
return { tiles: i, Controls: t.GlobeControls, cameraFrame: w };
|
|
1391
|
+
}
|
|
1392
|
+
const [a, o, l, c] = await Promise.all([
|
|
1393
|
+
import("./gis-tiles-vendor-DIbUOKJM.js").then((h) => h.b),
|
|
1394
|
+
import("./three-vendor-CGmeNMm5.js").then((h) => h.cr),
|
|
1395
|
+
import("./three-vendor-CGmeNMm5.js").then((h) => h.cs),
|
|
1396
|
+
import("./three-vendor-CGmeNMm5.js").then((h) => h.ct)
|
|
1397
|
+
]);
|
|
1398
|
+
if (e.type === "cesium-ion") {
|
|
1399
|
+
const h = this.imageConfigs.find(
|
|
1400
|
+
(p) => p.type === "cesium-ion"
|
|
1401
|
+
);
|
|
1402
|
+
i.registerPlugin(new a.CesiumIonAuthPlugin({
|
|
1403
|
+
apiToken: h.token,
|
|
1404
|
+
assetId: String(e.assetId ?? h.assetId),
|
|
1405
|
+
autoRefreshToken: e.autoRefreshToken ?? !0
|
|
1406
|
+
}));
|
|
1407
|
+
}
|
|
1408
|
+
const m = new o.DRACOLoader(), u = this.options.dracoDecoderPath ?? te("draco/", import.meta.url);
|
|
1409
|
+
m.setDecoderPath(u), i.registerPlugin(new r.GLTFExtensionsPlugin({
|
|
1410
|
+
dracoLoader: m,
|
|
1411
|
+
meshoptDecoder: c.MeshoptDecoder,
|
|
1412
|
+
autoDispose: !0
|
|
1413
|
+
})), this.options.creasedNormals !== !1 && i.registerPlugin(new l.TileCreasedNormalsPlugin({
|
|
1414
|
+
creaseAngle: (this.options.creaseAngle ?? 30) * L
|
|
1415
|
+
})), this.options.fade === !0 && i.registerPlugin(new r.TilesFadePlugin()), i.registerPlugin(new r.UpdateOnChangePlugin()), i.errorTarget = this.options.errorTarget ?? K, i.loadSiblings = this.options.loadSiblings ?? !0;
|
|
1416
|
+
const { CAMERA_FRAME: g } = t;
|
|
1417
|
+
return { tiles: i, Controls: t.GlobeControls, cameraFrame: g };
|
|
1418
|
+
}
|
|
1419
|
+
resolveLocalOrigin() {
|
|
1420
|
+
if (this.options.localOrigin === !1) return null;
|
|
1421
|
+
const e = { ...V, ...this.options.initialView }, t = this.options.localOrigin, r = t?.latitude ?? e.latitude, i = t?.longitude ?? e.longitude;
|
|
1422
|
+
if (!Number.isFinite(r) || r < -90 || r > 90)
|
|
1423
|
+
throw new RangeError("GIS 局部原点纬度必须是 -90 到 90 之间的有限数值。");
|
|
1424
|
+
if (!Number.isFinite(i) || i < -180 || i > 180)
|
|
1425
|
+
throw new RangeError("GIS 局部原点经度必须是 -180 到 180 之间的有限数值。");
|
|
1426
|
+
const s = t?.height ?? 0;
|
|
1427
|
+
if (!Number.isFinite(s))
|
|
1428
|
+
throw new RangeError("GIS 局部原点高度必须是有限数值。");
|
|
1429
|
+
return { latitude: r, longitude: i, height: s };
|
|
1430
|
+
}
|
|
1431
|
+
/** 将高德瓦片的 GCJ-02 投影适配到引擎统一使用的 WGS84 地表。 */
|
|
1432
|
+
async correctGcj02Overlay(e) {
|
|
1433
|
+
await e.init();
|
|
1434
|
+
const t = new $e(e.tiling.projection);
|
|
1435
|
+
e.tiling.setProjection(t), e.tiling.setContentBounds(...t.getBounds());
|
|
1436
|
+
}
|
|
1437
|
+
registerHeightOffset(e, t, r) {
|
|
1438
|
+
if (r === 0) return;
|
|
1439
|
+
const i = () => {
|
|
1440
|
+
const s = new G();
|
|
1441
|
+
if (!e.getBoundingSphere(s)) return;
|
|
1442
|
+
if (this.resolveLocalOrigin())
|
|
1443
|
+
e.group.position.y += r;
|
|
1444
|
+
else {
|
|
1445
|
+
const o = e.ellipsoid.getPositionToNormal(s.center, new f());
|
|
1446
|
+
e.group.updateWorldMatrix(!0, !1), o.transformDirection(e.group.matrixWorld), e.group.parent && (e.group.parent.updateWorldMatrix(!0, !1), o.transformDirection(new y().copy(e.group.parent.matrixWorld).invert())), e.group.position.addScaledVector(o, r);
|
|
1447
|
+
}
|
|
1448
|
+
e.group.updateMatrixWorld(!0), e.removeEventListener("load-root-tileset", i), t.onRootLoaded = void 0, this.lifecycle?.onTilesetReady?.(e), this.engine.requestRender();
|
|
1449
|
+
};
|
|
1450
|
+
t.onRootLoaded = i, e.addEventListener("load-root-tileset", i), e.root && i();
|
|
1451
|
+
}
|
|
1452
|
+
/** 根瓦片加载完成后再冻结校准基准,避免使用尚未建立的包围球锚点。 */
|
|
1453
|
+
registerTilesetReady(e, t) {
|
|
1454
|
+
const r = () => {
|
|
1455
|
+
const i = new G();
|
|
1456
|
+
e.getBoundingSphere(i) && (e.removeEventListener("load-root-tileset", r), t.onRootLoaded = void 0, this.lifecycle?.onTilesetReady?.(e));
|
|
1457
|
+
};
|
|
1458
|
+
t.onRootLoaded = r, e.addEventListener("load-root-tileset", r), e.root && r();
|
|
1459
|
+
}
|
|
1460
|
+
waitForBoundingSphere(e, t) {
|
|
1461
|
+
const r = () => {
|
|
1462
|
+
const s = new G();
|
|
1463
|
+
return e.getBoundingSphere(s) ? (e.group.updateWorldMatrix(!0, !1), s.applyMatrix4(e.group.matrixWorld)) : null;
|
|
1464
|
+
}, i = r();
|
|
1465
|
+
return i ? Promise.resolve(i) : new Promise((s, a) => {
|
|
1466
|
+
const o = () => {
|
|
1467
|
+
clearTimeout(m), e.removeEventListener("load-root-tileset", l), e.removeEventListener("load-error", c);
|
|
1468
|
+
}, l = () => {
|
|
1469
|
+
const u = r();
|
|
1470
|
+
u && (o(), s(u));
|
|
1471
|
+
}, c = (u) => {
|
|
1472
|
+
e.root || (o(), a(u.error ?? new Error("3D Tiles 根节点加载失败。")));
|
|
1473
|
+
}, m = setTimeout(() => {
|
|
1474
|
+
o(), a(new Error("等待 3D Tiles 根节点加载超时。"));
|
|
1475
|
+
}, Math.max(1, t));
|
|
1476
|
+
e.addEventListener("load-root-tileset", l), e.addEventListener("load-error", c);
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
assertUsable() {
|
|
1480
|
+
if (this.disposed) throw new Error("GIS LoaderModule 已销毁。");
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
const ee = "gis-local.invalid";
|
|
1484
|
+
class Xe {
|
|
1485
|
+
constructor(e, t, r, i, s) {
|
|
1486
|
+
this.engine = e, this.tilesLoader = t, this.coordinateModule = r, this.layerManager = i, this.calibrationManager = s;
|
|
1487
|
+
}
|
|
1488
|
+
engine;
|
|
1489
|
+
tilesLoader;
|
|
1490
|
+
coordinateModule;
|
|
1491
|
+
layerManager;
|
|
1492
|
+
calibrationManager;
|
|
1493
|
+
resources = /* @__PURE__ */ new Map();
|
|
1494
|
+
sequence = 0;
|
|
1495
|
+
disposed = !1;
|
|
1496
|
+
/** 按 URL 加载 3D Tiles、glTF/GLB、GeoJSON 或 v3 BIM 模型。 */
|
|
1497
|
+
async loadModel(e, t = {}) {
|
|
1498
|
+
this.assertUsable();
|
|
1499
|
+
const r = this.normalizeUrl(e), i = t.type ?? this.inferUrlType(r);
|
|
1500
|
+
switch (i) {
|
|
1501
|
+
case "3dtiles":
|
|
1502
|
+
return this.loadTileset(r, t);
|
|
1503
|
+
case "gltf":
|
|
1504
|
+
return this.loadGltfUrl(r, t);
|
|
1505
|
+
case "geojson":
|
|
1506
|
+
return this.loadGeoJsonUrl(r, t);
|
|
1507
|
+
case "bim":
|
|
1508
|
+
return this.loadBimUrl(r, t);
|
|
1509
|
+
default:
|
|
1510
|
+
throw new RangeError(`不支持的 GIS 模型类型:${String(i)}`);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
* 加载浏览器选择的本地资源文件。
|
|
1515
|
+
* 支持 3D Tiles 文件集、glTF/GLB、GeoJSON 和 v3 BIM 包,不包含 SHP。
|
|
1516
|
+
*/
|
|
1517
|
+
async loadModelFile(e, t = {}) {
|
|
1518
|
+
this.assertUsable();
|
|
1519
|
+
const r = Array.from(e ?? []);
|
|
1520
|
+
if (r.length === 0) throw new TypeError("本地模型文件不能为空。");
|
|
1521
|
+
const i = r.find((o) => this.baseName(o).toLowerCase() === "tileset.json");
|
|
1522
|
+
if (i) return [await this.loadLocalTileset(r, i, t)];
|
|
1523
|
+
const s = r.find((o) => /\.(?:glb|gltf)$/i.test(o.name));
|
|
1524
|
+
if (s) return [await this.loadLocalGltf(r, s, t)];
|
|
1525
|
+
const a = r.find((o) => /\.(?:geojson|json)$/i.test(o.name));
|
|
1526
|
+
if (a) {
|
|
1527
|
+
const o = JSON.parse(await a.text());
|
|
1528
|
+
if (this.isGeoJson(o))
|
|
1529
|
+
return [this.createGeoJsonModel(o, a.name, t)];
|
|
1530
|
+
}
|
|
1531
|
+
return this.loadLocalBim(r, t);
|
|
1532
|
+
}
|
|
1533
|
+
/** 通过 SDK ViewToken 解析资源,再进入相同的类型分流和生命周期。 */
|
|
1534
|
+
async loadModelByViewToken(e, t = {}) {
|
|
1535
|
+
if (this.assertUsable(), typeof e != "string" || e.trim().length === 0)
|
|
1536
|
+
throw new TypeError("ViewToken 不能为空。");
|
|
1537
|
+
const r = await Ie(
|
|
1538
|
+
this.engine.options,
|
|
1539
|
+
e.trim(),
|
|
1540
|
+
"3d"
|
|
1541
|
+
), i = r.urls?.length > 0 ? r.urls : [r.url], s = t.type ?? this.inferResolvedType(
|
|
1542
|
+
r.fileFormat,
|
|
1543
|
+
r.assetCategory,
|
|
1544
|
+
i[0]
|
|
1545
|
+
), a = [];
|
|
1546
|
+
for (let o = 0; o < i.length; o += 1)
|
|
1547
|
+
a.push(await this.loadModel(i[o], {
|
|
1548
|
+
...t,
|
|
1549
|
+
id: o === 0 ? t.id : void 0,
|
|
1550
|
+
type: s
|
|
1551
|
+
}));
|
|
1552
|
+
return a;
|
|
1553
|
+
}
|
|
1554
|
+
/** 移除统一资源句柄对应的模型和图层。 */
|
|
1555
|
+
removeModel(e) {
|
|
1556
|
+
const t = typeof e == "string" ? e : e.id, r = this.resources.get(t);
|
|
1557
|
+
return r ? (this.resources.delete(t), this.layerManager.unregisterLayer(t), this.calibrationManager.unregister(t), r.model.type === "bim" && this.calibrationManager.unregister(r.model.source), r.release(), this.engine.events.trigger("gis:model-removed", r.model), this.engine.requestRender(), !0) : !1;
|
|
1558
|
+
}
|
|
1559
|
+
/** 查询由本模块加载的全部资源句柄。 */
|
|
1560
|
+
getModels() {
|
|
1561
|
+
return [...this.resources.values()].map(({ model: e }) => ({ ...e }));
|
|
1562
|
+
}
|
|
1563
|
+
/** 释放独立资源;3D Tiles 与 BIM 分别交还各自底层加载器卸载。 */
|
|
1564
|
+
dispose() {
|
|
1565
|
+
this.disposed || ([...this.resources.keys()].forEach((e) => this.removeModel(e)), this.disposed = !0);
|
|
1566
|
+
}
|
|
1567
|
+
async loadTileset(e, t) {
|
|
1568
|
+
const r = await this.tilesLoader.loadTileset(e, t);
|
|
1569
|
+
t.flyTo !== !1 && await this.tilesLoader.flyToTileset(r);
|
|
1570
|
+
const i = this.layerManager.getLayer(r.group.name), s = {
|
|
1571
|
+
id: i?.id ?? r.group.name,
|
|
1572
|
+
name: i?.name ?? t.name ?? e,
|
|
1573
|
+
type: "3dtiles",
|
|
1574
|
+
source: e,
|
|
1575
|
+
object: r.group,
|
|
1576
|
+
raw: r
|
|
1577
|
+
};
|
|
1578
|
+
return this.track(s, () => {
|
|
1579
|
+
this.tilesLoader.removeTileset(r);
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
async loadGltfUrl(e, t) {
|
|
1583
|
+
const r = await this.parseGltf(e);
|
|
1584
|
+
return this.addStandaloneModel(r, e, "gltf", t);
|
|
1585
|
+
}
|
|
1586
|
+
async loadLocalGltf(e, t, r) {
|
|
1587
|
+
const i = [], s = /* @__PURE__ */ new Map();
|
|
1588
|
+
e.forEach((o) => {
|
|
1589
|
+
const l = URL.createObjectURL(o);
|
|
1590
|
+
i.push(l);
|
|
1591
|
+
const c = this.filePath(o);
|
|
1592
|
+
s.set(c, l), s.set(this.baseName(o), l);
|
|
1593
|
+
});
|
|
1594
|
+
const a = new fe();
|
|
1595
|
+
a.setURLModifier((o) => {
|
|
1596
|
+
const l = this.normalizePath(decodeURIComponent(o));
|
|
1597
|
+
return s.get(l) ?? s.get(this.baseNameFromPath(l)) ?? o;
|
|
1598
|
+
});
|
|
1599
|
+
try {
|
|
1600
|
+
const o = s.get(this.filePath(t)), l = await this.parseGltf(o, a);
|
|
1601
|
+
return this.addStandaloneModel(l, t.name, "gltf", r, () => {
|
|
1602
|
+
i.forEach((c) => URL.revokeObjectURL(c));
|
|
1603
|
+
});
|
|
1604
|
+
} catch (o) {
|
|
1605
|
+
throw i.forEach((l) => URL.revokeObjectURL(l)), o;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
async parseGltf(e, t) {
|
|
1609
|
+
const [{ GLTFLoader: r }, { DRACOLoader: i }, { MeshoptDecoder: s }] = await Promise.all([
|
|
1610
|
+
import("./three-vendor-CGmeNMm5.js").then((l) => l.cq),
|
|
1611
|
+
import("./three-vendor-CGmeNMm5.js").then((l) => l.cr),
|
|
1612
|
+
import("./three-vendor-CGmeNMm5.js").then((l) => l.ct)
|
|
1613
|
+
]), a = new i(t);
|
|
1614
|
+
a.setDecoderPath(te("draco/", import.meta.url));
|
|
1615
|
+
const o = new r(t);
|
|
1616
|
+
o.setDRACOLoader(a), o.setMeshoptDecoder(s);
|
|
1617
|
+
try {
|
|
1618
|
+
return (await o.loadAsync(e)).scene;
|
|
1619
|
+
} finally {
|
|
1620
|
+
a.dispose();
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
async loadGeoJsonUrl(e, t) {
|
|
1624
|
+
const r = await fetch(e);
|
|
1625
|
+
if (!r.ok) throw new Error(`GeoJSON 加载失败(${r.status}):${e}`);
|
|
1626
|
+
const i = await r.json();
|
|
1627
|
+
if (!this.isGeoJson(i)) throw new TypeError(`资源不是有效的 GeoJSON:${e}`);
|
|
1628
|
+
return this.createGeoJsonModel(i, e, t);
|
|
1629
|
+
}
|
|
1630
|
+
createGeoJsonModel(e, t, r) {
|
|
1631
|
+
const i = [], s = [], a = (u) => {
|
|
1632
|
+
if (!Array.isArray(u) || u.length < 2) return;
|
|
1633
|
+
const g = Number(u[0]), h = Number(u[1]), p = u.length > 2 ? Number(u[2]) : r.height ?? 0, d = this.coordinateModule.toWorld({ longitude: g, latitude: h, height: p });
|
|
1634
|
+
i.push(d.x, d.y, d.z);
|
|
1635
|
+
}, o = (u, g = !1) => {
|
|
1636
|
+
if (!Array.isArray(u) || u.length < 2) return;
|
|
1637
|
+
const h = u.map((d) => !Array.isArray(d) || d.length < 2 ? null : this.coordinateModule.toWorld({
|
|
1638
|
+
longitude: Number(d[0]),
|
|
1639
|
+
latitude: Number(d[1]),
|
|
1640
|
+
height: d.length > 2 ? Number(d[2]) : r.height ?? 0
|
|
1641
|
+
})).filter((d) => d !== null), p = g ? h.length : h.length - 1;
|
|
1642
|
+
for (let d = 0; d < p; d += 1) {
|
|
1643
|
+
const w = h[d], C = h[(d + 1) % h.length];
|
|
1644
|
+
s.push(w.x, w.y, w.z, C.x, C.y, C.z);
|
|
1645
|
+
}
|
|
1646
|
+
}, l = (u) => {
|
|
1647
|
+
if (!u) return;
|
|
1648
|
+
const g = u.coordinates;
|
|
1649
|
+
switch (u.type) {
|
|
1650
|
+
case "Point":
|
|
1651
|
+
a(g);
|
|
1652
|
+
break;
|
|
1653
|
+
case "MultiPoint":
|
|
1654
|
+
g?.forEach(a);
|
|
1655
|
+
break;
|
|
1656
|
+
case "LineString":
|
|
1657
|
+
o(g);
|
|
1658
|
+
break;
|
|
1659
|
+
case "MultiLineString":
|
|
1660
|
+
g?.forEach((h) => o(h));
|
|
1661
|
+
break;
|
|
1662
|
+
case "Polygon":
|
|
1663
|
+
g?.forEach((h) => o(h, !0));
|
|
1664
|
+
break;
|
|
1665
|
+
case "MultiPolygon":
|
|
1666
|
+
g?.forEach((h) => {
|
|
1667
|
+
Array.isArray(h) && h.forEach((p) => o(p, !0));
|
|
1668
|
+
});
|
|
1669
|
+
break;
|
|
1670
|
+
case "GeometryCollection":
|
|
1671
|
+
u.geometries?.forEach(l);
|
|
1672
|
+
break;
|
|
1673
|
+
}
|
|
1674
|
+
}, c = (u) => {
|
|
1675
|
+
u.type === "FeatureCollection" ? u.features?.forEach(c) : u.type === "Feature" ? l(u.geometry) : l(u);
|
|
1676
|
+
};
|
|
1677
|
+
c(e);
|
|
1678
|
+
const m = new O();
|
|
1679
|
+
if (i.length > 0) {
|
|
1680
|
+
const u = new H();
|
|
1681
|
+
u.setAttribute("position", new J(i, 3)), m.add(new ye(u, new Me({ color: 16763955, size: 6 })));
|
|
1682
|
+
}
|
|
1683
|
+
if (s.length > 0) {
|
|
1684
|
+
const u = new H();
|
|
1685
|
+
u.setAttribute("position", new J(s, 3)), m.add(new be(u, new we({ color: 2598888 })));
|
|
1686
|
+
}
|
|
1687
|
+
if (m.children.length === 0)
|
|
1688
|
+
throw new TypeError(`GeoJSON 不包含可显示的点、线或多边形:${t}`);
|
|
1689
|
+
return this.addStandaloneModel(m, t, "geojson", r);
|
|
1690
|
+
}
|
|
1691
|
+
async loadBimUrl(e, t) {
|
|
1692
|
+
await this.engine.loaderModule.loadModels([e], t);
|
|
1693
|
+
const r = b(this.engine, e);
|
|
1694
|
+
if (!r) throw new Error(`BIM 模型加载完成后未找到根对象:${e}`);
|
|
1695
|
+
return this.addBimModel(e, r, t);
|
|
1696
|
+
}
|
|
1697
|
+
async loadLocalBim(e, t) {
|
|
1698
|
+
const r = new Set(
|
|
1699
|
+
this.engine.models.map((s) => s?.url).filter((s) => typeof s == "string")
|
|
1700
|
+
);
|
|
1701
|
+
await this.engine.loaderModule.loadModelFile(e, t);
|
|
1702
|
+
const i = this.engine.models.map((s) => s?.url).filter((s) => typeof s == "string" && !r.has(s));
|
|
1703
|
+
if (i.length === 0) throw new Error("本地 BIM 已加载,但未发现新的模型根对象。");
|
|
1704
|
+
return i.map((s, a) => {
|
|
1705
|
+
const o = b(this.engine, s);
|
|
1706
|
+
if (!o) throw new Error(`本地 BIM 缺少模型根对象:${s}`);
|
|
1707
|
+
return this.addBimModel(s, o, {
|
|
1708
|
+
...t,
|
|
1709
|
+
id: a === 0 ? t.id : void 0,
|
|
1710
|
+
name: a === 0 ? t.name : void 0
|
|
1711
|
+
});
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
addBimModel(e, t, r) {
|
|
1715
|
+
const i = this.resolveId(r.id, "bim"), s = {
|
|
1716
|
+
id: i,
|
|
1717
|
+
name: r.name?.trim() || this.baseNameFromPath(e) || e,
|
|
1718
|
+
type: "bim",
|
|
1719
|
+
source: e,
|
|
1720
|
+
object: t,
|
|
1721
|
+
raw: t
|
|
1722
|
+
}, a = this.track(s, () => this.engine.loaderModule.unloadModel(e), "model");
|
|
1723
|
+
return this.calibrationManager.registerBimModel(e, { id: i, name: s.name }), a;
|
|
1724
|
+
}
|
|
1725
|
+
addStandaloneModel(e, t, r, i, s) {
|
|
1726
|
+
const a = this.resolveId(i.id, r);
|
|
1727
|
+
e.name = a, this.engine.scene.add(e);
|
|
1728
|
+
const o = {
|
|
1729
|
+
id: a,
|
|
1730
|
+
name: i.name?.trim() || this.baseNameFromPath(t) || t,
|
|
1731
|
+
type: r,
|
|
1732
|
+
source: t,
|
|
1733
|
+
object: e,
|
|
1734
|
+
raw: e
|
|
1735
|
+
};
|
|
1736
|
+
return this.track(o, () => {
|
|
1737
|
+
this.engine.scene.remove(e), this.disposeObject(e), s?.();
|
|
1738
|
+
}, r === "geojson" ? "vector" : "model");
|
|
1739
|
+
}
|
|
1740
|
+
track(e, t, r = "tileset") {
|
|
1741
|
+
if (this.resources.has(e.id))
|
|
1742
|
+
throw t(), new RangeError(`GIS 模型 id 已存在:${e.id}`);
|
|
1743
|
+
return this.layerManager.registerLayer({
|
|
1744
|
+
id: e.id,
|
|
1745
|
+
name: e.name,
|
|
1746
|
+
type: r,
|
|
1747
|
+
source: e.source,
|
|
1748
|
+
object: e.object,
|
|
1749
|
+
remove: () => this.removeModel(e.id)
|
|
1750
|
+
}), this.resources.set(e.id, { model: e, release: t }), this.engine.events.trigger("gis:model-loaded", e), this.engine.requestRender(), e;
|
|
1751
|
+
}
|
|
1752
|
+
async loadLocalTileset(e, t, r) {
|
|
1753
|
+
const i = `tileset-${Date.now()}-${++this.sequence}`, s = `https://${ee}/${i}/`, a = this.filePath(t), o = a.includes("/") ? a.slice(0, a.lastIndexOf("/") + 1) : "", l = /* @__PURE__ */ new Map();
|
|
1754
|
+
e.forEach((p) => {
|
|
1755
|
+
const d = this.filePath(p), w = o && d.startsWith(o) ? d.slice(o.length) : d;
|
|
1756
|
+
l.set(this.normalizePath(w), p);
|
|
1757
|
+
});
|
|
1758
|
+
const c = new URL(this.baseName(t), s).toString(), m = async (p) => {
|
|
1759
|
+
const d = new URL(p);
|
|
1760
|
+
if (d.host !== ee) return fetch(p);
|
|
1761
|
+
const w = `/${i}/`, C = this.normalizePath(decodeURIComponent(d.pathname.split(w)[1] ?? "")), P = l.get(C);
|
|
1762
|
+
return P ? new Response(P, {
|
|
1763
|
+
status: 200,
|
|
1764
|
+
headers: P.type ? { "Content-Type": P.type } : void 0
|
|
1765
|
+
}) : new Response(`本地 3D Tiles 文件不存在:${C}`, { status: 404 });
|
|
1766
|
+
}, u = await this.tilesLoader.loadTileset(c, {
|
|
1767
|
+
id: r.id,
|
|
1768
|
+
name: r.name ?? t.name,
|
|
1769
|
+
fetchData: m
|
|
1770
|
+
}), g = this.layerManager.getLayer(u.group.name), h = {
|
|
1771
|
+
id: g?.id ?? u.group.name,
|
|
1772
|
+
name: g?.name ?? r.name ?? t.name,
|
|
1773
|
+
type: "3dtiles",
|
|
1774
|
+
source: t.name,
|
|
1775
|
+
object: u.group,
|
|
1776
|
+
raw: u
|
|
1777
|
+
};
|
|
1778
|
+
return this.track(h, () => {
|
|
1779
|
+
this.tilesLoader.removeTileset(u);
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
disposeObject(e) {
|
|
1783
|
+
e.traverse((t) => {
|
|
1784
|
+
const r = t;
|
|
1785
|
+
r.geometry?.dispose(), (Array.isArray(r.material) ? r.material : r.material ? [r.material] : []).forEach((s) => {
|
|
1786
|
+
Object.values(s).forEach((a) => {
|
|
1787
|
+
const o = a;
|
|
1788
|
+
o?.isTexture && o.dispose();
|
|
1789
|
+
}), s.dispose();
|
|
1790
|
+
});
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
inferUrlType(e) {
|
|
1794
|
+
const t = e.split(/[?#]/, 1)[0].toLowerCase();
|
|
1795
|
+
return /(?:^|\/)tileset\.json$/.test(t) ? "3dtiles" : /\.geojson$/.test(t) ? "geojson" : /\.(?:glb|gltf)$/.test(t) ? "gltf" : "bim";
|
|
1796
|
+
}
|
|
1797
|
+
inferResolvedType(e, t, r) {
|
|
1798
|
+
const i = `${e ?? ""} ${t ?? ""} ${r}`.toLowerCase();
|
|
1799
|
+
return /3d.?tiles|tileset\.json/.test(i) ? "3dtiles" : /geojson/.test(i) ? "geojson" : /\bglb\b|\bgltf\b|\.(?:glb|gltf)(?:[?#]|$)/.test(i) ? "gltf" : "bim";
|
|
1800
|
+
}
|
|
1801
|
+
isGeoJson(e) {
|
|
1802
|
+
return !!(e && [
|
|
1803
|
+
"FeatureCollection",
|
|
1804
|
+
"Feature",
|
|
1805
|
+
"GeometryCollection",
|
|
1806
|
+
"Point",
|
|
1807
|
+
"MultiPoint",
|
|
1808
|
+
"LineString",
|
|
1809
|
+
"MultiLineString",
|
|
1810
|
+
"Polygon",
|
|
1811
|
+
"MultiPolygon"
|
|
1812
|
+
].includes(e.type ?? ""));
|
|
1813
|
+
}
|
|
1814
|
+
resolveId(e, t) {
|
|
1815
|
+
const r = e?.trim() || `${t}-${++this.sequence}`;
|
|
1816
|
+
if (this.resources.has(r) || this.layerManager.getLayer(r))
|
|
1817
|
+
throw new RangeError(`GIS 模型 id 已存在:${r}`);
|
|
1818
|
+
return r;
|
|
1819
|
+
}
|
|
1820
|
+
normalizeUrl(e) {
|
|
1821
|
+
if (typeof e != "string" || e.trim().length === 0)
|
|
1822
|
+
throw new TypeError("模型 URL 不能为空。");
|
|
1823
|
+
return e.trim();
|
|
1824
|
+
}
|
|
1825
|
+
filePath(e) {
|
|
1826
|
+
return this.normalizePath(e.webkitRelativePath || e.name);
|
|
1827
|
+
}
|
|
1828
|
+
baseName(e) {
|
|
1829
|
+
return this.baseNameFromPath(this.filePath(e));
|
|
1830
|
+
}
|
|
1831
|
+
baseNameFromPath(e) {
|
|
1832
|
+
return this.normalizePath(e).split("/").pop() ?? "";
|
|
1833
|
+
}
|
|
1834
|
+
normalizePath(e) {
|
|
1835
|
+
return e.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
1836
|
+
}
|
|
1837
|
+
assertUsable() {
|
|
1838
|
+
if (this.disposed) throw new Error("GISResourceLoaderModule 已销毁。");
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
class Ze {
|
|
1842
|
+
constructor(e, t) {
|
|
1843
|
+
this.options = t, this.engine = e, this.runtimeEngine = e, this.layerManager = new xe(this.runtimeEngine), this.activeBaseSource = t.source ?? t.imageId ?? "cesium";
|
|
1844
|
+
}
|
|
1845
|
+
options;
|
|
1846
|
+
engine;
|
|
1847
|
+
tiles = null;
|
|
1848
|
+
loaderModule;
|
|
1849
|
+
resourceLoaderModule = null;
|
|
1850
|
+
layerManager;
|
|
1851
|
+
coordinateModule = null;
|
|
1852
|
+
controls = null;
|
|
1853
|
+
cameraModule = null;
|
|
1854
|
+
controllerModule = null;
|
|
1855
|
+
modelPositionManager = null;
|
|
1856
|
+
modelCalibrationManager = null;
|
|
1857
|
+
label = null;
|
|
1858
|
+
viewCube = null;
|
|
1859
|
+
skyManager = null;
|
|
1860
|
+
resizeObserver = null;
|
|
1861
|
+
removeFrameCallback = null;
|
|
1862
|
+
previousTime = 0;
|
|
1863
|
+
disposed = !1;
|
|
1864
|
+
baseMapSwitchVersion = 0;
|
|
1865
|
+
activeBaseSource;
|
|
1866
|
+
runtimeEngine;
|
|
1867
|
+
/** 按 Three.js 官方示例注册插件、定位相机并启动瓦片调度。 */
|
|
1868
|
+
async initialize() {
|
|
1869
|
+
this.loaderModule = new Ye(
|
|
1870
|
+
this.runtimeEngine,
|
|
1871
|
+
this.options,
|
|
1872
|
+
this.usesWebGpuBackend(),
|
|
1873
|
+
this.layerManager
|
|
1874
|
+
);
|
|
1875
|
+
const { tiles: e, Controls: t, cameraFrame: r } = await this.loaderModule.loadImage();
|
|
1876
|
+
if (this.disposed) throw new Error("GIS 已在初始化完成前销毁。");
|
|
1877
|
+
return e.setCamera(this.runtimeEngine.camera), e.setResolutionFromRenderer(this.runtimeEngine.camera, this.runtimeEngine.renderer), this.runtimeEngine.scene.add(e.group), this.tiles = e, this.layerManager.registerLayer({
|
|
1878
|
+
id: "base-imagery",
|
|
1879
|
+
name: "GIS 底图",
|
|
1880
|
+
type: "imagery",
|
|
1881
|
+
source: this.getBaseLayerSource(),
|
|
1882
|
+
object: e.group
|
|
1883
|
+
}), this.runtimeEngine.viewCube?.setHomeBoundingBoxProvider?.(
|
|
1884
|
+
() => this.layerManager.getModelBoundingBox()
|
|
1885
|
+
), this.coordinateModule = new ze(e, r), this.modelCalibrationManager = new Oe(
|
|
1886
|
+
this.runtimeEngine,
|
|
1887
|
+
this.coordinateModule
|
|
1888
|
+
), this.resourceLoaderModule = new Xe(
|
|
1889
|
+
this.runtimeEngine,
|
|
1890
|
+
this.loaderModule,
|
|
1891
|
+
this.coordinateModule,
|
|
1892
|
+
this.layerManager,
|
|
1893
|
+
this.modelCalibrationManager
|
|
1894
|
+
), this.loaderModule.setTilesetLifecycle({
|
|
1895
|
+
onTilesetAdded: (i, s, a) => {
|
|
1896
|
+
this.modelCalibrationManager?.registerTileset(
|
|
1897
|
+
s.id,
|
|
1898
|
+
s.name,
|
|
1899
|
+
s.source,
|
|
1900
|
+
i,
|
|
1901
|
+
a
|
|
1902
|
+
);
|
|
1903
|
+
},
|
|
1904
|
+
onTilesetReady: (i) => {
|
|
1905
|
+
this.modelCalibrationManager?.markTilesetReady(i);
|
|
1906
|
+
},
|
|
1907
|
+
onTilesetRemoved: (i, s) => {
|
|
1908
|
+
s && this.modelCalibrationManager?.unregister(s);
|
|
1909
|
+
}
|
|
1910
|
+
}), this.modelPositionManager = new De(
|
|
1911
|
+
this.runtimeEngine,
|
|
1912
|
+
e,
|
|
1913
|
+
this.layerManager,
|
|
1914
|
+
this.modelCalibrationManager
|
|
1915
|
+
), this.cameraModule = new Y(
|
|
1916
|
+
this.runtimeEngine.camera,
|
|
1917
|
+
e,
|
|
1918
|
+
r,
|
|
1919
|
+
this.requestRender,
|
|
1920
|
+
this.options.initialView,
|
|
1921
|
+
this.coordinateModule
|
|
1922
|
+
), this.configureControls(t), this.cameraModule.setControls(this.controls), this.controllerModule = new X(
|
|
1923
|
+
this.runtimeEngine,
|
|
1924
|
+
this.controls,
|
|
1925
|
+
this.requestRender,
|
|
1926
|
+
this.layerManager
|
|
1927
|
+
), this.controllerModule.switchToGIS(), this.viewCube = new Re(
|
|
1928
|
+
this.cameraModule,
|
|
1929
|
+
{ ...V, ...this.options.initialView }
|
|
1930
|
+
), this.label = new Le(
|
|
1931
|
+
this.runtimeEngine,
|
|
1932
|
+
this.coordinateModule,
|
|
1933
|
+
this.layerManager
|
|
1934
|
+
), this.options.temporalAntialiasing !== !0 && this.runtimeEngine.composerModule.isEffectEnabled?.("traa") && this.runtimeEngine.composerModule.setEffectEnabled("traa", !1), this.skyManager = new je(this.runtimeEngine), this.options.skyBox ? await this.skyManager.setSkyBox(this.options.skyBox) : this.options.sky === void 0 ? await this.skyManager.setSkyBox() : this.skyManager.setSky(this.options.sky), e.addEventListener("needs-update", this.requestRender), e.addEventListener("load-error", this.handleLoadError), this.resizeObserver = new ResizeObserver(this.updateResolution), this.resizeObserver.observe(this.runtimeEngine.container), this.runtimeEngine.setIdleFrameRateExempt("gis-3d-tiles", !0), this.removeFrameCallback = this.runtimeEngine.addFrameCallback(this.update), this;
|
|
1935
|
+
}
|
|
1936
|
+
/** 返回 GIS 是否已完成初始化且尚未销毁。 */
|
|
1937
|
+
isInitialized() {
|
|
1938
|
+
return !this.disposed && this.tiles !== null && this.cameraModule !== null;
|
|
1939
|
+
}
|
|
1940
|
+
/** 兼容旧版业务入口,加载 3D Tiles 并返回稳定模型句柄。 */
|
|
1941
|
+
async load3DTiles(e) {
|
|
1942
|
+
return await this.requireResourceLoader().loadModel(e.url, {
|
|
1943
|
+
...e,
|
|
1944
|
+
type: "3dtiles"
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
/** 统一加载在线 3D Tiles、glTF/GLB、GeoJSON 或 BIM 模型。 */
|
|
1948
|
+
async loadModel(e, t) {
|
|
1949
|
+
return this.requireResourceLoader().loadModel(e, t);
|
|
1950
|
+
}
|
|
1951
|
+
/** 加载本地 3D Tiles 文件集、glTF/GLB、GeoJSON 或 BIM 文件包。 */
|
|
1952
|
+
async loadModelFile(e, t) {
|
|
1953
|
+
return this.requireResourceLoader().loadModelFile(e, t);
|
|
1954
|
+
}
|
|
1955
|
+
/** 解析 ViewToken 并通过统一 GIS 资源加载链路加载。 */
|
|
1956
|
+
async loadModelByViewToken(e, t) {
|
|
1957
|
+
return this.requireResourceLoader().loadModelByViewToken(e, t);
|
|
1958
|
+
}
|
|
1959
|
+
/** 按统一模型句柄或 id 卸载资源。 */
|
|
1960
|
+
removeModel(e) {
|
|
1961
|
+
return this.resourceLoaderModule?.removeModel(e) ?? !1;
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
* 运行时切换 GIS 底图,并保留当前 WGS84 相机视角和业务图层。
|
|
1965
|
+
* 新底图加载失败时不会修改当前场景。
|
|
1966
|
+
*/
|
|
1967
|
+
async switchBaseMap(e) {
|
|
1968
|
+
if (!this.tiles || !this.cameraModule || !this.coordinateModule)
|
|
1969
|
+
throw new Error("GIS 尚未完成初始化。");
|
|
1970
|
+
const t = ++this.baseMapSwitchVersion, r = this.cameraModule.getView(), i = this.controllerModule?.getMode(), s = typeof e == "string" ? await this.loaderModule.loadImageById(e) : await this.loaderModule.load3dtiles(e);
|
|
1971
|
+
if (this.disposed || t !== this.baseMapSwitchVersion) {
|
|
1972
|
+
if (s.tiles.dispose(), this.disposed) throw new Error("GIS 已销毁。");
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
const a = this.tiles;
|
|
1976
|
+
a.removeEventListener("needs-update", this.requestRender), a.removeEventListener("load-error", this.handleLoadError), a.deleteCamera(this.runtimeEngine.camera), this.runtimeEngine.scene.remove(a.group), this.layerManager.unregisterLayer("base-imagery"), this.cameraModule.dispose(), this.controllerModule?.dispose(), this.controls?.dispose(), this.tiles = s.tiles, this.activeBaseSource = e, s.tiles.setCamera(this.runtimeEngine.camera), s.tiles.setResolutionFromRenderer(this.runtimeEngine.camera, this.runtimeEngine.renderer), this.runtimeEngine.scene.add(s.tiles.group), this.layerManager.registerLayer({
|
|
1977
|
+
id: "base-imagery",
|
|
1978
|
+
name: "GIS 底图",
|
|
1979
|
+
type: "imagery",
|
|
1980
|
+
source: this.getBaseLayerSource(),
|
|
1981
|
+
object: s.tiles.group
|
|
1982
|
+
}), this.coordinateModule.setTiles(s.tiles, s.cameraFrame), this.modelPositionManager?.setTiles(s.tiles), this.cameraModule = new Y(
|
|
1983
|
+
this.runtimeEngine.camera,
|
|
1984
|
+
s.tiles,
|
|
1985
|
+
s.cameraFrame,
|
|
1986
|
+
this.requestRender,
|
|
1987
|
+
r,
|
|
1988
|
+
this.coordinateModule
|
|
1989
|
+
), this.configureControls(s.Controls), this.cameraModule.setControls(this.controls), this.controllerModule = new X(
|
|
1990
|
+
this.runtimeEngine,
|
|
1991
|
+
this.controls,
|
|
1992
|
+
this.requestRender,
|
|
1993
|
+
this.layerManager
|
|
1994
|
+
), this.controllerModule.switchController(i ?? "gis"), this.viewCube?.setCameraModule(this.cameraModule), s.tiles.addEventListener("needs-update", this.requestRender), s.tiles.addEventListener("load-error", this.handleLoadError), a.dispose(), this.runtimeEngine.events.trigger("gis:base-map-changed", {
|
|
1995
|
+
source: e,
|
|
1996
|
+
timestamp: Date.now()
|
|
1997
|
+
}), this.runtimeEngine.requestRender();
|
|
1998
|
+
}
|
|
1999
|
+
/** 同时支持 3D Tiles 图层 id 和 BIM 模型 URL 的 ENU 校准。 */
|
|
2000
|
+
applyCalibration(e) {
|
|
2001
|
+
return this.modelCalibrationManager?.setTransform(e) ?? !1;
|
|
2002
|
+
}
|
|
2003
|
+
/** 返回全部 3D Tiles 与 BIM 校准状态。 */
|
|
2004
|
+
getModelCalibrationItems() {
|
|
2005
|
+
return this.modelCalibrationManager?.getItems() ?? [];
|
|
2006
|
+
}
|
|
2007
|
+
/** 恢复指定 3D Tiles 或 BIM 模型的校准基准矩阵。 */
|
|
2008
|
+
resetModelCalibration(e) {
|
|
2009
|
+
return this.modelCalibrationManager?.reset(e) ?? !1;
|
|
2010
|
+
}
|
|
2011
|
+
/** 释放控制器、瓦片缓存、事件以及 v3 引擎持有的 GPU 资源。 */
|
|
2012
|
+
dispose() {
|
|
2013
|
+
this.disposed || (this.disposed = !0, this.baseMapSwitchVersion += 1, this.removeFrameCallback?.(), this.removeFrameCallback = null, this.runtimeEngine.setIdleFrameRateExempt("gis-3d-tiles", !1), this.resizeObserver?.disconnect(), this.label?.dispose(), this.controllerModule?.dispose(), this.skyManager?.dispose(), this.cameraModule?.dispose(), this.modelPositionManager?.dispose(), this.controls?.dispose(), this.resourceLoaderModule?.dispose(), this.loaderModule?.dispose(), this.modelCalibrationManager?.dispose(), this.runtimeEngine.viewCube?.setHomeBoundingBoxProvider?.(null), this.tiles && (this.tiles.removeEventListener("needs-update", this.requestRender), this.tiles.removeEventListener("load-error", this.handleLoadError), this.tiles.deleteCamera(this.runtimeEngine.camera), this.runtimeEngine.scene.remove(this.tiles.group), this.tiles.dispose()), this.layerManager.dispose(), this.controls = null, this.tiles = null, this.coordinateModule = null, this.cameraModule = null, this.controllerModule = null, this.modelPositionManager = null, this.modelCalibrationManager = null, this.resourceLoaderModule = null, this.label = null, this.viewCube = null, this.skyManager = null, this.runtimeEngine.dispose());
|
|
2014
|
+
}
|
|
2015
|
+
configureControls(e) {
|
|
2016
|
+
this.runtimeEngine.controlModule.disActive();
|
|
2017
|
+
const t = new e(this.tiles.group, this.runtimeEngine.camera, this.runtimeEngine.renderer.domElement);
|
|
2018
|
+
this.disableIncompatiblePivotMarker(t), t.setEllipsoid(this.tiles.ellipsoid, this.tiles.group), t.enableDamping = !0, t.cameraRadius = 0, t.minDistance = 0, t.adjustHeight = !1, t.addEventListener("change", this.requestRender), this.controls = t;
|
|
2019
|
+
}
|
|
2020
|
+
/** WebGPU 不编译 GlobeControls 内置的 GLSL ShaderMaterial,因此禁用该辅助圆圈。 */
|
|
2021
|
+
disableIncompatiblePivotMarker(e) {
|
|
2022
|
+
if (!this.usesWebGpuBackend()) return;
|
|
2023
|
+
const t = e.pivotMesh;
|
|
2024
|
+
if (!t?.material) return;
|
|
2025
|
+
const i = (Array.isArray(t.material) ? t.material : [t.material]).filter((s) => s.isShaderMaterial === !0 && s.isNodeMaterial !== !0);
|
|
2026
|
+
i.length !== 0 && (i.forEach((s) => {
|
|
2027
|
+
s.visible = !1;
|
|
2028
|
+
}), t.visible = !1);
|
|
2029
|
+
}
|
|
2030
|
+
/** WebGPURenderer 可承载 WebGPU 或 WebGL2,兼容逻辑必须按实际后端判断。 */
|
|
2031
|
+
usesWebGpuBackend() {
|
|
2032
|
+
return this.runtimeEngine.renderer.coordinateSystem === ve;
|
|
2033
|
+
}
|
|
2034
|
+
update = (e) => {
|
|
2035
|
+
if (this.disposed) return;
|
|
2036
|
+
const t = this.previousTime === 0 ? 0 : (e - this.previousTime) / 1e3;
|
|
2037
|
+
this.previousTime = e, this.cameraModule?.update(e), this.controls?.enabled && this.controls.update(t), this.tiles?.group.visible && this.tiles.update(), this.loaderModule?.update(), this.label?.update(), this.runtimeEngine.requestRender();
|
|
2038
|
+
};
|
|
2039
|
+
updateResolution = () => {
|
|
2040
|
+
this.tiles?.setResolutionFromRenderer(this.runtimeEngine.camera, this.runtimeEngine.renderer), this.loaderModule?.updateResolution(), this.runtimeEngine.requestRender();
|
|
2041
|
+
};
|
|
2042
|
+
requestRender = () => {
|
|
2043
|
+
this.runtimeEngine.requestRender();
|
|
2044
|
+
};
|
|
2045
|
+
handleLoadError = (e) => {
|
|
2046
|
+
const t = e.error instanceof Error ? e.error : new Error("3D Tiles 加载失败。");
|
|
2047
|
+
this.options.onError?.(t);
|
|
2048
|
+
};
|
|
2049
|
+
getBaseLayerSource() {
|
|
2050
|
+
const e = this.activeBaseSource;
|
|
2051
|
+
return typeof e == "string" ? e : e.type === "url" || e.type === "xyz" ? e.url : `cesium-ion:${e.assetId ?? 2275207}`;
|
|
2052
|
+
}
|
|
2053
|
+
requireResourceLoader() {
|
|
2054
|
+
if (!this.resourceLoaderModule) throw new Error("GIS 尚未完成初始化。");
|
|
2055
|
+
return this.resourceLoaderModule;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
async function Qe(n) {
|
|
2059
|
+
n.assetFiles ? await F(n.assetFiles) : n.assetFile ? await re(n.assetFile) : n.assetBaseUrl && await F(null), n.assetBaseUrl && ie(n.assetBaseUrl);
|
|
2060
|
+
}
|
|
2061
|
+
async function mt(n, e = {}) {
|
|
2062
|
+
await Qe(n);
|
|
2063
|
+
const t = n.engineVersion === "v2" ? new (await import("./engine-3d-BsWcD-bl.js").then((i) => i.e)).EngineKernelV2(n) : new (await import("./EngineKernel-CZX0Omjs.js")).EngineKernelV3(n), r = new Ze(t, e);
|
|
2064
|
+
try {
|
|
2065
|
+
return "ready" in t && await t.ready, await r.initialize();
|
|
2066
|
+
} catch (i) {
|
|
2067
|
+
throw r.dispose(), i;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
var W = /* @__PURE__ */ ((n) => (n.ModelLoadStart = "model-load-start", n.ModelLoadProgress = "model-load-progress", n.ModelLoaded = "model-loaded", n.ModelError = "model-error", n.SelectionChanged = "selection-changed", n.HoverChanged = "hover-changed", n.Click = "click", n.MouseMove = "mouse-move", n.ModelPlaceSelected = "model-place-selected", n.ModelCalibrationChanged = "model-calibration-changed", n.CameraChanged = "camera-changed", n.CameraIdle = "camera-idle", n.EngineFree = "engine-free", n.EngineBusy = "engine-busy", n.MeasureChanged = "measure-changed", n.MeasureClick = "measure-click", n.SectionMove = "section-move", n.LoadingCompleted = "loading_completed", n))(W || {});
|
|
2071
|
+
class pt {
|
|
2072
|
+
options;
|
|
2073
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
2074
|
+
isDisposed = !1;
|
|
2075
|
+
eventsBound = !1;
|
|
2076
|
+
handleLoadingCompleted = () => {
|
|
2077
|
+
this.options.enabled && this.replaceAll();
|
|
2078
|
+
};
|
|
2079
|
+
engine;
|
|
2080
|
+
constructor(e, t = {}, r = {}) {
|
|
2081
|
+
this.engine = e;
|
|
2082
|
+
const i = this.normalizeInput(t, r);
|
|
2083
|
+
this.options = {
|
|
2084
|
+
enabled: i.enabled ?? this.hasRules(i),
|
|
2085
|
+
rules: this.normalizeRules(i)
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2088
|
+
/** 注册模型加载事件并立即处理当前已有模型。 */
|
|
2089
|
+
init(e = {}) {
|
|
2090
|
+
this.isDisposed || (Object.keys(e).length > 0 && (this.options.enabled = e.enabled ?? this.options.enabled, (e.rules != null || e.materialName != null || e.materialParams != null) && (this.options.rules = this.normalizeRules(e))), this.eventsBound || (this.engine.events?.on?.(W.LoadingCompleted, this.handleLoadingCompleted), this.eventsBound = !0), this.options.enabled && this.replaceAll());
|
|
2091
|
+
}
|
|
2092
|
+
isEnabled() {
|
|
2093
|
+
return this.options.enabled;
|
|
2094
|
+
}
|
|
2095
|
+
/** 开关自动替换;关闭时恢复管理器修改过的材质。 */
|
|
2096
|
+
setEnabled(e) {
|
|
2097
|
+
return this.options.enabled = e, e ? this.replaceAll() : (this.restoreAll(), 0);
|
|
2098
|
+
}
|
|
2099
|
+
setMaterialName(e) {
|
|
2100
|
+
return this.restoreAll(), this.options.rules = [{
|
|
2101
|
+
materialName: e,
|
|
2102
|
+
materialParams: {}
|
|
2103
|
+
}], this.options.enabled ? this.replaceAll() : 0;
|
|
2104
|
+
}
|
|
2105
|
+
setMaterialParams(e) {
|
|
2106
|
+
const t = this.options.rules[0] ?? {
|
|
2107
|
+
materialName: "",
|
|
2108
|
+
materialParams: {}
|
|
2109
|
+
};
|
|
2110
|
+
return t.materialParams = {
|
|
2111
|
+
...t.materialParams,
|
|
2112
|
+
...e
|
|
2113
|
+
}, this.options.rules = [t, ...this.options.rules.slice(1)], this.options.enabled ? this.replaceAll() : 0;
|
|
2114
|
+
}
|
|
2115
|
+
setRules(e) {
|
|
2116
|
+
return this.restoreAll(), this.options.rules = e.map((t) => ({
|
|
2117
|
+
...t,
|
|
2118
|
+
materialParams: { ...t.materialParams }
|
|
2119
|
+
})), this.options.enabled ? this.replaceAll() : 0;
|
|
2120
|
+
}
|
|
2121
|
+
addRule(e) {
|
|
2122
|
+
return this.options.rules.push({ ...e, materialParams: { ...e.materialParams } }), this.options.enabled ? this.replaceAll() : 0;
|
|
2123
|
+
}
|
|
2124
|
+
removeRule(e) {
|
|
2125
|
+
return e >= 0 && e < this.options.rules.length && this.options.rules.splice(e, 1), this.options.enabled ? this.replaceAll() : 0;
|
|
2126
|
+
}
|
|
2127
|
+
getRules() {
|
|
2128
|
+
return this.options.rules.map((e) => ({
|
|
2129
|
+
...e,
|
|
2130
|
+
materialParams: { ...e.materialParams }
|
|
2131
|
+
}));
|
|
2132
|
+
}
|
|
2133
|
+
/** 替换所有已加载模型中匹配名称的材质,返回受影响的材质数量。 */
|
|
2134
|
+
replaceAll() {
|
|
2135
|
+
if (this.isDisposed || !this.options.enabled)
|
|
2136
|
+
return 0;
|
|
2137
|
+
const e = /* @__PURE__ */ new Set();
|
|
2138
|
+
return le(this.engine, (t) => (this.collectObjectMaterials(t, e), !1)), this.applyMaterials(e);
|
|
2139
|
+
}
|
|
2140
|
+
/** 替换指定模型 URL 或模型对象中的材质。 */
|
|
2141
|
+
replaceModel(e) {
|
|
2142
|
+
if (this.isDisposed || !this.options.enabled)
|
|
2143
|
+
return 0;
|
|
2144
|
+
const t = typeof e == "string" ? b(this.engine, e) : e;
|
|
2145
|
+
if (t == null)
|
|
2146
|
+
return 0;
|
|
2147
|
+
const r = /* @__PURE__ */ new Set();
|
|
2148
|
+
return t.traverse((i) => this.collectObjectMaterials(i, r)), this.applyMaterials(r);
|
|
2149
|
+
}
|
|
2150
|
+
/** 对单个材质执行当前规则,供加载器在合批前调用。 */
|
|
2151
|
+
applyMaterial(e) {
|
|
2152
|
+
if (this.isDisposed || !this.options.enabled || e == null)
|
|
2153
|
+
return !1;
|
|
2154
|
+
const t = this.options.rules.filter((s) => this.matches(e, s));
|
|
2155
|
+
if (t.length === 0)
|
|
2156
|
+
return !1;
|
|
2157
|
+
const r = Object.assign({}, ...t.map((s) => s.materialParams)), i = Object.keys(r);
|
|
2158
|
+
if (!this.snapshots.has(e))
|
|
2159
|
+
this.snapshots.set(e, this.captureSnapshot(e, i));
|
|
2160
|
+
else {
|
|
2161
|
+
const s = this.snapshots.get(e);
|
|
2162
|
+
i.forEach((a) => {
|
|
2163
|
+
s.has(a) || s.set(a, this.cloneMaterialValue(e[a]));
|
|
2164
|
+
});
|
|
2165
|
+
}
|
|
2166
|
+
return Object.entries(r).forEach(([s, a]) => this.applyMaterialValue(e, s, a)), e.needsUpdate = !0, !0;
|
|
2167
|
+
}
|
|
2168
|
+
getReplacedMaterialCount() {
|
|
2169
|
+
return this.snapshots.size;
|
|
2170
|
+
}
|
|
2171
|
+
/** 恢复管理器修改过的全部材质。 */
|
|
2172
|
+
restoreAll() {
|
|
2173
|
+
this.snapshots.forEach((e, t) => {
|
|
2174
|
+
e.forEach((r, i) => this.applyMaterialValue(t, i, r)), t.needsUpdate = !0;
|
|
2175
|
+
}), this.snapshots.clear(), this.engine.requestComposerRender?.();
|
|
2176
|
+
}
|
|
2177
|
+
dispose() {
|
|
2178
|
+
this.isDisposed || (this.eventsBound && (this.engine.events?.off?.(W.LoadingCompleted, this.handleLoadingCompleted), this.eventsBound = !1), this.restoreAll(), this.isDisposed = !0);
|
|
2179
|
+
}
|
|
2180
|
+
normalizeRules(e) {
|
|
2181
|
+
return Array.isArray(e.rules) ? e.rules.map((t) => ({
|
|
2182
|
+
...t,
|
|
2183
|
+
materialParams: { ...t.materialParams }
|
|
2184
|
+
})) : [{
|
|
2185
|
+
materialName: e.materialName,
|
|
2186
|
+
materialParams: { ...e.materialParams ?? {} }
|
|
2187
|
+
}].filter((t) => t.materialName != null);
|
|
2188
|
+
}
|
|
2189
|
+
normalizeInput(e, t) {
|
|
2190
|
+
return Array.isArray(e) ? { enabled: !0, rules: e } : typeof e == "string" || e instanceof RegExp || typeof e == "function" ? { enabled: !0, materialName: e, materialParams: t } : e;
|
|
2191
|
+
}
|
|
2192
|
+
hasRules(e) {
|
|
2193
|
+
return Array.isArray(e.rules) && e.rules.length > 0 || e.materialName != null;
|
|
2194
|
+
}
|
|
2195
|
+
applyMaterials(e) {
|
|
2196
|
+
let t = 0;
|
|
2197
|
+
return e.forEach((r) => {
|
|
2198
|
+
this.applyMaterial(r) && (t += 1);
|
|
2199
|
+
}), t > 0 && this.engine.requestComposerRender?.(), t;
|
|
2200
|
+
}
|
|
2201
|
+
collectObjectMaterials(e, t) {
|
|
2202
|
+
const r = e;
|
|
2203
|
+
(Array.isArray(r.material) ? r.material : r.material != null ? [r.material] : []).filter(Boolean).forEach((s) => t.add(s));
|
|
2204
|
+
}
|
|
2205
|
+
matches(e, t) {
|
|
2206
|
+
const r = t.materialName ?? t.name;
|
|
2207
|
+
return typeof r == "function" ? r(e) : r instanceof RegExp ? (r.lastIndex = 0, r.test(e.name)) : typeof r == "string" && r.length > 0 && e.name.includes(r);
|
|
2208
|
+
}
|
|
2209
|
+
captureSnapshot(e, t) {
|
|
2210
|
+
const r = /* @__PURE__ */ new Map();
|
|
2211
|
+
return t.forEach((i) => r.set(i, this.cloneMaterialValue(e[i]))), r;
|
|
2212
|
+
}
|
|
2213
|
+
cloneMaterialValue(e) {
|
|
2214
|
+
return e?.isColor === !0 && typeof e.clone == "function" ? e.clone() : e;
|
|
2215
|
+
}
|
|
2216
|
+
applyMaterialValue(e, t, r) {
|
|
2217
|
+
const i = e[t];
|
|
2218
|
+
if (t === "color" && i?.isColor === !0) {
|
|
2219
|
+
r instanceof Ee ? i.copy(r) : i.set(r);
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
r !== void 0 && (t in e || t === "envMapIntensity") && (e[t] = r);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
const Ke = {
|
|
2226
|
+
vxgi: {
|
|
2227
|
+
// VXGI 需要体素化整套场景,默认关闭以避免影响现有项目的初始化性能。
|
|
2228
|
+
enabled: !1,
|
|
2229
|
+
resolution: 128,
|
|
2230
|
+
coneCount: 3,
|
|
2231
|
+
coneAngle: 40,
|
|
2232
|
+
giIntensity: 1.5,
|
|
2233
|
+
aoIntensity: 1,
|
|
2234
|
+
aoMinVisibility: 0,
|
|
2235
|
+
aoDistance: 1,
|
|
2236
|
+
normalOffset: 1.5,
|
|
2237
|
+
maxDistance: 50,
|
|
2238
|
+
stepScale: 1,
|
|
2239
|
+
bounces: 0,
|
|
2240
|
+
directionalRadiance: !1,
|
|
2241
|
+
useTemporalFiltering: !0,
|
|
2242
|
+
updateOnChange: !0
|
|
2243
|
+
},
|
|
2244
|
+
colorGrading: {
|
|
2245
|
+
/**
|
|
2246
|
+
* 色温,单位 Kelvin;6500K 为中性白。
|
|
2247
|
+
* 性能:偏离 6500K 时才会创建色彩分级全屏通道;单个色温乘法本身开销很小。
|
|
2248
|
+
*/
|
|
2249
|
+
colorTemperature: 5e3,
|
|
2250
|
+
/**
|
|
2251
|
+
* 饱和度倍率;0 为黑白,1 为原始饱和度。
|
|
2252
|
+
* 性能:只增加一次全屏颜色计算;若所有颜色分级参数均为中性,则该通道不会创建。
|
|
2253
|
+
*/
|
|
2254
|
+
saturation: 1.05,
|
|
2255
|
+
/**
|
|
2256
|
+
* 对比度倍率;1 为原始对比度。
|
|
2257
|
+
* 性能:只增加一次全屏颜色计算;调节数值不会增加采样次数。
|
|
2258
|
+
*/
|
|
2259
|
+
contrast: 1.05,
|
|
2260
|
+
/**
|
|
2261
|
+
* RCAS 锐化强度,范围 0~1;0 关闭,1 最强。
|
|
2262
|
+
* 性能:大于 0 会额外执行锐化全屏通道,并产生邻域纹理采样;通常是本组参数中
|
|
2263
|
+
* 成本最高的项目,移动端建议保持 0 或使用较低值。
|
|
2264
|
+
*/
|
|
2265
|
+
sharpen: 0.1
|
|
2266
|
+
},
|
|
2267
|
+
ssgi: {
|
|
2268
|
+
/**
|
|
2269
|
+
* 是否启用屏幕空间间接光照(SSGI),同时包含环境遮蔽贡献。
|
|
2270
|
+
* 性能:启用后增加高成本的全屏光线采样和临时纹理;关闭可直接节省整组开销。
|
|
2271
|
+
*/
|
|
2272
|
+
enabled: !0,
|
|
2273
|
+
/**
|
|
2274
|
+
* 每像素的采样方向数(slice)。
|
|
2275
|
+
* 性能:与 `stepCount` 相乘决定主要采样量,成本近似线性增加;优先降低此值
|
|
2276
|
+
* 以换取帧率,过低会出现方向性噪声。
|
|
2277
|
+
*/
|
|
2278
|
+
sliceCount: 1,
|
|
2279
|
+
/**
|
|
2280
|
+
* 每个方向的光线步进数。
|
|
2281
|
+
* 性能:与 `sliceCount` 相乘决定主要采样量,成本近似线性增加;提高后会明显
|
|
2282
|
+
* 增加 GPU 时间,默认低步数适合实时场景。
|
|
2283
|
+
*/
|
|
2284
|
+
stepCount: 2,
|
|
2285
|
+
/**
|
|
2286
|
+
* 环境遮蔽强度;0 表示不增加遮蔽,1 为常用强度。
|
|
2287
|
+
* 性能:只改变结果合成,不改变采样次数,性能影响很小。
|
|
2288
|
+
*/
|
|
2289
|
+
aoIntensity: 1,
|
|
2290
|
+
/**
|
|
2291
|
+
* 间接光照强度;0 表示不叠加间接光,1 为基准强度。
|
|
2292
|
+
* 性能:只改变结果合成,不改变采样次数,性能影响很小。
|
|
2293
|
+
*/
|
|
2294
|
+
giIntensity: 2,
|
|
2295
|
+
/**
|
|
2296
|
+
* 屏幕空间采样半径,单位为场景单位。
|
|
2297
|
+
* 性能:主要影响光线覆盖范围和命中结果,不直接增加采样次数;过大可能扩大
|
|
2298
|
+
* 有效搜索区域并增加纹理访问压力。
|
|
2299
|
+
*/
|
|
2300
|
+
radius: 1.5,
|
|
2301
|
+
/**
|
|
2302
|
+
* 是否使用屏幕空间半径计算;关闭时使用视图空间计算。
|
|
2303
|
+
* 性能:两种路径采样次数相同,差异主要来自每步坐标换算;通常不是首要调优项。
|
|
2304
|
+
*/
|
|
2305
|
+
useScreenSpaceSampling: !0,
|
|
2306
|
+
/**
|
|
2307
|
+
* 遮蔽/光照衰减指数,控制远距离样本的权重。
|
|
2308
|
+
* 性能:只增加少量幂运算,不改变采样数量;主要是画质参数。
|
|
2309
|
+
*/
|
|
2310
|
+
expFactor: 1,
|
|
2311
|
+
/**
|
|
2312
|
+
* 深度厚度容差,用于判断样本是否与几何体相交。
|
|
2313
|
+
* 性能:只影响命中判定,不改变采样数量;过大可能造成漏光或光晕。
|
|
2314
|
+
*/
|
|
2315
|
+
thickness: 1,
|
|
2316
|
+
/**
|
|
2317
|
+
* 是否使用线性厚度计算,改善不同深度距离下的相交一致性。
|
|
2318
|
+
* 性能:增加少量分支和坐标换算,开销远小于增加步进数。
|
|
2319
|
+
*/
|
|
2320
|
+
useLinearThickness: !1,
|
|
2321
|
+
/**
|
|
2322
|
+
* 背面光照权重;0 不使用背面光照。
|
|
2323
|
+
* 性能:主要影响样本合成,增加的计算很小;较高值可能放大背面漏光。
|
|
2324
|
+
*/
|
|
2325
|
+
backfaceLighting: 0,
|
|
2326
|
+
/**
|
|
2327
|
+
* 是否启用 SSGI 时域滤波。
|
|
2328
|
+
* 性能:需要维护历史结果并增加时域合成开销和显存占用;可降低噪声,但仅在
|
|
2329
|
+
* 时域渲染条件满足(通常同时启用 TRAA)时生效。
|
|
2330
|
+
*/
|
|
2331
|
+
useTemporalFiltering: !0
|
|
2332
|
+
},
|
|
2333
|
+
traa: {
|
|
2334
|
+
/**
|
|
2335
|
+
* 是否启用时域抗锯齿(TRAA)。
|
|
2336
|
+
* 性能:增加一次全屏时域重投影、历史纹理和运动信息处理;关闭可省掉中等开销,
|
|
2337
|
+
* 但 SSGI/GTAO 的时域滤波也会随条件失效。
|
|
2338
|
+
*/
|
|
2339
|
+
enabled: !0,
|
|
2340
|
+
/**
|
|
2341
|
+
* 深度相似阈值,用于判断历史样本是否可复用。
|
|
2342
|
+
* 性能:只影响每像素判定,不改变采样次数,性能影响很小。
|
|
2343
|
+
*/
|
|
2344
|
+
depthThreshold: 5e-4,
|
|
2345
|
+
/**
|
|
2346
|
+
* 边缘深度差阈值,用于处理几何边缘的历史权重。
|
|
2347
|
+
* 性能:只影响每像素判定,不改变采样次数,性能影响很小。
|
|
2348
|
+
*/
|
|
2349
|
+
edgeDepthDiff: 1e-3,
|
|
2350
|
+
/**
|
|
2351
|
+
* 允许的最大速度长度,用于限制运动矢量重投影范围。
|
|
2352
|
+
* 性能:只影响向量限制和历史采样位置,性能影响很小;过大可能增加拖影。
|
|
2353
|
+
*/
|
|
2354
|
+
maxVelocityLength: 128,
|
|
2355
|
+
/**
|
|
2356
|
+
* 是否启用亚像素修正。
|
|
2357
|
+
* 性能:增加少量边缘权重计算,开销较小;密集细杆接近单像素宽度时可减少闪烁。
|
|
2358
|
+
*/
|
|
2359
|
+
useSubpixelCorrection: !1
|
|
2360
|
+
},
|
|
2361
|
+
ssr: {
|
|
2362
|
+
/**
|
|
2363
|
+
* 是否启用屏幕空间反射(SSR)。
|
|
2364
|
+
* 性能:增加全屏光线步进、命中判定及可能的模糊通道,是最昂贵的效果之一;
|
|
2365
|
+
* 关闭可直接省掉整组 GPU 和临时纹理开销。
|
|
2366
|
+
*/
|
|
2367
|
+
enabled: !0,
|
|
2368
|
+
/**
|
|
2369
|
+
* 是否使用随机 GGX 反射方向;关闭时使用单一镜面反射路径。
|
|
2370
|
+
* 性能:随机路径通常需要更多光线步进和去噪,GPU 成本更高;只有在需要粗糙材质
|
|
2371
|
+
* 反射质量时才建议开启。
|
|
2372
|
+
*/
|
|
2373
|
+
stochastic: !1,
|
|
2374
|
+
/**
|
|
2375
|
+
* 是否对环境贴图执行重要性采样,用于 SSR 未命中时的环境反射。
|
|
2376
|
+
* 性能:会在环境贴图上预计算亮度分布表,增加初始化/重建时间、显存和采样开销;
|
|
2377
|
+
* 没有有效环境贴图时不会生效。
|
|
2378
|
+
*/
|
|
2379
|
+
envImportanceSampling: !1,
|
|
2380
|
+
/**
|
|
2381
|
+
* SSR 未命中时使用的环境节点;null 表示不补充环境反射。
|
|
2382
|
+
* 性能:提供环境节点会增加环境纹理读取;保持 null 时无额外环境采样。
|
|
2383
|
+
*/
|
|
2384
|
+
environmentNode: null,
|
|
2385
|
+
/**
|
|
2386
|
+
* 是否反射非金属材质;关闭时仅处理金属表面。
|
|
2387
|
+
* 性能:开启后更多屏幕像素进入反射计算,通常会增加 GPU 时间;建筑场景可按需开启。
|
|
2388
|
+
*/
|
|
2389
|
+
reflectNonMetals: !0,
|
|
2390
|
+
/**
|
|
2391
|
+
* 是否对命中位置进行二分细化。
|
|
2392
|
+
* 性能:命中后增加二分搜索步进,成本随命中像素数增加;能改善反射边缘精度。
|
|
2393
|
+
*/
|
|
2394
|
+
binaryRefine: !1,
|
|
2395
|
+
/**
|
|
2396
|
+
* SSR 渲染分辨率倍率;1 为全分辨率,0.5 的像素数约为全分辨率的四分之一。
|
|
2397
|
+
* 性能:按宽高平方影响光线步进、纹理带宽和临时目标显存,是 SSR 最有效的调优项。
|
|
2398
|
+
*/
|
|
2399
|
+
resolutionScale: 0.5,
|
|
2400
|
+
/**
|
|
2401
|
+
* 最大反射距离,单位为场景单位。
|
|
2402
|
+
* 性能:距离越大,光线覆盖范围越广,命中和纹理访问压力通常越高;不会简单地
|
|
2403
|
+
* 线性增加固定步数,但会增加长距离反射的平均成本。
|
|
2404
|
+
*/
|
|
2405
|
+
maxDistance: 8,
|
|
2406
|
+
/**
|
|
2407
|
+
* 深度厚度容差,用于 SSR 命中判定。
|
|
2408
|
+
* 性能:主要改变命中结果,不增加步进次数,性能影响很小。
|
|
2409
|
+
*/
|
|
2410
|
+
thickness: 0.15,
|
|
2411
|
+
/**
|
|
2412
|
+
* 反射叠加强度;0 表示不叠加反射。
|
|
2413
|
+
* 性能:只改变最终合成,不改变光线步进数量,性能影响很小。
|
|
2414
|
+
*/
|
|
2415
|
+
intensity: 0.85,
|
|
2416
|
+
/**
|
|
2417
|
+
* 屏幕边缘反射淡出宽度,范围通常为 0~1。
|
|
2418
|
+
* 性能:只增加少量边缘计算,不改变光线步进数量。
|
|
2419
|
+
*/
|
|
2420
|
+
screenEdgeFade: 0.2,
|
|
2421
|
+
/**
|
|
2422
|
+
* 是否使用黑色边缘淡出路径。
|
|
2423
|
+
* 性能:属于编译期分支,影响很小;修改后需要重建 SSR 节点。
|
|
2424
|
+
*/
|
|
2425
|
+
screenEdgeFadeBlack: !1,
|
|
2426
|
+
/**
|
|
2427
|
+
* 反射亮度上限,用于抑制 HDR 过曝。
|
|
2428
|
+
* 性能:只增加亮度限制计算,不改变采样数量。
|
|
2429
|
+
*/
|
|
2430
|
+
maxLuminance: 10,
|
|
2431
|
+
/**
|
|
2432
|
+
* SSR 光线步进质量,范围 0~1;越高步进越密。
|
|
2433
|
+
* 性能:直接影响每像素光线步数,成本近似线性增加;是仅次于分辨率的主要调优项。
|
|
2434
|
+
*/
|
|
2435
|
+
quality: 0.5,
|
|
2436
|
+
/**
|
|
2437
|
+
* 随机 GGX 反射的镜面偏置,控制采样方向集中程度。
|
|
2438
|
+
* 性能:不改变随机路径的步数,主要影响噪声和反射形状,性能影响很小。
|
|
2439
|
+
*/
|
|
2440
|
+
mirrorBias: 0.5,
|
|
2441
|
+
/**
|
|
2442
|
+
* 非随机 SSR 的模糊质量,通常为 1~3。
|
|
2443
|
+
* 性能:仅在 `stochastic=false` 时生效;质量越高模糊邻域采样越多,成本和显存
|
|
2444
|
+
* 带宽越高,并且修改会重建模糊材质。
|
|
2445
|
+
*/
|
|
2446
|
+
blurQuality: 2,
|
|
2447
|
+
/**
|
|
2448
|
+
* 光线步进在近端/远端的分布指数。
|
|
2449
|
+
* 性能:通常是编译期常量,不改变总步数;主要改变采样分布和命中质量,修改会重建节点。
|
|
2450
|
+
*/
|
|
2451
|
+
stepExponent: 2,
|
|
2452
|
+
/**
|
|
2453
|
+
* 环境反射强度,用于 SSR 未命中时的环境贡献。
|
|
2454
|
+
* 性能:只影响环境结果合成;启用环境节点时会保留对应纹理读取。
|
|
2455
|
+
*/
|
|
2456
|
+
environmentIntensity: Math.PI
|
|
2457
|
+
},
|
|
2458
|
+
bloom: {
|
|
2459
|
+
/**
|
|
2460
|
+
* Bloom 会创建多级降采样纹理,默认关闭以保持现有画面和显存占用不变。
|
|
2461
|
+
* 业务可通过 postProcessing.bloom 显式开启。
|
|
2462
|
+
*/
|
|
2463
|
+
enabled: !1,
|
|
2464
|
+
/** 辉光叠加强度,只影响最终合成,不改变采样数量。 */
|
|
2465
|
+
strength: 0.05,
|
|
2466
|
+
/** 辉光扩散半径,值越大光晕越宽。 */
|
|
2467
|
+
radius: 0.1,
|
|
2468
|
+
/** 仅让高亮区域产生辉光,避免普通材质整体泛白。 */
|
|
2469
|
+
threshold: 0.1,
|
|
2470
|
+
/** 平滑亮度阈值边缘,减少阈值附近的闪烁和硬边。 */
|
|
2471
|
+
smoothWidth: 0.01,
|
|
2472
|
+
/** 半分辨率可显著降低多级模糊的纹理带宽和显存成本。 */
|
|
2473
|
+
resolutionScale: 0.5
|
|
2474
|
+
},
|
|
2475
|
+
gtao: {
|
|
2476
|
+
/**
|
|
2477
|
+
* 是否启用 GTAO 屏幕空间环境遮蔽。
|
|
2478
|
+
* 性能:增加一次全屏遮蔽采样和合成;关闭可省掉整组 GPU、临时纹理和带宽开销。
|
|
2479
|
+
*/
|
|
2480
|
+
enabled: !0,
|
|
2481
|
+
/**
|
|
2482
|
+
* GTAO 渲染分辨率倍率;1 为全分辨率,0.5 的像素数约为全分辨率的四分之一。
|
|
2483
|
+
* 性能:按宽高平方影响采样、带宽和显存,是 GTAO 最有效的性能调优项。
|
|
2484
|
+
*/
|
|
2485
|
+
resolutionScale: 1,
|
|
2486
|
+
/**
|
|
2487
|
+
* 遮蔽半径,单位为场景单位。
|
|
2488
|
+
* 性能:主要影响搜索范围和结果,不直接增加固定采样数;过大可能增加有效纹理访问。
|
|
2489
|
+
*/
|
|
2490
|
+
radius: 0.15,
|
|
2491
|
+
/**
|
|
2492
|
+
* 深度厚度容差,用于遮蔽命中判断。
|
|
2493
|
+
* 性能:不改变采样数量,主要影响漏光和遮蔽边界,性能影响很小。
|
|
2494
|
+
*/
|
|
2495
|
+
thickness: 0.5,
|
|
2496
|
+
/**
|
|
2497
|
+
* 距离衰减指数。
|
|
2498
|
+
* 性能:只影响每像素衰减计算,不增加采样数量。
|
|
2499
|
+
*/
|
|
2500
|
+
distanceExponent: 0.5,
|
|
2501
|
+
/**
|
|
2502
|
+
* 距离衰减范围控制。
|
|
2503
|
+
* 性能:只影响每像素衰减计算,不增加采样数量。
|
|
2504
|
+
*/
|
|
2505
|
+
distanceFallOff: 1,
|
|
2506
|
+
/**
|
|
2507
|
+
* GTAO 结果缩放;0 表示不增加遮蔽,1 为基准效果。
|
|
2508
|
+
* 性能:只改变结果合成,不改变采样数量。
|
|
2509
|
+
*/
|
|
2510
|
+
scale: 0.5,
|
|
2511
|
+
/**
|
|
2512
|
+
* 每像素采样数。
|
|
2513
|
+
* 性能:直接影响 AO 采样循环,成本近似线性增加;提高后画质更稳定,但 GPU 时间同步增加。
|
|
2514
|
+
*/
|
|
2515
|
+
samples: 10,
|
|
2516
|
+
/**
|
|
2517
|
+
* 是否启用 GTAO 时域滤波。
|
|
2518
|
+
* 性能:需要历史纹理和额外时域合成开销,并增加显存占用;可降低低采样数下的噪声,
|
|
2519
|
+
* 仅在时域渲染条件满足时实际生效。
|
|
2520
|
+
*/
|
|
2521
|
+
useTemporalFiltering: !0
|
|
2522
|
+
}
|
|
2523
|
+
}, $ = ["vxgi", "ssgi", "gtao", "ssr", "traa"];
|
|
2524
|
+
function et() {
|
|
2525
|
+
const n = {};
|
|
2526
|
+
return $.forEach((e) => {
|
|
2527
|
+
const { enabled: t, ...r } = Ke[e];
|
|
2528
|
+
n[e] = { ...r };
|
|
2529
|
+
}), n;
|
|
2530
|
+
}
|
|
2531
|
+
function M(n, e, t, r, i, s = {}) {
|
|
2532
|
+
const a = et();
|
|
2533
|
+
return $.forEach((o) => {
|
|
2534
|
+
Object.assign(a[o], s[o] ?? {});
|
|
2535
|
+
}), {
|
|
2536
|
+
level: n,
|
|
2537
|
+
name: e,
|
|
2538
|
+
shadow: { enabled: t, mapSize: r },
|
|
2539
|
+
effects: {
|
|
2540
|
+
vxgi: !1,
|
|
2541
|
+
ssgi: !1,
|
|
2542
|
+
gtao: !1,
|
|
2543
|
+
ssr: !1,
|
|
2544
|
+
traa: !1,
|
|
2545
|
+
...i
|
|
2546
|
+
},
|
|
2547
|
+
parameters: a
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
const ft = [
|
|
2551
|
+
M(1, "L1", !1, 512, {}),
|
|
2552
|
+
M(2, "L2", !1, 512, { gtao: !0 }, { gtao: { resolutionScale: 0.5, samples: 4, scale: 0.25, useTemporalFiltering: !1 } }),
|
|
2553
|
+
M(3, "L3", !0, 512, { gtao: !0 }, { gtao: { resolutionScale: 0.5, samples: 4, scale: 0.3, useTemporalFiltering: !1 } }),
|
|
2554
|
+
M(4, "L4", !0, 1024, { gtao: !0 }, { gtao: { resolutionScale: 0.5, samples: 6, scale: 0.35, useTemporalFiltering: !1 } }),
|
|
2555
|
+
M(5, "L5", !0, 1024, { gtao: !0, traa: !0 }, { gtao: { resolutionScale: 0.5, samples: 6, scale: 0.4 } }),
|
|
2556
|
+
M(6, "L6", !0, 2048, { gtao: !0, traa: !0 }, { gtao: { resolutionScale: 0.75, samples: 8, scale: 0.45 } }),
|
|
2557
|
+
M(7, "L7", !0, 2048, { gtao: !0, ssgi: !0, traa: !0 }, { gtao: { resolutionScale: 1, samples: 6, scale: 0.25 }, ssgi: { sliceCount: 1, stepCount: 2, aoIntensity: 0.45, giIntensity: 1 } }),
|
|
2558
|
+
// Level 8 完整继承 defaults.ts 的参数,作为十二档画质的中间基准。
|
|
2559
|
+
M(8, "L8", !0, 2048, { gtao: !0, ssgi: !0, traa: !0, ssr: !0 }),
|
|
2560
|
+
M(9, "L9", !0, 2048, { gtao: !0, ssgi: !0, traa: !0, ssr: !0 }, { gtao: { resolutionScale: 1, samples: 12, scale: 0.55 }, ssgi: { sliceCount: 1, stepCount: 3, aoIntensity: 1.05, giIntensity: 2.1 }, ssr: { resolutionScale: 0.6, quality: 0.6, blurQuality: 2 } }),
|
|
2561
|
+
M(10, "L10", !0, 2048, { gtao: !0, ssgi: !0, traa: !0, ssr: !0 }, { gtao: { resolutionScale: 1, samples: 12, scale: 0.6 }, ssgi: { sliceCount: 2, stepCount: 4, aoIntensity: 1.1, giIntensity: 2.2 }, ssr: { resolutionScale: 0.75, quality: 0.7, binaryRefine: !0, blurQuality: 2 } }),
|
|
2562
|
+
M(11, "L11", !0, 4096, { gtao: !0, ssgi: !0, traa: !0, ssr: !0 }, { gtao: { resolutionScale: 1, samples: 16, scale: 0.7 }, ssgi: { sliceCount: 2, stepCount: 6, aoIntensity: 1.2, giIntensity: 2.4 }, ssr: { resolutionScale: 1, quality: 0.8, binaryRefine: !0, blurQuality: 3 } }),
|
|
2563
|
+
M(12, "L12", !0, 4096, { vxgi: !0, gtao: !0, ssgi: !0, traa: !0, ssr: !0 }, { vxgi: { resolution: 128, coneCount: 4, bounces: 1 }, gtao: { resolutionScale: 1, samples: 20, scale: 0.8 }, ssgi: { sliceCount: 3, stepCount: 8, aoIntensity: 1.3, giIntensity: 2.6 }, ssr: { resolutionScale: 1, quality: 0.9, binaryRefine: !0, blurQuality: 3 } })
|
|
2564
|
+
], yt = [
|
|
2565
|
+
{ id: "indoor", name: "Indoor", category: "indoor", hdrId: "11", hdrIntensity: 0.45, ambientIntensity: 0.5, directionalIntensity: 2 },
|
|
2566
|
+
{ id: "outdoor", name: "Outdoor", category: "outdoor", hdrId: "1", hdrIntensity: 0.5, ambientIntensity: 0.2, directionalIntensity: 3 }
|
|
2567
|
+
];
|
|
2568
|
+
function Mt(n) {
|
|
2569
|
+
return {
|
|
2570
|
+
...n,
|
|
2571
|
+
shadow: { ...n.shadow },
|
|
2572
|
+
effects: { ...n.effects },
|
|
2573
|
+
parameters: Object.fromEntries(
|
|
2574
|
+
$.map((e) => [e, { ...n.parameters[e] }])
|
|
2575
|
+
)
|
|
2576
|
+
};
|
|
2577
|
+
}
|
|
2578
|
+
async function j(n) {
|
|
2579
|
+
n?.assetFiles ? await F(n.assetFiles) : n?.assetFile ? await re(n.assetFile) : n?.assetBaseUrl && await F(null), n?.assetBaseUrl && ie(n.assetBaseUrl);
|
|
2580
|
+
}
|
|
2581
|
+
async function bt(n, e) {
|
|
2582
|
+
if (await j(n), (e ?? (typeof navigator < "u" && "gpu" in navigator ? "webgpu" : "webgl")) === "webgpu") {
|
|
2583
|
+
const { EngineKernelV3: i } = await import("./EngineKernel-CZX0Omjs.js"), s = new i(n);
|
|
2584
|
+
return await s.ready, s;
|
|
2585
|
+
}
|
|
2586
|
+
const { EngineKernelV2: r } = await import("./engine-3d-BsWcD-bl.js").then((i) => i.e);
|
|
2587
|
+
return new r(n);
|
|
2588
|
+
}
|
|
2589
|
+
async function wt(n) {
|
|
2590
|
+
await j(n);
|
|
2591
|
+
const { EngineKernel2d: e } = await import("./engine-2d-CA5-uNuf.js").then((t) => t.E);
|
|
2592
|
+
return new e(n);
|
|
2593
|
+
}
|
|
2594
|
+
async function vt(n) {
|
|
2595
|
+
await j(n);
|
|
2596
|
+
const { EngineKernel720: e } = await import("./engine-720-DiTJEtdN.js");
|
|
2597
|
+
return new e(n);
|
|
2598
|
+
}
|
|
2599
|
+
async function Et(n) {
|
|
2600
|
+
await j(n);
|
|
2601
|
+
const { EngineKernelGaussian: e } = await import("./engine-gaussian-BHUGuaw_.js");
|
|
2602
|
+
return new e(n);
|
|
2603
|
+
}
|
|
2604
|
+
export {
|
|
2605
|
+
wt as A,
|
|
2606
|
+
vt as B,
|
|
2607
|
+
Y as C,
|
|
2608
|
+
Ke as D,
|
|
2609
|
+
W as E,
|
|
2610
|
+
mt as F,
|
|
2611
|
+
v as G,
|
|
2612
|
+
Et as H,
|
|
2613
|
+
pt as M,
|
|
2614
|
+
gt as a,
|
|
2615
|
+
ot as b,
|
|
2616
|
+
lt as c,
|
|
2617
|
+
ct as d,
|
|
2618
|
+
nt as e,
|
|
2619
|
+
ut as f,
|
|
2620
|
+
at as g,
|
|
2621
|
+
b as h,
|
|
2622
|
+
le as i,
|
|
2623
|
+
ft as j,
|
|
2624
|
+
Mt as k,
|
|
2625
|
+
yt as l,
|
|
2626
|
+
se as m,
|
|
2627
|
+
dt as n,
|
|
2628
|
+
Pe as o,
|
|
2629
|
+
X as p,
|
|
2630
|
+
ze as q,
|
|
2631
|
+
Le as r,
|
|
2632
|
+
ht as s,
|
|
2633
|
+
xe as t,
|
|
2634
|
+
Re as u,
|
|
2635
|
+
Oe as v,
|
|
2636
|
+
Xe as w,
|
|
2637
|
+
je as x,
|
|
2638
|
+
De as y,
|
|
2639
|
+
bt as z
|
|
2640
|
+
};
|