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.
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:
new World3D()World3D.createFloor()World3D.setCurrentFloor()World3D.init()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
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.
World3D.mode
World3D.mode property is equivalent to Floor.mode of the
World3D.currentFloor.
There are currently three different modes available:
navigationmeasurement:selectmeasurement: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.
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");