跳到主要内容

改变视角

上一章回顾: 展示三维空间

  • 你了解什么是 Work, 以及如何获取和加载。
  • 如何展示三维空间,并且在此基础上开发功能来控制三维空间。
本章你可以学习到
  • 了解什么是 State。
  • 如何改变三维空间观察的方向 / 位置。
  • 了解上一章的代码,比如 setState stateChange 是如何工作的。
  • 通过 State 完成了自动环视的功能。

准备工作​

和上一章节一样,我们新建一个目录(src/2.knowing-state)以及对应的 html 文件 以及 js 或 ts 文件。

js 或 ts 文件可以先拷贝上一章节的内容。

src/2.knowing-state/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>展示空间 | Displaying work</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css"
rel="stylesheet"
crossorigin="anonymous"
/>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css"
rel="stylesheet"
/>
<style>
html,
body,
#app {
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="app"></div>
<!-- 模式切换 -->
<nav class="navbar fixed-bottom navbar-light bg-light">
<div class="container-fluid justify-content-center">
<div class="btn-group">
<a href="javascript:;" class="btn btn-primary active js-Panorama"
>全景漫游</a
>
<a href="javascript:;" class="btn btn-primary js-Floorplan"
>空间总览</a
>
</div>
</div>
</nav>
<script type="module" src="./index"></script>
</body>
</html>
src/2.knowing-state/index.js
import { Five, parseWork } from "@realsee/five";

const workURL =
"https://vrlab-public.ljcdn.com/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";

const five = new Five();

five.appendTo(document.querySelector("#app"));

fetch(workURL)
.then((res) => res.json())
.then((json) => {
const work = parseWork(json);
five.load(work);
});

window.addEventListener("resize", () => five.refresh(), false);

{
// === 模式切换 ===
const buttons = {
Panorama: document.querySelector(".js-Panorama"),
Floorplan: document.querySelector(".js-Floorplan"),
};

for (const [modeName, element] of Object.entries(buttons)) {
element.addEventListener(
"click",
() => {
five.setState({ mode: modeName });
},
false
);
}

five.on("stateChange", (state) => {
for (const [modeName, element] of Object.entries(buttons)) {
if (modeName === state.mode) {
element.classList.add("active");
} else {
element.classList.remove("active");
}
}
});
}

export {};

启动服务 npm run dev。 并跳转到当前页面 " http://localhost:3000/src/2.knowing-state/index.html "。

提示

请查看你的控制台,端口号会因为你的配置以及当前端口占用情况变更,请已控制台输出的为准。
如果你使用其他开发构建工具,请按照自己的开发构建工具的要求启动服务。

什么是 State​

又来了解概念了,我保证这是现阶段需要了解的最后一个理论知识了。

State 简介​

State 用于描述状态的数据结构。上一章我们了解了 Work, Work 是用于描述一个三维空间,那么 State 则是描述在这个三维空间内,目前正在处于什么状态。他包含了模态、位于的采集点位、相机的方向、相机可视角度的信息。

State 的数据结构及说明​

interface State {
mode: Five.Mode;
panoIndex: number;
longitude: number;
latitude: number;
fov: number;
offset: THEEE.Vector3;
}

State的数据描述

  • mode: 当前的模态 Five 常用有 5 种模态,可以使用 Five.Mode 获得:

    • Panorama: 全景游走模态,该模态下视图将在采集点间游走,手势操作可以旋转/放大视角/切换采集点,适合查看采集的全景信息。
    • Floorplan: 空间总览模态, 该模态下视图以模型为中心,手势操作可以旋转/放大模型/切换楼层,适合查看模型的整体效果。
    • Topview: 户型图模态,该模态下视图以模型为中心,垂直俯视模型,手势操作可以平移/放大模型/切换楼层,适合查看模型平面结构。
    • Model: 模型游走模态,该模态下视图将在模型中自由游走,手势操作可以旋转/放大视角/位移,适合查看模型的细节,做一些定位操作。
    • VRPanorama: VR 眼镜模态,该模态下可以使用 Cardboard 眼镜 或者他的第三方衍生产品,实现 VR 虚拟显示效果。
  • panoIndex: 采集点位,即在 Panorama 模态可以落脚的位置。是一个 work[observers] 的一个索引。

  • longitude / latitude: 相机的水平角 / 相机的偏航角(弧度),我们使用类似经纬度的方式描述相机位置。

    整个模型场景为一个右手笛卡尔坐标系, XZ平面平行于地面, Y轴垂直于地面。

    右手坐标系图示补充图片

    初始相机方向为原点看向 Z 轴负方向。

    • 增加 longitude 相机向左旋转。
    • 减少 longitude 相机向右旋转。
    • 增加 latitude 相机向下旋转。
    • 减少 latitude 相机向上旋转。
  • fov: 相机垂直方向的可视角度 (角度)。

  • offset: 当前相机的三维坐标。

state 相关的 api 有什么​

可以通过 state currentState 获取当前状态,通过 setState setCurrentState 设置状态。

state / currentState 的区别​

currentState 是当前状态,就是画面中的状态,目前展示的状态。 state 是目标状态或者说下一时间的稳定状态。

可以简单的这样认为:
当 setSate 被调用后,state 当即会变成 setState 的值,而 currentState 则不会马上改变,他会在动画过渡的过程中逐步逼近 state 并最终变成和 state 一样的值。就和你在画面中看到的动画一样。

在上一章的代码示例中,我们已经使用了 mode 属性,来切换 Panorama 以及 Floorplan 模态。你也可以尝试把其他的几种模态都补充上,看看各种模态下有什么区别。

VRPanorama 模态需要使用设备陀螺仪信息,需要使用移动设备。 并且如果在 iOS 设备下,需要服务处于 https 下,否则 iOS 不允许访问陀螺仪信息。

开发一个自动环视的功能​

我们已经获取和设置过了 mode,这次我们修改下 longitude / latitude 试试。这次我们开发一个自动环视的功能。通过一个按钮控制激活自动环视的功能,该功能会自动水平旋转镜头。

书写环视功能​

添加环视功能的 UI 按钮​

在屏幕右上角添加两个按钮,开始 和 停止。

src/2.knowing-state/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>改变视角 | Knowing state</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css"
rel="stylesheet"
crossorigin="anonymous"
/>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css"
rel="stylesheet"
/>
<style>
html,
body,
#app {
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="app"></div>
<!-- 模式切换 -->
<nav class="navbar fixed-bottom navbar-light bg-light">
<div class="container-fluid justify-content-center">
<div class="btn-group">
<button class="btn btn-primary active js-Panorama">全景漫游</button>
<button class="btn btn-primary js-Floorplan">空间总览</button>
</div>
</div>
</nav>
<!-- 环视 -->
<div class="card position-fixed m-2 top-0 end-0">
<button class="btn btn-light js-lookAround-start">
<i class="bi bi-arrow-repeat"></i>
</button>
<button class="btn btn-light js-lookAround-stop d-none">
<i class="bi bi-pause"></i>
</button>
</div>
<script type="module" src="./index"></script>
</body>
</html>

书写逻辑代码​

  1. 通过 setInterval 定时触发器,修改 state 的 longitude 值。
  2. 开始 按钮点击时,触发定时器开始。
  3. 停止 按钮点击时,触发定时器停止。

在上一张的 模式切换 的代码后追加:

src/2.knowing-state/LookAroundController.js
{
// === 环视 ===
let timer;
const startButton = document.querySelector(".js-lookAround-start");
const stopButton = document.querySelector(".js-lookAround-stop");
startButton.addEventListener(
"click",
() => {
window.clearInterval(timer);
timer = window.setInterval(() => {
five.setState({ longitude: five.state.longitude + Math.PI / 360 });
}, 16);
startButton.classList.add("d-none");
stopButton.classList.remove("d-none");
},
false
);
stopButton.addEventListener(
"click",
() => {
window.clearInterval(timer);
startButton.classList.remove("d-none");
stopButton.classList.add("d-none");
},
false
);
}

回到你的浏览器查看,会发现你的页面右上角出现一个环视按钮。点击可以触发和关闭环视。

真是一个不错的功能 🥳 !

下一章节你会学到​

下一章我们再使用 State 做一些复杂的功能,深入体会一下 State 的能力。
  • 通过 State 完成用户操作的录制。
  • 通过 State 还原用户操作画面。