Recap of the previous chapter: Displaying a 3D space
- You learned what a Work is, and how to fetch and load it.
- How to display a 3D space, and how to build components on top of it to control the 3D space.
In this chapter you will learn
- What a State is.
- How to change the direction / position from which the 3D space is observed.
- How the code from the previous chapter works — for example, how
useCurrentStateand the other reactive pieces operate. - How to build an auto look-around feature using State.
Preparation
Just like in the previous chapter, create a new directory (src/2.knowing-state) together with the corresponding html file and js or ts file.
You can start the js or ts file by copying the contents from the previous chapter.
<!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>Changing the Viewpoint | 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>
* {
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 };<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 })"
>
Panorama Walkthrough
</button>
<button
:class="
state.mode == 'Panorama'
? 'btn btn-primary'
: 'btn btn-primary active'
"
@click="() => setState({ mode: Five.Mode.Floorplan })"
>
Space Overview
</button>
</div>
</div>
</nav>
</template>
<script setup>
import { useFiveCurrentState } from "@realsee/five/vue";
import { Five } from "@realsee/five";
const [state, setState] = useFiveCurrentState();
</script><template>
<FiveProvider :work="work">
<FiveCanvas :width="width" :height="height" />
<ModeController />
</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";
const work = ref();
const workURL =
"https://vr-public.realsee-cdn.cn/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>import { createApp, h } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");import { ref, onBeforeUnmount } from "vue";
function useWindowDimensions() {
const width = ref<number>(window.innerWidth);
const height = ref<number>(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 };<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 })"
>
Panorama Walkthrough
</button>
<button
:class="
state.mode == 'Panorama'
? 'btn btn-primary'
: 'btn btn-primary active'
"
@click="() => setState({ mode: Five.Mode.Floorplan })"
>
Space Overview
</button>
</div>
</div>
</nav>
</template>
<script setup lang="ts">
import { useFiveCurrentState } from "@realsee/five/vue";
import { Five } from "@realsee/five";
const [state, setState] = useFiveCurrentState();
</script><template>
<FiveProvider :work="work">
<FiveCanvas :width="width" :height="height" />
<ModeController />
</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";
const work = ref();
const workURL =
"https://vr-public.realsee-cdn.cn/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>import { createApp, h } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");Start the dev server with npm run dev, then navigate to the current page at "http://localhost:3000/src/2.knowing-state/index.html".
Info
Check your console — the port number may change depending on your configuration and on which ports are currently in use, so trust whatever the console prints. If you use a different build tool, start the server according to that tool's requirements.
What is State
It's time to learn a concept again. I promise this is the last piece of theory you need at this stage.
Introduction to State
State is the data structure used to describe a state. In the previous chapter we learned about Work: a Work describes a 3D space, while State describes the current condition within that 3D space. It carries the mode, the capture point currently being observed, the camera's direction, and the camera's field of view.
The data structure of State and its explanation
interface State {
mode: Five.Mode;
panoIndex: number;
longitude: number;
latitude: number;
fov: number;
offset: THREE.Vector3;
}Description of the State data:
mode: the current mode.
Five SDK commonly has 5 modes, which you can access via Five.Mode.
- Panorama: panorama walkthrough mode. In this mode the view moves between capture points; gestures let you rotate / zoom the view and switch capture points. It is well suited to viewing the captured panorama information.
- Floorplan: space overview mode. In this mode the view is centered on the model; gestures let you rotate / zoom the model and switch floors. It is well suited to viewing the model as a whole.
- Topview: floor plan mode. In this mode the view is centered on the model and looks straight down at it; gestures let you pan / zoom the model and switch floors. It is well suited to viewing the model's plan structure.
- Model: model walkthrough mode. In this mode the view roams freely through the model; gestures let you rotate / zoom the view and move around. It is well suited to inspecting the model's details and performing positioning operations.
- VRPanorama: VR headset mode. In this mode you can use Cardboard glasses or one of their third-party derivatives to achieve a VR virtual-display effect.
panoIndex: the capture point, i.e. a position you can land on in Panorama mode. It is an index intowork[observers].longitude/latitude: the camera's horizontal angle (yaw) / the camera's vertical angle (pitch), in radians. We describe the camera's orientation using a system similar to longitude and latitude.
The entire model scene is a right-handed Cartesian coordinate system: the XZ plane is parallel to the ground, and the Y axis is perpendicular to the ground.
The initial camera direction is from the origin looking toward the negative Z axis.
- Increasing
longituderotates the camera to the left. - Decreasing
longituderotates the camera to the right. - Increasing
latituderotates the camera downward. - Decreasing
latituderotates the camera upward.
fov: the camera's vertical field of view (in degrees).offset: the camera's current 3D coordinates.
What APIs are related to state
- [
[state, setState] = useFiveState;](https://unpkg.com/@realsee/five@6.8.9/docs/functions/vue.useFiveState.html) - [
[currentState, setCurrentState] = useFiveCurrentState;](https://unpkg.com/@realsee/five@6.8.9/docs/functions/vue.useFiveCurrentState.html)
You can read the current state through state / currentState, and set the state through setState / setCurrentState.
The difference between state / currentState
currentState is the current state — the state on screen, the state being displayed right now. state is the target state, or in other words the stable state at the next moment in time.
You can think of it simply like this:
When setState is called, state immediately becomes the value passed to setState, whereas currentState does not change at once — it gradually approaches state over the course of the transition animation and finally becomes equal to state. This is exactly the animation you see on screen.
In the code example from the previous chapter, we already used the mode property to switch between the Panorama and Floorplan modes. You can also try adding the other modes to see what differences each mode has.
VRPanorama mode requires the device's gyroscope information, so a mobile device is needed.
Additionally, on iOS devices the service must be served over
https; otherwise iOS will not allow access to the gyroscope information.
Building an auto look-around feature
We have already read and set the mode. This time let's try modifying longitude / latitude instead. We will build an auto look-around feature: a button toggles the feature on, and once active it automatically rotates the camera horizontally.
Writing the look-around component
- Add a LookAroundController file to write the component in.
- Define an active Vue ref to control whether the look-around feature is enabled.
- The look-around feature is implemented by using
setIntervalto fire a function on a timer that modifies Five SDK's state.
<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><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 lang="ts">
import { ref } from "vue";
import { useFiveCurrentState } from "@realsee/five/vue";
const [currentState, setState] = useFiveCurrentState();
const isShow = ref(true);
let timer: number | undefined;
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>Using the auto look-around component
Insert it into the FiveProvider in the App file.
<template>
<FiveProvider :work="work">
<FiveCanvas :width="width" :height="height" />
<ModelControl />
// highlight-start
<LookAroundController />
// 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 ModelControl from "./ModelControl.vue";
// highlight-start
import LookAroundController from "./LookAroundController.vue";
// highlight-end
const work = ref();
const workURL =
"https://vr-public.realsee-cdn.cn/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><template>
<FiveProvider :work="work">
<FiveCanvas :width="width" :height="height" />
<ModelControl />
// highlight-start
<LookAroundController />
// highlight-end
</FiveProvider>
</template>
<script setup lang="ts">
import { FiveProvider, FiveCanvas } from "@realsee/five/vue";
import { parseWork, Work } from "@realsee/five";
import { ref } from "vue";
import { useWindowDimensions } from "./useWindowDimensions";
import ModelControl from "./ModelControl.vue";
// highlight-start
import LookAroundController from "./LookAroundController.vue";
// highlight-end
const work = ref<Work>();
const workURL =
"https://vr-public.realsee-cdn.cn/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>Go back to your browser and you will see a look-around button appear in the top-right corner of the page. Clicking it toggles the look-around on and off.
What a nice feature 🥳!
What you will learn in the next chapter
In the next chapter we will use **State** to build some more advanced features and get a deeper feel for what **State** can do.
- Recording user operations using State.
- Replaying the user's operations using State.
