u-space 0.0.27 → 0.0.28
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 +316 -324
- package/dist/plugins/u-manager/index.cjs +56 -56
- package/dist/plugins/u-manager/index.d.ts +2 -0
- package/dist/plugins/u-manager/index.js +8214 -8304
- package/dist/plugins/u-manager/instances/SemanticInstanceObject.d.ts +37 -0
- package/dist/plugins/u-manager/instances/SemanticModelInstancedLayer.d.ts +33 -0
- package/dist/plugins/u-manager/instances/index.d.ts +2 -0
- package/dist/plugins/u-manager/loaders/SceneInstancedLayer.d.ts +6 -22
- package/dist/plugins/u-manager/loaders/UManagerLoader.d.ts +24 -0
- package/dist/plugins/u-manager/semantics/SemanticLoader.d.ts +2 -2
- package/dist/plugins/u-manager/semantics/SemanticParser.d.ts +3 -3
- package/dist/plugins/u-manager/semantics/index.d.ts +1 -1
- package/dist/plugins/u-manager/semantics/objects/BuildingGroup.d.ts +4 -10
- package/dist/plugins/u-manager/semantics/objects/FacilityInstancedLayer.d.ts +5 -39
- package/dist/plugins/u-manager/semantics/objects/FloorMesh.d.ts +30 -15
- package/dist/plugins/u-manager/semantics/objects/SemanticGroup.d.ts +26 -0
- package/dist/plugins/u-manager/semantics/types.d.ts +1 -0
- package/dist/src/effects/MaterialEffects.d.ts +1 -0
- package/docs/api-effects.md +3 -2
- package/docs/api-managers.md +1 -1
- package/docs/api-plugin-u-manager.md +145 -90
- package/docs/changelog.md +21 -9
- package/docs/examples-guide.md +28 -14
- package/docs/getting-started.md +1 -1
- package/docs/mcp.md +14 -7
- package/package.json +1 -1
- package/dist/plugins/u-manager/semantics/objects/SemanticSceneGroup.d.ts +0 -31
|
@@ -4,9 +4,29 @@
|
|
|
4
4
|
|
|
5
5
|
所有加载器均继承自 Three.js 的 `Loader`,并提供 `setPath(path)` 方法,在调用 `loadAsync()` 前配置基础数据目录。
|
|
6
6
|
|
|
7
|
+
## `UManagerLoader`
|
|
8
|
+
|
|
9
|
+
`UManagerLoader` 是推荐的一体化入口。它会在同一路径下同时加载 `SemanticLoader` 和 `SceneLoader`:语义文件中已经存在的 `id` 会由 `SceneLoader` 自动跳过,避免建筑、楼层、设备被重复创建;非语义场景树节点仍会正常加载。返回值是一个 `UManagerSceneGroup`,可直接通过 `semanticGroup` 访问 `SemanticGroup`,通过 `sceneGroup` 访问非语义场景组。
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { UManagerLoader } from 'u-space/plugins/u-manager';
|
|
13
|
+
|
|
14
|
+
const loader = new UManagerLoader(viewer);
|
|
15
|
+
loader.setPath('./scenes/my-scene');
|
|
16
|
+
loader.setKey('YOUR_LICENSE_KEY');
|
|
17
|
+
|
|
18
|
+
const group = await loader.loadAsync();
|
|
19
|
+
viewer.scene.add(group);
|
|
20
|
+
|
|
21
|
+
const semanticGroup = group.semanticGroup;
|
|
22
|
+
const sceneGroup = group.sceneGroup;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
完整可运行示例见 [`examples/test_umanager_loader.html`](https://u-space-phi.vercel.app/examples/test_umanager_loader.html)。该示例展示了如何从返回根组中直接读取 `semanticGroup` / `sceneGroup`,并通过 `viewer.objectManager` 获取 `SceneInstanceObject` / `FacilityInstanceObject` 后调用统一 `getSemanticBoundingBox()`、`setSemanticHighlight()`、`setSemanticVisible()` 和 `setSemanticOpacity()`。
|
|
26
|
+
|
|
7
27
|
## `SemanticLoader`
|
|
8
28
|
|
|
9
|
-
加载 `db/semantic_model.json` 楼层语义数据。`loadAsync()` 返回顶层 `
|
|
29
|
+
加载 `db/semantic_model.json` 楼层语义数据。`loadAsync()` 返回顶层 `SemanticGroup`,用于组织整次语义解析产生的建筑、楼层以及 scene-level facility layer。每个建筑包含多个 `FloorMesh`,每个楼层把墙、柱、空间、楼梯等语义对象合并为一个 TSL 材质驱动的网格;建筑级电梯井和通风井会使用 `ExtrudeMesh` 独立渲染。语义模型包含 `Facilities` 时,`SemanticLoader` 会额外读取 `SceneMetadata.json` 指向的 `tree_models`,按场景授权配置解密,再把对应设备模型挂载到所属楼层。
|
|
10
30
|
|
|
11
31
|
```typescript
|
|
12
32
|
import { Vector3, Vector4 } from 'three';
|
|
@@ -16,30 +36,34 @@ const loader = new SemanticLoader(viewer);
|
|
|
16
36
|
loader.setPath('./scenes/my-scene');
|
|
17
37
|
loader.setKey('YOUR_LICENSE_KEY'); // 官方授权场景解析 tree_models 时必需
|
|
18
38
|
|
|
19
|
-
const
|
|
20
|
-
viewer.scene.add(
|
|
39
|
+
const semanticGroup = await loader.loadAsync(); // SemanticGroup
|
|
40
|
+
viewer.scene.add(semanticGroup);
|
|
21
41
|
|
|
22
|
-
const building =
|
|
42
|
+
const building = semanticGroup.getBuildingAt(0);
|
|
23
43
|
const floor = building.getFloorAt(0);
|
|
24
44
|
|
|
25
45
|
building.isolateFloor(floor);
|
|
26
46
|
await viewer.controls.flyToObject(floor);
|
|
27
47
|
|
|
28
48
|
floor.addEventListener('click', ({ event }) => {
|
|
29
|
-
const {
|
|
49
|
+
const { currentTarget, intersect } = event;
|
|
50
|
+
if (!intersect) return;
|
|
51
|
+
|
|
30
52
|
const index = intersect.semanticIndex;
|
|
31
53
|
|
|
32
|
-
|
|
33
|
-
|
|
54
|
+
currentTarget.setColorAt(index, new Vector4(1, 0, 0, 0.5));
|
|
55
|
+
currentTarget.setScaleAt(index, new Vector3(1, 0.1, 1));
|
|
34
56
|
viewer.render();
|
|
35
57
|
});
|
|
36
58
|
```
|
|
37
59
|
|
|
38
|
-
|
|
60
|
+
所有可按 ID 操作的实例都会尽量暴露统一的 `SemanticInstanceObject` API。楼层多边形语义使用轻量 `FloorSemanticInstanceObject` 代理合并几何中的一个实例;Facilities 和 SceneLoader path instancing 使用同一套模型实例化底层。业务代码优先使用 `setSemanticVisible()`、`setSemanticColor()`、`setSemanticOpacity()` 和 `getSemanticBoundingBox()`,而不是关心底层是合并几何、`InstancedMesh` 还是 fallback `Model`。
|
|
61
|
+
|
|
62
|
+
### `SemanticGroup`
|
|
39
63
|
|
|
40
|
-
`
|
|
64
|
+
`SemanticGroup` 继承自 `BaseGroup`,是单次 `SemanticParser` 解析结果的顶层容器。它管理所有 `BuildingGroup`,并把跨楼层共享的 Facilities `InstancedMesh` 渲染层作为建筑的同级对象挂在 `facilityLayer`,而不是挂到某个 `BuildingGroup` 内。
|
|
41
65
|
|
|
42
|
-
建筑 ID 和设备 ID 查询使用内部 `Map` 索引维护;`getBuildingById()
|
|
66
|
+
建筑 ID 和设备 ID 查询使用内部 `Map` 索引维护;`getBuildingById()` 和 `getFacilityById()` 不会通过数组扫描查找语义对象。
|
|
43
67
|
|
|
44
68
|
| 属性 / 方法 | 说明 |
|
|
45
69
|
| :---------- | :--- |
|
|
@@ -50,18 +74,13 @@ floor.addEventListener('click', ({ event }) => {
|
|
|
50
74
|
| `floorMeshes` | 聚合返回所有建筑内的 `FloorMesh[]`。 |
|
|
51
75
|
| `setFacilityLayer(layer)` | 设置跨楼层设备渲染层;传入 `null` 会移除已有 layer。 |
|
|
52
76
|
| `getFacilityById(id)` | 在整次语义解析结果中按 `Facility.ID` 或显式别名查找设备。 |
|
|
53
|
-
| `showFacility(id \| object)` / `hideFacility(id \| object)` | 显示或隐藏单个设备。 |
|
|
54
|
-
| `setFacilityVisible(id \| object, visible)` | 设置单个设备显隐状态。 |
|
|
55
|
-
| `setFacilityColor(id \| object, color)` | 设置单个设备材质颜色。 |
|
|
56
|
-
| `resetFacilityColor(id \| object)` | 清除单个设备颜色 override,恢复模型原始颜色;透明度 override 不受影响。 |
|
|
57
|
-
| `setFacilityOpacity(id \| object, opacity)` | 设置单个设备材质透明度,`opacity` 会被限制在 `0..1`。 |
|
|
58
77
|
| `showAllFacilities()` / `hideAllFacilities()` | 显示或隐藏整次语义解析结果中的全部设备。 |
|
|
59
78
|
| `showAllFloors()` | 显示所有建筑的所有楼层。 |
|
|
60
79
|
| `planishFloors(kind?)` / `unplanishFloors(kind?)` | 压扁或恢复所有楼层;默认覆盖所有合并语义实例和 Facilities,并按包围盒贴到楼层平面附近,同时添加稳定的轻微 Y 偏移以减少共面闪烁;传入 `kind` 时只作用于该语义类型,`kind` 可包含 `Facilities`。 |
|
|
61
80
|
|
|
62
81
|
### `BuildingGroup`
|
|
63
82
|
|
|
64
|
-
`BuildingGroup` 继承自 `BaseGroup`,用于组织一个建筑下的所有楼层、电梯井和通风井。设备语义对象不会直接挂到 `BuildingGroup`;能 instancing 的设备会渲染到 `
|
|
83
|
+
`BuildingGroup` 继承自 `BaseGroup`,用于组织一个建筑下的所有楼层、电梯井和通风井。设备语义对象不会直接挂到 `BuildingGroup`;能 instancing 的设备会渲染到 `SemanticGroup.facilityLayer`,同时在对应 `FloorMesh` 下保留可检索、可控制的轻量引用。
|
|
65
84
|
|
|
66
85
|
建筑内设备 ID 到楼层的关系由内部 `Map` 索引维护,并通过楼层设备索引版本自动同步。
|
|
67
86
|
|
|
@@ -84,11 +103,6 @@ floor.addEventListener('click', ({ event }) => {
|
|
|
84
103
|
| `isolateFloors(floors \| indexes)` | 只显示指定多个楼层,隐藏其他楼层。 |
|
|
85
104
|
| `showAllFloors()` | 显示全部楼层。 |
|
|
86
105
|
| `getFacilityById(id)` | 在当前建筑的楼层中按 `Facility.ID` 或显式别名查找设备。 |
|
|
87
|
-
| `showFacility(id \| object)` / `hideFacility(id \| object)` | 显示或隐藏当前建筑内的单个设备。 |
|
|
88
|
-
| `setFacilityVisible(id \| object, visible)` | 设置当前建筑内单个设备显隐状态。 |
|
|
89
|
-
| `setFacilityColor(id \| object, color)` | 设置当前建筑内单个设备材质颜色。 |
|
|
90
|
-
| `resetFacilityColor(id \| object)` | 清除当前建筑内单个设备颜色 override。 |
|
|
91
|
-
| `setFacilityOpacity(id \| object, opacity)` | 设置当前建筑内单个设备材质透明度。 |
|
|
92
106
|
| `showAllFacilities()` | 显示整栋建筑内全部楼层设备。 |
|
|
93
107
|
| `hideAllFacilities()` | 隐藏整栋建筑内全部楼层设备。 |
|
|
94
108
|
| `planishFloors(kind?)` | 压扁所有楼层;默认覆盖所有合并语义实例和 Facilities,并按包围盒贴到楼层平面附近,同时添加稳定的轻微 Y 偏移以减少共面闪烁;传入 `kind` 时只压扁该语义类型,`kind` 可包含 `Facilities`。 |
|
|
@@ -121,11 +135,11 @@ if (vent) {
|
|
|
121
135
|
|
|
122
136
|
`tree_models.matrix` 是相对父节点的本地矩阵;`SemanticLoader` 会沿树父级累乘得到设备世界矩阵,再转换为对应 `FloorMesh` 的局部矩阵。因此楼层移动、隔离或显示隐藏时设备会跟随楼层一起工作。即使某个 story 只有 `Facilities`、没有墙柱空间等合并几何,也会创建一个空的 `FloorMesh` 作为设备容器。
|
|
123
137
|
|
|
124
|
-
Facilities 会在单次 `SemanticParser` 解析范围内按模型 URL 自动分组。静态模型会被转换为 scene-level `InstancedMesh` batch 并挂到 `
|
|
138
|
+
Facilities 会在单次 `SemanticParser` 解析范围内按模型 URL 自动分组。静态模型会被转换为 scene-level `InstancedMesh` batch 并挂到 `SemanticGroup.facilityLayer`;每个设备仍会在所属 `FloorMesh` 下保留一个 `FacilityInstanceObject` 语义实例引用,用于 `getFacilityById()`、显隐、颜色、透明度、包围盒和 `viewer.objectManager` 检索。设备 batch 使用普通 `Group` 包裹,以保证每帧渲染前都能执行相机实例裁剪和 hide/show 后的实例同步;带动画、骨骼、morph target 或缺少合法 `geometry.groups` 的多材质 Mesh 会自动回退为普通 `Model`,但普通 `Model` 也会挂到同一个 `FacilityInstanceObject` wrapper 下,对外 API 不变。
|
|
125
139
|
|
|
126
|
-
`facilityLayer` 使用 `FacilityInstancedLayer`,并继承自 `BaseGroup`。当 `facilityLayer.visible = false` 时,layer 会阻止内部 `InstancedMesh`
|
|
140
|
+
`facilityLayer` 使用 `FacilityInstancedLayer`,并继承自 `BaseGroup`。当 `facilityLayer.visible = false` 时,layer 会阻止内部 `InstancedMesh` 继续参与射线检测,避免隐藏的批处理设备仍被点击命中;单个设备的显隐应先通过 `getFacilityById()` 或 `viewer.objectManager.getById()` 取回实例,再调用 `setSemanticVisible()` 控制。
|
|
127
141
|
|
|
128
|
-
Instanced 设备通过 per-instance opacity attribute 控制单个设备透明度。batch 材质默认沿用源材质的透明状态;只有源材质本身透明,或当前参与渲染的设备存在 `opacity < 1` 时,材质才会切到 `transparent: true`,`
|
|
142
|
+
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。
|
|
129
143
|
|
|
130
144
|
加载成功后,设备引用会写入 `userData.facilityId`、`semanticId`、`semanticKind: 'Facilities'`、`semanticName`、`spaces`、`twinsIdentifier`、`storyId` 和 `floorIndex`,并按 `Facility.ID` 注册到 `viewer.objectManager`。自动 instancing 的引用还会带有 `userData.instanced: true` 和 `userData.modelUrl`。
|
|
131
145
|
|
|
@@ -139,10 +153,7 @@ const facility = floor.getFacilityById('FACILITY_001') ?? viewer.objectManager.g
|
|
|
139
153
|
const facilityEntity = floor.getSemanticById('FACILITY_001');
|
|
140
154
|
|
|
141
155
|
if (facility && facilityEntity?.type === 'object') {
|
|
142
|
-
const facilityBox =
|
|
143
|
-
typeof facility.getFacilityBoundingBox === 'function'
|
|
144
|
-
? facility.getFacilityBoundingBox(box, true)
|
|
145
|
-
: box.setFromObject(facility);
|
|
156
|
+
const facilityBox = facility.getSemanticBoundingBox(box, true);
|
|
146
157
|
|
|
147
158
|
await viewer.controls.flyToBox(facilityBox, {
|
|
148
159
|
viewpoint: 'rightFrontTop',
|
|
@@ -152,23 +163,22 @@ if (facility && facilityEntity?.type === 'object') {
|
|
|
152
163
|
|
|
153
164
|
const facilities = floor.getSemanticsByKind('Facilities');
|
|
154
165
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
.
|
|
158
|
-
.
|
|
159
|
-
.
|
|
160
|
-
.showFacility('FACILITY_001');
|
|
166
|
+
facility
|
|
167
|
+
.setSemanticColor('#ff5533')
|
|
168
|
+
.setSemanticOpacity(0.45)
|
|
169
|
+
.resetSemanticColor()
|
|
170
|
+
.setSemanticVisible(true);
|
|
161
171
|
```
|
|
162
172
|
|
|
163
|
-
对 Facilities 推荐使用 `controls.flyToBox()`
|
|
173
|
+
对 Facilities 推荐使用 `controls.flyToBox()` 飞向设备包围盒。无论设备实际渲染来自 `InstancedMesh` 还是 fallback `Model`,都可以通过 `getSemanticBoundingBox(box, true)` 取得完整世界包围盒。
|
|
164
174
|
|
|
165
175
|
### `FacilityInstanceObject`
|
|
166
176
|
|
|
167
|
-
`FacilityInstanceObject`
|
|
177
|
+
`FacilityInstanceObject` 继承自统一的 `SemanticInstanceObject`,表示一个设备语义实例。它会挂在所属 `FloorMesh` 下,用于 ID 检索、事件派发、显隐、颜色、透明度和包围盒查询;如果设备可以 instancing,真正几何由 `SemanticGroup.facilityLayer` 里的 `InstancedMesh` 统一渲染;如果不能 instancing,fallback `Model` 会作为它的子对象挂载。
|
|
168
178
|
|
|
169
|
-
通常不需要手动创建 `FacilityInstanceObject`。`SemanticParser` 在解析 `Facilities` 时会自动创建它,并写入 `userData.facilityId`、`semanticId`、`semanticKind: 'Facilities'`、`semanticName`、`spaces`、`twinsIdentifier`、`storyId`、`floorIndex`、`instanced
|
|
179
|
+
通常不需要手动创建 `FacilityInstanceObject`。`SemanticParser` 在解析 `Facilities` 时会自动创建它,并写入 `userData.facilityId`、`semanticId`、`semanticKind: 'Facilities'`、`semanticName`、`spaces`、`twinsIdentifier`、`storyId`、`floorIndex`、`instanced` 和 `modelUrl`。`userData.instanced` 表示当前设备是否由 `InstancedMesh` 渲染;fallback 时仍然保留同一套语义实例 API。
|
|
170
180
|
|
|
171
|
-
|
|
181
|
+
加载完成后,设备可以按 `Facility.ID` 从全局 `objectManager` 取回。推荐使用 `getSemanticBoundingBox(box, true)` 取得完整世界包围盒,再调用 `viewer.controls.flyToBox()`。
|
|
172
182
|
|
|
173
183
|
```typescript
|
|
174
184
|
import { Box3 } from 'three';
|
|
@@ -177,18 +187,13 @@ import { FacilityInstanceObject } from 'u-space/plugins/u-manager';
|
|
|
177
187
|
const facility = viewer.objectManager.getById<FacilityInstanceObject>('FACILITY_001');
|
|
178
188
|
|
|
179
189
|
if (facility?.isFacilityInstanceObject) {
|
|
180
|
-
const box = facility.
|
|
190
|
+
const box = facility.getSemanticBoundingBox(new Box3(), true);
|
|
181
191
|
|
|
182
192
|
await viewer.controls.flyToBox(box, {
|
|
183
193
|
viewpoint: 'rightFrontTop',
|
|
184
194
|
padding: 0.2,
|
|
185
195
|
enableTransition: true,
|
|
186
196
|
});
|
|
187
|
-
} else if (facility) {
|
|
188
|
-
await viewer.controls.flyToObject(facility, {
|
|
189
|
-
viewpoint: 'rightFrontTop',
|
|
190
|
-
padding: 0.2,
|
|
191
|
-
});
|
|
192
197
|
}
|
|
193
198
|
```
|
|
194
199
|
|
|
@@ -196,15 +201,15 @@ if (facility?.isFacilityInstanceObject) {
|
|
|
196
201
|
| :---------- | :--- |
|
|
197
202
|
| `isFacilityInstanceObject` | 类型标记,值为 `true`。 |
|
|
198
203
|
| `type` | Three.js 对象类型名,值为 `'FacilityInstanceObject'`。 |
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
202
|
-
| `
|
|
203
|
-
| `
|
|
204
|
-
| `
|
|
205
|
-
| `
|
|
206
|
-
| `
|
|
207
|
-
| `
|
|
204
|
+
| `setSemanticBounds(bounds)` | 设置模板在引用本地空间内的包围盒,并返回自身;主要由 `SemanticModelInstancedLayer.addSemanticBatch()` 在建 batch 时写入。 |
|
|
205
|
+
| `getSemanticBoundingBox(target?, world?)` | 获取完整包围盒。`world = true` 时会应用 `matrixWorld`,适合传给 `viewer.controls.flyToBox()`;没有 bounds 时返回 empty `Box3`。 |
|
|
206
|
+
| `setSemanticVisible(visible)` | 设置单个语义实例的 `visible`,并返回自身;渲染和射线检测都会跳过不可见引用。 |
|
|
207
|
+
| `setSemanticColor(color)` | 设置单个语义实例的颜色 override,并返回自身。`color` 支持 Three.js `ColorRepresentation`。 |
|
|
208
|
+
| `resetSemanticColor()` | 清除颜色 override,恢复使用源材质颜色,并返回自身;不会清除透明度 override。 |
|
|
209
|
+
| `setSemanticOpacity(opacity)` | 设置单个语义实例透明度,并返回自身;值会被限制在 `0..1`。 |
|
|
210
|
+
| `getSemanticColor(target?)` | 读取当前颜色 override;没有 override 时返回白色 `(1, 1, 1)`。 |
|
|
211
|
+
| `hasSemanticColor()` | 判断当前实例是否存在颜色 override。 |
|
|
212
|
+
| `getSemanticOpacity()` | 读取当前透明度 override。 |
|
|
208
213
|
|
|
209
214
|
```typescript
|
|
210
215
|
import { Box3 } from 'three';
|
|
@@ -215,11 +220,11 @@ const facility = floor.getFacilityById('FACILITY_001');
|
|
|
215
220
|
|
|
216
221
|
if (facility instanceof FacilityInstanceObject) {
|
|
217
222
|
facility
|
|
218
|
-
.
|
|
219
|
-
.
|
|
220
|
-
.
|
|
223
|
+
.setSemanticColor('#29ccff')
|
|
224
|
+
.setSemanticOpacity(0.6)
|
|
225
|
+
.setSemanticVisible(true);
|
|
221
226
|
|
|
222
|
-
await viewer.controls.flyToBox(facility.
|
|
227
|
+
await viewer.controls.flyToBox(facility.getSemanticBoundingBox(box, true), {
|
|
223
228
|
viewpoint: 'rightFrontTop',
|
|
224
229
|
padding: 0.2,
|
|
225
230
|
});
|
|
@@ -228,9 +233,9 @@ if (facility instanceof FacilityInstanceObject) {
|
|
|
228
233
|
|
|
229
234
|
### `FacilityInstancedLayer`
|
|
230
235
|
|
|
231
|
-
`FacilityInstancedLayer` 继承自 `
|
|
236
|
+
`FacilityInstancedLayer` 继承自 `SemanticModelInstancedLayer`,是 scene-level Facilities 的批量渲染层。`SemanticGroup.facilityLayer` 默认就是该类型;它会按模型 URL 把同一模板的静态设备合并为一个或多个 `InstancedMesh`,并用普通 `Group` 包裹每个模型 URL batch,保证设备显隐、颜色、透明度和相机实例裁剪能在每帧渲染前同步,同时保留每个楼层下的 `FacilityInstanceObject` 引用用于业务 API 和事件冒泡。
|
|
232
237
|
|
|
233
|
-
使用 `SemanticLoader` 时通常不需要直接调用 `
|
|
238
|
+
使用 `SemanticLoader` 时通常不需要直接调用 `addSemanticBatch()`;只有自定义语义解析或自建设备 batch 时才需要手动创建 layer,然后通过 `semanticGroup.setFacilityLayer(layer)` 挂回语义场景。
|
|
234
239
|
|
|
235
240
|
| 属性 / 方法 | 说明 |
|
|
236
241
|
| :---------- | :--- |
|
|
@@ -239,11 +244,11 @@ if (facility instanceof FacilityInstanceObject) {
|
|
|
239
244
|
| `name` | 构造函数默认设为 `'Facilities'`。 |
|
|
240
245
|
| `getInstanceCulling()` | 返回当前实例裁剪配置:`enabled`、`frustum`、`minScreenRadius`。 |
|
|
241
246
|
| `setInstanceCulling(options)` | 设置实例裁剪配置,并返回自身。默认 `enabled: false`、`frustum: true`、`minScreenRadius: 0`;默认不按相机裁剪设备,避免相机距离影响设备显隐。需要性能压缩时可设置 `enabled: true` 开启视锥裁剪,或再把 `minScreenRadius` 设为大于 0 的值开启屏幕尺寸裁剪。 |
|
|
242
|
-
| `
|
|
247
|
+
| `addSemanticBatch(url, template, instances)` | 尝试把同一模型 URL 的语义实例引用合并为 instanced batch。成功返回 `true`,失败返回 `false`,失败时调用方应回退为普通模型渲染。 |
|
|
243
248
|
|
|
244
|
-
`
|
|
249
|
+
`addSemanticBatch(url, template, instances)` 会从 `template` 收集可 instancing 的可见 `Mesh`,克隆源材质,并为每个 batch 建立 instance matrix、颜色和透明度 attribute。没有动画、没有骨骼、没有 morph target,且每个多材质 `Mesh` 都有合法 `geometry.groups` 的模板会进入 instancing;不满足条件、模板没有可用 Mesh 或 `instances` 为空时会返回 `false`。
|
|
245
250
|
|
|
246
|
-
渲染前,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`,并附加 `
|
|
251
|
+
渲染前,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: 'Facilities'`、`semanticName` 和 `semanticIndex`,事件随后会按楼层引用对象继续冒泡。
|
|
247
252
|
|
|
248
253
|
```typescript
|
|
249
254
|
import { FacilityInstancedLayer, FacilityInstanceObject } from 'u-space/plugins/u-manager';
|
|
@@ -251,16 +256,20 @@ import { FacilityInstancedLayer, FacilityInstanceObject } from 'u-space/plugins/
|
|
|
251
256
|
const layer = new FacilityInstancedLayer();
|
|
252
257
|
const refs = facilityItems.map(() => new FacilityInstanceObject());
|
|
253
258
|
|
|
254
|
-
if (layer.
|
|
255
|
-
|
|
259
|
+
if (layer.addSemanticBatch('/models/camera.glb', templateObject, refs)) {
|
|
260
|
+
semanticGroup.setFacilityLayer(layer);
|
|
256
261
|
}
|
|
257
262
|
```
|
|
258
263
|
|
|
259
|
-
如果只想整体隐藏或禁用 scene-level batch,可以设置 `
|
|
264
|
+
如果只想整体隐藏或禁用 scene-level batch,可以设置 `semanticGroup.facilityLayer.visible = false`;如果只想控制单个设备,推荐从 `floor.getFacilityById(id)` 或 `viewer.objectManager.getById(id)` 取回 `FacilityInstanceObject` 后调用 `setSemanticVisible()` / `setSemanticOpacity()`。
|
|
265
|
+
|
|
266
|
+
### `SemanticModelInstancedLayer`
|
|
267
|
+
|
|
268
|
+
`SemanticModelInstancedLayer` 是 `SceneInstancedLayer` 和 `FacilityInstancedLayer` 的共享实现。它负责模板 mesh 收集、材质克隆、`InstancedMesh` 创建、instance matrix/color/opacity buffer 同步、raycast hit remap、fallback 判定和可选相机实例裁剪。通常业务代码不需要直接使用它,除非要为新的语义实例类型复用同一套模型 instancing 能力。
|
|
260
269
|
|
|
261
270
|
### `FloorMesh`
|
|
262
271
|
|
|
263
|
-
`FloorMesh` 继承自 `BaseMesh`,表示一个楼层。它不是 `BatchedMesh`,而是把同一楼层内的语义对象合并为一份几何,并通过 TSL + `DataTexture` 在 GPU 侧按 `semanticIndex` 控制颜色、透明度、显示隐藏和实例矩阵。楼层语义网格会按实例透明度拆分为不透明和透明两个材质通道;墙、柱、门等默认不透明实例会进入 opaque pipeline,`Spaces`、`Windows` 或通过 `setOpacityAt()` 改成半透明的实例会进入 transparent pipeline。Facilities 通过楼层下的引用对象进行检索和控制;实际渲染可能来自 `
|
|
272
|
+
`FloorMesh` 继承自 `BaseMesh`,表示一个楼层。它不是 `BatchedMesh`,而是把同一楼层内的语义对象合并为一份几何,并通过 TSL + `DataTexture` 在 GPU 侧按 `semanticIndex` 控制颜色、透明度、显示隐藏和实例矩阵。楼层语义网格会按实例透明度拆分为不透明和透明两个材质通道;墙、柱、门等默认不透明实例会进入 opaque pipeline,`Spaces`、`Windows` 或通过 `setOpacityAt()` 改成半透明的实例会进入 transparent pipeline。Facilities 通过楼层下的引用对象进行检索和控制;实际渲染可能来自 `SemanticGroup.facilityLayer` 的 `InstancedMesh` batch,也可能是 fallback 的普通 `Model`。
|
|
264
273
|
|
|
265
274
|
这种结构的目标是让每个楼层通常只占一个主渲染 draw call,同时仍保留实例级控制能力。`xxxAt` 方法全部作用于单个语义实例;没有 `At` 后缀的方法作用于整个楼层 mesh。
|
|
266
275
|
|
|
@@ -270,7 +279,7 @@ if (layer.addFacilityBatch('/models/camera.glb', templateObject, refs)) {
|
|
|
270
279
|
| `semanticInstances` | `Map<string, number>`,从语义对象 ID 映射到实例下标。 |
|
|
271
280
|
| `wallsInstances` / `columnsInstances` / `spacesInstances` / `doorsInstances` / `windowsInstances` / `staircasesInstances` | 按语义类型维护的 ID 到实例下标映射。 |
|
|
272
281
|
| `semanticEntities` | `Map<string, FloorSemanticEntity>`,统一索引楼层内合并几何语义和 `Facilities` 对象语义。 |
|
|
273
|
-
| `facilityObjectsById` | `Map<string,
|
|
282
|
+
| `facilityObjectsById` | `Map<string, FacilityInstanceObject>`,按 `Facility.ID` 或显式别名查找设备引用。 |
|
|
274
283
|
| `facilityObjects` | 当前楼层挂载的设备引用数组。 |
|
|
275
284
|
| `getSemanticIndexById(id)` | 根据语义 ID 获取实例下标。 |
|
|
276
285
|
| `getSemanticById(id)` | 根据语义 ID 或设备别名获取统一语义实体。 |
|
|
@@ -279,17 +288,13 @@ if (layer.addFacilityBatch('/models/camera.glb', templateObject, refs)) {
|
|
|
279
288
|
| `getSemanticIdAt(index)` | 获取实例语义 ID。 |
|
|
280
289
|
| `getSemanticKindAt(index)` | 获取合并几何实例类型:`'Walls' \| 'Columns' \| 'Spaces' \| 'Doors' \| 'Windows' \| 'Staircases'`。 |
|
|
281
290
|
| `getSemanticNameAt(index)` | 获取实例名称;当前 `Spaces` 会从语义数据的 `Name` 写入,其他类型可能为 `undefined`。 |
|
|
291
|
+
| `getSemanticObjectAt(index)` | 获取合并几何实例对应的 `FloorSemanticInstanceObject` 轻量对象。 |
|
|
282
292
|
| `showAllFacilities()` | 显示当前楼层的全部设备对象。 |
|
|
283
293
|
| `hideAllFacilities()` | 隐藏当前楼层的全部设备对象。 |
|
|
284
294
|
| `addFacility(object, ids?)` | 把设备对象挂到楼层,并按显式 ID、`facilityId`、`semanticId`、`id` 建立检索别名。 |
|
|
285
295
|
| `getFacilityById(id)` | 根据 `Facility.ID` 或别名获取设备对象。 |
|
|
286
296
|
| `getFacilityAt(index)` | 按楼层设备数组下标获取设备对象。 |
|
|
287
297
|
| `getFacilities()` | 获取当前楼层全部设备对象。 |
|
|
288
|
-
| `showFacility(id \| object)` / `hideFacility(id \| object)` | 显示或隐藏当前楼层内的单个设备。 |
|
|
289
|
-
| `setFacilityVisible(id \| object, visible)` | 设置当前楼层内单个设备显隐状态。 |
|
|
290
|
-
| `setFacilityColor(id \| object, color)` | 设置当前楼层内单个设备材质颜色。 |
|
|
291
|
-
| `resetFacilityColor(id \| object)` | 清除当前楼层内单个设备颜色 override。 |
|
|
292
|
-
| `setFacilityOpacity(id \| object, opacity)` | 设置当前楼层内单个设备材质透明度。 |
|
|
293
298
|
| `removeFacility(facility)` | 按设备对象或 ID 从楼层移除设备,并同步清理检索索引。 |
|
|
294
299
|
| `setColorAt(index, color)` / `getColorAt(index, target?)` | 修改或读取实例颜色,支持 `Color`、`Vector3`、`Vector4`。 |
|
|
295
300
|
| `setOpacityAt(index, opacity)` / `getOpacityAt(index)` | 修改或读取实例透明度。 |
|
|
@@ -305,7 +310,7 @@ if (layer.addFacilityBatch('/models/camera.glb', templateObject, refs)) {
|
|
|
305
310
|
|
|
306
311
|
### 飞向语义实例
|
|
307
312
|
|
|
308
|
-
点击、射线检测命中 `FloorMesh` 里的语义几何或设备时,`intersect` 上会附加语义字段:`semanticIndex`、`semanticId`、`semanticKind`。普通楼层语义几何的事件目标是 `
|
|
313
|
+
点击、射线检测命中 `FloorMesh` 里的语义几何或设备时,`intersect` 上会附加语义字段:`semanticIndex`、`semanticId`、`semanticKind` 和 `semanticObject`。普通楼层语义几何的事件目标是 `FloorSemanticInstanceObject`;instanced Facilities 的事件目标是楼层下的 `FacilityInstanceObject` 引用,事件会继续冒泡到所属 `FloorMesh`、`BuildingGroup` 和 `SemanticGroup`。因此楼层监听器中应使用 `event.currentTarget` 操作楼层,用 `intersect.semanticObject` 或 `event.target` 取得具体语义实例。
|
|
309
314
|
|
|
310
315
|
```typescript
|
|
311
316
|
import { Box3 } from 'three';
|
|
@@ -317,11 +322,8 @@ floor.addEventListener('click', async ({ event }) => {
|
|
|
317
322
|
if (!intersect) return;
|
|
318
323
|
|
|
319
324
|
if (intersect.semanticKind === 'Facilities') {
|
|
320
|
-
const facility = intersect.
|
|
321
|
-
const facilityBox =
|
|
322
|
-
typeof facility.getFacilityBoundingBox === 'function'
|
|
323
|
-
? facility.getFacilityBoundingBox(box, true)
|
|
324
|
-
: box.setFromObject(facility);
|
|
325
|
+
const facility = intersect.semanticObject ?? event.target;
|
|
326
|
+
const facilityBox = facility.getSemanticBoundingBox(box, true);
|
|
325
327
|
|
|
326
328
|
await viewer.controls.flyToBox(facilityBox, {
|
|
327
329
|
viewpoint: 'rightFrontTop',
|
|
@@ -330,7 +332,8 @@ floor.addEventListener('click', async ({ event }) => {
|
|
|
330
332
|
return;
|
|
331
333
|
}
|
|
332
334
|
|
|
333
|
-
const semanticBox =
|
|
335
|
+
const semanticBox = intersect.semanticObject?.getSemanticBoundingBox(box, true)
|
|
336
|
+
?? currentTarget.getBoundingBoxAt(intersect.semanticIndex, box, true);
|
|
334
337
|
|
|
335
338
|
if (semanticBox) {
|
|
336
339
|
await viewer.controls.flyToBox(semanticBox, {
|
|
@@ -354,17 +357,17 @@ if (index !== undefined) {
|
|
|
354
357
|
|
|
355
358
|
### 楼层压扁
|
|
356
359
|
|
|
357
|
-
`planish()` / `unplanish()` 默认作用于当前楼层的全部合并语义实例,包含墙、柱、空间、门窗、楼梯等语义几何,也包含楼层下的 Facilities 引用;传入 `kind` 时只处理该类型,例如 `floor.planish('Walls')` 或 `floor.planish('Facilities')`。所有被压平的实例都会按自身包围盒把压平后的薄片贴到楼层平面 `FloorMesh.pivot.y` 附近,而不是围绕实例自身基点压缩;压平时还会按语义类型和实例 ID 加入稳定的轻微 Y 偏移,减少门、墙或其他重叠实例之间的共面闪烁。`unplanish()` 会恢复合并几何实例的原始矩阵,`unplanish('Facilities')` 会恢复设备原始 `position.y` 和 `scale.y`。instanced Facilities 会通过楼层下的 `FacilityInstanceObject` transform 同步到 `
|
|
360
|
+
`planish()` / `unplanish()` 默认作用于当前楼层的全部合并语义实例,包含墙、柱、空间、门窗、楼梯等语义几何,也包含楼层下的 Facilities 引用;传入 `kind` 时只处理该类型,例如 `floor.planish('Walls')` 或 `floor.planish('Facilities')`。所有被压平的实例都会按自身包围盒把压平后的薄片贴到楼层平面 `FloorMesh.pivot.y` 附近,而不是围绕实例自身基点压缩;压平时还会按语义类型和实例 ID 加入稳定的轻微 Y 偏移,减少门、墙或其他重叠实例之间的共面闪烁。`unplanish()` 会恢复合并几何实例的原始矩阵,`unplanish('Facilities')` 会恢复设备原始 `position.y` 和 `scale.y`。instanced Facilities 会通过楼层下的 `FacilityInstanceObject` transform 同步到 `SemanticGroup.facilityLayer`。
|
|
358
361
|
|
|
359
362
|
如果只想调整单个合并几何实例,可以使用 `setScaleAt()` 或 `setScaleYAt()`,它们会保留该语义对象自己的基点矩阵,因此压扁墙、柱、房间时不会被压到世界原点。
|
|
360
363
|
|
|
361
364
|
## `SceneLoader`
|
|
362
365
|
|
|
363
|
-
|
|
366
|
+
从服务端场景包加载完整场景树(模型、组、基础形状),并自动将所有已加载对象按 `id` 和 `sid` 注册到 `viewer.objectManager`。
|
|
364
367
|
默认会尝试读取同一路径下的 `db/semantic_model.json`,如果场景树节点的 `id` 已存在于语义文件中,则跳过该节点,方便和 `SemanticLoader` 同时加载而不重复创建建筑或设备。
|
|
365
|
-
对语义去重后仍保留的 3D 节点,`SceneLoader` 默认会按模型 `path` 自动合并重复静态模型:同一 `path` 的多个实例只加载一次模板,并由 `SceneInstancedLayer`
|
|
368
|
+
对语义去重后仍保留的 3D 节点,`SceneLoader` 默认会按模型 `path` 自动合并重复静态模型:同一 `path` 的多个实例只加载一次模板,并由 `SceneInstancedLayer` 通过共享的 `SemanticModelInstancedLayer` 批量渲染。原场景树中仍会保留每个 `id` / `sid` 对应的 `SceneInstanceObject` 逻辑对象,用于业务检索、显隐、颜色、透明度、包围盒和事件派发。
|
|
366
369
|
|
|
367
|
-
模板包含动画、骨骼、morph target
|
|
370
|
+
模板包含动画、骨骼、morph target、非法多材质 groups 或没有可实例化网格时,会自动回退为普通 `Model` 渲染;fallback `Model` 会挂在同一个 `SceneInstanceObject` 下,因此调用侧不需要额外配置。
|
|
368
371
|
|
|
369
372
|
```typescript
|
|
370
373
|
import { SceneLoader } from 'u-space/plugins/u-manager';
|
|
@@ -387,19 +390,58 @@ viewer.scene.add(group);
|
|
|
387
390
|
|
|
388
391
|
### `SceneInstanceObject`
|
|
389
392
|
|
|
390
|
-
`SceneInstanceObject`
|
|
393
|
+
`SceneInstanceObject` 继承自统一的 `SemanticInstanceObject`,表示一个由 `SceneInstancedLayer` 批量渲染或 fallback 普通渲染的场景模型引用。它会保留在原场景树层级中,并注册到 `viewer.objectManager`,因此 `getById()`、`show()` / `hide()`、`isolate()`、`showAll()`、`setOpacity()` 和 `getBoundingBox()` 可以继续按单个模型 ID 使用。
|
|
394
|
+
|
|
395
|
+
`SceneInstanceObject` 本身只增加场景模型语义:`isSceneInstanceObject = true`、`type = 'SceneInstanceObject'`,并把 `userData.semanticKind` 设为 `SceneInstances`。加载器会把场景树节点的 `id` 和 `sid` 都注册到 `viewer.objectManager`,因此同一个对象可以通过任一 ID 取回。对重复模型的 instanced 路径,`userData.instanced = true`,并带有 `modelPath`、`modelUrl`、`semanticId` 和 `semanticName`;如果模板不支持 instancing,会退回普通 `Model`,但仍挂在同一个 `SceneInstanceObject` 下,对外 API 不变。
|
|
391
396
|
|
|
392
|
-
|
|
397
|
+
**识别字段:**
|
|
393
398
|
|
|
394
|
-
|
|
399
|
+
| 字段 | 说明 |
|
|
400
|
+
| :--- | :--- |
|
|
401
|
+
| `isSceneInstanceObject` | 固定为 `true`,用于判断对象是否来自 `SceneLoader` 的场景实例。 |
|
|
402
|
+
| `isSemanticInstanceObject` | 固定为 `true`,表示它支持统一语义实例 API。 |
|
|
403
|
+
| `type` | 固定为 `SceneInstanceObject`。 |
|
|
404
|
+
| `userData.id` / `userData.sid` | 原始场景树节点 ID;两个 ID 都会注册到 `viewer.objectManager`。 |
|
|
405
|
+
| `userData.semanticId` | 语义事件和 raycast remap 使用的实例 ID,默认等于场景树节点 `id`。 |
|
|
406
|
+
| `userData.semanticKind` | 固定为 `SceneInstances`。 |
|
|
407
|
+
| `userData.semanticName` | 原始场景树节点名称。 |
|
|
408
|
+
| `userData.instanced` | `true` 表示实际渲染来自 `SceneInstancedLayer`;`false` 表示 fallback `Model` 挂在当前对象下。 |
|
|
409
|
+
| `userData.modelPath` / `userData.modelUrl` | instanced 场景实例对应的模型相对路径和完整 URL。 |
|
|
410
|
+
|
|
411
|
+
**方法:**
|
|
412
|
+
|
|
413
|
+
| 方法 | 说明 |
|
|
414
|
+
| :--- | :--- |
|
|
415
|
+
| `setSemanticBounds(bounds)` | 设置本地包围盒。`SceneLoader` 会在创建 instanced batch 时根据模板模型自动设置;业务一般不需要手动调用。 |
|
|
416
|
+
| `setSemanticRenderObject(object)` | 设置 fallback 渲染对象并作为子对象挂载。模板无法 instancing 时由 `SceneLoader` 自动调用;自定义语义实例渲染时可手动使用。 |
|
|
417
|
+
| `getSemanticRenderObject()` | 返回 fallback 渲染对象;instanced 渲染路径通常返回 `null`,因为真实 Mesh 在 `SceneInstancedLayer` 中。 |
|
|
418
|
+
| `getSemanticBoundingBox(target?, world?)` | 返回实例包围盒。`world = true` 时会应用 `matrixWorld`,适合直接传给 `viewer.controls.flyToBox()`;无 bounds 和 render object 时返回 empty `Box3`。 |
|
|
419
|
+
| `setSemanticVisible(visible)` | 设置单个实例显隐并标记 batch dirty;隐藏后该实例不会继续渲染,也不会被 instanced raycast 命中。 |
|
|
420
|
+
| `setSemanticColor(color)` | 设置单个实例颜色 override。instanced 路径写入 per-instance color;fallback `Model` 路径会委托到材质高亮逻辑。 |
|
|
421
|
+
| `resetSemanticColor()` | 清除颜色 override,恢复使用源模型颜色;不会清除透明度 override。 |
|
|
422
|
+
| `setSemanticOpacity(opacity)` | 设置单个实例透明度,值会限制在 `0..1`。instanced 路径写入 per-instance opacity;fallback 路径会按原始材质透明度相乘。 |
|
|
423
|
+
| `getSemanticColor(target?)` | 获取当前生效颜色。存在颜色或高亮 override 时返回该颜色,否则返回白色。传入 `Color` 可复用对象。 |
|
|
424
|
+
| `hasSemanticColor()` | 返回当前是否存在颜色或高亮颜色 override。 |
|
|
425
|
+
| `getSemanticOpacity()` | 获取当前生效透明度;高亮存在时返回高亮透明度,否则返回基础透明度。 |
|
|
426
|
+
| `setSemanticHighlight(color, opacity, overwrite?)` | 设置临时高亮样式。`overwrite = false` 时按 tint 模式与原色混合,`true` 时直接替换颜色;高亮会覆盖基础颜色/透明度直到调用 `clearSemanticHighlight()`。 |
|
|
427
|
+
| `clearSemanticHighlight()` | 清除临时高亮,并恢复到基础颜色/透明度状态。 |
|
|
428
|
+
| `getSemanticColorMode()` | 返回当前颜色模式:`SEMANTIC_COLOR_MODE_NONE`、`SEMANTIC_COLOR_MODE_OVERRIDE` 或 `SEMANTIC_COLOR_MODE_TINT`。 |
|
|
429
|
+
| `onSemanticRenderDirty(callback)` | 监听实例渲染状态变化,返回取消监听函数。实例显隐、颜色、透明度或世界矩阵变化时会触发。 |
|
|
430
|
+
| `markSemanticRenderDirty()` | 手动标记实例渲染状态已变化,通知所属 batch 在下一帧同步 instance buffer。 |
|
|
431
|
+
|
|
432
|
+
`MaterialEffects.highlightColor()` / `removeHighlightColor()` 会识别 `SceneInstanceObject` 并调用 `setSemanticHighlight()` / `clearSemanticHighlight()`,不会直接修改共享 batch 材质。`ObjectManager.setOpacity()`、`ObjectManager.hide()`、`ObjectManager.show()` 也会通过统一 API 作用到单个实例。
|
|
433
|
+
|
|
434
|
+
加载完成后可以按场景树节点 `id` 从 `objectManager` 取回 `SceneInstanceObject`,再用 `getSemanticBoundingBox(box, true)` 取得世界包围盒并传给 `viewer.controls.flyToBox()`。`SceneInstanceObject` 内部带有不可见的本地 bounds,因此不需要加载真实 Mesh 副本也能计算飞行包围盒。
|
|
395
435
|
|
|
396
436
|
```typescript
|
|
437
|
+
import { Box3 } from 'three/webgpu';
|
|
397
438
|
import { SceneInstanceObject } from 'u-space/plugins/u-manager';
|
|
398
439
|
|
|
399
440
|
const object = viewer.objectManager.getById<SceneInstanceObject>('SCENE_NODE_ID');
|
|
400
441
|
|
|
401
442
|
if (object?.isSceneInstanceObject) {
|
|
402
|
-
|
|
443
|
+
const box = object.getSemanticBoundingBox(new Box3(), true);
|
|
444
|
+
await viewer.controls.flyToBox(box, {
|
|
403
445
|
viewpoint: 'current',
|
|
404
446
|
padding: 0.2,
|
|
405
447
|
enableTransition: true,
|
|
@@ -407,20 +449,33 @@ if (object?.isSceneInstanceObject) {
|
|
|
407
449
|
}
|
|
408
450
|
```
|
|
409
451
|
|
|
452
|
+
颜色、透明度和显隐可以直接链式调用:
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
object
|
|
456
|
+
?.setSemanticColor('#29ccff')
|
|
457
|
+
.setSemanticOpacity(0.55)
|
|
458
|
+
.setSemanticVisible(true);
|
|
459
|
+
|
|
460
|
+
object?.setSemanticHighlight('#ffb703', 0.75, true);
|
|
461
|
+
object?.clearSemanticHighlight();
|
|
462
|
+
object?.resetSemanticColor();
|
|
463
|
+
```
|
|
464
|
+
|
|
410
465
|
如果需要自己控制包围盒,也可以取世界包围盒后调用 `flyToBox()`:
|
|
411
466
|
|
|
412
467
|
```typescript
|
|
413
468
|
import { Box3 } from 'three/webgpu';
|
|
414
469
|
|
|
415
|
-
const box = object.
|
|
470
|
+
const box = object.getSemanticBoundingBox(new Box3(), true);
|
|
416
471
|
await viewer.controls.flyToBox(box, { viewpoint: 'frontTop', padding: 0.2 });
|
|
417
472
|
```
|
|
418
473
|
|
|
419
|
-
射线检测命中批处理实例时,`intersect.object` 会被改写为对应的 `SceneInstanceObject`,并附加 `
|
|
474
|
+
射线检测命中批处理实例时,`intersect.object` 会被改写为对应的 `SceneInstanceObject`,并附加 `semanticObject`、`semanticId`、`semanticKind: 'SceneInstances'` 和 `semanticIndex`,事件会按逻辑对象所在的原场景树继续冒泡。
|
|
420
475
|
|
|
421
476
|
### `SceneInstancedLayer`
|
|
422
477
|
|
|
423
|
-
`SceneInstancedLayer` 是 `SceneLoader` 内部使用的批量渲染层,默认 `name = 'SceneInstances'
|
|
478
|
+
`SceneInstancedLayer` 是 `SceneLoader` 内部使用的批量渲染层,默认 `name = 'SceneInstances'`。它继承自 `SemanticModelInstancedLayer`,每个重复模型 URL 会生成一个 batch,batch 内按模板的可实例化 Mesh 创建 `InstancedMesh`。渲染前只在实例矩阵、显隐、颜色或透明度变化时同步 instance buffer;普通相机移动不会重新上传所有实例数据。
|
|
424
479
|
|
|
425
480
|
## `TopologiesLoader` / `TopologyParser`
|
|
426
481
|
|
package/docs/changelog.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## 未发布
|
|
4
4
|
|
|
5
|
+
- **u-manager** — 新增 `UManagerLoader` 一体化入口,可同时加载 `SemanticLoader` 和 `SceneLoader`,返回带 `semanticGroup` / `sceneGroup` 直接属性的 `UManagerSceneGroup`,并复用 semantic id 去重避免重复建筑/设备。
|
|
6
|
+
- **u-manager breaking** — `SceneInstanceObject`、`FacilityInstanceObject` 和楼层多边形语义统一到 `SemanticInstanceObject` API:使用 `setSemanticVisible()`、`setSemanticColor()`、`setSemanticOpacity()`、`getSemanticBoundingBox()` 等方法,不再使用 scene/facility 专属实例方法。
|
|
7
|
+
- **u-manager** — 新增共享 `SemanticModelInstancedLayer`,`SceneInstancedLayer` 和 `FacilityInstancedLayer` 共用同一套模型模板 instancing、material groups、多材质、dirty sync、raycast remap 和 fallback wrapper 逻辑。
|
|
8
|
+
- **ObjectManager / MaterialEffects** — 对语义实例的显隐、透明度和高亮控制统一识别 `SemanticInstanceObject`,不再维护 SceneInstance/FacilityInstance 两套特殊分支。
|
|
9
|
+
- **examples/docs/mcp** — 新增 `examples/test_umanager_loader.html`,演示 `UManagerLoader` 一次加载语义楼层、Facilities 和非语义场景树,并补充 Examples Guide 与 MCP 检索入口。
|
|
10
|
+
|
|
11
|
+
## 0.0.27
|
|
12
|
+
|
|
5
13
|
### 变更
|
|
6
14
|
|
|
7
15
|
- **u-manager semantics** — `FacilityInstancedLayer` 的相机逐实例裁剪改为显式开启:默认不再按当前相机视锥或 `minScreenRadius` 压缩 active instances,避免相机距离影响设备显隐;需要性能压缩时可通过 `setInstanceCulling({ enabled: true })` 开启。
|
|
@@ -12,11 +20,15 @@
|
|
|
12
20
|
- **fire 插件** — 新增 `FireEffect`,封装 Three.js 官方 `webgpu_volume_fire` 体积火焰示例,支持 WebGPU 3D 流体模拟、烟雾散射、动态光照、体积阴影、output effect 叠加和 `cameraChange` 相机切换同步。
|
|
13
21
|
- **u-manager loader** — `SceneLoader` 默认读取同路径 `db/semantic_model.json` 并按语义 `ID` 跳过重复场景树节点,支持与 `SemanticLoader` 同时加载。
|
|
14
22
|
- **u-manager loader** — `SceneLoader` 默认按模型 `path` 合并重复静态 3D 节点,通过 `SceneInstancedLayer` 批量渲染,同时保留 `SceneInstanceObject` 逻辑对象用于单模型 ID 检索、显隐、颜色、透明度、包围盒和事件派发。
|
|
15
|
-
- **docs/mcp** — 补充 `SceneInstanceObject` 通过 `objectManager.getById()` + `
|
|
23
|
+
- **docs/mcp** — 补充 `SceneInstanceObject` 和 `FacilityInstanceObject` 通过 `objectManager.getById()` + `getSemanticBoundingBox()` + `controls.flyToBox()` 飞向单个实例的示例与 MCP 检索说明。
|
|
16
24
|
- **ObjectManager / MaterialEffects** — `setOpacity()`、`show()` / `hide()`、`isolate()`、`showAll()` 以及 `highlightColor()` / `removeHighlightColor()` 支持 `SceneInstanceObject` 的 per-instance 控制。
|
|
17
25
|
- **u-manager loader** — `SceneLoader` 创建的根组、`GROUP` 和 `STUB` 节点改为 `BaseGroup`,隐藏后可通过对象自身 `raycast()` 返回值阻止子级继续参与递归射线检测。
|
|
18
26
|
- **Viewer** — 默认使用 Three.js `reversedDepthBuffer` 替代 `logarithmicDepthBuffer`,减少 WebGPU 顶视角多层透明场景的 fragment 压力,同时保留远距离深度精度。
|
|
19
27
|
|
|
28
|
+
### 示例与文档
|
|
29
|
+
|
|
30
|
+
- **在线示例** — `examples/importmap.js` 的 CDN fallback 版本更新为 `0.0.27`,Vercel 部署会继续按 `package.json` 注入当前版本。
|
|
31
|
+
|
|
20
32
|
## 0.0.26
|
|
21
33
|
|
|
22
34
|
### 修复
|
|
@@ -38,17 +50,17 @@
|
|
|
38
50
|
|
|
39
51
|
### 新增
|
|
40
52
|
|
|
41
|
-
- **u-manager semantics** — 新增 `
|
|
42
|
-
- **u-manager semantics** — `
|
|
43
|
-
- **u-manager semantics** — `
|
|
44
|
-
- **u-manager semantics** — `FloorMesh.planish()` / `unplanish()`、`BuildingGroup.planishFloors()` / `unplanishFloors()` 和 `
|
|
45
|
-
- **u-manager semantics** — 新增 scene-level Facilities auto instancing:同模型 URL 的静态设备会合并到 `
|
|
46
|
-
- **u-manager semantics** — `
|
|
47
|
-
- **docs/mcp** — 补充 Facilities auto instancing 的材质透明度行为、`
|
|
53
|
+
- **u-manager semantics** — 新增 `SemanticGroup`,作为单次 `SemanticParser` 解析结果的顶层容器;`SemanticLoader.loadAsync()` / `SemanticParser.parseAsync()` 直接返回完整语义场景。
|
|
54
|
+
- **u-manager semantics** — `SemanticGroup` 新增 `getBuildingById(id)`,支持按语义数据里的 `Building.ID` 获取建筑。
|
|
55
|
+
- **u-manager semantics** — `SemanticGroup` 和 `BuildingGroup` 的建筑/设备 ID 查询改为内部 `Map` 索引,并通过楼层设备索引版本自动同步,避免每次查询扫描数组。
|
|
56
|
+
- **u-manager semantics** — `FloorMesh.planish()` / `unplanish()`、`BuildingGroup.planishFloors()` / `unplanishFloors()` 和 `SemanticGroup.planishFloors()` / `unplanishFloors()` 会把所有合并语义实例和 Facilities 都按包围盒贴到楼层平面,而不是围绕实例自身基点压缩。
|
|
57
|
+
- **u-manager semantics** — 新增 scene-level Facilities auto instancing:同模型 URL 的静态设备会合并到 `SemanticGroup.facilityLayer` 的 `InstancedMesh` batch,同时保留楼层下的设备引用用于检索、显隐、颜色和透明度控制;带动画、骨骼、morph target 或多材质 Mesh 的设备自动回退为普通 `Model`。
|
|
58
|
+
- **u-manager semantics** — `SemanticGroup`、`BuildingGroup` 和 `FloorMesh` 新增 `getFacilityById()` 设备检索,返回的 `FacilityInstanceObject` 可通过统一 `setSemanticVisible()`、`setSemanticColor()`、`resetSemanticColor()` 和 `setSemanticOpacity()` 控制普通设备模型或 scene-level instanced 设备引用。
|
|
59
|
+
- **docs/mcp** — 补充 Facilities auto instancing 的材质透明度行为、`resetSemanticColor()` 语义,以及多材质 fallback 对颜色/透明度 override 的限制说明。
|
|
48
60
|
|
|
49
61
|
### 修复
|
|
50
62
|
|
|
51
|
-
- **u-manager semantics** — 修复 scene-level instanced Facilities 的点击和指针事件派发:射线命中会映射回楼层下的 `FacilityInstanceObject`,并继续冒泡到所属 `FloorMesh`、`BuildingGroup` 和 `
|
|
63
|
+
- **u-manager semantics** — 修复 scene-level instanced Facilities 的点击和指针事件派发:射线命中会映射回楼层下的 `FacilityInstanceObject`,并继续冒泡到所属 `FloorMesh`、`BuildingGroup` 和 `SemanticGroup`。
|
|
52
64
|
- **u-manager semantics** — `FacilityInstancedLayer` 继承 `BaseGroup`,避免 `facilityLayer.visible = false` 后内部 `InstancedMesh` 仍参与射线检测并触发隐藏设备点击事件。
|
|
53
65
|
- **u-manager semantics** — 移除语义设备解析里的 `sid` 检索和注册逻辑;Facilities 只按 `Facility.ID` 以及显式传入的别名进入楼层索引和 `viewer.objectManager`。
|
|
54
66
|
|