跳到主要内容

状态录制

上一章回顾: 改变视角

  • 你了解什么是 State, 以及如何获取和修改。
  • 通过 State 完成了自动环视的功能。
本章你可以学习到
  • 通过 State 完成用户操作的录制。
  • 通过 State 还原用户操作画面。

准备工作

和上一章节一样,我们新建一个目录(src/3.recording-state)以及对应的 html 文件 以及 jsts 文件。

jsts 文件可以先拷贝上一章节的内容。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="data:;base64,iVBORw0KGgo=" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>状态录制 | Recording state</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      html,
      body #app {
        width: 100%;
        height: 100%;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="./index"></script>
  </body>
</html>
import { ref, onBeforeUnmount } from "vue";

function useWindowDimensions() {
  const width = ref(window.innerWidth);
  const height = ref(window.innerHeight);

  const listener = () => {
    width.value = window.innerWidth;
    height.value = window.innerHeight;
  };

  window.addEventListener("resize", listener, false);
  onBeforeUnmount(() => {
    window.removeEventListener("resize", listener, false);
  });
  return { width, height };
}
export { useWindowDimensions };
src/3.recording-state/ModeController.vue
<template>
<nav class="navbar fixed-bottom navbar-light bg-light">
<div class="container-fluid justify-content-center">
<div class="btn-group">
<button
:class="
state.mode == 'Panorama'
? 'btn btn-primary active'
: 'btn btn-primary'
"
@click="() => setState({ mode: Five.Mode.Panorama })"
>
全景漫游
</button>
<button
:class="
state.mode == 'Panorama'
? 'btn btn-primary'
: 'btn btn-primary active'
"
@click="() => setState({ mode: Five.Mode.Floorplan })"
>
空间总览
</button>
</div>
</div>
</nav>
</template>

<script setup>
import { useFiveCurrentState } from "@realsee/five/vue";
import { Five } from "@realsee/five";

const [state, setState] = useFiveCurrentState();
</script>
src/3.recording-state/LookAroundController.vue
<template>
<div class="card position-fixed m-2 top-0 end-0">
<button class="btn btn-light" v-show="isShow" @click="startFunc">
<i class="bi bi-arrow-repeat"></i>
</button>
<button class="btn btn-light" v-show="!isShow" @click="stopFunc">
<i class="bi bi-pause"></i>
</button>
</div>
</template>

<script setup>
import { ref } from "vue";
import { useFiveCurrentState } from "@realsee/five/vue";

const [currentState, setState] = useFiveCurrentState();
const isShow = ref(true);
let timer;

const startFunc = () => {
window.clearInterval(timer);
isShow.value = false;
timer = window.setInterval(() => {
setState({ longitude: currentState.value.longitude + Math.PI / 360 });
}, 16);
};

const stopFunc = () => {
window.clearInterval(timer);
isShow.value = true;
};
</script>
src/3.recording-state/App.vue
<template>
<FiveProvider :work="work">
<FiveCanvas :width="width" :height="height" />
<ModeController />
<LookAroundController />
</FiveProvider>
</template>

<script setup lang="ts">
import { FiveProvider, FiveCanvas } from "@realsee/five/vue";
import { parseWork } from "@realsee/five";
import { ref } from "vue";
import { useWindowDimensions } from "./useWindowDimensions";
import ModeController from "./ModeController.vue";
import LookAroundController from "./LookAroundController.vue";

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

fetch(workURL)
.then((response) => response.text())
.then((text) => (work.value = parseWork(text)));
const { width, height } = useWindowDimensions();
</script>
src/3.recording-state/index.js
import { createApp, h } from "vue";
import App from "./App.vue";

createApp(App).mount("#app");

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

信息

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

录制 / 回放

这章我们继续通过 State 来完成一个有意思的应用。 本章我们会完成这样的一个应用,记录用户在页面上发生的 State,并且可以回放这些操作。

编写 Recorder

首先,我们需要编写 Recorder 类,来支持记录和回放。 Recorder 类 并非 Five 的内容,只是为了达成本章的效果编写的。

  1. 实现 startRecording / endRecording 方法,用于开始录制和结束录制。
  2. 实现 record(state: State) 方法,记录录制内容。记录 startRecording / endRecording 之间的内容。
  3. 实现 play(callback) 方法,用于回放,调用 play 之后,会安装 record 的内容,依次调用 callback 方法,回放 State
/**
 * 录制类
 */
class Recorder {
  constructor() {
    this.startTime = 0;
    this.records = null;
  }
  /**
   * 是否已录制
   */
  hasRecords() {
    return this.records !== null;
  }
  /**
   * 录制关键帧
   * @param state five 的 state
   * @returns
   */
  record(state) {
    if (this.records === null) return;
    this.records.push({
      state: Object.assign({}, state),
      time: Date.now() - this.startTime,
    });
  }
  /**
   * 开始录制
   */
  startRecording() {
    this.startTime = Date.now();
    this.records = [];
  }
  /**
   * 结束录制
   */
  endRecording() {
    this.startTime = 0;
  }
  /**
   * 回放录制
   * @param callback 关键帧回调
   * @returns 当前是否有录制
   */
  play(callback) {
    if (this.records === null || this.records.length === 0) return false;
    const records = this.records.slice();
    const keyframe = (keyIndex) => {
      const current = records[keyIndex];
      const next = records[keyIndex + 1];
      callback(current.state, next === undefined);
      if (next) {
        const delay = next.time - current.time;
        setTimeout(() => keyframe(keyIndex + 1), delay);
      }
    };
    keyframe(0);
    return true;
  }
}

export { Recorder };

编写录制组件

Recorder 类,封装成 Vue 组件。

  1. 添加一个 RecorderController 文件,用于编写组件。
  2. 组件内有状态 recording 以及 playing。分别作为 录制中 / 回放中的状态。
  3. useFiveEventCallback 可以获取到 Five 的内置方法回调。
    这边我们调用 stateChange 事件,当 state 发生改变时,会触发该事件, 进而将 State 调用 recorder.record(state) 方法记录下来。

    更多的事件说明请看 Five 的事件列表

  4. 当回放按钮被按下后,调用 recorder.play(callback) 方法,会将之前录制的 state 逐条调用 callback 方法,回放记录。
  5. 将记录调用 five 的 setState 方法,将回放内容应用,使得画面变化。
<template>
  <div class="card position-fixed m-2 top-0 start-0">
    <div class="btn-group align-items-center">
      <button class="btn btn-light " v-show="recordStart" @click="startRecord">
        <i class="bi bi-record-fill"></i>
      </button>
      <button class="btn btn-light " v-show="recordEnd" @click="stopRecord">
        <i class="bi bi-stop-fill"></i>
      </button>
      <button class="btn btn-light " v-show="playingStart" @click="playRecord">
        <i class="bi bi-play-fill"></i>
      </button>
      <p class="badge bg-primary m-2 " v-show="recording" @click="">录制中</p>
      <p class="badge bg-primary m-2 " v-show="playing" @click="">播放中</p>
    </div>
  </div>
</template>

<script setup>
import { ref } from "vue";
import { Recorder } from "./recorder";
import { useFiveState, useFiveEventCallback } from "@realsee/five/vue";
const recorder = new Recorder();
const [state, setState] = useFiveState();

const recordStart = ref(true);
const recordEnd = ref(false);
const playingStart = ref(true);
const recording = ref(false);
const playing = ref(false);

useFiveEventCallback("stateChange", (state) => {
  if (recording.value === true) {
    recorder.record(state);
  }
});

const startRecord = () => {
  recorder.startRecording();
  recordStart.value = false;
  recordEnd.value = true;
  recording.value = true;
  playingStart.value = false;
};

const stopRecord = () => {
  recorder.endRecording();
  recordStart.value = true;
  recordEnd.value = false;
  recording.value = false;
  playingStart.value = true;
};

const playRecord = () => {
  recordStart.value = false;
  recordEnd.value = false;
  recording.value = false;
  playingStart.value = false;
  playing.value = true;
  const hasRecrod = recorder.play((state, isFinal) => {
    setState(state);
    if (isFinal) {
      recordStart.value = true;
      recordEnd.value = false;
      recording.value = false;
      playingStart.value = true;
      playing.value = false;
    }
  });
  if (!hasRecrod) {
    recordStart.value = true;
    recordEnd.value = false;
    recording.value = false;
    playingStart.value = true;
    playing.value = false;
  }
};
</script>

使用状态录制组件

插入到 App 文件的 FiveProvider

<template>
  <FiveProvider :work="work">
    <FiveCanvas :width="width" :height="height" />
    <ModeController />
    <LookAroundController />
    // highlight-start
    <RecorderController />
    // highlight-end
  </FiveProvider>
</template>

<script setup>
import { FiveProvider, FiveCanvas } from "@realsee/five/vue";
import { parseWork } from "@realsee/five";
import { ref } from "vue";
import { useWindowDimensions } from "./useWindowDimensions";
import ModeController from "./ModeController.vue";
import LookAroundController from "./LookAroundController.vue";
// highlight-start
import RecorderController from "./RecorderController.vue";
// highlight-end
const work = ref();
const workURL =
  "https://vrlab-public.ljcdn.com/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";

fetch(workURL)
  .then((response) => response.text())
  .then((text) => (work.value = parseWork(text)));
const { width, height } = useWindowDimensions();
</script>

回到你的浏览器查看,会发现你的页面左上角出现一个录制和播放按钮。试试功能是不是符合预期。

真厉害,你以及可以编写那么复杂的程序了 🥳 。

下一章节你会学到

下一章我们需要和三维空间的模型打交道了。
  • 了解 Five SDK 的手势交互系统。
  • 获取到点的三维位置。