# 户型图集成指南

本文档面向**接入众趣 zqsdk 的开发者**，说明如何在自己的项目中实现「户型图」功能。SDK 提供两种户型图形态：**全景模式下的小户型图（迷你图 + 雷达）**与**平面模式下的全屏户型图**，需在不同事件中拉取数据并更新你的 UI。

---

## 一、功能简介与前置条件

### 1.1 什么是户型图

- **全景模式（panorama）**：在 3D 场景旁展示一张**小户型图**，图上有一个**雷达点**表示当前所在房间位置与朝向；随用户漫游，雷达位置与当前房间名/面积需同步更新。
- **平面模式（floorplan）**：整个画布为**全屏户型图**，可显示房间名称、雷达、入户门、宽高标尺、南北朝向等；切换楼层时需更新图片与覆盖层数据。

两种形态共用同一套「户型图数据源」，但**触发更新的时机不同**：全景依赖 `panoChange` / `flyingEnd` / `compass` / `floorChange` / `modeChange`；平面依赖 `modeChange` / `floorChange` 及可选的窗口 resize 后重算。

### 1.2 展示效果
![小户型图展示效果（全景模式）](../../screenshot/floorplan/小户型图.png)
![大户型图展示效果（平面模式）](../../screenshot/floorplan/大户型图.png)

### 1.3 前置条件

- 已接入众趣 zqsdk，在 `complete` 回调中拿到 `housePlay` 并已调用 `housePlay.start()`。
- 模型支持户型图（多数 VR 模型均支持）；SDK 通过 `getFloorPlanParam()` 或 `getFloorPlanParam2()`（若存在）返回户型图相关数据。

---

## 二、集成步骤概览

| 步骤 | 内容 |
|------|------|
| 1 | 在 `modeChange` 中根据新模式（panorama / floorplan）拉取户型图参数并更新「户型图图片、尺寸、全屏定位」等 |
| 2 | 在 `floorChange` 中更新当前楼层对应的户型图图片（及平面模式下的房间名、入户门、雷达等） |
| 3 | 全景模式：在 `panoChange` / `flyingEnd` 中更新雷达位置与当前区域名/面积；在 `compass` 中更新雷达旋转角度 |
| 4 | 平面模式：在进入 floorplan 时拉取雷达在户型图中的像素位置（若有 `getCurrentRadarPostionInFloorPlan`）；窗口 resize 时建议重新拉取参数并重算覆盖层 |
| 5 | 使用你项目中的状态驱动「迷你户型图」「全屏户型图」等 UI 的展示与定位 |

以下按数据来源与时机展开。

---

## 三、第一步：获取户型图参数

**核心接口**：`housePlay.getFloorPlanParam()` 或（若 SDK 支持）`housePlay.getFloorPlanParam2()`。建议先判断 `housePlay.canUseNewFloorPlan()`，为 true 时优先使用 `getFloorPlanParam2()`，否则使用 `getFloorPlanParam()`。返回值通常包含：

- `model`：当前楼层、户型图开关、户型图图片接口等；常用 `model.getCurrentHouseTypeImage()` 得到当前楼层户型图图片 URL。
- `project`：户型图在画布上的投影信息，如 `top`、`left`、`width`、`height`（像素），用于全屏户型图的定位与雷达坐标换算。
- `floorIndex`：当前楼层索引。
- （部分版本）`radar`：当前雷达信息；平面模式下还可使用 `housePlay.getCurrentRadarPostionInFloorPlan()` 获取雷达在户型图中的像素位置。

**调用时机**：

- **modeChange**：当 `newMode === 'panorama'` 或 `newMode === 'floorplan'` 时，拉取一次参数，用于更新户型图图片、投影尺寸、朝向（南北）等；进入 floorplan 时还需更新房间名称、标尺、入户门、雷达像素位置。
- **floorChange**：切换楼层后，用新楼层的 `params`（或再次调用 `getFloorPlanParam()`）更新户型图图片；若当前为 floorplan 模式，需同步更新房间名、入户门、雷达等。

```javascript
function getParams() {
  let hp = window.housePlay
  if (!hp) return null
  if (hp.canUseNewFloorPlan && hp.canUseNewFloorPlan()) {
    return hp.getFloorPlanParam2 ? hp.getFloorPlanParam2() : null
  }
  return hp.getFloorPlanParam ? hp.getFloorPlanParam() : null
}

housePlay.on('modeChange', function(currentMode, newMode) {
  let params = getParams()
  if (!params) return

  if (newMode === 'panorama') {
    // 更新迷你户型图图片；可选：用缓存的雷达数据恢复雷达位置
    let url = params.model.getCurrentHouseTypeImage()
    if (url) yourApp.setFloorPlanImageUrl(url)
  }

  if (newMode === 'panorama' || newMode === 'floorplan') {
    // 更新全屏户型图定位与朝向（project.top/left/width/height、南北朝向等）
    yourApp.updateFloorPlanProject(params.model, params.project)
    yourApp.updateCompass(params.model)
  }

  if (newMode === 'floorplan') {
    // 全屏户型图模式：更新房间名称、标尺、入户门、雷达像素位置
    yourApp.updateRoomLabels(params.model, params.project, params.floorIndex)
    yourApp.updateDimension(params.model, params.project)
    yourApp.updateEntrance(params.model, params.project, params.floorIndex)
    try {
      let radar = housePlay.getCurrentRadarPostionInFloorPlan()
      if (radar && radar.uv)
        yourApp.updateFullRadar(radar.uv.v, radar.uv.u, radar.angle)
    } catch (e) {}
  }
})
```

---

## 四、第二步：监听楼层变化

**事件**：`housePlay.on('floorChange', function(params) { ... })`

- 回调中的 `params` 通常包含 `floorIndex`、`imageUrl` 等；切换楼层后，当前楼层的户型图图片会变化。
- **全景模式**：用 `params.imageUrl` 或再次 `getParams()` 后 `model.getCurrentHouseTypeImage()` 更新迷你户型图图片即可。
- **平面模式**：除图片外，需同步更新房间名称、入户门、雷达在户型图中的位置（再次调用 `getCurrentRadarPostionInFloorPlan()` 或从 params 取）。

```javascript
housePlay.on('floorChange', function(params) {
  if (params.imageUrl) yourApp.setFloorPlanImageUrl(params.imageUrl)

  let params2 = getParams()
  if (params2 && yourApp.getCurrentMode() === 'floorplan') {
    yourApp.updateRoomLabels(params2.model, params2.project, params.floorIndex)
    yourApp.updateEntrance(params2.model, params2.project, params.floorIndex)
    try {
      let radar = housePlay.getCurrentRadarPostionInFloorPlan()
      if (radar && radar.uv)
        yourApp.updateFullRadar(radar.uv.v, radar.uv.u, radar.angle)
    } catch (e) {}
  }
})
```

---

## 五、第三步：全景模式下更新雷达与区域信息

迷你户型图上的**雷达位置**和**当前房间名/面积**来自全景点位，需在点位变化时更新。

### 5.1 雷达位置（uv + 角度）

- **位置**：来自 `panoChange` 或 `flyingEnd` 回调中的 `data.pano`。若 SDK 支持 `housePlay.getRadarData(position, floorIndex)`，建议用其将 `pano` 转为雷达 uv 与尺寸，再根据 uv 在迷你图上绘制雷达点（注意：迷你图宽高比需与雷达所在户型图区域一致，否则会偏移）。
- **角度**：来自 `housePlay.on('compass', function(angle) { ... })`，常用 `angle.director` 的负值作为雷达旋转角度。

```javascript
housePlay.on('panoChange', function(data) {
  let pano = data.pano
  if (!pano) return

  yourApp.updateArea(pano.areaName, pano.areaSize)

  if (housePlay.getRadarData) {
    try {
      let pos = clonePosition(pano)  // 深拷贝 pano.position，避免被 SDK 修改
      let radar = housePlay.getRadarData(pos, pano.floorIndex || 0)
      if (radar && radar.uv) {
        yourApp.updateMiniRadarPosition(radar.uv.u, radar.uv.v, data.time)
        if (radar.size) yourApp.updateMiniSizeFromRadar(radar.size.width, radar.size.height)
      }
    } catch (e) {}
  } else if (pano.u != null && pano.v != null) {
    yourApp.updateMiniRadarPosition(pano.u, pano.v, data.time)
  }
})

housePlay.on('flyingEnd', function(data) {
  if (!data.pano) return
  let pano = data.pano
  yourApp.updateArea(pano.areaName, pano.areaSize)
  // 同 panoChange：用 getRadarData 或 pano.u/v 更新雷达位置，time 可传 0
})

housePlay.on('compass', function(angle) {
  yourApp.updateMiniRadarAngle(-angle.director)
})
```

### 5.2 区域名称与面积

`pano.areaName`、`pano.areaSize` 在 `panoChange` / `flyingEnd` 中提供，用于在迷你图旁或底部展示「当前房间 + 约 X m²」。

---

## 六、第四步：平面模式下的补充与窗口 resize

- **雷达像素位置**：进入 floorplan 或切换楼层后，调用 `housePlay.getCurrentRadarPostionInFloorPlan()`（若存在），将返回的 `uv` 与 `angle` 换算到 `project` 的像素坐标，用于在全屏户型图上绘制雷达。
- **窗口 resize**：SDK 内部会重新计算 `project` 的 top/left/width/height。在 resize 后、当前模式为 floorplan 时，建议重新调用 `getFloorPlanParam()`（或 Param2），用新的 `project` 更新全屏户型图定位与房间名、入户门、雷达等，避免错位。

---

## 七、第五步：实现户型图 UI

- **迷你户型图**：仅在全景模式显示；展示户型图图片 + 雷达点（位置由 uv 换算到百分比或像素）+ 可选雷达旋转角；可展示当前区域名与面积。样式与尺寸由你自定义。
- **全屏户型图**：仅在平面模式显示；根据 `project` 定位图片，在其上叠加房间名、入户门、宽高标尺、南北朝向、雷达等。所有叠加元素需使用 SDK 提供的像素或比例数据，与 `project` 一致。

---

## 八、API 速查表

### 8.1 方法（housePlay）

| 方法 | 说明 |
|------|------|
| `getFloorPlanParam()` | 获取户型图相关数据（旧版） |
| `getFloorPlanParam2()` | 获取户型图相关数据（新版，若存在） |
| `canUseNewFloorPlan()` | 是否支持新版户型图 API |
| `getRadarData(position, floorIndex)` | 根据 pano 位置与楼层获取雷达 uv、size（若存在） |
| `getCurrentRadarPostionInFloorPlan()` | 平面模式下当前雷达在户型图中的位置（若存在） |

### 8.2 事件

| 事件 | 说明 |
|------|------|
| `modeChange` | 模式切换；newMode 为 `panorama` / `floorplan` 时更新户型图与雷达 |
| `floorChange` | 楼层切换；更新当前楼层户型图图片及平面模式下的覆盖层 |
| `panoChange` | 全景下点位开始切换；可更新雷达位置与区域信息 |
| `flyingEnd` | 飞行动画结束；可更新雷达位置与区域信息 |
| `compass` | 指北针/朝向；用于雷达旋转角度 |

### 8.3 参数结构（简要）

- `params.model`：含 `getCurrentHouseTypeImage()`、`currentFloor`、`settings` 等。
- `params.project`：`top`、`left`、`width`、`height`（像素）。
- `params.floorIndex`：当前楼层索引。
- `pano`：`areaName`、`areaSize`、`u`、`v`、`floorIndex`、`position` 等。

---

## 九、常见问题与注意事项

1. **getRadarData 可能抛错**：在某些状态或旧版 SDK 下可能不存在或抛异常，建议 try/catch，失败时可用 `pano.u` / `pano.v` 作为简单百分比位置。
2. **迷你图宽高比**：迷你图宽高比应与雷达所在区域的户型图比例一致（可用 `getRadarData` 的 size 或 project 的 width/height 计算），否则雷达会偏移。
3. **平面模式雷达**：仅在有 `getCurrentRadarPostionInFloorPlan` 时可用；返回的 uv 需结合当前 `project` 换算为像素再绘制。
4. **入户门**：一般仅在实际入户门所在楼层显示，需根据 `model.settings.building.house.flooridx` 或等价配置判断。

---

以上完成即可在自己的项目中实现户型图（迷你 + 全屏）与雷达、房间名、入户门等联动。本仓库实现可参考 `src/stores/floor-plan.ts`、`src/stores/vr.ts` 中的 modeChange/floorChange/panoChange/compass/flyingEnd 及 `src/utils/sdk-helpers.ts` 中的 `getFloorPlanParams`。
