Skip to main content

World3D

World3D is the main public API of 3d-tour-next. It’s also conceptually the biggest when it comes to composing a 3D scene. It includes all the essential components and necessary tools to create a 3D scene, including things like THREE.Scene, THREE.PerspectiveCamera, THREE.Frustum, THREE.WebGLRenderer, and THREE.Raycaster.

constructor()

When initialized, World3D will have a uuid that distinguishes itself from other World3Ds. The uuid is necessary to keep track of shared resources within each World3D such as geometries and materials.

Sometimes, it’s possible to share things like THREE.Material and THREE.BufferGeometry across different THREE.Object3Ds, which often results in a better performance and a better resource management.

Not only for keeping track of said shared resources, uuid is also necessary to apply independent Options to each World3D instantiated, so that users can configure different World3D with different Options.

To instantiate World3D, a user needs to provide at least an HTMLElement that’s going to be used as the parent node of the HTMLCanvasElement created by THREE.WebGLRenderer, along with optional Options.

The constructor of World3D will, in turn, instantiate the necessary components to render a 3D scene as well as some other classes such as MousePointer and DomEvents. And then, at the end of the constructor, THREE.WebGLRenderer.setAnimationLoop() is called to start a render loop.

World3D.createFloor()

A user can create a Floor using World3D.createFloor(params: FloorUserParams). FloorUserParams includes things like id, spots, and cameraHeight.

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

interface FloorSpot {
id: string;
position: THREE.Vector3;
rotation: THREE.Vector3;
images: CubeImages;
neighbors?: FloorSpot[];
}

export interface FloorDollhouse {
url?: {
obj: string;
mtl: string;
};
position?: Vector3Object;
rotation?: Vector3Object;
rotationOrder?: EulerOrder;
}

export interface FloorUserParams {
id: string;
spots: FloorSpotRaw[];
dollhouse?: FloorDollhouse;
cameraHeight?: number; // don't use this directly.
defaultMeasurementUnit?: MeasurementUnit;
}

cameraHeight

One particularly important parameter is FloorUserParams['cameraHeight'].

cameraHeight is important because a lot of internal parameters depend on it; it is used to determine things like cubeSize, cameraOrbitRadius, cameraFar, floorHotspotSize, etc. cameraHeight is assumed to be different from Floor to Floor so if that’s the case, the user is responsible for providing the correct cameraHeight for each Floor.

If cameraHeight is omitted in FloorUserParams, the default camera height specified in Options['floor']['cameraheight'] will be used, which is 150 (in centimeter) by default.

If all the floors that will be created is expected to have a uniform cameraHeight, it’s sufficient to only set Options['floor']['cameraheight'] to the desirable value once and for all and not to bother with providing FloorUserParams['cameraHeight'] every time a Floor is created.

spots

Another notable parameter from FloorUserParams is spots.

A spot conceptually represents an actual spot where the 360 photos were captured. For a spot, user must provide at least the followings: id, position, images along with an optional rotation.

spots is nothing more than an array of spots.

These so-called spots will transform into either a Cube or a FloorHotspot depending on the situation.

You need to provide information of these spots when creating a Floor with World3D.createFloor() so that the Floor can create a Cube (a panorama), or FloorHotspots that can be transformed into a Cube when clicked.

World3D.setCurrentFloor()

it’s important to remember the followng initial sequence:

  1. new World3D()
  2. World3D.createFloor()
  3. World3D.setCurrentFloor()
  4. World3D.init()
  5. World3D.animateDollhouse()

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.

World3D.mode

World3D.mode property is equivalent to Floor.mode of the World3D.currentFloor.

There are currently three different modes available:

  • navigation
  • measurement:select
  • measurement:measure

When a user is in navigation mode, all the interactions with FloorHotspot are enable, and thus they can move from one cube to another.

On the other hand, in both measurement:select and measurement:measure modes, all the floor hotspots are removed from the floor, and so the user cannot travel to another cube by clicking floor hotspots.

Current Measurement implementation only makes sense when movement is restricted to a particular cube – i.e., no traveling. It’s not a very accurate system, only relying on the cameraHeight parameter as a guide.

This means you should prevent the user from traveling to another cube via any means other than clicking a floor hotspot, e.g., World3D.goToCube(), and prevent them from entering the dollhouse view while they are in measurement:select or measurement:measure mode.

If your UI needs to be reactive to the mode change, subscribe to MODE_CHANGE event like this:

World3D.addEventListener("MODE_CHANGE", (event) => console.log(event.value));

The current mode value can be retrieved from event.value.

World3D.view

There are currently two different views: cube and dollhouse.

In cube view, OrbitControls.target is set to the center of the cube and THREE.PerspectiveCamera orbits around that target, allowing users to experience 360 degree panoramic perspective.

In contrast, dollhouse view sets the position of the camera higher than the cube and the dollhouse so that the users can have a good vantage point of looking down the dollhouse.

When implementing UI components related to setting the view, make sure to use World3D.setView() rather than setting the property value directly; World3D.view = "dollhouse" or World3D.view = "cube". Setting a value this way is internal-use only.

Also, if your UI component needs to be reactive to the change of World3D.view, add an event listener like so:

World3D.addEventListener("VIEW_CHANGE", (event) => console.log(event.value));

The changed view can be retrieved from event.value.

World3D.magnifyingGlassEnabled

World3D.magnifyingGlassEnabled property is the same as Floor.measurement.magnifyingGlassEnabled of the World3D.currentFloor.

When set to true, MagnifyingGlass is enabled; however the glass will only appear when the World3D.mode is measurement:measure.

It won’t show in the measurement:select mode or navigation mode.

World3D.snappingEnabled

Setting this property is the same as setting Floor.measurement.snappingEnabled of World3D.currentFloor.

If set to true, automatic snapping feature is enabled in both measurement:select and measurement:measure mode.

World3D.measurementUnit

Same as setting Floor.measurement.unit of World3D.currentFloor.

It can be one of cm, m, ft, in.

If you don’t like the default text generated for displaying a particular unit, you can override it by assigning your own generateDisplayText function to World3D.generateMeasurementDisplayText like the example below.

world3D.generateMeasurementDisplayText = (value, unit) => {
if (unit === "ft") {
const feet = Math.floor(value);
const inches = Math.floor((feet !== 0 ? value % feet : value) * 12);
return `${feet}' ${inches}"`;
}
// you can return undefined for the units you don't want to modify
return;
};

World3D.removeSelectedObject()

Same as calling Floor.measurement.removeSelectedObject() of World3D.currentFloor.

Removes currently selected MeasuringObject. After removing the object, a MEASUREMENT_OBJECT_REMOVED event fires; if you want to get notified when the event fires, add an event listener like this:

World3D.addEventListener("MEASUREMENT_OBJECT_REMOVED", () => {
// do something
});

Also, whenever a user selects a MeasuringObject in measurement:select mode, a MEASUREMENT_OBJECT_SELECTED event fires; likewise, if you want to get notified when the event fires, add an event listener like this:

World3D.addEventListener("MEASUREMENT_OBJECT_SELECTED", (event) =>
console.log(event.value)
);

Newly selected MeasuringObject can be accessed from event.value.

Another event called MEASUREMENT_OBJECT_UNSELECTED will fire whenever currently selected MeasuringObject gets unselected.

This happens when the user drags a dot of MeasuringObject in measurement:select mode, or if there’s a selected object when the user change mode to measurement:measure or navigation.

Again, if you want to get notified when the event fires, add an event listener like this:

World3D.addEventListener("MEASUREMENT_OBJECT_UNSELECTED", (event) =>
console.log(event.value)
);

Unselected MeasuringObject can be accessed from event.value.

World3D.animateDollhouse()

Same as calling Floor.animateDollhouse() of World3D.currentFloor.

Calling it only has an effect when World3D.currentFloor has Floor.currentCube and Floor.dollhouse. It doesn’t do anything otherwise.

At the end of the dollhouse animation, the camera will automatically focus on the position of Floor.currentCube and descend down to said cube.

For the entire duration of the animation and the transition, OrbitControls is locked so a user cannot move the camera around during this moment.

At the start of this animation, an event called IS_TRANSITIONING is fired with event.value being true.

At the end, the event is fired again with event.value being false.

If you want to subscribe to this event, add a handler like this:

World3D.addEventListener("IS_TRANSITIONING", (event) =>
console.log(event.value)
);

In fact, a pair of IS_TRANSITIONING events fire whenever there’s one of the following transition animations:

  • dollhouse animation
  • moving to another cube
  • switching view (cube <=> dollhouse)

World3D.goToCube()

Same as calling Floor.goToCube() of World3D.currentFloor.

A primary way for a user to go to a different cube is by clicking a floor hotspot on the screen. However, you can call World3D.goToCube() manually to send the user to a different cube.

At the end of the execution of World3D.goToCube(), an event called SPOT_CHANGE is fired. If your UI components need to be reactive to the change of the current cube, add an event listener like this:

World3D.addEventListener("SPOT_CHANGE", (event) => console.log(event.value));

event.value will contain the id of the new current cube.

The images of a cube are being fetched during Cube.init(), which is called before the transition to the next cube happens.

If the image fetching takes too long, an event called TO_NEXT_CUBE:NEXT_CUBE_DELAYED will be fired; the time to wait before the event triggers is configurable through Options.transition.cubeToCube.waitTriggerMS.

note

Currently TO_NEXT_CUBE:NEXT_CUBE_DELAYED is only fired when moving from one cube to another. It doesn’t get fired when moving from dollhouse view to cube view.

When the images are finally downloaded and thus the next cube is initialized successfully, another event called TO_NEXT_CUBE:NEXT_CUBE_INITIALIZED will be fired.

Both of these events can be subscribed:

World3D.addEventListener("TO_NEXT_CUBE:NEXT_CUBE_DELAYED", () =>
console.log("Please wait");

World3D.addEventListener("TO_NEXT_CUBE:NEXT_CUBE_INITIALIZED", () =>
console.log("next cube initialized");