Skip to main content
Documentation

Points in 3D Space

Convert between screen and 3D coordinates with Five SDK in vanilla JavaScript or TypeScript.

Recap of the previous chapter: Recording State

- You learned how to record and restore operations through State.

- You became comfortable using the methods and events related to State.

In this chapter you will learn

  • Understand the event system of the Five SDK.
  • Obtain the 3D position of a point.

Getting Ready

Let's create a new directory (src/4.points-in-3d) along with the corresponding html file and js or ts file. Carrying over the State code from the previous chapter would be too cumbersome, so we'll build on the content from the Displaying 3D Space chapter instead.

<!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>Points in 3D</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 switch -->
  <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 Walkthrough</button>
        <button class="btn btn-primary js-Floorplan">Space Overview</button>
      </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 switch ===
  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 {};

Start the dev server with npm run dev, then navigate to the current page at "http://localhost:3000/src/4.points-in-3d/index.html".

Tip

Please check your console. The port number may change depending on your configuration and which ports are currently in use, so always rely on whatever the console prints. If you use a different build tool, start the server according to that tool's requirements.

The Event System

When you click on the screen, the default behavior of the Five SDK is to select the most appropriate observer (observation point) near the clicked location and move there. This matches what most users intend to do, much like how a browser handles an anchor (<a>) tag by navigating to its link. This default behavior is the Five SDK's built-in tapGesture event.

Built-in Events

The Five SDK provides the following built-in events:

  • tapGesture: A left-mouse click or a finger tap. The default behavior is to move to a point.
  • panGesture: Holding down the mouse button or dragging a finger across the screen. Rotates the camera (in Topview, this pans the camera instead).
  • pinchGesture: A two-finger pinch gesture. The default behavior is to change the camera's field of view.
  • mouseWheel: The mouse wheel. The default behavior is to change the camera's field of view.
  • gesture: Any of the events above.

Preventing the Default Behavior

Just like a browser's handling of an anchor (<a>) tag, every event lets you prevent its default action. All you need to do is listen for the event whose name starts with wants and return false from the callback. For example, to prevent the default point-movement behavior of tapGesture, you can do the following.

five.on("wantsTapGesture", () => {
  // Prevent tapGesture from firing
  return false;
});

For the detailed API of each event, see the reference documentation:

Getting Coordinates from tapGesture

We'll build a simple feature that marks the 3D position clicked on the canvas.

To avoid conflicting with the point-movement feature, we'll use a Switch button to control whether marking mode is enabled.

Adding Dependencies at the Top

This chapter requires a brief introduction to three.js. three.js is a 3D graphics library, and the Five SDK uses its math library and renderer. There are two three.js concepts involved in this chapter. You don't need to fully understand three.js; the explanations below are enough to follow along.

  • `THREE.Vector3`: You can simply think of it as a { x: number, y: number, z: number } struct, with some extra math methods added (we won't use any of the math methods this time, we just record x, y, z).
  • `THREE.Raycaster`: The raycasting class. You can simply understand it as: a point on the screen corresponds to a ray in 3D space.

Diagram of a screen ray intersecting objects inside a 3D volume

A ray has many uses. For example, by testing the intersection between a ray and a model, you can determine whether an object has been selected.

Building the UI

<!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>Points in 3D</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 switch -->
  <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 Walkthrough</button>
        <button class="btn btn-primary js-Floorplan">Space Overview</button>
      </div>
    </div>
  </nav>
  <!-- highlight-start -->
  <!-- Mark 3D coordinates -->
  <div class="card position-fixed m-2 top-0 start-0">
    <div class="form-check form-switch m-2">
      <input class="form-check-input js-mark-switch" type="checkbox">
      <label class="form-check-label" for="flexSwitchCheckDefault">Mark</label>
    </div>
    <div class="js-marks"></div>
  </div>
  <!-- highlight-end -->
  <script type="module" src="./index"></script>
</body>
</html>

Writing the MarkController Component

  1. switcher is the toggle that determines whether marking mode is enabled.
  2. When marking mode is on, capture the click action through wantsTapGesture. The callback's first argument is the raycaster, the collision ray.
  3. Calling model.intersectRaycaster(raycaster) returns the focus information intersect, and intersect.point is the coordinate of the intersection point.

Append the following after the Mode switch code:

{ // === Mark 3D coordinates ===
  const list = document.querySelector(".js-marks");
  const switcher = document.querySelector(".js-mark-switch");

  five.on("wantsTapGesture", (raycaster) => {
    if (switcher.checked) {
      const [intersect] = five.model.intersectRaycaster(raycaster);
      if (intersect) {
        const { x, y, z } = intersect.point;
        const p = document.createElement("p");
        p.className = "badge bg-primary d-block m-2";
        list.appendChild(p);
        const span = document.createElement("span");
        span.innerHTML = `x=${x.toFixed(2)} y=${y.toFixed(2)} z=${z.toFixed(2)}`;
        p.appendChild(span);
        const close = document.createElement("i");
        close.className = "bi bi-x-circle ms-2";
        close.addEventListener("click", () => list.removeChild(p), false);
        p.appendChild(close);
      }
      return false;
    }
  });
}

Go back to your browser, and you'll see a toggle switch appear in the top-left corner of the page. Turn it on, click somewhere in the canvas, and the coordinates of the clicked position will be printed out.

Excellent, you've understood and obtained 3D coordinates in no time 🥳.

What You'll Learn in the Next Chapter

In the next chapter we'll implement a spatial tag feature, so don't miss it.