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.Object3D
s, 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
FloorHotspot
s 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:
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
.
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");