u-space 0.0.28 → 0.0.29
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/index.cjs +3 -3
- package/dist/index.js +192 -189
- package/dist/plugins/u-manager/index.cjs +32 -32
- package/dist/plugins/u-manager/index.js +4021 -3794
- package/dist/plugins/u-manager/instances/SemanticInstanceObject.d.ts +15 -1
- package/dist/plugins/u-manager/instances/SemanticModelInstancedLayer.d.ts +11 -2
- package/dist/plugins/u-manager/loaders/SceneLoader.d.ts +12 -3
- package/dist/plugins/u-manager/loaders/UManagerLoader.d.ts +4 -4
- package/dist/plugins/u-manager/semantics/objects/BuildingGroup.d.ts +8 -0
- package/dist/plugins/u-manager/semantics/objects/FloorMesh.d.ts +8 -0
- package/dist/plugins/u-manager/semantics/objects/SemanticGroup.d.ts +4 -3
- package/docs/api-plugin-u-manager.md +101 -43
- package/docs/changelog.md +23 -1
- package/docs/examples-guide.md +32 -16
- package/docs/getting-started.md +1 -1
- package/docs/mcp.md +10 -9
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box3, Color, Object3D, type ColorRepresentation } from 'three/webgpu';
|
|
1
|
+
import { Box3, BufferGeometry, Color, Object3D, Sphere, type ColorRepresentation } from 'three/webgpu';
|
|
2
2
|
import { BaseGroup } from 'u-space';
|
|
3
3
|
export interface SemanticInstanceStyle {
|
|
4
4
|
color: Color | null;
|
|
@@ -8,6 +8,11 @@ export interface SemanticInstanceStyle {
|
|
|
8
8
|
export interface SemanticInstanceObjectOptions {
|
|
9
9
|
boundsName?: string;
|
|
10
10
|
}
|
|
11
|
+
export interface SemanticInstanceIdentity {
|
|
12
|
+
id?: string | null;
|
|
13
|
+
kind?: string | null;
|
|
14
|
+
name?: string | null;
|
|
15
|
+
}
|
|
11
16
|
export declare const SEMANTIC_COLOR_MODE_NONE = 0;
|
|
12
17
|
export declare const SEMANTIC_COLOR_MODE_OVERRIDE = 1;
|
|
13
18
|
export declare const SEMANTIC_COLOR_MODE_TINT = 2;
|
|
@@ -15,10 +20,19 @@ export declare class SemanticInstanceObject extends BaseGroup {
|
|
|
15
20
|
#private;
|
|
16
21
|
isSemanticInstanceObject: boolean;
|
|
17
22
|
type: string;
|
|
23
|
+
semanticId: string;
|
|
24
|
+
semanticKind: string;
|
|
25
|
+
semanticName: string;
|
|
26
|
+
geometry: BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>;
|
|
27
|
+
boundingBox: Box3 | null;
|
|
28
|
+
boundingSphere: Sphere | null;
|
|
18
29
|
constructor(options?: SemanticInstanceObjectOptions);
|
|
30
|
+
setSemanticIdentity(identity: SemanticInstanceIdentity): this;
|
|
19
31
|
setSemanticBounds(bounds: Box3): this;
|
|
20
32
|
setSemanticRenderObject(object: Object3D | null): this;
|
|
21
33
|
getSemanticRenderObject(): Object3D<import("three").Object3DEventMap> | null;
|
|
34
|
+
computeBoundingBox(): void;
|
|
35
|
+
computeBoundingSphere(): void;
|
|
22
36
|
getSemanticBoundingBox(target?: Box3, world?: boolean): Box3;
|
|
23
37
|
setSemanticVisible(visible: boolean): this;
|
|
24
38
|
setSemanticColor(color: ColorRepresentation): this;
|
|
@@ -8,7 +8,7 @@ export interface SemanticInstanceCullingOptions {
|
|
|
8
8
|
}
|
|
9
9
|
export interface SemanticModelInstancedLayerOptions<T extends SemanticInstanceObject> {
|
|
10
10
|
name?: string;
|
|
11
|
-
|
|
11
|
+
semanticKind?: string;
|
|
12
12
|
getHitUserData?: (instance: T, renderIndex: number) => Record<string, unknown>;
|
|
13
13
|
}
|
|
14
14
|
export declare class SemanticModelInstancedLayer<T extends SemanticInstanceObject = SemanticInstanceObject> extends BaseGroup {
|
|
@@ -16,7 +16,10 @@ export declare class SemanticModelInstancedLayer<T extends SemanticInstanceObjec
|
|
|
16
16
|
isSemanticModelInstancedLayer: boolean;
|
|
17
17
|
type: string;
|
|
18
18
|
constructor(options?: SemanticModelInstancedLayerOptions<T>);
|
|
19
|
+
get semanticKind(): string;
|
|
19
20
|
getSemanticInstances(): T[];
|
|
21
|
+
getSemanticInstanceById(id: string): T | undefined;
|
|
22
|
+
hasSemanticInstance(id: string): boolean;
|
|
20
23
|
getInstanceCulling(): {
|
|
21
24
|
enabled: boolean;
|
|
22
25
|
frustum: boolean;
|
|
@@ -29,5 +32,11 @@ export declare class SemanticModelInstancedLayer<T extends SemanticInstanceObjec
|
|
|
29
32
|
setSemanticOpacity(opacity: number): this;
|
|
30
33
|
setSemanticHighlight(color: Parameters<SemanticInstanceObject['setSemanticHighlight']>[0], opacity: number, overwrite?: boolean): this;
|
|
31
34
|
clearSemanticHighlight(): this;
|
|
32
|
-
|
|
35
|
+
reserveSemanticBatch(url: string, template: Object3D, capacity: number): boolean;
|
|
36
|
+
addSemanticInstance(url: string, template: Object3D, instance: T): boolean;
|
|
37
|
+
addSemanticInstances(url: string, template: Object3D, instances: T[]): boolean;
|
|
38
|
+
removeSemanticInstance(instanceOrId: T | string): boolean;
|
|
39
|
+
removeSemanticInstances(instancesOrIds: Iterable<T | string> | T | string): number;
|
|
40
|
+
removeSemanticBatch(url: string): boolean;
|
|
41
|
+
clearSemanticBatches(): this;
|
|
33
42
|
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Model, type Viewer } from 'u-space';
|
|
1
|
+
import { Loader } from 'three/webgpu';
|
|
2
|
+
import { Model, type Viewer, BaseGroup } from 'u-space';
|
|
3
|
+
import { SceneInstancedLayer } from './SceneInstancedLayer';
|
|
3
4
|
import type { ITreeData } from '../types';
|
|
5
|
+
export declare class SceneGroup extends BaseGroup {
|
|
6
|
+
isSceneGroup: boolean;
|
|
7
|
+
type: string;
|
|
8
|
+
sceneLayer: SceneInstancedLayer | null;
|
|
9
|
+
constructor();
|
|
10
|
+
setSceneLayer(layer: SceneInstancedLayer | null): this;
|
|
11
|
+
getDefaultSceneLayer(): SceneInstancedLayer | null;
|
|
12
|
+
}
|
|
4
13
|
export declare class SceneLoader extends Loader {
|
|
5
14
|
#private;
|
|
6
15
|
viewer: Viewer;
|
|
@@ -18,7 +27,7 @@ export declare class SceneLoader extends Loader {
|
|
|
18
27
|
* @param options
|
|
19
28
|
* @returns
|
|
20
29
|
*/
|
|
21
|
-
loadAsync(): Promise<
|
|
30
|
+
loadAsync(): Promise<SceneGroup>;
|
|
22
31
|
clearCache(): void;
|
|
23
32
|
dispose(): void;
|
|
24
33
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { Loader
|
|
1
|
+
import { Loader } from 'three/webgpu';
|
|
2
2
|
import { BaseGroup, type Viewer } from 'u-space';
|
|
3
|
-
import { SceneLoader } from './SceneLoader';
|
|
3
|
+
import { SceneLoader, type SceneGroup } from './SceneLoader';
|
|
4
4
|
import { SemanticLoader } from '../semantics/SemanticLoader';
|
|
5
5
|
import type { SemanticGroup } from '../semantics';
|
|
6
6
|
export declare class UManagerSceneGroup extends BaseGroup {
|
|
7
7
|
isUManagerSceneGroup: boolean;
|
|
8
8
|
type: string;
|
|
9
9
|
semanticGroup: SemanticGroup;
|
|
10
|
-
sceneGroup:
|
|
11
|
-
constructor(semanticGroup: SemanticGroup, sceneGroup:
|
|
10
|
+
sceneGroup: SceneGroup;
|
|
11
|
+
constructor(semanticGroup: SemanticGroup, sceneGroup: SceneGroup);
|
|
12
12
|
}
|
|
13
13
|
export declare class UManagerLoader extends Loader {
|
|
14
14
|
#private;
|
|
@@ -9,10 +9,18 @@ export declare class BuildingGroup extends BaseGroup {
|
|
|
9
9
|
#private;
|
|
10
10
|
isBuildingGroup: boolean;
|
|
11
11
|
type: string;
|
|
12
|
+
semanticId: string;
|
|
13
|
+
semanticKind: string;
|
|
14
|
+
semanticName: string;
|
|
12
15
|
floorMeshes: FloorMesh[];
|
|
13
16
|
elevatorMeshes: ElevatorMesh[];
|
|
14
17
|
ventMeshes: VentMesh[];
|
|
15
18
|
constructor();
|
|
19
|
+
setSemanticIdentity(identity: {
|
|
20
|
+
id?: string | null;
|
|
21
|
+
kind?: string | null;
|
|
22
|
+
name?: string | null;
|
|
23
|
+
}): this;
|
|
16
24
|
addFloor(mesh: FloorMesh): this;
|
|
17
25
|
hasFloor(mesh: FloorMesh): boolean;
|
|
18
26
|
getFloorAt(index: number): FloorMesh;
|
|
@@ -67,6 +67,9 @@ export declare class FloorMesh extends BaseMesh<BufferGeometry, FloorMeshMateria
|
|
|
67
67
|
#private;
|
|
68
68
|
isFloorMergedSemanticMesh: boolean;
|
|
69
69
|
type: string;
|
|
70
|
+
semanticId: string;
|
|
71
|
+
semanticKind: string;
|
|
72
|
+
semanticName: string;
|
|
70
73
|
pivot: Vector3;
|
|
71
74
|
boundingBox: Box3 | null;
|
|
72
75
|
boundingSphere: Sphere | null;
|
|
@@ -82,6 +85,11 @@ export declare class FloorMesh extends BaseMesh<BufferGeometry, FloorMeshMateria
|
|
|
82
85
|
facilityObjects: SemanticInstanceObject[];
|
|
83
86
|
facilityIndexVersion: number;
|
|
84
87
|
constructor(entries: SemanticGeometryEntry[]);
|
|
88
|
+
setSemanticIdentity(identity: {
|
|
89
|
+
id?: string | null;
|
|
90
|
+
kind?: string | null;
|
|
91
|
+
name?: string | null;
|
|
92
|
+
}): this;
|
|
85
93
|
planish(kind?: FloorSemanticKind): this;
|
|
86
94
|
unplanish(kind?: FloorSemanticKind): this;
|
|
87
95
|
get semanticCount(): number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type Object3D } from 'three/webgpu';
|
|
2
1
|
import { BaseGroup } from 'u-space';
|
|
3
2
|
import type { BuildingGroup } from './BuildingGroup';
|
|
3
|
+
import type { FacilityInstancedLayer } from './FacilityInstancedLayer';
|
|
4
4
|
import type { FloorMesh, FloorSemanticKind } from './FloorMesh';
|
|
5
5
|
import type { SemanticInstanceObject } from '../../instances';
|
|
6
6
|
export declare class SemanticGroup extends BaseGroup {
|
|
@@ -8,7 +8,7 @@ export declare class SemanticGroup extends BaseGroup {
|
|
|
8
8
|
isSemanticGroup: boolean;
|
|
9
9
|
type: string;
|
|
10
10
|
buildings: BuildingGroup[];
|
|
11
|
-
facilityLayer:
|
|
11
|
+
facilityLayer: FacilityInstancedLayer | null;
|
|
12
12
|
constructor();
|
|
13
13
|
addBuilding(group: BuildingGroup): this;
|
|
14
14
|
getBuildingAt(index: number): BuildingGroup;
|
|
@@ -16,7 +16,8 @@ export declare class SemanticGroup extends BaseGroup {
|
|
|
16
16
|
getBuildings(): BuildingGroup[];
|
|
17
17
|
removeBuilding(group: BuildingGroup): this;
|
|
18
18
|
get floorMeshes(): FloorMesh[];
|
|
19
|
-
setFacilityLayer(layer:
|
|
19
|
+
setFacilityLayer(layer: FacilityInstancedLayer | null): this;
|
|
20
|
+
getDefaultFacilityLayer(): FacilityInstancedLayer | null;
|
|
20
21
|
showAllFacilities(): this;
|
|
21
22
|
hideAllFacilities(): this;
|
|
22
23
|
getFacilityById(id: string): SemanticInstanceObject | undefined;
|
|
@@ -20,9 +20,11 @@ viewer.scene.add(group);
|
|
|
20
20
|
|
|
21
21
|
const semanticGroup = group.semanticGroup;
|
|
22
22
|
const sceneGroup = group.sceneGroup;
|
|
23
|
+
const facilityLayer = semanticGroup.getDefaultFacilityLayer();
|
|
24
|
+
const sceneLayer = sceneGroup.getDefaultSceneLayer();
|
|
23
25
|
```
|
|
24
26
|
|
|
25
|
-
完整可运行示例见 [`examples/test_umanager_loader.html`](https://u-space-phi.vercel.app/examples/test_umanager_loader.html)。该示例展示了如何从返回根组中直接读取 `semanticGroup` / `sceneGroup
|
|
27
|
+
完整可运行示例见 [`examples/test_umanager_loader.html`](https://u-space-phi.vercel.app/examples/test_umanager_loader.html)。该示例展示了如何从返回根组中直接读取 `semanticGroup` / `sceneGroup`,通过 `getDefaultFacilityLayer()` / `getDefaultSceneLayer()` 获取默认合批层,并通过 `viewer.objectManager` 获取 `SceneInstanceObject` / `FacilityInstanceObject` 后调用统一 `viewer.controls.flyToObject()`、`setSemanticHighlight()`、`setSemanticVisible()` 和 `setSemanticOpacity()`。运行时动态新增/删除 batch 实例的示例见 [`examples/test_umanager_dynamic_instances.html`](https://u-space-phi.vercel.app/examples/test_umanager_dynamic_instances.html)。
|
|
26
28
|
|
|
27
29
|
## `SemanticLoader`
|
|
28
30
|
|
|
@@ -57,7 +59,9 @@ floor.addEventListener('click', ({ event }) => {
|
|
|
57
59
|
});
|
|
58
60
|
```
|
|
59
61
|
|
|
60
|
-
所有可按 ID 操作的实例都会尽量暴露统一的 `SemanticInstanceObject` API。楼层多边形语义使用轻量 `FloorSemanticInstanceObject` 代理合并几何中的一个实例;Facilities 和 SceneLoader path instancing 使用同一套模型实例化底层。业务代码优先使用 `setSemanticVisible()`、`setSemanticColor()`、`setSemanticOpacity()` 和 `
|
|
62
|
+
所有可按 ID 操作的实例都会尽量暴露统一的 `SemanticInstanceObject` API。楼层多边形语义使用轻量 `FloorSemanticInstanceObject` 代理合并几何中的一个实例;Facilities 和 SceneLoader path instancing 使用同一套模型实例化底层。业务代码优先使用 `setSemanticVisible()`、`setSemanticColor()`、`setSemanticOpacity()` 和 `viewer.controls.flyToObject(instance)`,而不是关心底层是合并几何、`InstancedMesh` 还是 fallback `Model`。
|
|
63
|
+
|
|
64
|
+
`SemanticInstanceObject` 也按 Three.js 对象级包围盒约定提供 `boundingBox`、`boundingSphere`、`computeBoundingBox()` 和 `computeBoundingSphere()`。它们缓存的是实例本地坐标包围体,`Box3.setFromObject()` 会自动调用 `computeBoundingBox()` 并应用 `matrixWorld`,因此 `viewer.controls.flyToObject()` 可以直接飞向 `SceneInstanceObject`、`FacilityInstanceObject` 和楼层语义实例。需要手动处理世界包围盒时,仍可使用 `getSemanticBoundingBox(target, true)`。
|
|
61
65
|
|
|
62
66
|
### `SemanticGroup`
|
|
63
67
|
|
|
@@ -73,6 +77,7 @@ floor.addEventListener('click', ({ event }) => {
|
|
|
73
77
|
| `getBuildingAt(index)` / `getBuildingById(id)` / `getBuildings()` | 按下标、`Building.ID` 或整体列表获取建筑。 |
|
|
74
78
|
| `floorMeshes` | 聚合返回所有建筑内的 `FloorMesh[]`。 |
|
|
75
79
|
| `setFacilityLayer(layer)` | 设置跨楼层设备渲染层;传入 `null` 会移除已有 layer。 |
|
|
80
|
+
| `getDefaultFacilityLayer()` | 返回当前默认 `FacilityInstancedLayer`;没有可实例化 Facilities 时返回 `null`。 |
|
|
76
81
|
| `getFacilityById(id)` | 在整次语义解析结果中按 `Facility.ID` 或显式别名查找设备。 |
|
|
77
82
|
| `showAllFacilities()` / `hideAllFacilities()` | 显示或隐藏整次语义解析结果中的全部设备。 |
|
|
78
83
|
| `showAllFloors()` | 显示所有建筑的所有楼层。 |
|
|
@@ -102,7 +107,7 @@ floor.addEventListener('click', ({ event }) => {
|
|
|
102
107
|
| `isolateFloor(floor \| index)` | 只显示指定楼层,隐藏其他楼层;也兼容传入楼层数组。 |
|
|
103
108
|
| `isolateFloors(floors \| indexes)` | 只显示指定多个楼层,隐藏其他楼层。 |
|
|
104
109
|
| `showAllFloors()` | 显示全部楼层。 |
|
|
105
|
-
| `getFacilityById(id)` | 在当前建筑的楼层中按 `Facility.ID`
|
|
110
|
+
| `getFacilityById(id)` | 在当前建筑的楼层中按 `Facility.ID` 或 `FloorMesh.addFacility()` 显式传入的别名查找设备。 |
|
|
106
111
|
| `showAllFacilities()` | 显示整栋建筑内全部楼层设备。 |
|
|
107
112
|
| `hideAllFacilities()` | 隐藏整栋建筑内全部楼层设备。 |
|
|
108
113
|
| `planishFloors(kind?)` | 压扁所有楼层;默认覆盖所有合并语义实例和 Facilities,并按包围盒贴到楼层平面附近,同时添加稳定的轻微 Y 偏移以减少共面闪烁;传入 `kind` 时只压扁该语义类型,`kind` 可包含 `Facilities`。 |
|
|
@@ -141,21 +146,16 @@ Facilities 会在单次 `SemanticParser` 解析范围内按模型 URL 自动分
|
|
|
141
146
|
|
|
142
147
|
Instanced 设备通过 per-instance opacity attribute 控制单个设备透明度。batch 材质默认沿用源材质的透明状态;只有源材质本身透明,或当前参与渲染的设备存在 `opacity < 1` 时,材质才会切到 `transparent: true`,`setSemanticOpacity(1)` 可在没有其他半透明实例时回到不透明渲染队列。`resetSemanticColor()` 只清除颜色 override,不影响透明度 override。`FacilityInstancedLayer` 会在设备显隐、颜色、透明度、floor planish/unplanish 改变设备 transform,或相机矩阵、投影矩阵、viewport 高度变化时,在下一次渲染前同步实例矩阵、颜色、透明度和当前 active instance count。普通 fallback `Model` 的颜色和透明度控制由 wrapper 委托到 `MaterialEffects`,单材质和多材质 Mesh 都会应用 override。
|
|
143
148
|
|
|
144
|
-
|
|
149
|
+
加载成功后,`BuildingGroup`、`FloorMesh` 和设备引用都会在对象字段上写入独立语义身份,例如 `semanticId`、`semanticKind` 和 `semanticName`。`SemanticGroup.getBuildingById()`、`BuildingGroup.getFloorById()`、`BuildingGroup.getFacilityById()`、`FloorMesh.getFacilityById()` 以及 `SemanticModelInstancedLayer` 的查询、删除和 raycast remap 都使用这些对象字段或显式传入的别名,不再从 `userData.id`、`userData.semanticId`、`userData.facilityId` 里扫描 ID。`userData` 只保留原始业务元数据,例如 `facilityId`、`spaces`、`twinsIdentifier`、`storyId`、`floorIndex`、`instanced` 和 `modelUrl`。
|
|
145
150
|
|
|
146
151
|
```typescript
|
|
147
|
-
import { Box3 } from 'three';
|
|
148
|
-
|
|
149
152
|
const floor = building.getFloorAt(0);
|
|
150
|
-
const box = new Box3();
|
|
151
153
|
|
|
152
154
|
const facility = floor.getFacilityById('FACILITY_001') ?? viewer.objectManager.getById('FACILITY_001');
|
|
153
155
|
const facilityEntity = floor.getSemanticById('FACILITY_001');
|
|
154
156
|
|
|
155
157
|
if (facility && facilityEntity?.type === 'object') {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
await viewer.controls.flyToBox(facilityBox, {
|
|
158
|
+
await viewer.controls.flyToObject(facility, {
|
|
159
159
|
viewpoint: 'rightFrontTop',
|
|
160
160
|
padding: 0.2,
|
|
161
161
|
});
|
|
@@ -170,26 +170,23 @@ facility
|
|
|
170
170
|
.setSemanticVisible(true);
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
-
对 Facilities
|
|
173
|
+
对 Facilities 推荐直接使用 `controls.flyToObject()` 飞向设备。无论设备实际渲染来自 `InstancedMesh` 还是 fallback `Model`,`FacilityInstanceObject` 都会暴露本地 `boundingBox` / `boundingSphere`,让 Three.js 的 `Box3.setFromObject()` 能计算完整世界包围盒。
|
|
174
174
|
|
|
175
175
|
### `FacilityInstanceObject`
|
|
176
176
|
|
|
177
177
|
`FacilityInstanceObject` 继承自统一的 `SemanticInstanceObject`,表示一个设备语义实例。它会挂在所属 `FloorMesh` 下,用于 ID 检索、事件派发、显隐、颜色、透明度和包围盒查询;如果设备可以 instancing,真正几何由 `SemanticGroup.facilityLayer` 里的 `InstancedMesh` 统一渲染;如果不能 instancing,fallback `Model` 会作为它的子对象挂载。
|
|
178
178
|
|
|
179
|
-
通常不需要手动创建 `FacilityInstanceObject`。`SemanticParser` 在解析 `Facilities`
|
|
179
|
+
通常不需要手动创建 `FacilityInstanceObject`。`SemanticParser` 在解析 `Facilities` 时会自动创建它,并通过 `setSemanticIdentity()` 写入 `semanticId`、`semanticKind` 和 `semanticName`。`userData.instanced` 表示当前设备是否由 `InstancedMesh` 渲染;fallback 时仍然保留同一套语义实例 API。
|
|
180
180
|
|
|
181
|
-
加载完成后,设备可以按 `Facility.ID` 从全局 `objectManager`
|
|
181
|
+
加载完成后,设备可以按 `Facility.ID` 从全局 `objectManager` 取回,并直接传给 `viewer.controls.flyToObject()`。如果需要自己合并多个对象或调整包围盒,也可以继续调用 `getSemanticBoundingBox(box, true)`。
|
|
182
182
|
|
|
183
183
|
```typescript
|
|
184
|
-
import { Box3 } from 'three';
|
|
185
184
|
import { FacilityInstanceObject } from 'u-space/plugins/u-manager';
|
|
186
185
|
|
|
187
186
|
const facility = viewer.objectManager.getById<FacilityInstanceObject>('FACILITY_001');
|
|
188
187
|
|
|
189
188
|
if (facility?.isFacilityInstanceObject) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
await viewer.controls.flyToBox(box, {
|
|
189
|
+
await viewer.controls.flyToObject(facility, {
|
|
193
190
|
viewpoint: 'rightFrontTop',
|
|
194
191
|
padding: 0.2,
|
|
195
192
|
enableTransition: true,
|
|
@@ -201,8 +198,15 @@ if (facility?.isFacilityInstanceObject) {
|
|
|
201
198
|
| :---------- | :--- |
|
|
202
199
|
| `isFacilityInstanceObject` | 类型标记,值为 `true`。 |
|
|
203
200
|
| `type` | Three.js 对象类型名,值为 `'FacilityInstanceObject'`。 |
|
|
204
|
-
| `
|
|
205
|
-
| `
|
|
201
|
+
| `semanticId` | 实例独立 ID,设备默认等于 `Facility.ID`;layer 的按 ID 查询、删除和 raycast remap 都使用该字段。 |
|
|
202
|
+
| `semanticKind` | 实例语义类型,设备为 `'Facilities'`。 |
|
|
203
|
+
| `semanticName` | 实例显示名称,设备默认使用 `Facility.Name`。 |
|
|
204
|
+
| `setSemanticIdentity({ id, kind, name })` | 设置实例独立身份字段,并返回自身;`semanticId` 在同一个 `SemanticModelInstancedLayer` 内必须唯一,加入 layer 后修改 id 时,下一次按 id 查询会刷新索引。 |
|
|
205
|
+
| `boundingBox` | 实例本地包围盒缓存,初始为 `null`;`Box3.setFromObject()` 或手动调用 `computeBoundingBox()` 时会计算。 |
|
|
206
|
+
| `boundingSphere` | 实例本地包围球缓存,初始为 `null`;手动调用 `computeBoundingSphere()` 时会由当前本地包围盒派生。 |
|
|
207
|
+
| `setSemanticBounds(bounds)` | 设置模板在引用本地空间内的包围盒,并返回自身;主要由 `SemanticModelInstancedLayer.addSemanticInstances()` / `reserveSemanticBatch()` 在建 batch 时写入。 |
|
|
208
|
+
| `computeBoundingBox()` / `computeBoundingSphere()` | 按 Three.js 对象级包围盒约定更新 `boundingBox` / `boundingSphere`,使 `viewer.controls.flyToObject()` 能直接飞向实例。 |
|
|
209
|
+
| `getSemanticBoundingBox(target?, world?)` | 获取完整包围盒。`world = true` 时会应用 `matrixWorld`,适合需要手动传给 `viewer.controls.flyToBox()` 的场景;没有 bounds 时返回 empty `Box3`。 |
|
|
206
210
|
| `setSemanticVisible(visible)` | 设置单个语义实例的 `visible`,并返回自身;渲染和射线检测都会跳过不可见引用。 |
|
|
207
211
|
| `setSemanticColor(color)` | 设置单个语义实例的颜色 override,并返回自身。`color` 支持 Three.js `ColorRepresentation`。 |
|
|
208
212
|
| `resetSemanticColor()` | 清除颜色 override,恢复使用源材质颜色,并返回自身;不会清除透明度 override。 |
|
|
@@ -212,10 +216,8 @@ if (facility?.isFacilityInstanceObject) {
|
|
|
212
216
|
| `getSemanticOpacity()` | 读取当前透明度 override。 |
|
|
213
217
|
|
|
214
218
|
```typescript
|
|
215
|
-
import { Box3 } from 'three';
|
|
216
219
|
import { FacilityInstanceObject } from 'u-space/plugins/u-manager';
|
|
217
220
|
|
|
218
|
-
const box = new Box3();
|
|
219
221
|
const facility = floor.getFacilityById('FACILITY_001');
|
|
220
222
|
|
|
221
223
|
if (facility instanceof FacilityInstanceObject) {
|
|
@@ -224,7 +226,7 @@ if (facility instanceof FacilityInstanceObject) {
|
|
|
224
226
|
.setSemanticOpacity(0.6)
|
|
225
227
|
.setSemanticVisible(true);
|
|
226
228
|
|
|
227
|
-
await viewer.controls.
|
|
229
|
+
await viewer.controls.flyToObject(facility, {
|
|
228
230
|
viewpoint: 'rightFrontTop',
|
|
229
231
|
padding: 0.2,
|
|
230
232
|
});
|
|
@@ -235,7 +237,7 @@ if (facility instanceof FacilityInstanceObject) {
|
|
|
235
237
|
|
|
236
238
|
`FacilityInstancedLayer` 继承自 `SemanticModelInstancedLayer`,是 scene-level Facilities 的批量渲染层。`SemanticGroup.facilityLayer` 默认就是该类型;它会按模型 URL 把同一模板的静态设备合并为一个或多个 `InstancedMesh`,并用普通 `Group` 包裹每个模型 URL batch,保证设备显隐、颜色、透明度和相机实例裁剪能在每帧渲染前同步,同时保留每个楼层下的 `FacilityInstanceObject` 引用用于业务 API 和事件冒泡。
|
|
237
239
|
|
|
238
|
-
使用 `SemanticLoader` 时通常不需要直接调用 `
|
|
240
|
+
使用 `SemanticLoader` 时通常不需要直接调用 `addSemanticInstances()`;只有自定义语义解析、运行时新增设备或自建设备 batch 时才需要手动创建 layer,然后通过 `semanticGroup.setFacilityLayer(layer)` 挂回语义场景。
|
|
239
241
|
|
|
240
242
|
| 属性 / 方法 | 说明 |
|
|
241
243
|
| :---------- | :--- |
|
|
@@ -244,11 +246,41 @@ if (facility instanceof FacilityInstanceObject) {
|
|
|
244
246
|
| `name` | 构造函数默认设为 `'Facilities'`。 |
|
|
245
247
|
| `getInstanceCulling()` | 返回当前实例裁剪配置:`enabled`、`frustum`、`minScreenRadius`。 |
|
|
246
248
|
| `setInstanceCulling(options)` | 设置实例裁剪配置,并返回自身。默认 `enabled: false`、`frustum: true`、`minScreenRadius: 0`;默认不按相机裁剪设备,避免相机距离影响设备显隐。需要性能压缩时可设置 `enabled: true` 开启视锥裁剪,或再把 `minScreenRadius` 设为大于 0 的值开启屏幕尺寸裁剪。 |
|
|
247
|
-
| `
|
|
249
|
+
| `getSemanticInstances()` | 返回当前 layer 内全部语义实例对象数组。 |
|
|
250
|
+
| `getSemanticInstanceById(id)` | 根据实例对象的 `semanticId` 快速获取语义实例;找不到时返回 `undefined`。 |
|
|
251
|
+
| `hasSemanticInstance(id)` | 判断当前 layer 是否包含对应 `semanticId` 的实例。 |
|
|
252
|
+
| `reserveSemanticBatch(url, template, capacity)` | 为某个模型 URL 预分配 batch 容量。适合接下来会多次运行时新增实例的场景,成功返回 `true`,模板不支持 instancing 时返回 `false`。 |
|
|
253
|
+
| `addSemanticInstance(url, template, instance)` | 向某个模型 URL 的 batch 新增一个语义实例。成功返回 `true`,失败时调用方应回退为普通模型渲染。 |
|
|
254
|
+
| `addSemanticInstances(url, template, instances)` | 向某个模型 URL 的 batch 批量新增语义实例。推荐运行时新增多个实例时优先使用,避免连续单个新增造成重复扩容检查。 |
|
|
255
|
+
| `removeSemanticInstance(instanceOrId)` | 删除一个语义实例,可传实例对象或完整 `semanticId`。删除成功返回 `true`。 |
|
|
256
|
+
| `removeSemanticInstances(instancesOrIds)` | 删除一个或多个语义实例。可传单个实例对象、单个字符串 `semanticId`,或混合实例对象和 ID 的 iterable;返回实际删除数量。 |
|
|
257
|
+
| `removeSemanticBatch(url)` | 删除某个模型 URL 的完整 batch,释放对应内部 `InstancedMesh` 和 cloned materials。 |
|
|
258
|
+
| `clearSemanticBatches()` | 清空当前 layer 的所有模型 batch,并返回自身。 |
|
|
259
|
+
|
|
260
|
+
`addSemanticInstances(url, template, instances)` 会按模型 URL 复用或创建 batch,从 `template` 收集可 instancing 的可见 `Mesh`,克隆源材质,并为每个 batch 建立 instance matrix、颜色和透明度 attribute。没有动画、没有骨骼、没有 morph target,且每个多材质 `Mesh` 都有合法 `geometry.groups` 的模板会进入 instancing;不满足条件、模板没有可用 Mesh、`instances` 为空,或待加入实例的 `semanticId` 与当前 layer 内已有实例重复时会返回 `false`。
|
|
261
|
+
|
|
262
|
+
动态新增时,layer 会维护每个 URL batch 的 `capacity`。容量足够时,新增实例只会写入逻辑数组、注册 dirty callback 并标记下一帧同步;容量不足时才按 2 倍增长策略重建该 URL 下的内部 `InstancedMesh` 和 attribute buffer。运行时需要连续新增大量实例时,推荐先调用 `reserveSemanticBatch(url, template, expectedCount)`,再调用 `addSemanticInstances()`。删除实例不会立即收缩 GPU buffer;只有 batch 为空、调用 `removeSemanticBatch()` 或 `clearSemanticBatches()` 时才释放内部渲染资源。
|
|
263
|
+
|
|
264
|
+
外部代码不需要直接写内部 `InstancedMesh.instanceMatrix`。要移动、旋转或缩放实例时,直接修改对应 `SemanticInstanceObject` 的 transform,然后让它更新世界矩阵或等待下一帧渲染即可;`SemanticInstanceObject.updateMatrixWorld()` 会标记 batch dirty,layer 会在渲染前把世界矩阵同步到内部 instance matrix 并设置 `instanceMatrix.needsUpdate = true`。因此即使底层使用 WebGPU storage instanced buffer,公开更新方式仍然是面向语义实例对象:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
const instance = layer.getSemanticInstanceById('FACILITY_001');
|
|
268
|
+
|
|
269
|
+
if (instance) {
|
|
270
|
+
instance.position.set(10, 0, 5);
|
|
271
|
+
instance.updateMatrixWorld(true);
|
|
272
|
+
viewer.render();
|
|
273
|
+
}
|
|
274
|
+
```
|
|
248
275
|
|
|
249
|
-
|
|
276
|
+
删除 API 支持单个字符串 ID,不需要为了删除一个实例额外包一层数组。下面两个调用等价,都会按完整 ID 删除实例:
|
|
250
277
|
|
|
251
|
-
|
|
278
|
+
```typescript
|
|
279
|
+
layer.removeSemanticInstance('FACILITY_001');
|
|
280
|
+
layer.removeSemanticInstances('FACILITY_001');
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
渲染前,layer 会同步每个 `FacilityInstanceObject` 的世界矩阵、颜色和透明度,并只在数据变化时上传对应 buffer。默认情况下,相机移动不会改变设备 active instance 集合;显式调用 `setInstanceCulling({ enabled: true })` 后,layer 才会按当前渲染相机逐实例压缩 `InstancedMesh.count`,视锥外设备不会进入本帧实例 buffer;只有同时设置 `minScreenRadius > 0` 时,屏幕半径小于该阈值的设备才会被跳过。相机矩阵、投影矩阵或 viewport 高度未变化时不会重复压缩;设备显隐、透明度为 0、颜色/透明度变化或 `planish('Facilities')` / `unplanish('Facilities')` 改变 transform 时会标记 batch dirty。全部设备不可渲染时,内部 `InstancedMesh.visible` 会被关闭;只有被当前相机裁掉时,mesh 会保留可见并把 `count` 设为 0,以便相机移动后自动恢复。射线检测命中 batch 内实例时,`hit.object` 会被改写为对应的 `FacilityInstanceObject`,并从实例对象字段附加 `semanticObject`、`semanticId`、`semanticKind`、`semanticName` 和 `semanticIndex`,事件随后会按楼层引用对象继续冒泡。
|
|
252
284
|
|
|
253
285
|
```typescript
|
|
254
286
|
import { FacilityInstancedLayer, FacilityInstanceObject } from 'u-space/plugins/u-manager';
|
|
@@ -256,16 +288,21 @@ import { FacilityInstancedLayer, FacilityInstanceObject } from 'u-space/plugins/
|
|
|
256
288
|
const layer = new FacilityInstancedLayer();
|
|
257
289
|
const refs = facilityItems.map(() => new FacilityInstanceObject());
|
|
258
290
|
|
|
259
|
-
|
|
291
|
+
layer.reserveSemanticBatch('/models/camera.glb', templateObject, refs.length + 100);
|
|
292
|
+
|
|
293
|
+
if (layer.addSemanticInstances('/models/camera.glb', templateObject, refs)) {
|
|
260
294
|
semanticGroup.setFacilityLayer(layer);
|
|
261
295
|
}
|
|
296
|
+
|
|
297
|
+
// 运行时按完整字符串 ID 删除一个实例;返回实际删除数量。
|
|
298
|
+
layer.removeSemanticInstances('FACILITY_001');
|
|
262
299
|
```
|
|
263
300
|
|
|
264
301
|
如果只想整体隐藏或禁用 scene-level batch,可以设置 `semanticGroup.facilityLayer.visible = false`;如果只想控制单个设备,推荐从 `floor.getFacilityById(id)` 或 `viewer.objectManager.getById(id)` 取回 `FacilityInstanceObject` 后调用 `setSemanticVisible()` / `setSemanticOpacity()`。
|
|
265
302
|
|
|
266
303
|
### `SemanticModelInstancedLayer`
|
|
267
304
|
|
|
268
|
-
`SemanticModelInstancedLayer` 是 `SceneInstancedLayer` 和 `FacilityInstancedLayer` 的共享实现。它负责模板 mesh 收集、材质克隆、`InstancedMesh`
|
|
305
|
+
`SemanticModelInstancedLayer` 是 `SceneInstancedLayer` 和 `FacilityInstancedLayer` 的共享实现。它负责模板 mesh 收集、材质克隆、`InstancedMesh` 创建、运行时实例新增/删除、capacity 扩容、instance matrix/color/opacity buffer 同步、raycast hit remap、fallback 判定和可选相机实例裁剪。layer 内部只依赖 `SemanticInstanceObject` 的 `semanticId`、`semanticKind`、`semanticName` 独立字段,不再扫描 `instance.userData` 的 `facilityId`、`id` 或 `sid`。`getSemanticInstanceById()` / `removeSemanticInstances(id)` 会按需刷新 id 索引;新增实例时如果发现重复 `semanticId` 会拒绝加入,避免同一个 layer 内出现不确定查询结果。通常业务代码不需要直接使用它,除非要为新的语义实例类型复用同一套模型 instancing 能力。
|
|
269
306
|
|
|
270
307
|
### `FloorMesh`
|
|
271
308
|
|
|
@@ -291,7 +328,7 @@ if (layer.addSemanticBatch('/models/camera.glb', templateObject, refs)) {
|
|
|
291
328
|
| `getSemanticObjectAt(index)` | 获取合并几何实例对应的 `FloorSemanticInstanceObject` 轻量对象。 |
|
|
292
329
|
| `showAllFacilities()` | 显示当前楼层的全部设备对象。 |
|
|
293
330
|
| `hideAllFacilities()` | 隐藏当前楼层的全部设备对象。 |
|
|
294
|
-
| `addFacility(object, ids?)` |
|
|
331
|
+
| `addFacility(object, ids?)` | 把设备对象挂到楼层,并按对象 `semanticId` 和显式 `ids` 建立检索别名。不会从 `object.userData` 自动扫描 ID。 |
|
|
295
332
|
| `getFacilityById(id)` | 根据 `Facility.ID` 或别名获取设备对象。 |
|
|
296
333
|
| `getFacilityAt(index)` | 按楼层设备数组下标获取设备对象。 |
|
|
297
334
|
| `getFacilities()` | 获取当前楼层全部设备对象。 |
|
|
@@ -323,17 +360,23 @@ floor.addEventListener('click', async ({ event }) => {
|
|
|
323
360
|
|
|
324
361
|
if (intersect.semanticKind === 'Facilities') {
|
|
325
362
|
const facility = intersect.semanticObject ?? event.target;
|
|
326
|
-
const facilityBox = facility.getSemanticBoundingBox(box, true);
|
|
327
363
|
|
|
328
|
-
await viewer.controls.
|
|
364
|
+
await viewer.controls.flyToObject(facility, {
|
|
365
|
+
viewpoint: 'rightFrontTop',
|
|
366
|
+
padding: 0.2,
|
|
367
|
+
});
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (intersect.semanticObject) {
|
|
372
|
+
await viewer.controls.flyToObject(intersect.semanticObject, {
|
|
329
373
|
viewpoint: 'rightFrontTop',
|
|
330
374
|
padding: 0.2,
|
|
331
375
|
});
|
|
332
376
|
return;
|
|
333
377
|
}
|
|
334
378
|
|
|
335
|
-
const semanticBox = intersect.
|
|
336
|
-
?? currentTarget.getBoundingBoxAt(intersect.semanticIndex, box, true);
|
|
379
|
+
const semanticBox = currentTarget.getBoundingBoxAt(intersect.semanticIndex, box, true);
|
|
337
380
|
|
|
338
381
|
if (semanticBox) {
|
|
339
382
|
await viewer.controls.flyToBox(semanticBox, {
|
|
@@ -375,8 +418,10 @@ import { SceneLoader } from 'u-space/plugins/u-manager';
|
|
|
375
418
|
const sceneLoader = new SceneLoader(viewer);
|
|
376
419
|
sceneLoader.setPath('./scenes/my-scene');
|
|
377
420
|
sceneLoader.setKey('YOUR_LICENSE_KEY'); // 官方授权场景必需
|
|
378
|
-
const group = await sceneLoader.loadAsync();
|
|
421
|
+
const group = await sceneLoader.loadAsync(); // SceneGroup
|
|
379
422
|
viewer.scene.add(group);
|
|
423
|
+
|
|
424
|
+
const sceneLayer = group.getDefaultSceneLayer();
|
|
380
425
|
```
|
|
381
426
|
|
|
382
427
|
**方法:**
|
|
@@ -384,15 +429,26 @@ viewer.scene.add(group);
|
|
|
384
429
|
| 方法 | 说明 |
|
|
385
430
|
| :------------------ | :------------------------------------------------------------------- |
|
|
386
431
|
| `setKey(key)` | 设置授权场景的 RSA 解密密钥。 |
|
|
387
|
-
| `loadAsync()` | 加载并解析场景,返回去除语义重复节点后的 `
|
|
432
|
+
| `loadAsync()` | 加载并解析场景,返回去除语义重复节点后的 `SceneGroup`。 |
|
|
388
433
|
| `clearCache()` | 从 `objectManager` 中移除此加载器注册的所有 ID。 |
|
|
389
434
|
| `dispose()` | 清除缓存并释放水印叠加层。 |
|
|
390
435
|
|
|
436
|
+
### `SceneGroup`
|
|
437
|
+
|
|
438
|
+
`SceneGroup` 继承自 `BaseGroup`,是 `SceneLoader.loadAsync()` 的返回根组。它保留 `tree_models.json` 解析后的原始父子层级,并把重复静态模型的默认合批渲染层挂在 `sceneLayer`,方便外部直接访问,而不需要遍历 `children`。
|
|
439
|
+
|
|
440
|
+
| 属性 / 方法 | 说明 |
|
|
441
|
+
| :---------- | :--- |
|
|
442
|
+
| `isSceneGroup` | 固定为 `true`,用于判断对象是否为 `SceneLoader` 根组。 |
|
|
443
|
+
| `sceneLayer` | 当前默认 `SceneInstancedLayer`;没有可合批的重复 3D 模型时为 `null`。 |
|
|
444
|
+
| `setSceneLayer(layer)` | 设置默认 `SceneInstancedLayer`;传入 `null` 会移除已有 layer。 |
|
|
445
|
+
| `getDefaultSceneLayer()` | 返回当前默认 `SceneInstancedLayer`;没有可合批模型时返回 `null`。 |
|
|
446
|
+
|
|
391
447
|
### `SceneInstanceObject`
|
|
392
448
|
|
|
393
449
|
`SceneInstanceObject` 继承自统一的 `SemanticInstanceObject`,表示一个由 `SceneInstancedLayer` 批量渲染或 fallback 普通渲染的场景模型引用。它会保留在原场景树层级中,并注册到 `viewer.objectManager`,因此 `getById()`、`show()` / `hide()`、`isolate()`、`showAll()`、`setOpacity()` 和 `getBoundingBox()` 可以继续按单个模型 ID 使用。
|
|
394
450
|
|
|
395
|
-
`SceneInstanceObject` 本身只增加场景模型语义:`isSceneInstanceObject = true`、`type = 'SceneInstanceObject'
|
|
451
|
+
`SceneInstanceObject` 本身只增加场景模型语义:`isSceneInstanceObject = true`、`type = 'SceneInstanceObject'`、`semanticKind = 'SceneInstances'`。加载器会把场景树节点的 `id` 和 `sid` 都注册到 `viewer.objectManager`,因此同一个对象可以通过任一 ID 取回。对重复模型的 instanced 路径,`semanticId` 默认等于场景树节点 `id`,`semanticName` 默认等于节点名称;`userData.instanced = true`,并保留 `modelPath` 和 `modelUrl` 等原始加载元数据。如果模板不支持 instancing,会退回普通 `Model`,但仍挂在同一个 `SceneInstanceObject` 下,对外 API 不变。
|
|
396
452
|
|
|
397
453
|
**识别字段:**
|
|
398
454
|
|
|
@@ -401,10 +457,13 @@ viewer.scene.add(group);
|
|
|
401
457
|
| `isSceneInstanceObject` | 固定为 `true`,用于判断对象是否来自 `SceneLoader` 的场景实例。 |
|
|
402
458
|
| `isSemanticInstanceObject` | 固定为 `true`,表示它支持统一语义实例 API。 |
|
|
403
459
|
| `type` | 固定为 `SceneInstanceObject`。 |
|
|
460
|
+
| `semanticId` | 实例独立 ID,默认等于场景树节点 `id`;layer 的按 ID 查询、删除和 raycast remap 都使用该字段。 |
|
|
461
|
+
| `semanticKind` | 实例语义类型,场景实例为 `'SceneInstances'`。 |
|
|
462
|
+
| `semanticName` | 实例显示名称,默认等于场景树节点名称。 |
|
|
463
|
+
| `setSemanticIdentity({ id, kind, name })` | 设置实例独立身份字段,并返回自身;运行时手动创建实例时建议先设置再加入 layer,加入后修改 id 会在下一次按 id 查询时刷新索引。 |
|
|
464
|
+
| `boundingBox` | 实例本地包围盒缓存,初始为 `null`;`Box3.setFromObject()` 或手动调用 `computeBoundingBox()` 时会计算。 |
|
|
465
|
+
| `boundingSphere` | 实例本地包围球缓存,初始为 `null`;手动调用 `computeBoundingSphere()` 时会由当前本地包围盒派生。 |
|
|
404
466
|
| `userData.id` / `userData.sid` | 原始场景树节点 ID;两个 ID 都会注册到 `viewer.objectManager`。 |
|
|
405
|
-
| `userData.semanticId` | 语义事件和 raycast remap 使用的实例 ID,默认等于场景树节点 `id`。 |
|
|
406
|
-
| `userData.semanticKind` | 固定为 `SceneInstances`。 |
|
|
407
|
-
| `userData.semanticName` | 原始场景树节点名称。 |
|
|
408
467
|
| `userData.instanced` | `true` 表示实际渲染来自 `SceneInstancedLayer`;`false` 表示 fallback `Model` 挂在当前对象下。 |
|
|
409
468
|
| `userData.modelPath` / `userData.modelUrl` | instanced 场景实例对应的模型相对路径和完整 URL。 |
|
|
410
469
|
|
|
@@ -415,6 +474,7 @@ viewer.scene.add(group);
|
|
|
415
474
|
| `setSemanticBounds(bounds)` | 设置本地包围盒。`SceneLoader` 会在创建 instanced batch 时根据模板模型自动设置;业务一般不需要手动调用。 |
|
|
416
475
|
| `setSemanticRenderObject(object)` | 设置 fallback 渲染对象并作为子对象挂载。模板无法 instancing 时由 `SceneLoader` 自动调用;自定义语义实例渲染时可手动使用。 |
|
|
417
476
|
| `getSemanticRenderObject()` | 返回 fallback 渲染对象;instanced 渲染路径通常返回 `null`,因为真实 Mesh 在 `SceneInstancedLayer` 中。 |
|
|
477
|
+
| `computeBoundingBox()` / `computeBoundingSphere()` | 按 Three.js 对象级包围盒约定更新 `boundingBox` / `boundingSphere`,使 `viewer.controls.flyToObject()` 能直接飞向实例。 |
|
|
418
478
|
| `getSemanticBoundingBox(target?, world?)` | 返回实例包围盒。`world = true` 时会应用 `matrixWorld`,适合直接传给 `viewer.controls.flyToBox()`;无 bounds 和 render object 时返回 empty `Box3`。 |
|
|
419
479
|
| `setSemanticVisible(visible)` | 设置单个实例显隐并标记 batch dirty;隐藏后该实例不会继续渲染,也不会被 instanced raycast 命中。 |
|
|
420
480
|
| `setSemanticColor(color)` | 设置单个实例颜色 override。instanced 路径写入 per-instance color;fallback `Model` 路径会委托到材质高亮逻辑。 |
|
|
@@ -431,17 +491,15 @@ viewer.scene.add(group);
|
|
|
431
491
|
|
|
432
492
|
`MaterialEffects.highlightColor()` / `removeHighlightColor()` 会识别 `SceneInstanceObject` 并调用 `setSemanticHighlight()` / `clearSemanticHighlight()`,不会直接修改共享 batch 材质。`ObjectManager.setOpacity()`、`ObjectManager.hide()`、`ObjectManager.show()` 也会通过统一 API 作用到单个实例。
|
|
433
493
|
|
|
434
|
-
加载完成后可以按场景树节点 `id` 从 `objectManager` 取回 `SceneInstanceObject
|
|
494
|
+
加载完成后可以按场景树节点 `id` 从 `objectManager` 取回 `SceneInstanceObject`,再直接传给 `viewer.controls.flyToObject()`。`SceneInstanceObject` 内部带有本地 `boundingBox` 缓存能力,因此不需要加载真实 Mesh 副本也能计算飞行包围盒。
|
|
435
495
|
|
|
436
496
|
```typescript
|
|
437
|
-
import { Box3 } from 'three/webgpu';
|
|
438
497
|
import { SceneInstanceObject } from 'u-space/plugins/u-manager';
|
|
439
498
|
|
|
440
499
|
const object = viewer.objectManager.getById<SceneInstanceObject>('SCENE_NODE_ID');
|
|
441
500
|
|
|
442
501
|
if (object?.isSceneInstanceObject) {
|
|
443
|
-
|
|
444
|
-
await viewer.controls.flyToBox(box, {
|
|
502
|
+
await viewer.controls.flyToObject(object, {
|
|
445
503
|
viewpoint: 'current',
|
|
446
504
|
padding: 0.2,
|
|
447
505
|
enableTransition: true,
|
package/docs/changelog.md
CHANGED
|
@@ -2,11 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## 未发布
|
|
4
4
|
|
|
5
|
+
### 变更
|
|
6
|
+
|
|
7
|
+
- **u-manager breaking** — `SemanticModelInstancedLayer` 移除旧的 `addSemanticBatch()` 公开入口,改为 `reserveSemanticBatch()`、`addSemanticInstance()` / `addSemanticInstances()`、`getSemanticInstanceById()`、`removeSemanticInstance()` / `removeSemanticInstances()`、`removeSemanticBatch()` 和 `clearSemanticBatches()`,支持运行时实例新增/删除、按 URL batch 的 capacity 预分配与扩容;`removeSemanticInstances()` 支持直接传单个字符串 `semanticId`,并且 layer 内部不再从 `instance.userData` 扫描 ID。
|
|
8
|
+
- **u-manager breaking** — `SemanticInstanceObject` 新增独立身份字段 `semanticId`、`semanticKind`、`semanticName` 和 `setSemanticIdentity()`;Scene / Facility instancing 的查询、删除和 raycast remap 都使用这些字段,`userData` 只保留原始业务元数据。
|
|
9
|
+
- **u-manager breaking** — `SemanticGroup`、`BuildingGroup` 和 `FloorMesh` 的 building/floor/facility 索引统一读取对象独立 `semanticId` 或显式传入的别名,不再把 `userData.id`、`userData.semanticId`、`userData.facilityId` 自动注册成查询 ID;`SemanticModelInstancedLayer` 会拒绝重复 `semanticId` 的新增实例,并在身份字段变化后按需刷新 id 索引。
|
|
10
|
+
- **u-manager** — `SemanticInstanceObject` 新增对象级 `boundingBox`、`boundingSphere`、`computeBoundingBox()` 和 `computeBoundingSphere()`,`SceneInstanceObject`、`FacilityInstanceObject` 以及楼层语义代理可直接传给 `viewer.controls.flyToObject()`。
|
|
11
|
+
- **u-manager** — `SemanticModelInstancedLayer` 的内部实例矩阵同步改为 WebGPU storage instanced buffer 路径,运行时新增或移动实例后会在下一帧立即同步 matrix;外部仍通过修改 `SemanticInstanceObject` transform 和 dirty sync 使用,不需要直接写内部 `InstancedMesh.instanceMatrix`。
|
|
12
|
+
- **u-manager** — `SemanticGroup` 新增 `getDefaultFacilityLayer()`,`SceneLoader.loadAsync()` 返回的 `SceneGroup` 新增 `sceneLayer` / `getDefaultSceneLayer()`,外部可直接获取默认 Facilities 和场景模型合批层。
|
|
13
|
+
- **docs/mcp** — 更新 u-manager 文档、Examples Guide、LLM 索引和 MCP 检索说明,推荐通过 `viewer.controls.flyToObject()` 飞向语义实例;`getSemanticBoundingBox(target, true)` 保留给手动合并或调整世界包围盒的场景。
|
|
14
|
+
|
|
15
|
+
## 0.0.28
|
|
16
|
+
|
|
17
|
+
### 新增
|
|
18
|
+
|
|
5
19
|
- **u-manager** — 新增 `UManagerLoader` 一体化入口,可同时加载 `SemanticLoader` 和 `SceneLoader`,返回带 `semanticGroup` / `sceneGroup` 直接属性的 `UManagerSceneGroup`,并复用 semantic id 去重避免重复建筑/设备。
|
|
20
|
+
- **examples/docs/mcp** — 新增 `examples/test_umanager_loader.html`,演示 `UManagerLoader` 一次加载语义楼层、Facilities 和非语义场景树,并补充 Examples Guide 与 MCP 检索入口。
|
|
21
|
+
|
|
22
|
+
### 变更
|
|
23
|
+
|
|
6
24
|
- **u-manager breaking** — `SceneInstanceObject`、`FacilityInstanceObject` 和楼层多边形语义统一到 `SemanticInstanceObject` API:使用 `setSemanticVisible()`、`setSemanticColor()`、`setSemanticOpacity()`、`getSemanticBoundingBox()` 等方法,不再使用 scene/facility 专属实例方法。
|
|
7
25
|
- **u-manager** — 新增共享 `SemanticModelInstancedLayer`,`SceneInstancedLayer` 和 `FacilityInstancedLayer` 共用同一套模型模板 instancing、material groups、多材质、dirty sync、raycast remap 和 fallback wrapper 逻辑。
|
|
8
26
|
- **ObjectManager / MaterialEffects** — 对语义实例的显隐、透明度和高亮控制统一识别 `SemanticInstanceObject`,不再维护 SceneInstance/FacilityInstance 两套特殊分支。
|
|
9
|
-
- **
|
|
27
|
+
- **u-manager loader** — `SceneLoader` 不再解析场景树里的 `AREA`、`FLOOR`、`ROOM` 语义面;楼层、区域和房间统一由 `SemanticLoader` / `SemanticGroup` 处理,避免同一语义对象在组合加载时重复出现。
|
|
28
|
+
|
|
29
|
+
### 示例与文档
|
|
30
|
+
|
|
31
|
+
- **在线示例** — `examples/importmap.js` 的 CDN fallback 版本更新为 `0.0.28`,Vercel 部署会继续按 `package.json` 注入当前版本。
|
|
10
32
|
|
|
11
33
|
## 0.0.27
|
|
12
34
|
|