Skip to main content

Getting Started

There are specific sequence of actions required before a user can see anything on the screen:

  1. create an instance of World3D
  2. create all the floors you intend to show the user, using World3D.createFloor()
  3. set the initial floor, using World3D.setCurrentFloor()
  4. initialize the World3D, using World3D.init()this must be called after all the floors are created because it internally calls Floor.init()s.
  5. call World3D.animateDollhouse() if you have a dollhouse for the current floor and wish to show the rotating animation before placing the camera at the center of the initial cube.
note

All the code snippets below are derived from testApp.ts in the source.

You can run it yourself by:

  • clone the source code
  • npm install
  • npm run dev
  • open localhost:3000

1. new World3D()

// be sure to publish the project as an NPM package first to import in this fashion.
import { World3D, Options } from "3d-tour-next";

const WORLD_OPTIONS: Options = { ... }

const container = document.createElement("div");
container.id = "canvas-container";
document.body.appendChild(container);

const world3D = new World3D(container, WORLD_OPTIONS);

You need to provide an HTMLElement as the first argument of World3D, which will be used as a parent node for THREE.WebGLRenderer.domElement.

2. World3D.createFloor()

import testTour from "./data/tour_two_floors.json";
import {
World3D,
Options,
_getCubeTestImages,
MeasurementUnit,
FloorUserParams,
} from "./main";

...

// looping over `sections` to create a floor for each section.
// note that you are responsible for implementing something similar to `_getCubeTestImages()`.
testTour.sections.forEach((section) => {
// cameraHeight must always be in centimeter.
let cameraHeight;
let defaultMeasurementUnit: MeasurementUnit | undefined;

if (section.cameraHeight.unit === "metric") {
cameraHeight = section.cameraHeight.value;
defaultMeasurementUnit = "cm";
} else if (section.cameraHeight.unit === "imperial") {
cameraHeight = section.cameraHeight.value * 2.54;
defaultMeasurementUnit = "ft";
}

let dollhouse: FloorUserParams["dollhouse"] = undefined;

dollhouse = {
url: section.mesh.url
? {
obj: `${section.mesh.url}/out.obj`,
mtl: `${section.mesh.url}/out.mtl`,
}
: undefined,
position: section.offset,
rotation: section.rotation
? { x: 0, y: -section.rotation, z: 0 }
: undefined,
rotationOrder: section.rotation ? "YXZ" : undefined,
};

const spots: FloorUserParams["spots"] = [];

section.scenesOrder.forEach((sceneId) => {
const scene = testTour.scenes.find((scene) => scene.id === sceneId);
if (scene) {
spots.push({
id: scene.id,
position: scene.position,
rotation: scene.rotation,
images: _getCubeTestImages(scene),
});
}
});

world3D.createFloor({
id: section.id,
spots,
dollhouse,
cameraHeight,
defaultMeasurementUnit,
});
});

FloorUserParams['spots'] type above is equivalent to FloorSpotRaw[]. And FloorSpotRaw type is defined like below:

src/objects/floor/Floor.ts
interface FloorSpotRaw {
id: string;
position: Vector3Object;
rotation?: Vector3Object;
images: CubeImages;
}

The images field of FloorSpotRaw is, in turn, CubeImages type, and this CubeImages type might be a little bit confusing if this is the first time you're encountering it.

How CubeImages type is constructed

src/objects/cube/Cube.interface.ts
export type CubeFacePosition =
| "front"
| "left"
| "right"
| "top"
| "bottom"
| "back";

export type CubeFaceOrder = [
CubeFacePosition,
CubeFacePosition,
CubeFacePosition,
CubeFacePosition,
CubeFacePosition,
CubeFacePosition
];

export type CubeSingleImage = {
url: string;
faceOrder?: CubeFaceOrder;
};

export type CubeEachFaceSingleImage = [
string,
string,
string,
string,
string,
string
];

export type CubeEachFaceMultipleImages = [
string[],
string[],
string[],
string[],
string[],
string[]
];

export type CubeResolutionImages =
| CubeSingleImage
| CubeEachFaceSingleImage
| CubeEachFaceMultipleImages;

export interface CubeImagesData<T> {
preview?: T;
resolutions: T[];
}

export type CubeImages = CubeImagesData<CubeResolutionImages>;

If it's too hard to grasp the structure by just reading through the type definitions, I recommend reading this section of 3d-tour docs as a supplement.

Make sure to understand the structure of CubeImages well so that you're able to implement your own _getCubeTestImages() that suits your tour data structure.

3. World3D.setCurrentFloor()

import testTour from "./data/tour_two_floors.json";

...

const firstSection = testTour.sections[0];
const firstSectionId = firstSection.id;

await world3D.setCurrentFloor(firstSectionId);

4. World3D.init() & World3D.animateDollhouse()

...

const initialLoadingScreen = document.getElementById("initial-loading");
const initialLoadingButton = initialLoadingScreen?.querySelector("button");
const initialLoadingText = initialLoadingScreen?.querySelector("span");

initialLoadingButton.onclick = async () => {
if (initialLoadingText && initialLoadingScreen) {
initialLoadingButton.style.display = "none";
initialLoadingText.style.display = "block";
await world3D.init();
initialLoadingScreen.style.display = "none";
await world3D.animateDollhouse();
...
}
};

...

const defaultLoadingText = initialLoadingText?.innerText;

world3D.addEventListener("INITIAL_PROGRESS", (event) => {
if (initialLoadingText && defaultLoadingText) {
initialLoadingText.innerText = `${defaultLoadingText} ${event.value}%`;
}
});

If you’re interested in reading the value of initial progress (0 - 100) when World3D.init() is callede.g. you may decide to implement a loading screen with the progress valuesubscribe to read the value like below:

World3D.addEventListener("INITIAL_PROGRESS", (event) => {
// do something
console.log(event.value);
});

The progress value is stored in event.value.

World3D.init() in turn, calls Floor.init() and Floor.init() does several things depending on whether it’s the current floor and whether it has a dollhouseif it is the current floor (World3D.currentFloor), Floor.init() will create the initial cube. If it has a dollhouse, it’ll download the images of the dollhouse during Floor.init().

So it’s important to set the current floor using World3D.setCurrentFloor() before calling World3D.init() since the first cube (panorama) the user sees will be the initial cube of the current floor.

World3D.setCurrentFloor() is not only used in the initialization process, but also used whenever you want to render a different floor.

It dispatches a FLOOR_CHANGE event at the end, so if your UI needs to react to the change of the current floor, you should subscribe to FLOOR_CHANGE event like this:

World3D.addEventListener("FLOOR_CHANGE", (event) => {
// do something
console.log(event.value));
});

The new current floor object is stored in event.value.