Getting Started
There are specific sequence of actions required before a user can see anything on the screen:
- create an instance of
World3D
- create all the floors you intend to show the user, using
World3D.createFloor()
- set the initial floor, using
World3D.setCurrentFloor()
- initialize the
World3D
, usingWorld3D.init()
—this must be called after all the floors are created because it internally callsFloor.init()
s. - 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.
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:
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
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 called—e.g. you may decide to implement a loading
screen with the progress value—subscribe 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
dollhouse—if 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
.