# Work (VR 数据实体)

- Summary: Work 是 Five 中最核心的数据实体，代表一个完整的 VR 项目，包含渲染场景所需的所有资源与配置。
- Schema: 包含 Observer, Model, Initial 等核心数据的接口。
- Concepts: Work Entity, Observer, WorkCode, Parsing。
- Examples: 数据加载、访问与多 Work 管理。

## Schema

> Definition: Work, LooseWork, WorkJson

`Work` 及其相关核心接口定义如下：

    import * as THREE from "three";
    import type { Mode } from "@realsee/five";

    type CubeFace = 'up' | 'down' | 'left' | 'right' | 'front' | 'back';

    /** Work 实体：VR 项目的根对象 */
    export interface Work {
      /** 唯一标识 (Work ID) */
      workCode: string;
      /** 项目名称 */
      name: string;
      /** 资源加载的基础路径 (Base URL) */
      baseURL: string;
      /** 数据签发人 */
      issuer: string;
      /** 过期时间 */
      expire: Date;
      /** 允许访问的安全域名列表 */
      allowHosts: readonly string[];

      /** 初始化参数 (默认模态、视角等) */
      initial: WorkInitial;
      /** 相对位姿 (用于多 Work 拼接时的坐标变换) */
      transform: THREE.Matrix4;

      /** 模型参数 (3D 模型资源信息) */
      model?: WorkModel;
      /** 全景点位信息 (所有可观测点位的列表) */
      observers: readonly WorkObserver[];
    }

    /** 观察点：空间中可驻足观看的点位 */
    export interface WorkObserver {
      /** 所属 Work 引用 */
      work: Work;
      /** 在 observers 数组中的索引 */
      index: number;
      /** 点位序号 (通常与 index 相同) */
      panoIndex: number;
      /** 点位唯一 ID (通常格式为 `${workCode}[${panoIndex}]`) */
      panoId: string;

      /** 点位是否激活可用 */
      active: boolean;
      /** 点位是否可加载 */
      loadable: boolean;

      /** 点位所在楼层索引 */
      floorIndex: number;

      /** 点位在模型坐标系下的观察位置 (用于渲染相机位置) */
      position: THREE.Vector3;
      /** 点位在模型坐标系下的地面位置 (用于导航和 UI 定位) */
      standingPosition: THREE.Vector3;
      /** 点位和模型的旋转对齐四元数 */
      quaternion: THREE.Quaternion;
      /** 点位和模型的旋转对齐矩阵 */
      matrix: THREE.Matrix4;

      /** 可连通的相关点位序号 (用于全景游走) */
      accessibleNodes: readonly number[];

      /** 点位全景图资源信息 */
      images: WorkImage;

      /** 获取点位的全局坐标 */
      getWorldPosition(this: WorkObserver): THREE.Vector3;

      /** 获取点位的全局地面坐标 */
      getWorldStandingPosition(this: WorkObserver): THREE.Vector3;

      /** 将方向向量转化为全景图 uv */
      vectorToEquirectangularUv(vector: THREE.Vector3, uvOrigin?: 'top-left' | 'bottom-left'): THREE.Vector2;
      /** 将全景图 uv 转化为方向向量 */
      equirectangularUvToVector(uv: THREE.Vector2, uvOrigin?: 'top-left' | 'bottom-left'): THREE.Vector3;
      /** 将方向向量转化为获六视图 uv */
      vectorToCubemapUv(vector: THREE.Vector3, uvOrigin?: 'top-left' | 'bottom-left'): [cubeFace: CubeFace, cubemapUv: THREE.Vector2];
      /** 将六视图 uv 转化为方向向量 */
      cubemapUvToVector(cubeFace: CubeFace, uv: THREE.Vector2, uvOrigin?: 'top-left' | 'bottom-left'): THREE.Vector3;
    }

    /** 初始化位姿参数 */
    export interface WorkInitial {
      work: Work;
      /** 模态 */
      mode?: Mode;
      /** 点位序号 */
      panoIndex?: number;
      /** 相机水平角 */
      longitude?: number;
      /** 相机俯仰角 */
      latitude?: number;
      /** 相机可视角度（垂直）*/
      fov?: number;
      /** 相机观察点位置 [x, y, z] */
      offset?: THREE.Vector3;
      /** 相机距离观察点距离 */
      distance?: number;
    }

    /** 模型数据 */
    export interface WorkModel {
      /** work */
      work: Work;
      /** 模型文件地址 at3d / domez */
      file?: string;
      /** 模型贴图文件地址 */
        textures?: readonly string[];
      /** 模型贴图文件地址的父目录 */
      textureBase?: string;
      /** 指定上方向，对于 obj， ply 等定义不明确的需要指定上方向 */
      upAxis?: string;
      /** 3d tile */
      layers: readonly WorkModelLayer[];
    }

    /** 模型图层信息 */
    export interface WorkModelLayer {
      /** work */
      work: Work;
      /** 模型类型 point_cloud mesh gaussian_splatting */
      type: 'point_cloud' | 'mesh' | 'gaussian_splatting';
      /** 模型名称 */
      name: string;
      /** 指定上方向 */
      upAxis: string;
      /** tileset.json 地址 */
      tileset: string;
    }

    /** 全景图资源信息 */
    export interface WorkImage {
      work: Work;
      /** 分辨率表 */
      sizeList: readonly number[];
      /** 瓦片信息 */
      tiles: readonly WorkTile[];
      /** 缩略图/预览图等 */
      [key: string]: any;
    }

## Concepts

### Work Entity (项目实体)

Work 是 Five 渲染内容的基石。它不仅包含资源地址，还包含空间结构数据。由于数据通常带有数字签名（防止篡改），开发者不应手动修改 Work 对象中的属性，尤其是资源路径和坐标数据。

### Data Types (数据类型)

Five 中有三种与 Work 相关的类型，它们分别对应不同的使用场景：

- LooseWork (宽泛数据):
  - 来源: 通常是服务端下发的原始 JSON 数据。
  - 特点: 结构宽松，允许缺省值（如使用默认模态）、简写（如 URL 模板）和非标准类型（如数组表示向量）。
  - 用途: 作为 `parseWork(json)` 的输入。
- Work (运行时实体):
  - 来源: 由 `parseWork` 解析生成。
  - 特点: 结构严谨，数据已标准化（如 URL 已补全，向量转为 `THREE.Vector3`），且被冻结（不可变）。
  - 用途: Five 内部渲染使用的核心对象，传给 `five.load(work)`。
- WorkJson (标准序列化):
  - 来源: 由 `workToJson(work)` 生成。
  - 特点: 纯 JSON 结构，去除了运行时对象（如 THREE.Vector3），但保留了标准化的数据结构。
  - 用途: 用于数据持久化存储或跨端传输。

### Layers vs File (数据源关系)

`WorkModel` 提供了两种定义模型数据的方式，其中 `layers` 是现代标准，`file` 主要用于兼容旧数据或简单场景。

- `layers`: (推荐) 数组结构，用于定义多层级、多类型的模型资源。
  - 支持 3DTiles 格式的分块加载。
  - 支持多种类型混合：`mesh` (模型), `point_cloud` (点云), `gaussian_splatting` (高斯泼溅)。
  - 默认行为: 列表中的所有图层默认都会被加载并显示。
- `file`: (兼容) 字符串路径，用于定义单文件的 Mesh 模型。
  - 仅支持部分格式（如 `.at3d`, `.obj`, `.glb`）。
  - 不支持点云或高斯泼溅。

加载优先级与兼容逻辑 (Fallback):

Five 在运行时会将 `file` 视为一个虚拟的 `mesh` 图层。加载逻辑如下：

1. 优先加载 Layers: Five 会遍历 `work.model.layers` 并加载其中定义的所有图层（包括 Mesh, PointCloud, GaussianSplatting 等）。

2. File 的条件加载: 检查当前已加载的图层中是否存在 `type: 'mesh'` 的图层。

   - 存在 Mesh 层: 忽略 `work.model.file` 字段（认为 `layers` 已经提供了更高精度的模型）。
   - 不存在 Mesh 层: 如果 `work.model.file` 有值，Five 会自动创建一个临时的 Mesh ViewLayer 来加载该文件。

### Observer (观察点)

Observer 是 VR 空间中预设的观察位置。在 Panorama (全景) 模式下，相机必须停留在某个 Observer 上。

- position: 相机镜头的物理位置。
- standingPosition: 对应的地面位置（脚底）。通常用于下绘制点位图标，或计算点位间的导航距离。
- accessibleNodes: 定义了该点位可以“走到”哪些相邻点位，构成了全景游走的图结构。

### WorkCode (唯一标识)

`workCode` 是 Work 的全局唯一标识符。

- 单 Work 场景: 通常不需要显式关注。
- 多 Work 场景: 当 Five 同时加载多个 Work（如拼接的大型空间）时，必须通过 `workCode` 来区分不同 Work 下的同名属性（如 `panoIndex`）。

### Parsing (数据解析)

从服务端获取的原始 JSON 数据（LooseWork）可能包含相对路径或简写。`parseWork` 函数负责将其标准化为严格的 `Work` 对象，处理 URL 拼接、默认值填充和数据校验。

## Examples

### 1. 解析与加载 Work

最基础的用法是从 API 获取数据并加载。

    import { Five, parseWork } from "@realsee/five";

    const five = new Five();
    five.appendTo(document.getElementById("app"));

    // 1. 获取数据
    fetch("https://vrlab-public.ljcdn.com/release/web/work.json")
      .then(res => res.json())
      .then(json => {
        // 2. 解析数据
        const work = parseWork(json);
        // 3. 加载到 Five
        five.load(work);
      });

### 2. 访问 Work 数据

加载完成后，可以通过 `five.work` 访问当前数据。

    // 获取所有观察点
    const observers = five.work.observers;

    // 获取当前所在的观察点
    const currentPanoIndex = five.state.panoIndex;
    const currentObserver = observers[currentPanoIndex];

    console.log("当前点位坐标:", currentObserver.position);
    console.log("可通往的点位:", currentObserver.accessibleNodes);

### 3. 多 Work 加载与管理

Five 支持同时加载多个 Work，常用于楼盘小区的多个样板间拼接，或大型商业空间的区域拼接。

    const work1 = parseWork(json1);
    const work2 = parseWork(json2);

    // 同时加载两个 Work
    five.load([work1, work2]);

    // 切换到指定 Work 的指定点位
    five.setState({
      mode: "Panorama",
      workCode: work2.workCode, // 必须指定 workCode
      panoIndex: 0
    });

### 4. 追加与替换 Work

可以使用 `load` 方法的参数来控制是替换当前场景还是追加到当前场景。

    const work3 = parseWork(json3);

    // 追加 Work (保留原有场景)
    // 第二个参数 "inherit" 表示保持当前相机视角和模态不变，不重置为新 Work 的初始视角
    five.load(work3, "inherit", { mode: "add" });

    // 替换 Work (清空原有场景) - 默认行为
    five.load(work3, "inherit", { mode: "replace" });

## Common Pitfalls

- 数据不可变性: Work 数据包含签名信息，直接修改 `work.observers[0].position.x = 100` 虽不会报错，但可能导致渲染异常或签名验证失败。请视 Work 为只读对象。
- 坐标系: Five 使用 Y-Up 右手坐标系，单位为米。与 Three.js 默认坐标系一致。
- 跨域问题 (CORS): `baseURL` 指向的资源服务器必须配置正确的 CORS 头，否则 WebGL 无法加载纹理。
- parseWork 必选: 永远不要尝试手动构造 `Work` 对象，总是使用 `parseWork`。服务端数据结构可能会升级，`parseWork` 会处理兼容性。

## Related

- State: 了解如何通过 State 切换点位。
- Mode: 了解不同模态下 Work 数据的表现。

    tags: [VR数据, 数据加载, 点位, 全景点, 数据结构, 多work, work, core, entity, observer, parseWork, workCode, JSON, load]