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 features on top of it to control that 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,
setStateandstateChange. - How to build an auto look-around feature using State.
Getting ready
Just like in the previous chapter, create a new directory (src/2.knowing-state) along with the corresponding html file and a js or ts file.
You can start by copying the contents of the previous chapter into the js or ts file.
<!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 a 3D Space</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>
<!-- Mode switching -->
<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"
>Panorama Roaming</a
>
<a href="javascript:;" class="btn btn-primary js-Floorplan"
>Space Overview</a
>
</div>
</div>
</nav>
<script type="module" src="./index"></script>
</body>
</html>import { Five, parseWork } from "@realsee/five";
const workURL =
"https://vr-public.realsee-cdn.cn/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);
{
// === Mode switching ===
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 {};import { Five, Mode, parseWork } from "@realsee/five";
const workURL =
"https://vr-public.realsee-cdn.cn/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);
{
// === Mode switching ===
const buttons: Partial<Record<Mode, Element>> = {
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 as Mode });
},
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 {};Start the dev server with npm run dev, then navigate to the current page " \<http://localhost:3000/src/2.knowing-state/index.html\> ".
Tip
Check your console — the port number may change depending on your configuration and which ports are currently in use, so always rely on the value printed in the console. If you use a different build/dev tool, start the server according to that tool's requirements.
What is State
Here comes another concept. I promise this is the last piece of theory you need to learn at this stage.
Introducing State
State is the data structure used to describe status. In the previous chapter we learned about Work — Work describes a 3D space, whereas State describes the current status within that 3D space. It includes the mode, the capture point you are standing at, the camera's direction, and the camera's field of view.
The State data structure and field reference
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 uses 5 modes, which you can obtain from Five.Mode:
- Panorama: panorama roaming mode. In this mode the view roams between capture points; gestures let you rotate / zoom the view or switch capture points. Suitable for inspecting captured panorama information.
- Floorplan: space overview mode. In this mode the view is centered on the model; gestures let you rotate / zoom the model or switch floors. Suitable for viewing the overall look of the model.
- Topview: floor plan mode. In this mode the view is centered on the model, looking straight down at it; gestures let you pan / zoom the model or switch floors. Suitable for viewing the plan layout of the model.
- Model: model roaming mode. In this mode the view roams freely through the model; gestures let you rotate / zoom the view or move around. Suitable for inspecting model details and performing positioning operations.
- VRPanorama: VR headset mode. In this mode you can use Cardboard goggles or their third-party derivatives to achieve a VR virtual-display effect.
panoIndex: the capture point, i.e. a position you can stand at 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 position using a scheme 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 State-related APIs are there
- [
[state, setState] = useFiveState;](https://unpkg.com/@realsee/five@6.8.9/docs/functions/react.useFiveState.html) - [
[currentState, setCurrentState] = useFiveCurrentState;](https://unpkg.com/@realsee/five@6.8.9/docs/functions/react.useFiveCurrentState.html)
You can read the current status via state / currentState, and set it via setState / setCurrentState.
The difference between state and currentState
currentState is the current status — the status on screen, the one being displayed right now. state is the target status, or in other words the stable status at the next moment.
You can think of it simply like this:
When setState is called, state immediately becomes the value passed to setState, while currentState does not change right away. Instead, during the animated transition it gradually approaches state and eventually becomes equal to it — exactly like the animation you see on screen.
In the code example from the previous chapter we already used the mode property to switch between Panorama and Floorplan modes. You can also try adding the other modes and see how each one behaves differently.
VRPanorama mode requires the device's gyroscope information, so a mobile device is required.
In addition, on iOS devices the server 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. We will build an auto look-around feature, controlled by a button that activates it, which automatically rotates the camera horizontally.
Writing the look-around feature
Adding the UI buttons for the look-around feature
Add two buttons in the top-right corner of the screen: Start and Stop.
<!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>Changing the Viewpoint</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>
<!-- Mode switching -->
<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">Panorama Roaming</button>
<button class="btn btn-primary js-Floorplan">Space Overview</button>
</div>
</div>
</nav>
<!-- highlight-start -->
<!-- Look around -->
<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>
<!-- highlight-end -->
<script type="module" src="./index"></script>
</body>
</html>Writing the logic
- Use a setInterval timer to repeatedly modify the longitude value of state.
- When the Start button is clicked, start the timer.
- When the Stop button is clicked, stop the timer.
Append the following after the mode switching code from the previous chapter:
{
// === Look around ===
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
);
}{
// === Look around ===
let timer: number | undefined;
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
);
}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 complex features and get a deeper feel for what **State** can do.
- Record user operations using State.
- Replay the recorded user operations using State.
