Skip to content

Objects API

u-space 提供了若干对象封装类,主要扩展自 Three.js 的基础节点,使 3D 资产的操作和加载更加便捷。

基础类

BaseMesh

继承自 THREE.Meshu-space 中所有基础网格类都继承于此。

关键属性:

属性类型默认值说明
castShadowbooleantrue默认参与阴影投射;可在单个对象上改为 false
receiveShadowbooleantrue默认接收阴影;可在不需要阴影的网格上改为 false
ignoreInvisibleWhenRaycastbooleantruetrue 时,不可见的网格在射线检测时会被跳过(不参与碰撞)。

BaseGroup

继承自 THREE.GroupModelTopology 均继承于此。

关键属性:

属性类型默认值说明
ignoreInvisibleWhenRaycastbooleantruetrue 时,不可见的组在射线检测时会被跳过(不参与碰撞)。

InstanceObject

InstanceObject 是核心包导出的统一实例对象基类。无论实例最终由合并楼层几何、InstancedMeshEditableGeometryBatchLayer 还是 fallback Model 渲染,业务代码都可以使用相同的身份、显隐、颜色、透明度、高亮、包围盒和 materialize API。

typescript
import {
  InstanceObject,
  isInstanceObject,
  INSTANCE_COLOR_MODE_NONE,
  INSTANCE_COLOR_MODE_OVERRIDE,
  INSTANCE_COLOR_MODE_TINT,
} from 'u-space';

类型和常量

InstanceStyle

属性类型说明
colorColor | null当前样式颜色;null 表示不使用实例颜色。
colorModenumber当前颜色模式,值为下表中的颜色模式常量。
opacitynumber当前透明度,实例设置 API 会将其限制在 01 之间。

InstanceObjectOptions

属性类型必填默认值说明
boundsName?string'InstanceBounds'内部手动包围盒节点的名称。

InstanceIdentity

属性类型必填说明
id?string | null实例 ID。省略时保持原值,显式传 null 时清为空字符串。
kind?string | null实例类型。省略时保持原值,显式传 null 时清为空字符串。
name?string | null实例名称。省略时保持原值,显式传 null 时清为空字符串。

InstanceIdentity 的三个字段都可省略且可为 null:省略的字段保持不变,显式传入 null 会将对应身份字段清为空字符串。

颜色模式常量

常量说明
INSTANCE_COLOR_MODE_NONE0不使用实例颜色。
INSTANCE_COLOR_MODE_OVERRIDE1使用指定颜色覆盖原材质颜色。
INSTANCE_COLOR_MODE_TINT2将指定颜色作为 tint 与原材质颜色混合。

字段

字段类型默认值说明
isInstanceObjectbooleantrue实例对象标识,供 isInstanceObject() 类型守卫使用。
typestring'InstanceObject'Three.js 对象类型名称。
instanceIdstring''实例 ID。
instanceKindstring''实例类型。
instanceNamestring''实例名称。
geometryBufferGeometry空几何体实例对象公开的兼容几何体字段。
boundingBoxBox3 | nullnull本地坐标包围盒缓存。
boundingSphereSphere | nullnull本地坐标包围球缓存。

构造函数

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设置基础透明度;传入值会被限制在 01 之间。
getInstanceColor(target = new Color())Color将当前生效样式的颜色写入 target 并返回;未设置颜色时返回白色。
hasInstanceColor()boolean当前生效样式是否包含颜色。
getInstanceOpacity()number返回当前生效样式的透明度。
setInstanceHighlight(color: ColorRepresentation, opacity: number, overwrite = false)this设置临时高亮;默认使用 INSTANCE_COLOR_MODE_TINToverwritetrue 时使用覆盖模式,透明度会被限制在 01 之间。
clearInstanceHighlight()this清除临时高亮并恢复基础样式。
getInstanceColorMode()number返回当前生效样式的颜色模式。

临时高亮优先于基础颜色和透明度;clearInstanceHighlight() 会恢复基础样式。默认场景矩阵自动更新开启时,实例 transform 的世界矩阵发生变化会自动触发 dirty callback,所属 ModelInstancedLayer 会在下一次渲染前同步 instance buffer。业务代码只需修改 positionrotationquaternionscale,不需要直接写内部 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。

包围盒和相机定位

boundingBoxboundingSphere 是本地坐标缓存;computeBoundingBox()computeBoundingSphere() 会按需更新它们。getInstanceBoundingBox(target, true) 返回世界坐标包围盒。

方法返回值说明
setInstanceBounds(bounds: Box3)this克隆并设置手动本地包围盒;空包围盒会被忽略。
computeBoundingBox()void计算并缓存本地坐标包围盒。
computeBoundingSphere()void基于本地包围盒计算并缓存本地坐标包围球。
getInstanceBoundingBox(target = new Box3(), world = false)Box3将包围盒写入 targetworldtrue 时返回世界坐标结果。

实例可以直接交给相机控制器定位:

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),简化了异步加载 glbgltf 等外部 3D 模型的流程,内置多层缓存支持。

typescript
import { Model } from 'u-space';

const myModel = new Model();

加载资产

loadAsync 方法负责从 URL 加载资产并将其添加到组中。

typescript
await myModel.loadAsync(parameters: ModelParameters)

ModelParameters

属性类型默认值说明
urlstring.gltf.glb 文件的 URL。
cachebooleantrue启用内存缓存。若模型已加载过,则直接从内存中获取并克隆。
persistentbooleantrue使用浏览器 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

属性:

属性类型说明
mixerAnimationMixer | null动画混合器,加载含动画模型后自动创建。
animationsAnimationClip[]模型内置的动画片段列表。

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

属性类型说明
geometryParametersSphereGeometry 构造函数参数radiuswidthSegmentsheightSegments 等。
materialParametersMeshStandardNodeMaterialParameters标准材质选项(颜色等)。

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

属性类型默认值说明
imgstring | CanvasImageSource''图标图片 URL 或元素。
textstring''标签文字。
fontSizenumber32字体大小(像素)。
fontFamilystring'Arial'字体族。
colorstring'#ffffff'文字颜色。
iconSizenumber64图标大小(像素)。
paddingnumber10内容四周的内边距。
backgroundColorstring'rgba(0, 0, 0, 0)'背景填充颜色。
borderRadiusnumber8背景圆角半径。
textPosition'top' | 'bottom' | 'left' | 'right''right'文字相对图标的位置。
scaleFactornumber0.01canvas 像素到世界单位的缩放系数。

方法

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

属性类型默认值说明
nodeColorColorRepresentation0x0000ff节点球体颜色。
nodeRadiusnumber0.5节点球体半径。
edgeColorColorRepresentation0x00ff00边管道颜色。
edgeRadiusnumber0.1边管道半径。
pathColorColorRepresentation0xff00ff路径可视化颜色。
pathRadiusnumber0.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()

清除图和路径网格。

u-space docs