docs: custom camera controls documentation guide

This commit is contained in:
Oscar Lorentzon 2021-05-19 22:34:57 +02:00
parent 397e4a3204
commit 505da04bbc
11 changed files with 451 additions and 93 deletions

View File

@ -119,9 +119,7 @@ function render(props) {
const cubeRenderer = new animation.RotatingCubeRenderer(cube);
viewer.addCustomRenderer(cubeRenderer);
viewer
.moveTo('H_g2NFQvEXdGGyTjY27FMA')
.catch((error) => console.error(error));
viewer.moveTo('H_g2NFQvEXdGGyTjY27FMA').catch(mapillaryErrorHandler);
}
function dispose() {
@ -144,6 +142,6 @@ function render(props) {
:::info
You can view the complete example code in the [Animation example](/examples/animation).
You can view the complete code in the [Animation](/examples/animation) example.
:::

View File

@ -3,10 +3,309 @@ id: fly-controls
title: Three.js Fly Controls
---
MapillaryJS comes with two different camera controls, `Street` and `Earth`. `Street` mode is for navigating at the street level while `Earth` mode works more like a map. When using MapillaryJS, you may want to interact with the visualized content in another way. You can do that by extending MapillaryJS with custom camera controls. Custom camera controls allow you to freely move the viewer's camera and define the camera projection used.
:::info You will learn
- How to use the Three.js fly controls to define the camera behavior
- How to implement the `ICustomCameraControls` interface
- How to attach your camera controls to the `Viewer`
- How to activate your camera controls
:::
## Creating the Camera Controls
In the data provider and custom render examples we worked with both [geodetic](/docs/theory/coordinates#geodetic-coordinates) and [local topocentric](/docs/theory/coordinates#local-topocentric-coordinates) coordinates and converted between them. For camera controls, we mainly operate in the local topocentric space, but will also do some coordiante conversions.
To create our fly controls, we will implement the [ICustomCameraControls](/api/interfaces/viewer.icustomcameracontrols) interface. Let us go through the interface implementation member by member.
### `constructor`
We can use the constructor to assign some readonly visualization options for our controls.
```js
class FlyCameraControls {
constructor() {
this.fov = options.fov;
this.movementSpeed = options.movementSpeed;
this.rollSpeed = options.rollSpeed;
}
// ...
}
```
### `onAttach`
ICustomCameraControls.[onAttach](/api/interfaces/viewer.icustomcameracontrols#onattach) is called when the controls have been attached to the `Viewer` with the Viewer.[attachCustomCameraControls](/api/classes/viewer.viewer-1#attachcustomcameracontrols) method. `onAttach` provides two important callback parameters, `viewMatrixCallback` and `projectionMatrixCallback`. You should invoke these callbacks to modify the pose and projection of the `Viewer`'s camera whenever the controls causes an update.
Custom camera controls trigger rerendering automatically when the camera pose or projection is changed through the `viewMatrixCallback` or `projectionMatrixCallback`. Invoking the callbacks has no effect if custom camera controls has not been [activated](/docs/extension/fly-controls#activating-and-deactivating).
In our controls `onAttach` implementation, we assign the callback parameters to instance properties to be able to invoke them later.
:::tip
If you want to learn more about view, and projection matrices, take a look at the [WebGL model view projection article](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_model_view_projection) on MDN.
:::
```js
class FlyCameraControls {
// ...
onAttach(viewer, viewMatrixCallback, projectionMatrixCallback) {
this.viewMatrixCallback = viewMatrixCallback;
this.projectionMatrixCallback = projectionMatrixCallback;
}
}
```
### `onActivate`
When our controls are activated, they take responsibility for moving the `Viewer`'s camera and defining it's projection. The ICustomCameraControls.[onActivate](/api/interfaces/viewer.icustomcameracontrols#onactivate) method gives the camera controls a chance to initialize resources, perform any transitions, and determine initial state.
`onActivate` provides four parameters that we can use for initialization. Let's go through the matrix parameters in detail.
#### `viewMatrix`
The `viewMatrix` array contains the `Viewer` view matrix entries for the moment our controls were activated. We can use the entries of the `viewMatrix`, and particularly the `viewMatrix` inverse, to determine initial properties such as eye, forward, and up vectors. We can also use them to transition smoothly so another position.
#### `projectionMatrix`
Simpilar to the `viewMatrix`, the `projectionMatrix` array contains the `Viewer` projection matrix entries for the moment our controls were activated. We can use the projection matrix entries to determine the initial projection if we want a smooth transition.
#### Implementation
Our `onActivate` implementation consist of the following steps.
1. We save the geodetic reference for the future in case it changes.
2. We use the Viewer.[getContainer](/api/classes/viewer.viewer-1#getcontainer) method to get the container for determining the viewer aspect and create a [PerspectiveCamera](https://threejs.org/docs/index.html?q=perspe#api/en/cameras/PerspectiveCamera). We want up to correspond to the Z-axis initially so we rotate the camera 90 degrees.
3. We create the fly controls, providing the camera and the container for attaching event handlers (which happens in the Three.js FlyControls constructor).
4. We use the `viewMatrix` inverse to set the initial camera position to the current `Viewer` position.
5. We create an event listener invoking the `viewMatrixCallback` whenever the controls emits a change.
6. Finally, we invoke the callbacks to update the `Viewer` camera with our initialized view and projection matrices. Note that we make sure to update both matrices properly before invoking the callbacks.
```js
class FlyCameraControls {
// ...
onActivate(viewer, viewMatrix, projectionMatrix, reference) {
this.reference = reference;
const {fov, movementSpeed, rollSpeed} = this;
// Create camera
const container = viewer.getContainer();
const aspect = calcAspect(container);
const camera = new PerspectiveCamera(fov, aspect, 0.1, 10000);
camera.rotateX(Math.PI / 2);
// Create controls
this.controls = new FlyControls(camera, container);
this.controls.movementSpeed = movementSpeed;
this.controls.rollSpeed = rollSpeed;
// Set camera position
const viewMatrixInverse = new Matrix4().fromArray(viewMatrix).invert();
const me = viewMatrixInverse.elements;
const translation = [me[12], me[13], me[14]];
this.controls.object.position.fromArray(translation);
// Listen to control changes
this.onControlsChange = () => {
this.controls.object.updateMatrixWorld(true);
this.viewMatrixCallback(
this.controls.object.matrixWorldInverse.toArray(),
);
};
this.controls.addEventListener('change', this.onControlsChange);
// Update pose and projection
this.clock = new Clock();
const delta = this.clock.getDelta();
this.controls.update(delta);
camera.updateProjectionMatrix();
this.projectionMatrixCallback(camera.projectionMatrix.toArray());
}
}
```
### `onAnimationFrame`
Custom camera controls can choose to make updates on each animation frame or based on input events only. Updating on each animation frame is more resource intensive. We will make a call to update the fly controls on each animation frame. The controls will notify our `change` event listener whenever its pose has changed.
```js
class FlyCameraControls {
// ...
onAnimationFrame(_viewer, _frameId) {
const delta = this.clock.getDelta();
this.controls.update(delta);
}
}
```
### `onReference`
Like [custom renderers](/docs/extension/webgl-custom-renderer#onreference), we need to handle updates to the MapillaryJS geodetic reference. We do that in the `onReference` method by first calculating the camera's geodetic coordinates using the previous reference. Then we calculate the new local topocentric east, north, up position using the current reference. We make sure that the camera matrices are updated and the current reference is saved.
```js
class FlyCameraControls {
// ...
onReference(viewer, reference) {
const oldReference = this.reference;
const enu = this.controls.object.position;
const [lng, lat, alt] = enuToGeodetic(
enu.x,
enu.y,
enu.z,
oldReference.lng,
oldReference.lat,
oldReference.alt,
);
const [e, n, u] = geodeticToEnu(
lng,
lat,
alt,
reference.lng,
reference.lat,
reference.alt,
);
this.controls.object.position.set(e, n, u);
this.controls.object.updateMatrixWorld(true);
this.reference = reference;
}
}
```
### `onResize`
Whenever the `Viewer` detects that it is resized, either through [browser resize tracking](/api/interfaces/viewer.vieweroptions#trackresize) or through you informing it with the Viewer.(resize)[/api/classes/viewer.viewer-1#resize] method, it will notify our camera controls. This gives us a chance to recalculate the aspect and update the projection matrix.
```js
class FlyCameraControls {
// ...
onResize(_viewer) {
const camera = this.controls.object;
camera.aspect = calcAspect(this.controls.domElement);
camera.updateProjectionMatrix();
this.projectionMatrixCallback(camera.projectionMatrix.toArray());
}
}
```
### `onDeactivate`
The `onDeactivate` method is called when other camera controls are activated with the Viewer.[setCameraControls](/api/classes/viewer.viewer-1#setcameracontrols) method. We use the `onDeactivate` method to dispose the fly controls and remove our event listener. Note that our controls are still attached to the `Viewer` and may be activated again in the future.
```js
class FlyCameraControls {
// ...
onDeactivate(_viewer) {
if (this.controls) {
this.controls.removeEventListener('change', this.onControlsChange);
this.controls.dispose();
this.controls = null;
}
}
}
```
### `onDetach`
The `onDetach` method is called when the camera controls have been detached from the viewer by calling Viewer.[detachCameraControls](/api/classes/viewer.viewer-1#detachcustomcameracontrols). This gives use a chance to remove the matrix callbacks.
```js
class FlyCameraControls {
// ...
onDetach(_viewer) {
this.projectionMatrixCallback = null;
this.viewMatrixCallback = null;
}
}
```
## Attaching and Detaching
Only a single custom camera control instance can be attached to the `Viewer` at any given time. A controls instance is attached with the Viewer.[attachCustomCameraControls](/api/classes/viewer.viewer-1#attachcustomcameracontrols) method. Although only a single control instance can be attached at any given time, multiple different controls can be used during the `Viewer` lifespan. By detaching a controls instance with the Viewer.[detachCustomCameraControls](/api/classes/viewer.viewer-1#detachcustomcameracontrols) method, another controls instance can be attached.
### Activating and Deactivating
You can activate an attached controls instance in two ways.
1. Use the the ViewerOptions.[cameraControls](/api/interfaces/viewer.vieweroptions#cameracontrols) option to specify the CameraControls.[Custom](/api/enums/viewer.cameracontrols#custom) mode upon initialization.
2. Set the `Custom` mode with the Viewer.[setCameraControls](/api/classes/viewer.viewer-1#setcameracontrols) method at any time during the `Viewer` lifespan.
Deactivating the custom controls is done by calling the Viewer.[setCameraControls](/api/classes/viewer.viewer-1#setcameracontrols) method with any oother [CameraControls] mode.
## Putting the Controls to Use
Now that we have implemented our custom cube renderer, we just need to add it to the `Viewer` through the Viewer.[addCustomRenderer](/api/classes/viewer.viewer-1#addcustomrenderer) method.
:::tip
Press the left button to fly forward and the right button to fly backward or use the [fly key commands](https://github.com/mrdoob/three.js/blob/r125/examples/jsm/controls/FlyControls.js#L57-L76) for specific motion.
Try to intialize the `Viewer` with `Street` or `Earth` controls and change to the `Custom` controls by calling Viewer.`setCameraControls`. Try changing the fly options to see how they affect the viewport and camera behavior.
:::
```jsx live
function render(props) {
let viewer;
function init(opts) {
const {appToken, container} = opts;
const cameraControls = CameraControls.Custom;
const options = {
apiClient: appToken,
cameraControls,
component: {
cover: false,
direction: false,
spatial: {cameraSize: 0.8, cellsVisible: true, pointSize: 0.2},
},
container,
};
viewer = new Viewer(options);
const flyOptions = {
fov: 90,
movementSpeed: 30,
rollSpeed: 0.25,
};
const flyControls = new flycontrols.FlyCameraControls(flyOptions);
viewer.attachCustomCameraControls(flyControls);
viewer.moveTo('lKiLKEpwHq6zs1kTywTbY6').catch(mapillaryErrorHandler);
}
function dispose() {
if (viewer) {
viewer.remove();
}
}
return (
<ViewerComponent init={init} dispose={dispose} style={{height: '400px'}} />
);
}
```
## Recap
- To add custom camera controls to MapillaryJS, implement the `ICustomCameraControls` interface
- Setup your controls in the `onActivate` method
- Invoke the `viewMatrix` and `projectionMatrix` callbacks whenever your camera pose or projection is updated
- Make use of any of the Three.js controls if they cover your use case
- Attach your custom camera controls to the `Viewer` and activate `Custom` mode to put them to use
:::info
You can view the complete example code in the [Three.js Fly Controls example](/examples/fly-controls).
You can view the complete code in the [Fly Controls](/examples/fly-controls) example.
For another example of custom camera controls leveraging Three.js, you can take a look at the [OpenSfM orbit camera controls](https://github.com/mapillary/OpenSfM/blob/398fe61fd970c7fa80b10b56606643408fa3dd7e/viewer/src/ui/OrbitCameraControls.js).
:::

View File

@ -90,7 +90,7 @@ function render(props) {
container,
};
viewer = new Viewer(options);
viewer.moveTo('image|fisheye|0').catch((error) => console.error(error));
viewer.moveTo('image|fisheye|0').catch(mapillaryErrorHandler);
}
function dispose() {

View File

@ -20,6 +20,7 @@ MapillaryJS depends on images being provided for each camera capture in the data
```jsx live
function Image(props) {
const [src, setSrc] = useState('');
const urlRef = useRef('');
const aspect = 2;
const tileSize = 10;
@ -30,20 +31,34 @@ function Image(props) {
const width = scale * tileSize * tilesX;
const height = scale * tileSize * tilesY;
useEffect(async () => {
const buffer = await procedural.generateImageBuffer({
tileSize,
tilesX: aspect * tilesY,
tilesY,
});
useEffect(() => {
let didCancel = false;
const blob = new Blob([buffer]);
const objectURL = window.URL.createObjectURL(blob);
async function createURL() {
const buffer = await procedural.generateImageBuffer({
tileSize,
tilesX: aspect * tilesY,
tilesY,
});
setSrc(objectURL);
if (didCancel) {
return;
}
const blob = new Blob([buffer]);
const objectURL = window.URL.createObjectURL(blob);
setSrc(objectURL);
urlRef.current = objectURL;
}
createURL();
return function cleanup() {
window.URL.revokeObjectURL(blob);
didCancel = true;
const objectURL = urlRef.current;
if (objectURL) {
window.URL.revokeObjectURL(objectURL);
}
};
}, []);
@ -143,6 +158,8 @@ When debugging your data provider, it is a good idea to call the methods directl
:::
### `constructor`
We populate the generated data in the constructor. Here we also generate geometry cells mapping a geo cell to an array of images.
```js
@ -157,6 +174,8 @@ class ProceduralDataProvider extends DataProviderBase {
}
```
### `getCluster`
We do not generate any point clouds for this example so we return empty clusters.
```js
@ -170,6 +189,8 @@ class ProceduralDataProvider extends DataProviderBase {
For this example, we return the complete image contract because we already have the generated data. For bandwidth, latency, and performance reasons in production, it is recommended to only request the [CoreImageEnt](/api/interfaces/api.coreimageent) properties from the service.
### `getCoreImages`
```js
class ProceduralDataProvider extends DataProviderBase {
// ...
@ -182,6 +203,8 @@ class ProceduralDataProvider extends DataProviderBase {
We generate our mango image buffer on the fly.
### `getImageBuffer`
```js
class ProceduralDataProvider extends DataProviderBase {
// ...
@ -191,6 +214,8 @@ class ProceduralDataProvider extends DataProviderBase {
}
```
### `getImages`
If an image has been generated, we return it as a node contract.
```js
@ -206,6 +231,8 @@ class ProceduralDataProvider extends DataProviderBase {
}
```
### `getImageTiles`
We will deactivate the image tiling functionality with a viewer option so we do not need to implement this method (we can omit this code completely).
```js
@ -217,6 +244,8 @@ class ProceduralDataProvider extends DataProviderBase {
}
```
### `getMesh`
We do not generate any triangles for this example so we return empty meshes.
```js
@ -228,6 +257,8 @@ class ProceduralDataProvider extends DataProviderBase {
}
```
### `getSequence`
If a sequence has been generated, we return it.
```js
@ -245,6 +276,8 @@ class ProceduralDataProvider extends DataProviderBase {
}
```
### `getSpatialImages`
We reuse the previously implemented `getImages` method.
```js
@ -285,7 +318,7 @@ function render(props) {
imageTiling: false,
};
viewer = new Viewer(options);
viewer.moveTo('image|fisheye|0').catch((error) => console.error(error));
viewer.moveTo('image|fisheye|0').catch(mapillaryErrorHandler);
}
function dispose() {
@ -310,7 +343,7 @@ Now you know how to provide MapillaryJS with your own data by:
:::info
You can view the complete example code in the [Procedural Data Provider example](/examples/procedural-data-provider).
You can view the complete code in the [Procedural Data Provider](/examples/procedural-data-provider) example.
If you want to build a data provider fetching files from a server, you can use the [OpenSfM data provider](https://github.com/mapillary/OpenSfM/blob/6585f0561e7c9d4907eadc7bc2fb9dbdad8a2945/viewer/src/provider/OpensfmDataProvider.js) as inspiration.

View File

@ -207,7 +207,7 @@ Now that we have implemented our custom cube renderer, we just need to add it to
:::tip
Try changing box geometry size and cube face colors.
Try changing the cube's geometry and face colors.
:::
@ -260,9 +260,7 @@ function render(props) {
const cubeRenderer = new threerenderer.ThreeCubeRenderer(cube);
viewer.addCustomRenderer(cubeRenderer);
viewer
.moveTo('H_g2NFQvEXdGGyTjY27FMA')
.catch((error) => console.error(error));
viewer.moveTo('H_g2NFQvEXdGGyTjY27FMA').catch(mapillaryErrorHandler);
}
function dispose() {
@ -287,7 +285,7 @@ function render(props) {
:::info
You can view the complete example code in the [Three.js Renderer example](/examples/three-renderer).
You can view the complete code in the [Three.js Renderer](/examples/three-renderer) example.
For more examples of custom renderers using Three.js, you can take a look at the [OpenSfM axes and earth grid renderers](https://github.com/mapillary/OpenSfM/tree/398fe61fd970c7fa80b10b56606643408fa3dd7e/viewer/src/renderer).

View File

@ -3,7 +3,7 @@ id: webgl-custom-renderer
title: WebGL Renderer
---
MapillaryJS comes with a core set of visualization features. If you want augment the MapillaryJS experience you can extend it by rendering your own 3D objects. There are mulitple ways to do this, in this guide we will WebGL.
MapillaryJS comes with a core set of visualization features. If you want augment the MapillaryJS experience you can extend it by rendering your own 3D objects. There are mulitple ways to do this, in this guide we will use the WebGL API.
:::info You will learn
@ -292,7 +292,7 @@ Now that we have implemented our custom cube renderer, we just need to add it to
:::tip
Try changing cube geo position, for example the altitude, to see how it affects where it is placed in relation to the imagery.
Try changing the cube's geo position, for example the altitude, to see how it affects where it is placed in relation to the imagery.
:::
@ -320,9 +320,7 @@ function render(props) {
const cubeRenderer = new webglrenderer.WebGLCubeRenderer(cube);
viewer.addCustomRenderer(cubeRenderer);
viewer
.moveTo('H_g2NFQvEXdGGyTjY27FMA')
.catch((error) => console.error(error));
viewer.moveTo('H_g2NFQvEXdGGyTjY27FMA').catch(mapillaryErrorHandler);
}
function dispose() {
@ -346,6 +344,6 @@ function render(props) {
:::info
You can view the complete example code in the [WebGL Renderer example](/examples/webgl-renderer).
You can view the complete code in the [WebGL Renderer](/examples/webgl-renderer) example.
:::

View File

@ -19,7 +19,7 @@ module.exports = {
},
{
type: 'category',
label: 'Data Provider API',
label: 'Data Provider',
collapsed: false,
items: [
{
@ -36,7 +36,7 @@ module.exports = {
},
{
type: 'category',
label: 'Custom Render API',
label: 'Custom Render',
collapsed: false,
items: [
{
@ -54,7 +54,7 @@ module.exports = {
},
{
type: 'category',
label: 'Camera Controls API',
label: 'Camera Controls',
collapsed: false,
items: [
{

View File

@ -20,8 +20,6 @@ import {
} from '../../../mods/three/build/three.module';
import {FlyControls} from '../../../mods/three/examples/jsm/controls/FlyControls';
const FOV = 90;
function calcAspect(element) {
const width = element.offsetWidth;
const height = element.offsetHeight;
@ -29,61 +27,73 @@ function calcAspect(element) {
}
export class FlyCameraControls {
constructor() {
this._controls = null;
this._reference = null;
this._projectionMatrixCallback = null;
this._viewMatrixCallback = null;
this._clock = null;
constructor(options) {
this.fov = options.fov;
this.movementSpeed = options.movementSpeed;
this.rollSpeed = options.rollSpeed;
}
onActivate(viewer, viewMatrix, projectionMatrix, reference) {
this._reference = reference;
this.reference = reference;
const {fov, movementSpeed, rollSpeed} = this;
const container = viewer.getContainer();
const aspect = calcAspect(container);
const camera = new PerspectiveCamera(FOV, aspect, 0.1, 10000);
const camera = new PerspectiveCamera(fov, aspect, 0.1, 10000);
camera.rotateX(Math.PI / 2);
const controls = new FlyControls(camera, container);
controls.movementSpeed = 30;
controls.rollSpeed = 0.25;
this._controls = controls;
this.controls = new FlyControls(camera, container);
this.controls.movementSpeed = movementSpeed;
this.controls.rollSpeed = rollSpeed;
const viewMatrixInverse = new Matrix4().fromArray(viewMatrix).invert();
const me = viewMatrixInverse.elements;
const translation = [me[12], me[13], me[14]];
controls.object.position.fromArray(translation);
this.controls.object.position.fromArray(translation);
this._clock = new Clock();
this.onControlsChange = () => {
this.controls.object.updateMatrixWorld(true);
this.viewMatrixCallback(
this.controls.object.matrixWorldInverse.toArray(),
);
};
this.controls.addEventListener('change', this.onControlsChange);
this._updateViewMatrix();
this._updateProjectionMatrix();
this.clock = new Clock();
const delta = this.clock.getDelta();
this.controls.update(delta);
this.updateProjectionMatrix();
}
onAnimationFrame(_viewer, _frameId) {
this._updateViewMatrix();
const delta = this.clock.getDelta();
this.controls.update(delta);
}
onAttach(viewer, viewMatrixCallback, projectionMatrixCallback) {
this._viewMatrixCallback = viewMatrixCallback;
this._projectionMatrixCallback = projectionMatrixCallback;
this.viewMatrixCallback = viewMatrixCallback;
this.projectionMatrixCallback = projectionMatrixCallback;
}
onDeactivate(_viewer) {
this._controls.dispose();
this._controls = null;
if (this.controls) {
this.controls.removeEventListener('change', this.onControlsChange);
this.controls.dispose();
this.controls = null;
}
}
onDetach(_viewer) {
this._projectionMatrixCallback = null;
this._viewMatrixCallback = null;
this.projectionMatrixCallback = null;
this.viewMatrixCallback = null;
}
onReference(viewer, reference) {
const oldReference = this._reference;
const oldReference = this.reference;
const enu = this._controls.object.position;
const enu = this.controls.object.position;
const [lng, lat, alt] = enuToGeodetic(
enu.x,
enu.y,
@ -101,30 +111,21 @@ export class FlyCameraControls {
reference.alt,
);
this._controls.object.position.set(e, n, u);
this._controls.object.updateMatrixWorld(true);
this.controls.object.position.set(e, n, u);
this.controls.object.updateMatrixWorld(true);
this._reference = reference;
this.reference = reference;
}
onResize(_viewer) {
this._updateProjectionMatrix();
this.updateProjectionMatrix();
}
_updateProjectionMatrix() {
const camera = this._controls.object;
camera.aspect = calcAspect(this._controls.domElement);
updateProjectionMatrix() {
const camera = this.controls.object;
camera.aspect = calcAspect(this.controls.domElement);
camera.updateProjectionMatrix();
this._projectionMatrixCallback(camera.projectionMatrix.toArray());
}
_updateViewMatrix() {
const delta = this._clock.getDelta();
this._controls.update(delta);
this._controls.object.updateMatrixWorld(true);
this._viewMatrixCallback(
this._controls.object.matrixWorldInverse.toArray(),
);
this.projectionMatrixCallback(camera.projectionMatrix.toArray());
}
}
@ -135,16 +136,24 @@ export function init(opts) {
const options = {
apiClient: appToken,
cameraControls: CameraControls.Custom,
component: {cover: false, spatial: {cellsVisible: true}},
component: {
cover: false,
direction: false,
spatial: {cameraSize: 0.8, cellsVisible: true, pointSize: 0.2},
},
container,
};
viewer = new Viewer(options);
viewer.attachCustomCameraControls(new FlyCameraControls());
viewer
.moveTo('ie9ktAVyhibDCD_V0m6apQ')
.catch((error) => console.error(error));
const flyOptions = {
fov: 90,
movementSpeed: 30,
rollSpeed: 0.25,
};
const flyControls = new FlyCameraControls(flyOptions);
viewer.attachCustomCameraControls(flyControls);
viewer.moveTo('lKiLKEpwHq6zs1kTywTbY6').catch((error) => console.warn(error));
}
export function dispose() {

18
doc/src/js/utils/error.js Normal file
View File

@ -0,0 +1,18 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {CancelMapillaryError} from '../../../mods/mapillary-js/dist/mapillary.module';
export function mapillaryErrorHandler(error) {
if (error instanceof CancelMapillaryError) {
console.warn(`Navigation request cancelled: ${error.message}`);
} else {
console.error(error);
}
}

View File

@ -12,18 +12,23 @@ import * as mapillary from '../../../mods/mapillary-js/dist/mapillary.module';
import * as three from '../../../mods/three/build/three.module';
import {appToken} from '../../js/utils/token';
import {mapillaryErrorHandler} from '../../js/utils/error';
import {ViewerComponent} from '../../js/components/ViewerComponent';
import * as procedural from '../../js/examples/procedural-data-provider';
import * as webglrenderer from '../../js/examples/webgl-renderer';
import * as threerenderer from '../../js/examples/three-renderer';
import * as animation from '../../js/examples/animation';
import * as flycontrols from '../../js/examples/fly-controls';
import * as procedural from '../../js/examples/procedural-data-provider';
import * as threerenderer from '../../js/examples/three-renderer';
import * as webglrenderer from '../../js/examples/webgl-renderer';
import '../../../mods/mapillary-js/dist/mapillary.css';
// Add react-live imports you need here
const ReactLiveScope = {
appToken,
mapillaryErrorHandler,
animation,
flycontrols,
procedural,
webglrenderer,
threerenderer,

View File

@ -19,7 +19,7 @@ import { IViewer } from "./IViewer";
* onDetach, onReference, and onResize methods.
*
* Custom camera controls trigger rerendering
* automatically when the camera properties or projection
* automatically when the camera pose or projection
* is changed through the projectionMatrix and
* viewMatrix callbacks.
*
@ -37,10 +37,10 @@ import { IViewer } from "./IViewer";
*/
export interface ICustomCameraControls {
/**
* Method called when the camera controls has been
* Method called when the camera controls have been
* activated and is responsible for moving the
* viewer's camera and defining its projection. This
* nethod gives the camera controls a chance to initialize
* method gives the camera controls a chance to initialize
* resources, perform any transitions, and determine
* initial state.
*
@ -75,7 +75,7 @@ export interface ICustomCameraControls {
*
* @desdcription Custom camera controls can choose to
* make updates on each animation frame or only based on
* user input. Invoking updates on each camera frame is
* user input. Invoking updates on each animation frame is
* more resource intensive.
*
* @param {IViewer} viewer - The viewer this custom
@ -88,7 +88,7 @@ export interface ICustomCameraControls {
frameId: number): void;
/**
* Method called when the camera controls has been
* Method called when the camera controls have been
* attached to the viewer with {@link Viewer#attachCameraControls}.
* This gives the camera controls a chance to initialize
* resources.
@ -98,7 +98,7 @@ export interface ICustomCameraControls {
* viewer's camera.
*
* Invoking the matrix callbacks has no effect if the
* custom camera controls has not been activated.
* custom camera controls have not been activated.
*
* @param {IViewer} viewer - The viewer this custom
* camera controls instance was just added to.
@ -110,7 +110,7 @@ export interface ICustomCameraControls {
): void;
/**
* Method called when the camera controls has been deactivated.
* Method called when the camera controls have been deactivated.
* This gives the camera controls a chance to clean up resources
* and event listeners.
*
@ -120,7 +120,7 @@ export interface ICustomCameraControls {
onDeactivate(viewer: IViewer): void;
/**
* Method called when the camera controls has been detached from
* Method called when the camera controls have been detached from
* the viewer by calling {@link Viewer#detachCameraControls}.
* This gives the camera controls a chance to clean up resources
* and event listeners.