Recap of the previous chapter: Getting Started
- You learned how to set up the environment.
- You also wrote a piece of code that — even though you may not have known exactly what it did — successfully loaded and displayed a VR work.
In this chapter you will learn
- What a Work is
- How the
FiveProvider/FiveCanvascomponents work
Preparation
Just like in the previous chapter, we create a new directory (src/1.displaying-work) along with the corresponding html file and a jsx or tsx file.
For the jsx or tsx file, you can start 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>Display a work</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>src/1.displaying-work/withFetchWork.jsx
import React, { Component } from "react";
import { parseWork } from "@realsee/five";
/**
* React HOC that fetches the work
* @param url the URL of work.json
*/
function withFetchWork(url) {
return function(Compnent) {
return class extends Component {
state = { work: null };
componentDidMount() {
fetch(url).then(res => res.json()).then(json => {
this.setState({ work: parseWork(json) });
});
}
render() {
if (this.state.work === null) return null;
return <Compnent work={this.state.work} {...this.props}/>;
}
}
}
}
export { withFetchWork };src/1.displaying-work/withWindowDimensions.jsx
import React, { Component } from "react";
/**
* React HOC: gets the current window dimensions
*/
function withWindowDimensions() {
return function(Compnent) {
return class extends Component {
state = this.getWindowDimensions();
resizeListener = () => {
this.setState(this.getWindowDimensions());
};
getWindowDimensions() {
return { width: window.innerWidth, height: window.innerHeight };
}
componentDidMount() {
window.addEventListener("resize", this.resizeListener, false);
}
componentWillUnmount() {
window.removeEventListener("resize", this.resizeListener, false);
}
render() {
const dimensions = { width: this.state.width, height: this.state.height };
return <Compnent windowDimensions={dimensions} {...this.props}/>;
}
}
}
}
export { withWindowDimensions };src/1.displaying-work/App.jsx
import React, { Component } from "react";
import { compose } from "@wordpress/compose";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { withFetchWork } from "./withFetchWork";
import { withWindowDimensions } from "./withWindowDimensions";
/** Data URL of work.json */
const workURL = "https://vr-public.realsee-cdn.cn/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const FiveProvider = createFiveProvider();
const App = compose(
withFetchWork(workURL),
withWindowDimensions()
)(class extends Component {
render() {
const { work, windowDimensions } = this.props;
return <FiveProvider initialWork={work}>
<FiveCanvas width={windowDimensions.width} height={windowDimensions.height}/>
</FiveProvider>;
}
});
export { App };src/1.displaying-work/index.jsx
import React from "react";
import ReactDOM from "react-dom";
import { App } from "./App";
ReactDOM.render(<App/>, document.querySelector("#app"));
export {};src/1.displaying-work/withFetchWork.tsx
import React, { Component, ComponentClass } from "react";
import { Work, parseWork } from "@realsee/five";
/**
* React HOC that fetches the work
* @param url the URL of work.json
*/
function withFetchWork<P extends Record<string, any>>(url: string) {
return function(Compnent: ComponentClass<P & { work: Work }>): ComponentClass<P> {
return class extends Component<P, {work: Work | null}> {
state = { work: null };
componentDidMount() {
fetch(url).then(res => res.json()).then(json => {
this.setState({ work: parseWork(json) });
});
}
render() {
if (this.state.work === null) return null;
return <Compnent work={this.state.work} {...this.props}/>;
}
}
}
}
export { withFetchWork };src/1.displaying-work/withWindowDimensions.tsx
import React, { Component, ComponentClass } from "react";
/**
* React HOC: gets the current window dimensions
*/
function withWindowDimensions<P extends Record<string, any>>() {
return function(Compnent: ComponentClass<P & { windowDimensions: { width: number, height: number} }>): ComponentClass<P> {
return class extends Component<P, {width: number, height: number}> {
state = this.getWindowDimensions();
resizeListener = () => {
this.setState(this.getWindowDimensions());
};
getWindowDimensions() {
return { width: window.innerWidth, height: window.innerHeight };
}
componentDidMount() {
window.addEventListener("resize", this.resizeListener, false);
}
componentWillUnmount() {
window.removeEventListener("resize", this.resizeListener, false);
}
render() {
const dimensions = { width: this.state.width, height: this.state.height };
return <Compnent windowDimensions={dimensions} {...this.props}/>;
}
}
}
}
export { withWindowDimensions };src/1.displaying-work/App.tsx
import React, { Component } from "react";
import { Work } from "@realsee/five";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { compose } from "@wordpress/compose";
import { withFetchWork } from "./withFetchWork";
import { withWindowDimensions } from "./withWindowDimensions";
/** Data URL of work.json */
const workURL = "https://vr-public.realsee-cdn.cn/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const FiveProvider = createFiveProvider();
const App = compose(
withFetchWork(workURL),
withWindowDimensions()
)(class extends Component<{work: Work, windowDimensions: { width: number, height: number }}> {
render() {
const { work, windowDimensions } = this.props;
return <FiveProvider initialWork={work}>
<FiveCanvas width={windowDimensions.width} height={windowDimensions.height}/>
</FiveProvider>;
}
});
export { App };src/1.displaying-work/index.tsx
import React from "react";
import ReactDOM from "react-dom";
import { App } from "./App";
ReactDOM.render(<App/>, document.querySelector("#app"));
export {};Start the dev server with npm run dev, then navigate to the current page at "http://localhost:3000/src/1.displaying-work/index.html".
Info
Check your console: the port number may change depending on your configuration and which ports are already in use, so always rely on what the console prints. If you are using a different development/build tool, start the server according to that tool's requirements.
What is a Work
Now put your hands down from the keyboard for a moment — let's understand a few concepts. Don't worry, it's simple.
Introduction to Work
We call a single 3D space a Work. A Work is described by a json structure, which we call work.json. You can obtain a Work through Realsee OpenAPI; see the API reference. You can also serve it statically, as we do in this example.
Five SDK provides the parseWork method to parse work.json, which then gives you a work object. Passing the work object to the initialWork prop of FiveProvider initializes the scene. This is exactly what you did in the previous chapter.
Usage restrictions for a work
A production Work can include domain and availability restrictions in its allow_hosts and expire_at fields. Five SDK enforces the values supplied with the Work data.
The restrictions above are NOT validated in the following cases, to make development and testing easier:
- The domain is localhost
- The domain is an IP address, such as 127.0.0.1, 192.168.0.5, 172.30.2.0, 10.33.10.2, etc.
- Access is over a non-http scheme, such as the file:// protocol
Sample work.json data and field descriptions
You don't need to understand the contents of work.json to use the Five SDK well.
That said, knowing a little more never hurts.
Click to view work.json
{
"_signature": "LLzk2WHDbRfzQ5R+v82uVQqAGWMPfvgQ05hTwxgWKGeXbTPKtoQ8eI6FmCxJjtVjQW+wuZmZfxpzXePv4MGrWidG/05i5RRSELlPLEFZN3qYPykJLEIYhg3Wtvr8XkOI8wdeMgV82CP4Hyi84k/W2g+S+HcFs5oXfbfbFXPL/Gs=",
"allow_hosts": [
"*"
],
"base_url": "https://vr-public.realsee-cdn.cn/release/auto3dhd/9380ce27b46cbc5ba8d47fcf0e4c9e4f/",
"certificate": "-----BEGIN CERTIFICATE-----\nMIIEMzCCAhsCCQDYAS/7ATZRmTANBgkqhkiG9w0BAQsFADCBkzELMAkGA1UEBhMC\nQ04xEDAOBgNVBAgMB0JlaWppbmcxEDAOBgNVBAcMB0JlaWppbmcxFDASBgNVBAoM\nC2xpYW5qaWEuY29tMRAwDgYDVQQLDAdSZWFsc2VlMREwDwYDVQQDDAhIYXJkd2Fy\nZTElMCMGCSqGSIb3DQEJARYWbml1aGFpcWluZ0BsaWFuamlhLmNvbTAeFw0yMTA5\nMTAwNTIwMDBaFw0zMTA5MDgwNTIwMDBaMIGmMQswCQYDVQQGEwJDTjEQMA4GA1UE\nCAwHQmVpSmluZzEQMA4GA1UEBwwHQmVpSmluZzEQMA4GA1UECgwHUmVhbHNlZTEZ\nMBcGA1UECwwQUmVhbHNlZUFwcEdldHdheTEgMB4GA1UEAwwXYXBwLWdhdGV3YXku\ncmVhbHNlZS5jb20xJDAiBgkqhkiG9w0BCQEWFWRldmVsb3BlckByZWFsc2VlLmNv\nbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuv/y3Ezsy/wh3LCA8vomPbgI\nSO9iO5kyR+oAetklD+epMU6J/ZbvTDEomZxuS5iyyKGBupzAh2ZFLIy7tsE71Vx1\nIIvT7Kdyq66lMU4YzdrpKUcxv7oOQnO8DA1orKluNa4jkyXBywHKs/Q+20LVc+RD\ngKXqFGJUdo8mAxEScs0CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAkMxsU4VLPd4J\n0rElBNBIyqPtvnlTs6VkhIK0l4oM58wtDKc1uG9UPSX5j29NguZM6LOe0jCsU2Vg\nEpUseMWQjx4o2yBg7MokQyjWc1zu6PppKhQ+RqHQy/biJ2zsIMpX3oMASXffvnW5\nn4Bjyo1JdDJiLm1fLvLlVVxQoraJD+rtpqWDEYixGVREUo5OIL5Y5dVjkHG2r9RQ\nQuu3yEiyr9gAW8yhz3YR6/sJ6boyGK8NC0v8Jih7NnCdT+9ML+3jn3P5F3TeXdSf\nVeYIm5oWAOTe3AjjKP8ARMb2RYACjg80/AcowD/dvRRjbwQmyucUNug2pXJynXpD\nNfx1IBmUmzSAT1Z5yNuY/f3VRBJvmIQ6Jpmef+g0/wUJpyS4SObguItyYlFPLqRH\nK1oKqNX/uV0GWWEQl6Lml986TzlHxc4ljtHBhjzlKYIYYZLWWipk4JiB8hxJcTK+\ncrgvclEQSxFlmAyoqxYFClrOOsPqZJdBhDTvoUWnnWuJLQt7DLHpyInp+S75Gg3o\n0zgHpt9m26B3YbjQGYMQlYmhl2VLQa+Ey0W8UZQXLcTvoRT4p+8crqr6cNNsxCyZ\nm08vBbEMIMvhBeLQvpM75oaMBmelegipFl2eelxVIHdGJWoyJSZQUdXN0uSidhZp\nI7AIgzhqK1Ku/IXK0OSXJonn+/9X/VI=\n-----END CERTIFICATE-----",
"create_time": "2020-07-29T15:18:06+08:00",
"expire_at": "2132653492169",
"initial": {
"flag_position": [
-1.8683282713225706,
-1.1184803014502747,
-2.536637882840082
],
"fov": 95,
"heading": 0,
"latitude": 0,
"longitude": 5.58602282229265,
"pano": 4,
"pano_index": 4
},
"model": {
"file_url": "model/auto3d-syVb5hzy8ZL1gZut_VvGDo.at3d",
"material_base_url": "materials/",
"material_textures": [
"texture_0.jpg",
"texture_1.jpg",
"texture_2.jpg",
"texture_3.jpg",
"texture_4.jpg",
"texture_5.jpg",
"texture_6.jpg",
"texture_7.jpg",
"texture_8.jpg"
],
"modify_time": "2020-07-29T15:37:17+08:00",
"type": 0
},
"observers": [
{
"accessible_nodes": [
1,
2,
3,
4,
6,
7,
8
],
"floor_index": 0,
"index": 0,
"offset_point_count": 551,
"position": [
-2.9148900508880615,
0,
1.392490029335022
],
"quaternion": {
"w": 0.44238951628615497,
"x": -0.0011834399273419546,
"y": -0.8968221073969266,
"z": -0.00045692663054024737
},
"standing_position": [
-2.9148900508880615,
-1.1193245547539874,
1.392490029335022
],
"visible_nodes": [
1,
2,
3,
4,
6,
7,
8
]
},
{
"accessible_nodes": [
0,
2,
3,
4
],
"floor_index": 0,
"index": 1,
"offset_point_count": 1382,
"position": [
-1.780460000038147,
-0.00217098998837173,
4.450079917907715
],
"quaternion": {
"w": 0.46776604948048534,
"x": -0.0011699393533674196,
"y": -0.883851410579187,
"z": -0.000490464139922521
},
"standing_position": [
-1.780460000038147,
-1.1214462854241507,
4.450079917907715
],
"visible_nodes": [
0,
2,
3,
4
]
},
{
"accessible_nodes": [
0,
1,
3,
4
],
"floor_index": 0,
"index": 2,
"offset_point_count": 378,
"position": [
-1.3428200483322144,
-0.0028426700737327337,
6.358610153198242
],
"quaternion": {
"w": 0.6069850728938949,
"x": -0.001073151580086956,
"y": -0.7947122170838032,
"z": -0.0006765059598702671
},
"standing_position": [
-1.3428200483322144,
-1.1225737228916606,
6.358610153198242
],
"visible_nodes": [
0,
1,
3,
4
]
},
{
"accessible_nodes": [
0,
1,
2,
4,
6,
7,
8
],
"floor_index": 0,
"index": 3,
"offset_point_count": 0,
"position": [
-2.554189920425415,
-0.005082080140709877,
6.343259811401367
],
"quaternion": {
"w": -0.030997155509175898,
"x": -0.0033666821765646494,
"y": -0.9994984128781037,
"z": -0.005546881036692224
},
"standing_position": [
-2.554189920425415,
-1.1224907918710612,
6.343259811401367
],
"visible_nodes": [
0,
1,
2,
4,
6,
7,
8
]
},
{
"accessible_nodes": [
0,
1,
2,
3,
5,
6,
7,
8,
9,
10,
11
],
"floor_index": 0,
"index": 4,
"offset_point_count": 941,
"position": [
-2.91471004486084,
-0.0003035110130440444,
0.06049229949712753
],
"quaternion": {
"w": 0.0513025987418296,
"x": -0.0012676475973127641,
"y": -0.9986823460816978,
"z": 0.00004879134915086605
},
"standing_position": [
-2.91471004486084,
-1.1184803014502747,
0.06049229949712753
],
"visible_nodes": [
0,
1,
2,
3,
5,
6,
7,
8,
9,
10,
11
]
},
{
"accessible_nodes": [
4,
6,
9,
10
],
"floor_index": 0,
"index": 5,
"offset_point_count": 863,
"position": [
-1.0017000436782837,
-0.005517140030860901,
-1.6075899600982666
],
"quaternion": {
"w": -0.0344319061946854,
"x": -0.001258810265919515,
"y": -0.9994062434209862,
"z": 0.0001571970577815824
},
"standing_position": [
-1.0017000436782837,
-1.1178040247150276,
-1.6075899600982666
],
"visible_nodes": [
4,
6,
9,
10
]
},
{
"accessible_nodes": [
0,
3,
4,
5,
7,
8
],
"floor_index": 0,
"index": 6,
"offset_point_count": 1322,
"position": [
-2.846139907836914,
-0.001211759983561933,
-3.169840097427368
],
"quaternion": {
"w": 0.32066123052403445,
"x": -0.0012332355032137405,
"y": -0.9471930957831524,
"z": -0.00029739399948980815
},
"standing_position": [
-2.846139907836914,
-1.1164889512469,
-3.169840097427368
],
"visible_nodes": [
0,
3,
4,
5,
7,
8
]
},
{
"accessible_nodes": [
0,
3,
4,
6,
8
],
"floor_index": 0,
"index": 7,
"offset_point_count": 2356,
"position": [
-3.0448501110076904,
-0.0010134399635717273,
-4.505109786987305
],
"quaternion": {
"w": 0.49087982982843503,
"x": -0.001156616652291604,
"y": -0.8712263733357108,
"z": -0.0005211039435373182
},
"standing_position": [
-3.0448501110076904,
-1.11562920091362,
-4.505109786987305
],
"visible_nodes": [
0,
3,
4,
6,
8
]
},
{
"accessible_nodes": [
0,
3,
4,
6,
7
],
"floor_index": 0,
"index": 8,
"offset_point_count": 234,
"position": [
-2.9152801036834717,
-0.01438829954713583,
-6.370659828186035
],
"quaternion": {
"w": -0.30469427695314993,
"x": 0.0033588028429806095,
"y": -0.952412964681632,
"z": -0.007724194714338225
},
"standing_position": [
-2.9152801036834717,
-1.1144715989725875,
-6.370659828186035
],
"visible_nodes": [
0,
3,
4,
6,
7
]
},
{
"accessible_nodes": [
4,
5,
10,
11,
12,
13
],
"floor_index": 0,
"index": 9,
"offset_point_count": 4886,
"position": [
-4.0423102378845215,
0.003333129920065403,
-0.03953389823436737
],
"quaternion": {
"w": -0.18957592794754674,
"x": 0.010895134657991246,
"y": -0.9818002618819436,
"z": -0.0032427031038127978
},
"standing_position": [
-4.0423102378845215,
-1.118231507556375,
-0.03953389823436737
],
"visible_nodes": [
4,
5,
10,
11,
12,
13
]
},
{
"accessible_nodes": [
4,
5,
9,
11
],
"floor_index": 0,
"index": 10,
"offset_point_count": 1773,
"position": [
-4.850550174713135,
0.004613489843904972,
0.1713390052318573
],
"quaternion": {
"w": 0.12768161943121548,
"x": -0.0012676586552423492,
"y": -0.9918143930048722,
"z": -0.00004849317962342722
},
"standing_position": [
-4.850550174713135,
-1.1182253683511887,
0.1713390052318573
],
"visible_nodes": [
4,
5,
9,
11
]
},
{
"accessible_nodes": [
4,
9,
10
],
"floor_index": 0,
"index": 11,
"offset_point_count": 654,
"position": [
-5.53085994720459,
-0.013371099717915058,
-0.143778994679451
],
"quaternion": {
"w": -0.0030183054753861976,
"x": -0.00047414000140664293,
"y": -0.9999897553992034,
"z": -0.0033406236879980804
},
"standing_position": [
-5.53085994720459,
-1.1179010210789888,
-0.143778994679451
],
"visible_nodes": [
4,
9,
10
]
},
{
"accessible_nodes": [
9,
13
],
"floor_index": 0,
"index": 12,
"offset_point_count": 1237,
"position": [
-4.178860187530518,
-0.013643399812281132,
1.3469799757003784
],
"quaternion": {
"w": 0.06683888675991313,
"x": -0.0047767000805582485,
"y": -0.9977055393311836,
"z": -0.00966420424898995
},
"standing_position": [
-4.178860187530518,
-1.1190794461899993,
1.3469799757003784
],
"visible_nodes": [
9,
13
]
},
{
"accessible_nodes": [
9,
12
],
"floor_index": 0,
"index": 13,
"offset_point_count": 466,
"position": [
-4.963950157165527,
-0.005961719900369644,
3.710700035095215
],
"quaternion": {
"w": -0.19462188396064967,
"x": 0.004454742146828046,
"y": -0.9807853643462634,
"z": -0.012749200565824454
},
"standing_position": [
-4.963950157165527,
-1.12043610464341,
3.710700035095215
],
"visible_nodes": [
9,
12
]
}
],
"panorama": {
"count": 14,
"list": [
{
"back": "images/cube_2048/0/9bf403506f2bc22eea0a81cdb09a8e22/0_b.jpg",
"down": "images/cube_2048/0/9bf403506f2bc22eea0a81cdb09a8e22/0_d.jpg",
"front": "images/cube_2048/0/9bf403506f2bc22eea0a81cdb09a8e22/0_f.jpg",
"index": 0,
"left": "images/cube_2048/0/9bf403506f2bc22eea0a81cdb09a8e22/0_l.jpg",
"right": "images/cube_2048/0/9bf403506f2bc22eea0a81cdb09a8e22/0_r.jpg",
"up": "images/cube_2048/0/9bf403506f2bc22eea0a81cdb09a8e22/0_u.jpg"
},
{
"back": "images/cube_2048/1/cb8db51bc30afdd40cf6e4f3f7341c40/1_b.jpg",
"down": "images/cube_2048/1/cb8db51bc30afdd40cf6e4f3f7341c40/1_d.jpg",
"front": "images/cube_2048/1/cb8db51bc30afdd40cf6e4f3f7341c40/1_f.jpg",
"index": 1,
"left": "images/cube_2048/1/cb8db51bc30afdd40cf6e4f3f7341c40/1_l.jpg",
"right": "images/cube_2048/1/cb8db51bc30afdd40cf6e4f3f7341c40/1_r.jpg",
"up": "images/cube_2048/1/cb8db51bc30afdd40cf6e4f3f7341c40/1_u.jpg"
},
{
"back": "images/cube_2048/2/67cdadb9caa0f844f56fee0aac7bb56d/2_b.jpg",
"down": "images/cube_2048/2/67cdadb9caa0f844f56fee0aac7bb56d/2_d.jpg",
"front": "images/cube_2048/2/67cdadb9caa0f844f56fee0aac7bb56d/2_f.jpg",
"index": 2,
"left": "images/cube_2048/2/67cdadb9caa0f844f56fee0aac7bb56d/2_l.jpg",
"right": "images/cube_2048/2/67cdadb9caa0f844f56fee0aac7bb56d/2_r.jpg",
"up": "images/cube_2048/2/67cdadb9caa0f844f56fee0aac7bb56d/2_u.jpg"
},
{
"back": "images/cube_2048/3/791c38edaf6bbcc16e19682543745e9b/3_b.jpg",
"down": "images/cube_2048/3/791c38edaf6bbcc16e19682543745e9b/3_d.jpg",
"front": "images/cube_2048/3/791c38edaf6bbcc16e19682543745e9b/3_f.jpg",
"index": 3,
"left": "images/cube_2048/3/791c38edaf6bbcc16e19682543745e9b/3_l.jpg",
"right": "images/cube_2048/3/791c38edaf6bbcc16e19682543745e9b/3_r.jpg",
"up": "images/cube_2048/3/791c38edaf6bbcc16e19682543745e9b/3_u.jpg"
},
{
"back": "images/cube_2048/4/ee318d536c51ef3de38c4aa059d65ef0/4_b.jpg",
"down": "images/cube_2048/4/ee318d536c51ef3de38c4aa059d65ef0/4_d.jpg",
"front": "images/cube_2048/4/ee318d536c51ef3de38c4aa059d65ef0/4_f.jpg",
"index": 4,
"left": "images/cube_2048/4/ee318d536c51ef3de38c4aa059d65ef0/4_l.jpg",
"right": "images/cube_2048/4/ee318d536c51ef3de38c4aa059d65ef0/4_r.jpg",
"up": "images/cube_2048/4/ee318d536c51ef3de38c4aa059d65ef0/4_u.jpg"
},
{
"back": "images/cube_2048/5/b19d98dc23ec04d86df755dd1a15fdb3/5_b.jpg",
"down": "images/cube_2048/5/b19d98dc23ec04d86df755dd1a15fdb3/5_d.jpg",
"front": "images/cube_2048/5/b19d98dc23ec04d86df755dd1a15fdb3/5_f.jpg",
"index": 5,
"left": "images/cube_2048/5/b19d98dc23ec04d86df755dd1a15fdb3/5_l.jpg",
"right": "images/cube_2048/5/b19d98dc23ec04d86df755dd1a15fdb3/5_r.jpg",
"up": "images/cube_2048/5/b19d98dc23ec04d86df755dd1a15fdb3/5_u.jpg"
},
{
"back": "images/cube_2048/6/b8f425e5f8943c38ad1386ab391b54bc/6_b.jpg",
"down": "images/cube_2048/6/b8f425e5f8943c38ad1386ab391b54bc/6_d.jpg",
"front": "images/cube_2048/6/b8f425e5f8943c38ad1386ab391b54bc/6_f.jpg",
"index": 6,
"left": "images/cube_2048/6/b8f425e5f8943c38ad1386ab391b54bc/6_l.jpg",
"right": "images/cube_2048/6/b8f425e5f8943c38ad1386ab391b54bc/6_r.jpg",
"up": "images/cube_2048/6/b8f425e5f8943c38ad1386ab391b54bc/6_u.jpg"
},
{
"back": "images/cube_2048/7/a05b06a01171babb1aceb5aad8406ab8/7_b.jpg",
"down": "images/cube_2048/7/a05b06a01171babb1aceb5aad8406ab8/7_d.jpg",
"front": "images/cube_2048/7/a05b06a01171babb1aceb5aad8406ab8/7_f.jpg",
"index": 7,
"left": "images/cube_2048/7/a05b06a01171babb1aceb5aad8406ab8/7_l.jpg",
"right": "images/cube_2048/7/a05b06a01171babb1aceb5aad8406ab8/7_r.jpg",
"up": "images/cube_2048/7/a05b06a01171babb1aceb5aad8406ab8/7_u.jpg"
},
{
"back": "images/cube_2048/8/2cd717b9c97f48ae79511855cc7e5138/8_b.jpg",
"down": "images/cube_2048/8/2cd717b9c97f48ae79511855cc7e5138/8_d.jpg",
"front": "images/cube_2048/8/2cd717b9c97f48ae79511855cc7e5138/8_f.jpg",
"index": 8,
"left": "images/cube_2048/8/2cd717b9c97f48ae79511855cc7e5138/8_l.jpg",
"right": "images/cube_2048/8/2cd717b9c97f48ae79511855cc7e5138/8_r.jpg",
"up": "images/cube_2048/8/2cd717b9c97f48ae79511855cc7e5138/8_u.jpg"
},
{
"back": "images/cube_2048/9/0b634fe985df7011472b81c75babfd95/9_b.jpg",
"down": "images/cube_2048/9/0b634fe985df7011472b81c75babfd95/9_d.jpg",
"front": "images/cube_2048/9/0b634fe985df7011472b81c75babfd95/9_f.jpg",
"index": 9,
"left": "images/cube_2048/9/0b634fe985df7011472b81c75babfd95/9_l.jpg",
"right": "images/cube_2048/9/0b634fe985df7011472b81c75babfd95/9_r.jpg",
"up": "images/cube_2048/9/0b634fe985df7011472b81c75babfd95/9_u.jpg"
},
{
"back": "images/cube_2048/10/40c1a6463b8c7462aadfca2a5f80f685/10_b.jpg",
"down": "images/cube_2048/10/40c1a6463b8c7462aadfca2a5f80f685/10_d.jpg",
"front": "images/cube_2048/10/40c1a6463b8c7462aadfca2a5f80f685/10_f.jpg",
"index": 10,
"left": "images/cube_2048/10/40c1a6463b8c7462aadfca2a5f80f685/10_l.jpg",
"right": "images/cube_2048/10/40c1a6463b8c7462aadfca2a5f80f685/10_r.jpg",
"up": "images/cube_2048/10/40c1a6463b8c7462aadfca2a5f80f685/10_u.jpg"
},
{
"back": "images/cube_2048/11/9663b7885a119f2f06da06212986bc9b/11_b.jpg",
"down": "images/cube_2048/11/9663b7885a119f2f06da06212986bc9b/11_d.jpg",
"front": "images/cube_2048/11/9663b7885a119f2f06da06212986bc9b/11_f.jpg",
"index": 11,
"left": "images/cube_2048/11/9663b7885a119f2f06da06212986bc9b/11_l.jpg",
"right": "images/cube_2048/11/9663b7885a119f2f06da06212986bc9b/11_r.jpg",
"up": "images/cube_2048/11/9663b7885a119f2f06da06212986bc9b/11_u.jpg"
},
{
"back": "images/cube_2048/12/01f84f2edd8642d0bf5e4874d01a28ac/12_b.jpg",
"down": "images/cube_2048/12/01f84f2edd8642d0bf5e4874d01a28ac/12_d.jpg",
"front": "images/cube_2048/12/01f84f2edd8642d0bf5e4874d01a28ac/12_f.jpg",
"index": 12,
"left": "images/cube_2048/12/01f84f2edd8642d0bf5e4874d01a28ac/12_l.jpg",
"right": "images/cube_2048/12/01f84f2edd8642d0bf5e4874d01a28ac/12_r.jpg",
"up": "images/cube_2048/12/01f84f2edd8642d0bf5e4874d01a28ac/12_u.jpg"
},
{
"back": "images/cube_2048/13/e9ce1249af9000a33d566c653560c3d6/13_b.jpg",
"down": "images/cube_2048/13/e9ce1249af9000a33d566c653560c3d6/13_d.jpg",
"front": "images/cube_2048/13/e9ce1249af9000a33d566c653560c3d6/13_f.jpg",
"index": 13,
"left": "images/cube_2048/13/e9ce1249af9000a33d566c653560c3d6/13_l.jpg",
"right": "images/cube_2048/13/e9ce1249af9000a33d566c653560c3d6/13_r.jpg",
"up": "images/cube_2048/13/e9ce1249af9000a33d566c653560c3d6/13_u.jpg"
}
]
},
"picture_url": "https://vr-public.realsee-cdn.cn/release/auto3dhd/9380ce27b46cbc5ba8d47fcf0e4c9e4f/screenshot/1596005211_4/pc0_3aoiit6zp.jpg",
"title_picture_url": "https://vr-public.realsee-cdn.cn/release/auto3dhd/9380ce27b46cbc5ba8d47fcf0e4c9e4f/screenshot/1596005211_4/pc1_iQLf2N6a6_1.jpg",
"vr_code": "8054djM0oR6ky3Ey8e"
}- _signature / certificate: digital signature of the work content
- expire_at: expiration time of the work
- allow_hosts: allowed domains for the work
- initial: initialization data, which is a State object. Describes the pose of the Work in its initial loaded state — also known as the VR's initial viewpoint.
- mode: the mode
- pano_index: the initial observation point
- longitude: the camera's horizontal angle
- latitude: the camera's vertical angle (pitch)
- fov: the camera's vertical field of view
- model: the 3D model
- file_url: the resource URL of the 3D model; the file extension .at3d is Realsee's custom model format
- material_textures: the resource URLs of the 3D model's textures
- panorama: panorama color information
- list:
- up / down / left / right / front / back: the panorama color information is stored and used as a cubemap.
- list:
- observers: observation point (capture point) information
- visible_nodes: the visibility list between observation points
- accessible_nodes: the connectivity list between observation points
- quaternion: the rotational offset between the observation point and the model coordinates
- standing_position: the ground coordinates of the observation point
- position: the coordinates of the observation point
- floor_index: the floor of the observation point
The Work object
Compared to the work.json above, what you will more often work with is the Work object, which is the product of the parseWork function. The Work object provides solid serialization on top of work.json. You can find detailed documentation in the Five SDK API docs.
Understanding FiveProvider and FiveCanvas
The origins of FiveProvider and FiveCanvas
FiveProvider / FiveCanvas are built around the ideas of the React Context API. You can think of FiveProvider as creating a 3D space instance, while FiveCanvas is the canvas it renders into the DOM structure. Any child component inside FiveProvider can use context to access the methods exposed by FiveProvider, in order to read information about the 3D space or to control it. We have also built a series of hooks to make these tasks even easier.
This description in words may be hard to grasp, so let's get hands-on and build something to see for ourselves.
Adding mode control buttons
The task we'll complete this time is adding mode control buttons. Five SDK has several built-in modes; by default it shows the Panorama mode, just like the result we ended up with in the previous chapter. We can also show the Model mode to view the 3D space from a macro perspective. To switch between them, we'll use the useCurrentState React hook provided by the Five SDK. Concepts such as mode, State, and useCurrentState will be covered in detail in the next chapter; for this chapter, what we want to do is:
- Write a React component
- Insert the component as a child of FiveProvider
- Control the 3D space through the API provided by Five SDK
Installing UI components
To keep the example code simple, we use material-UI as the UI component library.
You could also use Ant Design, Fluent UI, and others.
npm install @mui/material @mui/icons-material @emotion/react @emotion/styledUnderstanding withFive
import { withFive } from "@realsee/five/react" is a React HOC provided by the Five SDK. It can inject 3D-space-related properties/features and methods via withFive. You declare the dependencies you need with the createFiveFeature method, import { createFiveFeature } from "@realsee/five/react", after which they become available through this.props.$five. This time we need to inject currentState and setState to switch modes.
Now let's start writing the code.
- Add a ModeController file to write the component in.
- First, at the top, import `Five` and `Mode` from
@realsee/five. - `Mode` is the type declaration for modes.
- [
Five.Mode[string]](https://unpkg.com/@realsee/five@6.8.9/docs/classes/five.Five.html#mode) is the enumeration of modes. - Also, at the top, import `withFive` and `createFiveFeature` from
@realsee/five/react. - Write the
ModeControllercomponent. - Add the UI using material-UI components.
- Through
this.props.$five.currentStateandthis.props.$five.setState()you can read/set the mode of the 3D space.
import React, { Component } from "react";
import { Five } from "@realsee/five";
import { withFive, createFiveFeature } from "@realsee/five/react";
import { compose } from "@wordpress/compose";
import BottomNavigation from "@mui/material/BottomNavigation";
import BottomNavigationAction from "@mui/material/BottomNavigationAction";
import Paper from "@mui/material/Paper";
import DirectionsWalkIcon from "@mui/icons-material/DirectionsWalk";
import ViewInArIcon from "@mui/icons-material/ViewInAr";
const FEATURES = createFiveFeature("currentState", "setState");
/**
* React Component: mode control
*/
const ModeController = compose(
withFive(FEATURES)
)(class extends Component {
render() {
return <Paper sx={{ position: "fixed", bottom: 0, left: 0, right: 0 }}>
<BottomNavigation
showLabels
value={this.props.$five.currentState.mode}
onChange={(_, newValue) => {
this.props.$five.setState({ mode: newValue });
}}
>
<BottomNavigationAction label="Panorama Walkthrough" icon={<DirectionsWalkIcon/>} value={Five.Mode.Panorama}/>
<BottomNavigationAction label="Space Overview" icon={<ViewInArIcon/>} value={Five.Mode.Floorplan}/>
</BottomNavigation>
</Paper>;
}
})
export { ModeController };import React, { Component } from "react";
import { Five, Mode } from "@realsee/five";
import { withFive, createFiveFeature, PropTypeOfFiveFeatures } from "@realsee/five/react";
import { compose } from "@wordpress/compose";
import BottomNavigation from "@mui/material/BottomNavigation";
import BottomNavigationAction from "@mui/material/BottomNavigationAction";
import Paper from "@mui/material/Paper";
import DirectionsWalkIcon from "@mui/icons-material/DirectionsWalk";
import ViewInArIcon from "@mui/icons-material/ViewInAr";
const FEATURES = createFiveFeature("currentState", "setState");
type Props = PropTypeOfFiveFeatures<typeof FEATURES>;
/**
* React Component: mode control
*/
const ModeController = compose(
withFive(FEATURES)
)(class extends Component<Props> {
render() {
return <Paper sx={{ position: "fixed", bottom: 0, left: 0, right: 0 }}>
<BottomNavigation
showLabels
value={this.props.$five.currentState.mode}
onChange={(_, newValue: Mode) => {
this.props.$five.setState({ mode: newValue });
}}
>
<BottomNavigationAction label="Panorama Walkthrough" icon={<DirectionsWalkIcon/>} value={Five.Mode.Panorama}/>
<BottomNavigationAction label="Space Overview" icon={<ViewInArIcon/>} value={Five.Mode.Floorplan}/>
</BottomNavigation>
</Paper>;
}
})
export { ModeController };Using the mode control button component
Next, insert the ModeController component into the FiveProvider in the App file.
import React, { Component } from "react";
import { compose } from "@wordpress/compose";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { withFetchWork } from "./withFetchWork";
import { withWindowDimensions } from "./withWindowDimensions";
//highlight-start
import { ModeController } from "./ModeController";
//highlight-end
/** Data URL of work.json */
const workURL = "https://vr-public.realsee-cdn.cn/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const FiveProvider = createFiveProvider();
const App = compose(
withFetchWork(workURL),
withWindowDimensions()
)(class extends Component {
render() {
const { work, windowDimensions } = this.props;
return <FiveProvider initialWork={work}>
<FiveCanvas width={windowDimensions.width} height={windowDimensions.height}/>
//highlight-start
<ModeController/>;
//highlight-end
</FiveProvider>;
}
});
export { App };import React, { Component } from "react";
import { Work } from "@realsee/five";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { compose } from "@wordpress/compose";
import { withFetchWork } from "./withFetchWork";
import { withWindowDimensions } from "./withWindowDimensions";
//highlight-start
import { ModeController } from "./ModeController";
//highlight-end
/** Data URL of work.json */
const workURL = "https://vr-public.realsee-cdn.cn/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const FiveProvider = createFiveProvider();
const App = compose(
withFetchWork(workURL),
withWindowDimensions()
)(class extends Component<{work: Work, windowDimensions: { width: number, height: number }}> {
render() {
const { work, windowDimensions } = this.props;
return <FiveProvider initialWork={work}>
<FiveCanvas width={windowDimensions.width} height={windowDimensions.height}/>
//highlight-start
<ModeController/>
//highlight-end
</FiveProvider>;
}
});
export { App };Go back to your browser and take a look: you'll see a control bar appear at the bottom of the page. Clicking the different buttons — Panorama Walkthrough and Space Overview — changes the state of the 3D space.
Nice work 🥳!
What you'll learn in the next chapter
In the next chapter you will learn
- What State is
- How to change the viewing direction / position of the 3D space
- How the code you just wrote — such as
currentState,setState, and the other responsive pieces — actually works - How to implement an automatic look-around feature using State
