# 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](./api-batches#editablegeometrybatchlayer)。

### 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()`

清除图和路径网格。
