Skip to main content
Documentation

Changing the Viewpoint

Read and update Five SDK viewpoint state in React class components and HOCs.

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 that to control the 3D space.

In this chapter you will learn

  • What State is.
  • How to change the direction / position from which the 3D space is viewed.
  • How the code from the previous chapter works, e.g. currentState, setState, and the other reactive pieces.
  • How to build an auto look-around feature using State.

Getting Ready

As in the previous chapter, create a new directory (src/2.knowing-state) along with its corresponding html file and a jsx or tsx file.

For the jsx or tsx files, 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>Changing the Viewpoint | Knowing 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>

src/2.knowing-state/withFetchWork.jsx

import { useState, useEffect } from "react";
import { parseWork } from "@realsee/five";

/**
 * React Hook: fetch a work object from the URL of a work.json file
 * @param url the data URL of work.json
 * @returns the work object, or null while it is being fetched
 */
 function useFetchWork(url) {
  const [work, setWork] = useState(null);
  useEffect(() => {
    setWork(null);
    fetch(url)
      .then(response => response.text())
      .then(text => setWork(parseWork(text)));
  },[url]);
  return work;
}

export { useFetchWork };

src/2.knowing-state/withWindowDimensions.js

import React, { Component } from "react";

/**
 * React HOC: get 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/2.knowing-state/ModeController.jsx

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 Roam" icon={<DirectionsWalkIcon/>} value={Five.Mode.Panorama}/>
        <BottomNavigationAction label="Space Overview" icon={<ViewInArIcon/>} value={Five.Mode.Floorplan}/>
      </BottomNavigation>
    </Paper>;
  }
})

export { ModeController };

src/2.knowing-state/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";
import { ModeController } from "./ModeController";

/** the 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}/>
      <ModeController/>;
    </FiveProvider>;
  }
});

export { App };

src/2.knowing-state/index.jsx

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/2.knowing-state/index.html".

Info

Check your console: the port number may change depending on your configuration and which ports are currently in use, so rely on what the console outputs. If you are using a different build tool, start the server according to that tool's requirements.

What is State

Here comes some conceptual ground to cover. I promise this is the last bit of theory you need at this stage.

Introduction to State

State is the data structure used to describe a status. In the previous chapter we learned about Work: Work describes a 3D space, while State describes what status that 3D space is currently in. It contains the mode, the capture point you are positioned at, the camera direction, and the camera's field of view.

The Data Structure of State and Its Fields

interface State {
  "mode": Five.Mode,
  "panoIndex": number,
  "longitude": number,
  "latitude": number,
  "fov": number,
  "offset": THREE.Vector3
}

The fields of State

  • mode: the current mode

Five SDK commonly uses 5 modes, which you can access via Five.Mode:

  • Panorama: panorama roam mode. In this mode the view roams between capture points; gestures can rotate the view, zoom, or switch capture points. Best for viewing the captured panorama information.
  • Floorplan: space overview mode. In this mode the view is centered on the model; gestures can rotate the model, zoom it, or switch floors. Best 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 can pan the model, zoom it, or switch floors. Best for viewing the model's planar structure.
  • Model: model roam mode. In this mode the view roams freely inside the model; gestures can rotate the view, zoom, or move. Best for inspecting model details and performing positioning operations.
  • VRPanorama: VR headset mode. In this mode you can use a Cardboard viewer or one of its 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 into work[observers].
  • longitude / latitude: the camera's horizontal angle (yaw) / the camera's vertical angle (pitch), in radians. We describe the camera position in a manner similar to longitude and latitude.

The entire model scene uses a right-handed Cartesian coordinate system: the XZ plane is parallel to the ground, and the Y axis is perpendicular to the ground.

Right-handed coordinate system illustration

The initial camera direction is from the origin looking toward the negative Z axis.

  • Increasing longitude rotates the camera to the left.
  • Decreasing longitude rotates the camera to the right.
  • Increasing latitude rotates the camera downward.
  • Decreasing latitude rotates the camera upward.
  • fov: the camera's vertical field of view (in degrees).
  • offset: the camera's current 3D coordinates.
  • [[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 the status via setState / setCurrentState.

The Difference Between state and currentState

currentState is the current status: the status shown on screen, the status currently being displayed. state is the target status, or in other words the stable status at the next point in time.

You can simply think of it this way:
When setState is called, state immediately becomes the value passed to setState, while currentState does not change right away. Over the course of the transition animation, currentState gradually approaches state and eventually becomes equal to state — just like 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 and see how each one differs.

VRPanorama mode requires the device's gyroscope data, so it requires a mobile device.

In addition, on iOS devices the service must be served over https; otherwise iOS will not allow access to the gyroscope data.

Building an Auto Look-Around Feature

We have already read and set mode; this time let's try modifying longitude / latitude instead. Here we will build an auto look-around feature: a button toggles the auto look-around feature, which automatically rotates the camera horizontally.

Writing the Look-Around Component

  1. Add a LookAroundController file to write the component in.
  2. Design an active React state to control the display status of the button that enables/disables the look-around feature.
  3. The look-around feature works by using setInterval to fire a function periodically that modifies five's state.
import React, { Component } from "react";
import { withFive, createFiveFeature } from "@realsee/five/react";
import { compose } from "@wordpress/compose";
import IconButton from "@mui/material/IconButton";
import Paper from "@mui/material/Paper";
import FlipCameraAndroidIcon from "@mui/icons-material/FlipCameraAndroid";
import PauseIcon from "@mui/icons-material/Pause";

const FEATURES = createFiveFeature("currentState", "setState");

/**
 * ReactComponent: auto look-around button
 */
const LookAroundController = compose(
  withFive(FEATURES)
)(class extends Component {

  timer;
  state = { active: false };

  toggleActive(active) {
    window.clearInterval(this.timer);
    this.setState({ active });
    if (active === true) {
      this.timer = window.setInterval(() => {
        this.props.$five.setState({
          longitude: this.props.$five.currentState.longitude + Math.PI / 360
        });
      }, 16);
    } else {
      delete this.timer;
    }
  }
  render() {
    return <Paper sx={{ position: "fixed", top: 10, right: 10 }}>
    {this.state.active ?
      <IconButton onClick={() => this.toggleActive(false)}><PauseIcon/></IconButton>:
      <IconButton onClick={() => this.toggleActive(true)}><FlipCameraAndroidIcon/></IconButton>
    }
    </Paper>;
  }
});

export { LookAroundController };

Using the Look-Around Component

Insert it inside 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";
import { ModeController } from "./ModeController";
// highlight-start
import { LookAroundController } from "./LookAroundController";
// highlight-end

/** the 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}/>
      <ModeController/>;
      // highlight-start
      <LookAroundController/>;
      // highlight-end
    </FiveProvider>;
  }
});

export { App };

Go back to your browser and take a look: 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 something more complex and dig deeper into what State can do.

  • Use State to record user operations.
  • Use State to replay the captured user operations.