Appearance
Objects API
u-space 提供了若干对象封装类,主要扩展自 Three.js 的基础节点,使 3D 资产的操作和加载更加便捷。
基础类
BaseMesh
继承自 THREE.Mesh,u-space 中所有基础网格类都继承于此。
关键属性:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
castShadow | boolean | true | 默认参与阴影投射;可在单个对象上改为 false。 |
receiveShadow | boolean | true | 默认接收阴影;可在不需要阴影的网格上改为 false。 |
ignoreInvisibleWhenRaycast | boolean | true | 为 true 时,不可见的网格在射线检测时会被跳过(不参与碰撞)。 |
BaseGroup
继承自 THREE.Group,Model 和 Topology 均继承于此。
关键属性:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
ignoreInvisibleWhenRaycast | boolean | true | 为 true 时,不可见的组在射线检测时会被跳过(不参与碰撞)。 |
InstanceObject
InstanceObject 是核心包导出的统一实例对象基类。无论实例最终由合并楼层几何、InstancedMesh、EditableGeometryBatchLayer 还是 fallback Model 渲染,业务代码都可以使用相同的身份、显隐、颜色、透明度、高亮、包围盒和 materialize API。
typescript
import {
InstanceObject,
isInstanceObject,
INSTANCE_COLOR_MODE_NONE,
INSTANCE_COLOR_MODE_OVERRIDE,
INSTANCE_COLOR_MODE_TINT,
} from 'u-space';类型和常量
InstanceStyle
| 属性 | 类型 | 说明 |
|---|---|---|
color | Color | null | 当前样式颜色;null 表示不使用实例颜色。 |
colorMode | number | 当前颜色模式,值为下表中的颜色模式常量。 |
opacity | number | 当前透明度,实例设置 API 会将其限制在 0 到 1 之间。 |
InstanceObjectOptions
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
boundsName? | string | 否 | 'InstanceBounds' | 内部手动包围盒节点的名称。 |
InstanceIdentity
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
id? | string | null | 否 | 实例 ID。省略时保持原值,显式传 null 时清为空字符串。 |
kind? | string | null | 否 | 实例类型。省略时保持原值,显式传 null 时清为空字符串。 |
name? | string | null | 否 | 实例名称。省略时保持原值,显式传 null 时清为空字符串。 |
InstanceIdentity 的三个字段都可省略且可为 null:省略的字段保持不变,显式传入 null 会将对应身份字段清为空字符串。
颜色模式常量
| 常量 | 值 | 说明 |
|---|---|---|
INSTANCE_COLOR_MODE_NONE | 0 | 不使用实例颜色。 |
INSTANCE_COLOR_MODE_OVERRIDE | 1 | 使用指定颜色覆盖原材质颜色。 |
INSTANCE_COLOR_MODE_TINT | 2 | 将指定颜色作为 tint 与原材质颜色混合。 |
字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
isInstanceObject | boolean | true | 实例对象标识,供 isInstanceObject() 类型守卫使用。 |
type | string | 'InstanceObject' | Three.js 对象类型名称。 |
instanceId | string | '' | 实例 ID。 |
instanceKind | string | '' | 实例类型。 |
instanceName | string | '' | 实例名称。 |
geometry | BufferGeometry | 空几何体 | 实例对象公开的兼容几何体字段。 |
boundingBox | Box3 | null | null | 本地坐标包围盒缓存。 |
boundingSphere | Sphere | null | null | 本地坐标包围球缓存。 |
构造函数
new InstanceObject(options?)
typescript
const instance = new InstanceObject({ boundsName: 'FloorBounds' });options 的类型为 InstanceObjectOptions;省略时使用默认的内部包围盒节点名称 InstanceBounds。
身份和样式
身份、显隐和基础样式方法都返回当前实例,可以链式调用:
typescript
const instance = new InstanceObject()
.setInstanceIdentity({ id: 'room-101', kind: 'room', name: '会议室 101' })
.setInstanceVisible(true)
.setInstanceColor('#4f8cff')
.setInstanceOpacity(0.65);
instance.setInstanceHighlight('#ffff00', 0.9);
instance.clearInstanceHighlight();
instance.resetInstanceColor();| 方法 | 返回值 | 说明 |
|---|---|---|
setInstanceIdentity(identity: InstanceIdentity) | this | 更新身份字段;省略字段保持不变,null 将对应字段清为空字符串。 |
setInstanceVisible(visible: boolean) | this | 设置实例显隐并标记渲染数据需要同步。 |
setInstanceColor(color: ColorRepresentation) | this | 设置基础覆盖色,颜色模式变为 INSTANCE_COLOR_MODE_OVERRIDE。 |
resetInstanceColor() | this | 清除基础颜色,颜色模式恢复为 INSTANCE_COLOR_MODE_NONE。 |
setInstanceOpacity(opacity: number) | this | 设置基础透明度;传入值会被限制在 0 到 1 之间。 |
getInstanceColor(target = new Color()) | Color | 将当前生效样式的颜色写入 target 并返回;未设置颜色时返回白色。 |
hasInstanceColor() | boolean | 当前生效样式是否包含颜色。 |
getInstanceOpacity() | number | 返回当前生效样式的透明度。 |
setInstanceHighlight(color: ColorRepresentation, opacity: number, overwrite = false) | this | 设置临时高亮;默认使用 INSTANCE_COLOR_MODE_TINT,overwrite 为 true 时使用覆盖模式,透明度会被限制在 0 到 1 之间。 |
clearInstanceHighlight() | this | 清除临时高亮并恢复基础样式。 |
getInstanceColorMode() | number | 返回当前生效样式的颜色模式。 |
临时高亮优先于基础颜色和透明度;clearInstanceHighlight() 会恢复基础样式。默认场景矩阵自动更新开启时,实例 transform 的世界矩阵发生变化会自动触发 dirty callback,所属 ModelInstancedLayer 会在下一次渲染前同步 instance buffer。业务代码只需修改 position、rotation、quaternion 或 scale,不需要直接写内部 instance matrix,也不需要手动调用 InstanceObject.updateMatrixWorld()。
如果为大型静态场景显式设置了 viewer.scene.matrixWorldAutoUpdate = false,renderer 不会再遍历该根节点,修改后必须用 Three.js 原生方法提交受影响范围并请求新帧:
typescript
instance.position.set(10, 0, 5);
instance.updateWorldMatrix(true, false);
viewer.invalidate();
// 修改父 Group 时更新整个受影响子树。
group.updateWorldMatrix(true, true);
viewer.invalidate();InstanceObject 覆盖了原生 updateWorldMatrix(updateParents, updateChildren) 和 updateMatrixWorld(force),两条路径都会检测世界矩阵及祖先显隐变化并触发 dirty callback。对 Group 使用 updateChildren: true 时,后代 InstanceObject 也会执行该检测;editable batch 中与烘焙矩阵不同的 SceneInstanceObject 会按合批规则 materialize。
包围盒和相机定位
boundingBox 和 boundingSphere 是本地坐标缓存;computeBoundingBox() 与 computeBoundingSphere() 会按需更新它们。getInstanceBoundingBox(target, true) 返回世界坐标包围盒。
| 方法 | 返回值 | 说明 |
|---|---|---|
setInstanceBounds(bounds: Box3) | this | 克隆并设置手动本地包围盒;空包围盒会被忽略。 |
computeBoundingBox() | void | 计算并缓存本地坐标包围盒。 |
computeBoundingSphere() | void | 基于本地包围盒计算并缓存本地坐标包围球。 |
getInstanceBoundingBox(target = new Box3(), world = false) | Box3 | 将包围盒写入 target;world 为 true 时返回世界坐标结果。 |
实例可以直接交给相机控制器定位:
typescript
await viewer.controls.flyToObject(instance);当实例没有可供计算的渲染几何体,或业务端已经拥有精确范围时,可以手动提供本地包围盒并按需获取世界范围:
typescript
import { Box3, Vector3 } from 'three/webgpu';
instance
.setInstanceBounds(new Box3(
new Vector3(-2, 0, -3),
new Vector3(2, 4, 3),
))
.position.set(100, 0, 50);
const worldBounds = instance.getInstanceBoundingBox(new Box3(), true);Fallback 渲染对象
setInstanceRenderObject() 用于挂载无法进入合并几何或 InstancedMesh 管线的 fallback Three.js 对象。InstanceObject 会将当前显隐、颜色、透明度和高亮语义统一应用到该对象。
| 方法 | 返回值 | 说明 |
|---|---|---|
setInstanceRenderObject(object: Object3D | null) | this | 替换 fallback 渲染对象;传入 null 时解除当前对象。 |
getInstanceRenderObject() | Object3D | null | 返回当前 fallback 渲染对象。 |
setInstanceMaterializer(materializer) | this | 设置 batching 实现使用的低层 materialize delegate;传入 null 时解除。普通业务通常不直接调用。 |
clearInstanceMaterializer(materializer?) | this | 不传参数时直接解除;传入 delegate 时只在当前 delegate 与其相同时解除,用于安全释放 materializer 所有权。 |
materialize() | Object3D | null | 调用当前 materializer;未连接 materializer 时返回已有 fallback render object 或 null。 |
typescript
import { Model } from 'u-space';
const fallbackModel = new Model();
await fallbackModel.loadAsync({ url: '/models/special-room.glb' });
instance
.setInstanceRenderObject(fallbackModel)
.setInstanceColor('#ff8a3d')
.setInstanceOpacity(0.8);EditableGeometryBatchLayer 会自动连接 setInstanceMaterializer(),并在 dispose 时通过所有权匹配安全解除。当实例 transform 或半透明状态无法继续由烘焙 Geometry 表达时,layer 会 clone 完整模板、克隆独立材质并挂到当前实例,同时立即提交新对象的世界矩阵并让其他 active layer 隐藏同一实例的旧烘焙副本;业务也可以显式调用 instance.materialize()。完整构建、unsupported fallback 和事件语义见 Batches API。
Dirty callback
| 方法 | 返回值 | 说明 |
|---|---|---|
onInstanceRenderDirty(callback: () => void) | () => boolean | 注册 dirty callback,并返回用于取消订阅的函数。 |
markInstanceRenderDirty() | this | 立即通知所有已注册的 dirty callback。 |
保存并调用取消订阅函数,避免监听方销毁后遗留回调:
typescript
const dirtyInstances = new Set<InstanceObject>();
const unsubscribe = instance.onInstanceRenderDirty(() => {
dirtyInstances.add(instance);
});
// 不再需要监听时
unsubscribe();
dirtyInstances.delete(instance);类型守卫
isInstanceObject(object)
isInstanceObject(object: Object3D): object is InstanceObject 检查对象的 isInstanceObject 标识,并在 TypeScript 中将其收窄为 InstanceObject。
typescript
if (isInstanceObject(object)) {
object.setInstanceHighlight('#ffff00', 1);
}模型
Model 类继承自 BaseGroup(进而继承自 THREE.Group),简化了异步加载 glb、gltf 等外部 3D 模型的流程,内置多层缓存支持。
typescript
import { Model } from 'u-space';
const myModel = new Model();加载资产
loadAsync 方法负责从 URL 加载资产并将其添加到组中。
typescript
await myModel.loadAsync(parameters: ModelParameters)ModelParameters
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
url | string | — | .gltf 或 .glb 文件的 URL。 |
cache | boolean | true | 启用内存缓存。若模型已加载过,则直接从内存中获取并克隆。 |
persistent | boolean | true | 使用浏览器 Cache API 启用持久化磁盘缓存,适合较大的模型。 |
缓存示例
javascript
// 默认开启双层缓存,直接加载即可
const model1 = new Model();
model1.loadAsync({ url: 'model.glb' });
// 禁用内存缓存(每次都从网络或持久化缓存重新加载)
const model2 = new Model();
model2.loadAsync({ url: 'model.glb', cache: false });
// 完全禁用缓存
const model3 = new Model();
model3.loadAsync({ url: 'model.glb', cache: false, persistent: false });动画
Model 在加载含内置动画的模型(如 glTF/FBX)后,会自动初始化 AnimationMixer 并收集 AnimationClip。
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
mixer | AnimationMixer | null | 动画混合器,加载含动画模型后自动创建。 |
animations | AnimationClip[] | 模型内置的动画片段列表。 |
playAnimation(nameOrIndex?, options?)
播放指定动画。不传参时播放第一个动画。
typescript
// 按名称播放
model.playAnimation('Walk');
// 按索引播放
model.playAnimation(0);
// 带选项
model.playAnimation('Run', {
loop: true, // 是否循环(默认 true)
repetitions: 3, // 循环次数(默认 Infinity)
timeScale: 1.5, // 播放速度(默认 1)
clampWhenFinished: true, // 播放结束后停留在最后一帧(默认 false)
});playAllAnimations(options?)
播放模型所有内置动画。参数同 playAnimation。
typescript
model.playAllAnimations({ timeScale: 2 });stopAnimation(nameOrIndex?)
停止指定动画。不传参时停止所有动画。
typescript
model.stopAnimation('Walk'); // 停止指定动画
model.stopAnimation(); // 停止所有动画updateAnimation(delta)
推进动画时间,需在渲染循环中调用。delta 为帧间隔时间(秒),可从 Viewer 事件的 delta 属性获取。
typescript
viewer.addEventListener('beforeRender', (e) => {
model.updateAnimation(e.delta);
});空间查询
getBoundingBox()
获取模型的轴对齐包围盒(AABB)。
typescript
const box = myModel.getBoundingBox();
const size = box.getSize(new Vector3());
console.log('模型尺寸:', size);getCenter()
获取模型包围盒的中心点。
typescript
const center = myModel.getCenter();
console.log('模型中心:', center);材质操作
setMaterial(material)
将模型内所有 Mesh 的材质统一替换为指定材质。
typescript
import { MeshStandardMaterial } from 'three/webgpu';
myModel.setMaterial(new MeshStandardMaterial({ color: 0xff0000 }));清除缓存
可通过 Model 类的静态方法手动清除内部缓存:
Model.clearMemoryCache():清除运行时内存缓存。await Model.clearPersistentCache():完全清除浏览器中u-space模型的持久化缓存。
网格
u-space 提供了多种精简的网格类,均继承自 BaseMesh。所有类默认使用 MeshStandardNodeMaterial,构造函数接受 { geometryParameters, materialParameters } 参数,并支持交互事件分发。
SphereMesh
typescript
import { SphereMesh } from 'u-space';
const sphere = new SphereMesh({
geometryParameters: { radius: 1, widthSegments: 32, heightSegments: 32 },
materialParameters: { color: 0x0077ff },
});SphereMeshParameters
| 属性 | 类型 | 说明 |
|---|---|---|
geometryParameters | SphereGeometry 构造函数参数 | radius、widthSegments、heightSegments 等。 |
materialParameters | MeshStandardNodeMaterialParameters | 标准材质选项(颜色等)。 |
PlaneMesh
水平平面网格。
typescript
import { PlaneMesh } from 'u-space';
const plane = new PlaneMesh({
geometryParameters: { width: 10, height: 10 },
materialParameters: { color: 0x888888 },
});CircleMesh
圆形平面网格。
typescript
import { CircleMesh } from 'u-space';
const circle = new CircleMesh({
geometryParameters: { radius: 5, segments: 64 },
});TubeMesh
沿指定 Curve<Vector3> 曲线生成的管道网格。
typescript
import { TubeMesh } from 'u-space';
import { CatmullRomCurve3, Vector3 } from 'three/webgpu';
const path = new CatmullRomCurve3([new Vector3(0,0,0), new Vector3(5,2,5)]);
const tube = new TubeMesh({
geometryParameters: { path, tubularSegments: 20, radius: 0.2, radialSegments: 8 },
materialParameters: { color: 0x00ff00 },
});ShapeMesh
由 THREE.Shape 或 2D 点生成的平面网格。
typescript
import { ShapeMesh } from 'u-space';
// 从显式 Shape 创建
const mesh = new ShapeMesh({ geometryParameters: { shape: myShape } });
// 静态辅助方法:从 x/z 点数组创建
const mesh2 = ShapeMesh.createFromPoints([{ x: 0, z: 0 }, { x: 5, z: 0 }, { x: 5, z: 5 }]);ExtrudeMesh
由 THREE.Shape 或 2D 点拉伸生成的 3D 实体网格。
typescript
import { ExtrudeMesh } from 'u-space';
const solid = new ExtrudeMesh({
geometryParameters: {
shape: myShape,
options: { depth: 3, bevelEnabled: false },
},
materialParameters: { color: 0xff8800 },
});
// 静态辅助方法
const solid2 = ExtrudeMesh.createFromPoints(
[{ x: 0, z: 0 }, { x: 5, z: 0 }, { x: 5, z: 5 }],
{ geometryParameters: { options: { depth: 2, bevelEnabled: false } } }
);Poi
Poi 是一个基于 canvas 渲染的公告板(精灵),用于在 3D 场景中放置图标和标签,继承自 BaseSprite。
typescript
import { Poi } from 'u-space';
const poi = new Poi({
img: '/icons/marker.png',
text: '我的位置',
fontSize: 28,
color: '#ffffff',
backgroundColor: 'rgba(0,0,0,0.6)',
textPosition: 'right',
});
// 构造时会自动调用 updateAsync(),无需手动调用
// 如需更新参数,可再次调用:
// await poi.updateAsync({ text: '新文字' });
poi.position.set(10, 5, 10);
viewer.scene.add(poi);PoiParameters
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
img | string | CanvasImageSource | '' | 图标图片 URL 或元素。 |
text | string | '' | 标签文字。 |
fontSize | number | 32 | 字体大小(像素)。 |
fontFamily | string | 'Arial' | 字体族。 |
color | string | '#ffffff' | 文字颜色。 |
iconSize | number | 64 | 图标大小(像素)。 |
padding | number | 10 | 内容四周的内边距。 |
backgroundColor | string | 'rgba(0, 0, 0, 0)' | 背景填充颜色。 |
borderRadius | number | 8 | 背景圆角半径。 |
textPosition | 'top' | 'bottom' | 'left' | 'right' | 'right' | 文字相对图标的位置。 |
scaleFactor | number | 0.01 | canvas 像素到世界单位的缩放系数。 |
方法
updateAsync(parameters?)
使用可选的参数覆盖重新渲染 canvas 纹理。构造时会自动调用一次,后续参数未变化时会跳过重绘。
typescript
await poi.updateAsync({ text: '更新后的标签', color: '#ffff00' });
viewer.render();dispose()
释放 canvas 纹理和材质。
Topology
Topology 是一个带有内置 3D 可视化的图数据结构。它存储节点(位置)和带权重的边,实现了 Dijkstra 最短路径算法,并将图渲染为球体和管道。
typescript
import { Topology } from 'u-space';
const topo = new Topology({
nodeColor: 0x0000ff,
nodeRadius: 0.3,
edgeColor: 0x00ff00,
edgeRadius: 0.05,
});
topo.addNode('A', new Vector3(0, 0, 0));
topo.addNode('B', new Vector3(5, 0, 0));
topo.addNode('C', new Vector3(5, 0, 5));
topo.addEdge('A', 'B');
topo.addEdge('B', 'C');
topo.renderGraph(); // 创建球体 + 管道网格
viewer.scene.add(topo);TopologyParameters
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
nodeColor | ColorRepresentation | 0x0000ff | 节点球体颜色。 |
nodeRadius | number | 0.5 | 节点球体半径。 |
edgeColor | ColorRepresentation | 0x00ff00 | 边管道颜色。 |
edgeRadius | number | 0.1 | 边管道半径。 |
pathColor | ColorRepresentation | 0xff00ff | 路径可视化颜色。 |
pathRadius | number | 0.2 | 路径管道半径。 |
方法
addNode(id, position)
向图中添加一个节点。
removeNode(id)
移除一个节点及其关联的边。
addEdge(from, to, weight?, bidirectional?)
在两个节点之间添加一条边。weight 默认为欧氏距离,bidirectional 默认为 true(双向)。
removeEdge(from, to, bidirectional?)
移除两个节点之间的边。
getShortestPath(startId, endId): Vector3[]
使用 Dijkstra 算法返回最短路径(世界坐标位置数组)。若不存在路径则返回 []。
renderGraph()
根据当前节点和边构建球体/管道场景图。修改图后需调用此方法刷新可视化。
clearGraph()
移除并释放所有图网格。
renderPath(points, color?): TubeMesh
将给定点通过 CatmullRomCurve3 平滑处理后渲染为管道网格。
typescript
const path = topo.getShortestPath('A', 'C');
topo.renderPath(path, 0xff0000);
viewer.render();clearPaths()
移除并释放所有路径网格。
getNeighbors(id): Map<string, number> | undefined
返回节点的邻接表(邻居 ID → 边权重)。
exportData(): TopologyData
将当前拓扑图(节点和边)导出为 JSON 可序列化对象。
typescript
const data = topo.exportData();
// { nodes: Record<string, {x,y,z}>, edges: Array<{from,to,weight}> }双向边在导出时会去重,只保留一条记录。
importData(data: TopologyData)
从 exportData() 返回的数据中还原拓扑图,替换当前所有节点、边和路径网格,并重新渲染。
typescript
topo.importData(data);dispose()
清除图和路径网格。