s3m-three 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/browser/s3m-three.iife.ext-three.js +163 -15
- package/dist/browser/s3m-three.iife.js +190 -42
- package/dist/core/AtlasAlpha.d.ts +31 -0
- package/dist/core/AtlasAlpha.d.ts.map +1 -0
- package/dist/core/AtlasAlpha.js +82 -0
- package/dist/core/AtlasAlpha.js.map +1 -0
- package/dist/core/AtlasPack.d.ts +57 -0
- package/dist/core/AtlasPack.d.ts.map +1 -0
- package/dist/core/AtlasPack.js +81 -0
- package/dist/core/AtlasPack.js.map +1 -0
- package/dist/core/AtlasUpgrade.d.ts +68 -0
- package/dist/core/AtlasUpgrade.d.ts.map +1 -0
- package/dist/core/AtlasUpgrade.js +154 -0
- package/dist/core/AtlasUpgrade.js.map +1 -0
- package/dist/core/DXTTextureDecode.d.ts.map +1 -1
- package/dist/core/DXTTextureDecode.js +28 -6
- package/dist/core/DXTTextureDecode.js.map +1 -1
- package/dist/core/Draco.d.ts +52 -0
- package/dist/core/Draco.d.ts.map +1 -0
- package/dist/core/Draco.js +323 -0
- package/dist/core/Draco.js.map +1 -0
- package/dist/core/MeshOpt.d.ts +14 -3
- package/dist/core/MeshOpt.d.ts.map +1 -1
- package/dist/core/MeshOpt.js +101 -18
- package/dist/core/MeshOpt.js.map +1 -1
- package/dist/core/S3M1Parser.d.ts +16 -0
- package/dist/core/S3M1Parser.d.ts.map +1 -0
- package/dist/core/S3M1Parser.js +367 -0
- package/dist/core/S3M1Parser.js.map +1 -0
- package/dist/core/S3MBParser.d.ts +43 -1
- package/dist/core/S3MBParser.d.ts.map +1 -1
- package/dist/core/S3MBParser.js +101 -23
- package/dist/core/S3MBParser.js.map +1 -1
- package/dist/core/S3MBlockParser.d.ts +209 -0
- package/dist/core/S3MBlockParser.d.ts.map +1 -0
- package/dist/core/S3MBlockParser.js +789 -0
- package/dist/core/S3MBlockParser.js.map +1 -0
- package/dist/core/TextureFormat.d.ts +0 -1
- package/dist/core/TextureFormat.d.ts.map +1 -1
- package/dist/core/TextureFormat.js +19 -2
- package/dist/core/TextureFormat.js.map +1 -1
- package/dist/core/types.d.ts +65 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/render/DatasetManager.d.ts +11 -0
- package/dist/render/DatasetManager.d.ts.map +1 -1
- package/dist/render/DatasetManager.js +68 -3
- package/dist/render/DatasetManager.js.map +1 -1
- package/dist/render/GeometryBuilder.d.ts +3 -0
- package/dist/render/GeometryBuilder.d.ts.map +1 -1
- package/dist/render/GeometryBuilder.js +189 -17
- package/dist/render/GeometryBuilder.js.map +1 -1
- package/dist/render/GroundManager.d.ts +57 -1
- package/dist/render/GroundManager.d.ts.map +1 -1
- package/dist/render/GroundManager.js +149 -21
- package/dist/render/GroundManager.js.map +1 -1
- package/dist/render/Picker.d.ts.map +1 -1
- package/dist/render/Picker.js +12 -4
- package/dist/render/Picker.js.map +1 -1
- package/dist/render/PointCloudMaterial.d.ts +36 -0
- package/dist/render/PointCloudMaterial.d.ts.map +1 -0
- package/dist/render/PointCloudMaterial.js +159 -0
- package/dist/render/PointCloudMaterial.js.map +1 -0
- package/dist/render/S3MViewer.d.ts +75 -3
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +284 -54
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/StyleMaterial.d.ts +6 -0
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +125 -2
- package/dist/render/StyleMaterial.js.map +1 -1
- package/dist/render/TextureFactory.d.ts.map +1 -1
- package/dist/render/TextureFactory.js +64 -10
- package/dist/render/TextureFactory.js.map +1 -1
- package/dist/tileset/S3MTileset.d.ts +177 -1
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +754 -60
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/dist/vendor/crunch_decoder.module.js +12 -18
- package/dist/vendor/draco_decoder.module.js +3178 -0
- package/docs/API.md +150 -11
- package/package.json +1 -1
- package/src/core/AtlasAlpha.ts +82 -0
- package/src/core/AtlasPack.ts +127 -0
- package/src/core/AtlasUpgrade.ts +182 -0
- package/src/core/DXTTextureDecode.ts +28 -6
- package/src/core/Draco.ts +409 -0
- package/src/core/MeshOpt.ts +124 -18
- package/src/core/S3M1Parser.ts +430 -0
- package/src/core/S3MBParser.ts +112 -21
- package/src/core/S3MBlockParser.ts +953 -0
- package/src/core/TextureFormat.ts +20 -2
- package/src/core/types.ts +63 -0
- package/src/index.ts +3 -1
- package/src/render/DatasetManager.ts +70 -5
- package/src/render/GeometryBuilder.ts +212 -16
- package/src/render/GroundManager.ts +174 -20
- package/src/render/Picker.ts +13 -4
- package/src/render/PointCloudMaterial.ts +174 -0
- package/src/render/S3MViewer.ts +322 -55
- package/src/render/StyleMaterial.ts +135 -2
- package/src/render/TextureFactory.ts +67 -11
- package/src/tileset/S3MTileset.ts +798 -65
- package/src/vendor/crunch_decoder.module.js +12 -18
- package/src/vendor/draco_decoder.module.d.ts +128 -0
- package/src/vendor/draco_decoder.module.js +3178 -0
package/src/render/S3MViewer.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* MapLibre / Mapbox 的胶水层在 s3m-maplibre 包的 S3MLayer.ts。
|
|
22
22
|
*/
|
|
23
23
|
import * as THREE from 'three';
|
|
24
|
-
import { S3MTileset } from '../tileset/S3MTileset.js';
|
|
24
|
+
import { S3MTileset, usableGeoBounds } from '../tileset/S3MTileset.js';
|
|
25
25
|
import type { BuildTileFn, TileNode } from '../tileset/S3MTileset.js';
|
|
26
26
|
import { setMaxAnisotropy } from './TextureFactory.js';
|
|
27
27
|
import {
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
createOutlineMaterial,
|
|
34
34
|
} from './StyleMaterial.js';
|
|
35
35
|
import type { OutlineMaterial, StyledMaterial, StyleMode, SelectMode, S3MStyle } from './StyleMaterial.js';
|
|
36
|
+
import { applyPointCloudStyle, isOwnedPointCloudMaterial } from './PointCloudMaterial.js';
|
|
36
37
|
import { Picker } from './Picker.js';
|
|
37
38
|
import {
|
|
38
39
|
buildFeatureEdgeSegments,
|
|
@@ -143,7 +144,42 @@ export interface PickResult extends FeatureAttributes {
|
|
|
143
144
|
mesh: THREE.Mesh;
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
export interface LightOptions {
|
|
148
|
+
/** 环境光强度,默认 0.4 */
|
|
149
|
+
ambientIntensity?: number;
|
|
150
|
+
/** 环境光颜色,十六进制或 CSS 颜色字符串,默认 0xffffff */
|
|
151
|
+
ambientColor?: number | string;
|
|
152
|
+
/** 半球光天空强度,默认 0.8 */
|
|
153
|
+
hemisphereIntensity?: number;
|
|
154
|
+
/** 半球光天空颜色,默认 0xffffff */
|
|
155
|
+
hemisphereColor?: number | string;
|
|
156
|
+
/** 半球光地面颜色,默认 0xb6c0cb */
|
|
157
|
+
hemisphereGroundColor?: number | string;
|
|
158
|
+
/** 主平行光强度,默认 1.0 */
|
|
159
|
+
keyLightIntensity?: number;
|
|
160
|
+
/** 主平行光颜色,默认 0xffffff */
|
|
161
|
+
keyLightColor?: number | string;
|
|
162
|
+
/** 补光强度,默认 0.35 */
|
|
163
|
+
fillLightIntensity?: number;
|
|
164
|
+
/** 补光颜色,默认 0xffffff */
|
|
165
|
+
fillLightColor?: number | string;
|
|
166
|
+
/**
|
|
167
|
+
* 光照朝向模式:
|
|
168
|
+
* - 'headlamp': 默认头灯模式(随相机 bearing 旋转,受光面始终面向镜头)
|
|
169
|
+
* - 'fixed': 固定世界坐标方位角(如太阳光模拟)
|
|
170
|
+
*/
|
|
171
|
+
mode?: 'headlamp' | 'fixed';
|
|
172
|
+
/** 方位角(度,0~360),固定模式下以正北为 0 顺时针,头灯模式下为相机相对偏角,默认 150 */
|
|
173
|
+
azimuth?: number;
|
|
174
|
+
/** 太阳/光源仰角(度,0~90),默认 50 */
|
|
175
|
+
altitude?: number;
|
|
176
|
+
}
|
|
177
|
+
|
|
146
178
|
export interface S3MViewerOptions {
|
|
179
|
+
/** 光照参数配置(强度、颜色、朝向模式、太阳方位角/仰角等) */
|
|
180
|
+
light?: LightOptions;
|
|
181
|
+
/** 全局总驻留瓦片上限(多数据集多图层共享预算),在所有可见数据集之间动态均衡分配,彻底防止爆显存 */
|
|
182
|
+
totalMaxTiles?: number;
|
|
147
183
|
/** 单数据集简写 */
|
|
148
184
|
configUrl?: string;
|
|
149
185
|
/** 多数据集 */
|
|
@@ -191,6 +227,8 @@ interface MeshUserData {
|
|
|
191
227
|
geoRef?: GeoPackageEntry;
|
|
192
228
|
tileRef?: ParsedTile;
|
|
193
229
|
primitiveType?: number;
|
|
230
|
+
/** 点云对象(THREE.Points):不吃面着色,也没有棱可描 */
|
|
231
|
+
isPointCloud?: boolean;
|
|
194
232
|
/** CPU 端现算的棱线对象(等宽线 Mesh) */
|
|
195
233
|
edges?: THREE.Object3D | null;
|
|
196
234
|
/** 数据自带棱线包(此时不再用 EdgesGeometry 现算) */
|
|
@@ -211,6 +249,11 @@ function styledMaterialOf(mesh: THREE.Mesh): StyledMaterial | null {
|
|
|
211
249
|
return styled?.userData?.isStyled ? styled : null;
|
|
212
250
|
}
|
|
213
251
|
|
|
252
|
+
/** 点云是 THREE.Points 不是 Mesh,样式 / 拾取 / 卸载链路都靠这个判定放行 */
|
|
253
|
+
function isPointCloudObject(o: THREE.Object3D): o is THREE.Points {
|
|
254
|
+
return (o as unknown as THREE.Points).isPoints === true;
|
|
255
|
+
}
|
|
256
|
+
|
|
214
257
|
/* ------------------------------------------------------------------ 图层 */
|
|
215
258
|
|
|
216
259
|
export class S3MViewer {
|
|
@@ -272,7 +315,7 @@ export class S3MViewer {
|
|
|
272
315
|
private _heightMax = -Infinity;
|
|
273
316
|
private readonly _boxScratch = new THREE.Box3();
|
|
274
317
|
private _selection: PickResult | null = null;
|
|
275
|
-
private _selectedMeshes: THREE.
|
|
318
|
+
private _selectedMeshes: THREE.Object3D[] = [];
|
|
276
319
|
private _outlineMesh: THREE.Mesh | null = null;
|
|
277
320
|
private _outlineMeshes: THREE.Mesh[] = [];
|
|
278
321
|
private _selectionEdges: THREE.Mesh | null = null;
|
|
@@ -286,8 +329,24 @@ export class S3MViewer {
|
|
|
286
329
|
private _needsRepaint = true;
|
|
287
330
|
private _frame = 0;
|
|
288
331
|
private readonly _fps = { value: 0, last: 0 };
|
|
289
|
-
private
|
|
290
|
-
private
|
|
332
|
+
private _ambientLight!: THREE.AmbientLight;
|
|
333
|
+
private _hemiLight!: THREE.HemisphereLight;
|
|
334
|
+
private _keyLight!: THREE.DirectionalLight;
|
|
335
|
+
private _fillLight!: THREE.DirectionalLight;
|
|
336
|
+
private readonly _lightOptions: Required<LightOptions> = {
|
|
337
|
+
ambientIntensity: 0.4,
|
|
338
|
+
ambientColor: 0xffffff,
|
|
339
|
+
hemisphereIntensity: 0.8,
|
|
340
|
+
hemisphereColor: 0xffffff,
|
|
341
|
+
hemisphereGroundColor: 0xb6c0cb,
|
|
342
|
+
keyLightIntensity: 1.0,
|
|
343
|
+
keyLightColor: 0xffffff,
|
|
344
|
+
fillLightIntensity: 0.35,
|
|
345
|
+
fillLightColor: 0xffffff,
|
|
346
|
+
mode: 'headlamp',
|
|
347
|
+
azimuth: 150,
|
|
348
|
+
altitude: 50,
|
|
349
|
+
};
|
|
291
350
|
|
|
292
351
|
private readonly _datasetQueue: S3MDatasetOptions[];
|
|
293
352
|
|
|
@@ -301,10 +360,20 @@ export class S3MViewer {
|
|
|
301
360
|
this._onRequestRepaint();
|
|
302
361
|
}
|
|
303
362
|
|
|
363
|
+
triggerRepaint(): void {
|
|
364
|
+
this._requestRepaint();
|
|
365
|
+
}
|
|
366
|
+
|
|
304
367
|
constructor(options: S3MViewerOptions = {}) {
|
|
305
368
|
this.options = options;
|
|
306
369
|
this.pickable = options.pickable !== false;
|
|
307
370
|
this._lodRangeScale = options.lodRangeScale ?? 1.0;
|
|
371
|
+
if (options.light) {
|
|
372
|
+
Object.assign(this._lightOptions, options.light);
|
|
373
|
+
}
|
|
374
|
+
if (options.totalMaxTiles) {
|
|
375
|
+
this._datasetManager.totalMaxTiles = options.totalMaxTiles;
|
|
376
|
+
}
|
|
308
377
|
this._onRequestRepaint = options.onRequestRepaint ?? (() => {});
|
|
309
378
|
this._getBearing = options.getBearing;
|
|
310
379
|
this.onStats = options.onStats ?? (() => {});
|
|
@@ -313,6 +382,11 @@ export class S3MViewer {
|
|
|
313
382
|
this.onError = options.onError ?? ((e) => console.warn('[s3m]', e));
|
|
314
383
|
|
|
315
384
|
this.groundManager = new GroundManager(options.ground);
|
|
385
|
+
if (!this.scene) {
|
|
386
|
+
this.scene = new THREE.Scene();
|
|
387
|
+
this.scene.add(this.groundManager.group);
|
|
388
|
+
this._addLights();
|
|
389
|
+
}
|
|
316
390
|
this._featureStyleManager = new FeatureStyleManager(
|
|
317
391
|
() => this.style.floorHeight,
|
|
318
392
|
() => this._requestRepaint(),
|
|
@@ -404,24 +478,70 @@ export class S3MViewer {
|
|
|
404
478
|
this.canvas = null;
|
|
405
479
|
}
|
|
406
480
|
|
|
481
|
+
private _createLights(): void {
|
|
482
|
+
this._ambientLight = new THREE.AmbientLight(this._lightOptions.ambientColor as any, this._lightOptions.ambientIntensity);
|
|
483
|
+
this._hemiLight = new THREE.HemisphereLight(
|
|
484
|
+
this._lightOptions.hemisphereColor as any,
|
|
485
|
+
this._lightOptions.hemisphereGroundColor as any,
|
|
486
|
+
this._lightOptions.hemisphereIntensity
|
|
487
|
+
);
|
|
488
|
+
this._keyLight = new THREE.DirectionalLight(this._lightOptions.keyLightColor as any, this._lightOptions.keyLightIntensity);
|
|
489
|
+
this._fillLight = new THREE.DirectionalLight(this._lightOptions.fillLightColor as any, this._lightOptions.fillLightIntensity);
|
|
490
|
+
this._updateLights();
|
|
491
|
+
}
|
|
492
|
+
|
|
407
493
|
private _addLights(): void {
|
|
408
494
|
if (!this.scene) return;
|
|
409
|
-
|
|
410
|
-
this.scene.add(
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
495
|
+
if (!this._ambientLight) this._createLights();
|
|
496
|
+
this.scene.add(this._ambientLight, this._hemiLight, this._keyLight, this._fillLight);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
private _applyLightOptions(): void {
|
|
500
|
+
if (this._ambientLight) {
|
|
501
|
+
this._ambientLight.intensity = this._lightOptions.ambientIntensity;
|
|
502
|
+
this._ambientLight.color.set(this._lightOptions.ambientColor as any);
|
|
503
|
+
}
|
|
504
|
+
if (this._hemiLight) {
|
|
505
|
+
this._hemiLight.intensity = this._lightOptions.hemisphereIntensity;
|
|
506
|
+
this._hemiLight.color.set(this._lightOptions.hemisphereColor as any);
|
|
507
|
+
this._hemiLight.groundColor.set(this._lightOptions.hemisphereGroundColor as any);
|
|
508
|
+
}
|
|
509
|
+
if (this._keyLight) {
|
|
510
|
+
this._keyLight.intensity = this._lightOptions.keyLightIntensity;
|
|
511
|
+
this._keyLight.color.set(this._lightOptions.keyLightColor as any);
|
|
512
|
+
}
|
|
513
|
+
if (this._fillLight) {
|
|
514
|
+
this._fillLight.intensity = this._lightOptions.fillLightIntensity;
|
|
515
|
+
this._fillLight.color.set(this._lightOptions.fillLightColor as any);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* 动态设置光照配置
|
|
521
|
+
*/
|
|
522
|
+
setLight(options: Partial<LightOptions>): void {
|
|
523
|
+
Object.assign(this._lightOptions, options);
|
|
524
|
+
this._applyLightOptions();
|
|
525
|
+
this._updateLights();
|
|
526
|
+
this._needsRepaint = true;
|
|
527
|
+
this.triggerRepaint();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* 获取当前光照配置快照
|
|
532
|
+
*/
|
|
533
|
+
getLight(): LightOptions {
|
|
534
|
+
return { ...this._lightOptions };
|
|
417
535
|
}
|
|
418
536
|
|
|
419
537
|
/**
|
|
420
|
-
*
|
|
421
|
-
*
|
|
538
|
+
* 更新光照位置与方向:
|
|
539
|
+
* - 'headlamp': 随地图方位角 bearing 旋转,主光始终在镜头上方,受光面面向镜头;
|
|
540
|
+
* - 'fixed': 固定世界方位角(如模拟恒定日光)。
|
|
422
541
|
*/
|
|
423
542
|
private _updateLights(): void {
|
|
424
|
-
|
|
543
|
+
if (!this._keyLight || !this._fillLight) return;
|
|
544
|
+
const bearing = this._lightOptions.mode === 'fixed' ? 0 : (((this._getBearing?.() ?? 0) * Math.PI) / 180);
|
|
425
545
|
const place = (light: THREE.DirectionalLight | null, azOffsetDeg: number, elevDeg: number): void => {
|
|
426
546
|
if (!light) return;
|
|
427
547
|
const az = bearing + (azOffsetDeg * Math.PI) / 180;
|
|
@@ -429,8 +549,8 @@ export class S3MViewer {
|
|
|
429
549
|
const c = Math.cos(el);
|
|
430
550
|
light.position.set(Math.sin(az) * c, Math.cos(az) * c, Math.sin(el)).multiplyScalar(1000);
|
|
431
551
|
};
|
|
432
|
-
place(this._keyLight,
|
|
433
|
-
place(this._fillLight,
|
|
552
|
+
place(this._keyLight, this._lightOptions.azimuth, this._lightOptions.altitude);
|
|
553
|
+
place(this._fillLight, this._lightOptions.azimuth + 65, Math.max(10, this._lightOptions.altitude - 20));
|
|
434
554
|
}
|
|
435
555
|
|
|
436
556
|
/* ---------------------------------------------------------------- 数据集 */
|
|
@@ -540,8 +660,11 @@ export class S3MViewer {
|
|
|
540
660
|
/**
|
|
541
661
|
* 物理地表网格的高度跟随「主数据集的应用层抬升量」,
|
|
542
662
|
* 保证贴地后地表覆盖 / 地下透视的物理关系与被抬升的模型一致。
|
|
663
|
+
* `ground.trackHeight = false` 时不跟随:地表钉在当前高度,
|
|
664
|
+
* 高程调整只动模型不动切割面。
|
|
543
665
|
*/
|
|
544
666
|
private _syncGroundHeight(): void {
|
|
667
|
+
if (!this.groundManager.trackHeight) return;
|
|
545
668
|
const primaryId = this.datasets.keys().next().value;
|
|
546
669
|
if (!primaryId) return;
|
|
547
670
|
const userOffset = this._datasetManager.getDatasetHeight(primaryId);
|
|
@@ -704,6 +827,15 @@ export class S3MViewer {
|
|
|
704
827
|
this._requestRepaint();
|
|
705
828
|
}
|
|
706
829
|
|
|
830
|
+
/**
|
|
831
|
+
* 开关「物理地表作为深度遮挡源」:开启后地表以下的几何被真正切掉。
|
|
832
|
+
* 不依赖宿主是否开启 MapLibre terrain——深度缓冲是共用的,谁更近谁遮挡。
|
|
833
|
+
*/
|
|
834
|
+
setGroundOcclude(enabled: boolean): void {
|
|
835
|
+
this.groundManager.setOcclude(enabled);
|
|
836
|
+
this._requestRepaint();
|
|
837
|
+
}
|
|
838
|
+
|
|
707
839
|
/* ---------------------------------------------------------------- 状态纹理同步 */
|
|
708
840
|
|
|
709
841
|
private _isFeatureHidden(smid: number | null, _batchId: number): boolean {
|
|
@@ -773,6 +905,7 @@ export class S3MViewer {
|
|
|
773
905
|
mesh.geometry?.dispose?.();
|
|
774
906
|
const styled = styledMaterialOf(mesh);
|
|
775
907
|
if (styled) styled.dispose();
|
|
908
|
+
if (isOwnedPointCloudMaterial(mesh.material)) (mesh.material as THREE.Material).dispose();
|
|
776
909
|
this._featureStyleManager.disposeMesh(mesh);
|
|
777
910
|
});
|
|
778
911
|
});
|
|
@@ -808,8 +941,10 @@ export class S3MViewer {
|
|
|
808
941
|
continue;
|
|
809
942
|
}
|
|
810
943
|
|
|
944
|
+
// 点云是 THREE.Points 不是 Mesh,但同样要进拾取表、吃不透明度、跟着着色模式走
|
|
945
|
+
const isPoints = isPointCloudObject(child);
|
|
811
946
|
const mesh = child as THREE.Mesh;
|
|
812
|
-
if (!mesh.isMesh) continue;
|
|
947
|
+
if (!mesh.isMesh && !isPoints) continue;
|
|
813
948
|
const ud = mesh.userData as MeshUserData;
|
|
814
949
|
ud.pickId = this._allocPickId();
|
|
815
950
|
ud.datasetId = entry.id;
|
|
@@ -822,41 +957,141 @@ export class S3MViewer {
|
|
|
822
957
|
});
|
|
823
958
|
this._indexMeshSmids(mesh);
|
|
824
959
|
this._addMeshHeight(mesh);
|
|
825
|
-
|
|
960
|
+
// 点云没有「棱」:给一堆孤立点建 EdgesGeometry 又慢又没意义
|
|
961
|
+
if (this._edgesEnabled && !hasNativeEdges && !isPoints) this._ensureEdges(mesh);
|
|
826
962
|
|
|
827
963
|
// 新瓦片要继承当前的不透明度与着色方式
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
mat
|
|
832
|
-
mat
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
964
|
+
if (isPoints) {
|
|
965
|
+
this._applyPointCloudStyle(child as THREE.Points);
|
|
966
|
+
} else {
|
|
967
|
+
const mat = styledMaterialOf(mesh);
|
|
968
|
+
if (mat) {
|
|
969
|
+
const o = Math.min(1, Math.max(0.05, this.style.opacity ?? 1));
|
|
970
|
+
mat.opacity = o;
|
|
971
|
+
mat.transparent = o < 0.999;
|
|
972
|
+
mat.depthWrite = o >= 0.999;
|
|
973
|
+
mat.flatShading = this.style.shading !== 'smooth';
|
|
974
|
+
mat.needsUpdate = true;
|
|
975
|
+
this._featureStyleManager.applyOverridesToMesh(mesh);
|
|
976
|
+
}
|
|
837
977
|
}
|
|
978
|
+
|
|
979
|
+
// 选中状态也要跟着继承:LOD 细化 / 瓦片重载会换掉 mesh,
|
|
980
|
+
// 不重放就出现「点中的那栋楼一拉近就没高亮了」
|
|
981
|
+
this._replaySelectionOnto(mesh);
|
|
838
982
|
}
|
|
839
983
|
}
|
|
840
984
|
|
|
841
|
-
/**
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
985
|
+
/**
|
|
986
|
+
* 把「当前选中」重放到一块新登记的 mesh 上。
|
|
987
|
+
*
|
|
988
|
+
* 为什么需要:选中高亮是直接写在各 mesh 材质的 uSelBatch / uSelWhole 上的,
|
|
989
|
+
* 而 LOD 细化、瓦片重载、淘汰回补都会换掉 mesh —— 不重放的话,用户点中的那栋楼
|
|
990
|
+
* 一细化就失去高亮(状态还在 _selection.smid 里,只是没人往新 mesh 上刷)。
|
|
991
|
+
*/
|
|
992
|
+
/**
|
|
993
|
+
* 判断一块无 SMID 单体化信息的网格(如倾斜摄影或白模整块)是否空间覆盖当前选中命中点
|
|
994
|
+
*/
|
|
995
|
+
private _isMeshCoveringSelection(mesh: THREE.Mesh, sel: PickResult): boolean {
|
|
996
|
+
if (!sel.point) return false;
|
|
997
|
+
const geom = mesh.geometry;
|
|
998
|
+
if (!geom) return false;
|
|
999
|
+
if (!geom.boundingBox) geom.computeBoundingBox();
|
|
1000
|
+
if (!geom.boundingBox) return false;
|
|
1001
|
+
|
|
1002
|
+
// 将选中的三维场景点转入网格局部坐标系进行包围盒检测
|
|
1003
|
+
const pt = new THREE.Vector3(sel.point[0], sel.point[1], sel.point[2]);
|
|
1004
|
+
mesh.updateWorldMatrix(true, false);
|
|
1005
|
+
const inv = mesh.matrixWorld.clone().invert();
|
|
1006
|
+
const localPt = pt.applyMatrix4(inv);
|
|
1007
|
+
|
|
1008
|
+
// 留出 0.5 米容差,避免刚好处于三角网格边缘缝隙时漏判
|
|
1009
|
+
const box = geom.boundingBox.clone().expandByScalar(0.5);
|
|
1010
|
+
return box.containsPoint(localPt);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
private _replaySelectionOnto(mesh: THREE.Mesh): void {
|
|
1014
|
+
const sel = this._selection;
|
|
1015
|
+
if (!sel) return;
|
|
1016
|
+
const mat = styledMaterialOf(mesh);
|
|
1017
|
+
if (!mat) return;
|
|
1018
|
+
|
|
1019
|
+
const ud = mesh.userData as MeshUserData;
|
|
1020
|
+
let batchId = -1;
|
|
1021
|
+
if (sel.smid != null) {
|
|
1022
|
+
const pickInfo = ud.geoRef?.pickInfo as Record<string | number, unknown> | undefined;
|
|
1023
|
+
const entry = pickInfo?.[sel.smid] ?? pickInfo?.[String(sel.smid)];
|
|
1024
|
+
batchId = entry ? pickEntryId(entry as any) : -1;
|
|
1025
|
+
}
|
|
1026
|
+
// 拾取命中的那块 mesh 用原始 batchId;同一 smid 散在别的几何里时按各自的区间算
|
|
1027
|
+
if (mesh === sel.mesh) batchId = sel.batchId;
|
|
1028
|
+
// 这块几何里根本没有这个单体 → 不动它(否则会把无关瓦片一起点亮)
|
|
1029
|
+
if (batchId < 0 && mesh !== sel.mesh) {
|
|
1030
|
+
if (sel.smid == null && ud.datasetId === sel.datasetId && this._isMeshCoveringSelection(mesh, sel)) {
|
|
1031
|
+
batchId = -1;
|
|
1032
|
+
} else {
|
|
1033
|
+
return;
|
|
852
1034
|
}
|
|
853
|
-
this._unindexMeshSmids(mesh);
|
|
854
|
-
this._removeMeshHeight(mesh);
|
|
855
|
-
this._featureStyleManager.disposeMesh(mesh);
|
|
856
|
-
const styled = styledMaterialOf(mesh);
|
|
857
|
-
styled?.dispose?.();
|
|
858
|
-
mesh.geometry?.dispose?.();
|
|
859
1035
|
}
|
|
1036
|
+
|
|
1037
|
+
mat.userData.uSelBatch.value = batchId;
|
|
1038
|
+
mat.userData.uSelWhole.value = batchId < 0 ? 1 : 0;
|
|
1039
|
+
if (!this._selectedMeshes.includes(mesh)) this._selectedMeshes.push(mesh);
|
|
1040
|
+
if (isPointCloudObject(mesh)) this._applyPointCloudStyle(mesh, true);
|
|
1041
|
+
this.uniforms.uSelMix.value = 1;
|
|
1042
|
+
this.uniforms.uDim.value = this.style.dimOthers ? 1 : 0;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/** 瓦片被淘汰:从拾取表和场景里摘掉,并深度彻底释放所有几何体与材质显存 */
|
|
1046
|
+
private _unregisterTile(node: TileNode, entry: S3MDatasetEntry): void {
|
|
1047
|
+
node.group.traverse((child) => {
|
|
1048
|
+
const obj = child as any;
|
|
1049
|
+
if (obj.userData?.pickId != null) {
|
|
1050
|
+
this._pickables.delete(obj.userData.pickId);
|
|
1051
|
+
this._releasePickId(obj.userData.pickId);
|
|
1052
|
+
obj.userData.pickId = undefined;
|
|
1053
|
+
}
|
|
1054
|
+
if (obj.isMesh) {
|
|
1055
|
+
const wasSelected = this._selectedMeshes.includes(obj);
|
|
1056
|
+
if (wasSelected) {
|
|
1057
|
+
this._selectedMeshes = this._selectedMeshes.filter((m) => m !== obj);
|
|
1058
|
+
}
|
|
1059
|
+
const currentSel = this._selection;
|
|
1060
|
+
if (currentSel && currentSel.mesh === obj) {
|
|
1061
|
+
const smid = currentSel.smid;
|
|
1062
|
+
const carrier = (
|
|
1063
|
+
smid != null
|
|
1064
|
+
? [...(this._smidMeshes.get(smid) ?? [])].find((m) => m !== obj && m.parent)
|
|
1065
|
+
: this._selectedMeshes.find((m) => m !== obj && m.parent && (m.parent as any).visible) ??
|
|
1066
|
+
this._selectedMeshes.find((m) => m !== obj && m.parent)
|
|
1067
|
+
) as THREE.Mesh | undefined;
|
|
1068
|
+
if (carrier) {
|
|
1069
|
+
// 选中身份交给承载同一 smid 的另一块 mesh(通常是接替显示的子瓦)
|
|
1070
|
+
this._selection = { ...currentSel, mesh: carrier };
|
|
1071
|
+
this._replaySelectionOnto(carrier);
|
|
1072
|
+
} else {
|
|
1073
|
+
this.select(null);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
this._unindexMeshSmids(obj);
|
|
1077
|
+
this._removeMeshHeight(obj);
|
|
1078
|
+
this._featureStyleManager.disposeMesh(obj);
|
|
1079
|
+
const styled = styledMaterialOf(obj);
|
|
1080
|
+
styled?.dispose?.();
|
|
1081
|
+
}
|
|
1082
|
+
// 深度释放材质(点云、普通材质、材质数组)
|
|
1083
|
+
if (isOwnedPointCloudMaterial(obj.material)) {
|
|
1084
|
+
(obj.material as THREE.Material).dispose();
|
|
1085
|
+
} else if (obj.material) {
|
|
1086
|
+
if (Array.isArray(obj.material)) {
|
|
1087
|
+
obj.material.forEach((m: any) => m?.dispose?.());
|
|
1088
|
+
} else {
|
|
1089
|
+
obj.material.dispose?.();
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
// 释放几何体显存缓冲
|
|
1093
|
+
obj.geometry?.dispose?.();
|
|
1094
|
+
});
|
|
860
1095
|
entry.root.remove(node.group);
|
|
861
1096
|
}
|
|
862
1097
|
|
|
@@ -890,6 +1125,8 @@ export class S3MViewer {
|
|
|
890
1125
|
this.style = { ...this.style, ...patch };
|
|
891
1126
|
this._applyStyleUniforms();
|
|
892
1127
|
if ('opacity' in patch || 'depthWrite' in patch) this._applyOpacity();
|
|
1128
|
+
// 点云不走 StyleMaterial 的 uMode,模式切换要单独同步它的材质
|
|
1129
|
+
if ('mode' in patch) this._applyPointCloudStyles();
|
|
893
1130
|
if ('shading' in patch) this._applyShading();
|
|
894
1131
|
if ('edges' in patch) this.setEdges(!!this.style.edges);
|
|
895
1132
|
else if ('edgeColor' in patch || 'edgeWidth' in patch || 'opacity' in patch || 'edgeOpacity' in patch) {
|
|
@@ -959,6 +1196,10 @@ export class S3MViewer {
|
|
|
959
1196
|
// 关键:若 depthWrite 开启(默认开启),即使半透明也保持深度写入,完美遮挡背面棱线,防止背面线条透出
|
|
960
1197
|
const keepDepth = this.style.depthWrite !== false;
|
|
961
1198
|
for (const p of this._pickables.values()) {
|
|
1199
|
+
if (isPointCloudObject(p.mesh)) {
|
|
1200
|
+
this._applyPointCloudStyle(p.mesh);
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
962
1203
|
const m = styledMaterialOf(p.mesh);
|
|
963
1204
|
if (!m) continue;
|
|
964
1205
|
m.opacity = o;
|
|
@@ -968,6 +1209,19 @@ export class S3MViewer {
|
|
|
968
1209
|
this._requestRepaint();
|
|
969
1210
|
}
|
|
970
1211
|
|
|
1212
|
+
/** 单个点云对象同步当前样式(着色模式 + 不透明度 + 选中高亮) */
|
|
1213
|
+
private _applyPointCloudStyle(points: THREE.Points, selected = this._selectedMeshes.includes(points)): void {
|
|
1214
|
+
applyPointCloudStyle(points, this.style, this.uniforms, selected);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/** 全部点云刷新:着色模式切换时用(uniform 是共享引用,改色不用走这里) */
|
|
1218
|
+
private _applyPointCloudStyles(): void {
|
|
1219
|
+
for (const p of this._pickables.values()) {
|
|
1220
|
+
if (isPointCloudObject(p.mesh)) this._applyPointCloudStyle(p.mesh);
|
|
1221
|
+
}
|
|
1222
|
+
this._requestRepaint();
|
|
1223
|
+
}
|
|
1224
|
+
|
|
971
1225
|
/** 平面/平滑着色:FLAT_SHADED 是编译期宏,改它必须重编译 */
|
|
972
1226
|
private _applyShading(): void {
|
|
973
1227
|
const flat = this.style.shading !== 'smooth';
|
|
@@ -1104,7 +1358,8 @@ export class S3MViewer {
|
|
|
1104
1358
|
.map((p) => p.mesh)
|
|
1105
1359
|
.filter((m) => {
|
|
1106
1360
|
const ud = m.userData as MeshUserData;
|
|
1107
|
-
|
|
1361
|
+
// 点云没有棱可描
|
|
1362
|
+
return !ud.edges && !ud.nativeEdges && !isPointCloudObject(m);
|
|
1108
1363
|
});
|
|
1109
1364
|
let i = 0;
|
|
1110
1365
|
const step = (): void => {
|
|
@@ -1614,8 +1869,13 @@ export class S3MViewer {
|
|
|
1614
1869
|
select(result: PickResult | null, { silent = false }: { silent?: boolean } = {}): void {
|
|
1615
1870
|
// 清掉上一次
|
|
1616
1871
|
if (this._selection) {
|
|
1617
|
-
for (const
|
|
1618
|
-
|
|
1872
|
+
for (const obj of this._selectedMeshes) {
|
|
1873
|
+
// 点云:恢复成当前着色模式(去掉选中混色)
|
|
1874
|
+
if (isPointCloudObject(obj)) {
|
|
1875
|
+
this._applyPointCloudStyle(obj, false);
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1878
|
+
const mat = styledMaterialOf(obj as THREE.Mesh);
|
|
1619
1879
|
if (mat) {
|
|
1620
1880
|
mat.userData.uSelBatch.value = -1;
|
|
1621
1881
|
mat.userData.uSelWhole.value = 0;
|
|
@@ -1666,14 +1926,22 @@ export class S3MViewer {
|
|
|
1666
1926
|
mat.userData.uSelWhole.value = batchId < 0 ? 1 : 0;
|
|
1667
1927
|
this._selectedMeshes.push(mesh);
|
|
1668
1928
|
}
|
|
1929
|
+
// 点云没有单体粒度:整块混成 selectColor,让选中至少看得见
|
|
1930
|
+
for (const mesh of this._selectedMeshes) {
|
|
1931
|
+
if (isPointCloudObject(mesh)) this._applyPointCloudStyle(mesh, true);
|
|
1932
|
+
}
|
|
1669
1933
|
this.uniforms.uSelMix.value = 1;
|
|
1670
1934
|
this.uniforms.uDim.value = this.style.dimOthers ? 1 : 0;
|
|
1671
1935
|
|
|
1672
1936
|
if (this.style.outline) {
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
this.
|
|
1937
|
+
// 棱线 / 外扩轮廓都是按「面」设计的,点云画不出来也不该画
|
|
1938
|
+
const decoMeshes = [...targetMeshes].filter((m) => !isPointCloudObject(m));
|
|
1939
|
+
if (decoMeshes.length) {
|
|
1940
|
+
if ((this.style.outlineStyle ?? 'edges') === 'edges') {
|
|
1941
|
+
this._addSelectionEdges(result, decoMeshes);
|
|
1942
|
+
} else {
|
|
1943
|
+
this._addSelectionHull(result, decoMeshes);
|
|
1944
|
+
}
|
|
1677
1945
|
}
|
|
1678
1946
|
}
|
|
1679
1947
|
this._requestRepaint();
|
|
@@ -1973,9 +2241,8 @@ export class S3MViewer {
|
|
|
1973
2241
|
let maxLon = -Infinity;
|
|
1974
2242
|
let maxLat = -Infinity;
|
|
1975
2243
|
for (const entry of this.datasets.values()) {
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
| undefined;
|
|
2244
|
+
// 同 DatasetManager.getBounds:退化 / 越界的 geoBounds 不参与,避免视角甩到 (0,0)
|
|
2245
|
+
const b = usableGeoBounds(entry.tileset?.config?.geoBounds);
|
|
1979
2246
|
if (!b) continue;
|
|
1980
2247
|
minLon = Math.min(minLon, b.left);
|
|
1981
2248
|
maxLon = Math.max(maxLon, b.right);
|