Skip to content

Commit

Permalink
WIP: virtual-tour options
Browse files Browse the repository at this point in the history
  • Loading branch information
mistic100 committed Aug 18, 2023
1 parent 8e89888 commit 5408abb
Show file tree
Hide file tree
Showing 10 changed files with 247 additions and 64 deletions.
3 changes: 2 additions & 1 deletion docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,9 @@ module.exports = {
},
],
['@vuepress/back-to-top'],
require('./plugins/gallery'),
require('./plugins/code-demo'),
require('./plugins/dialog'),
require('./plugins/gallery'),
require('./plugins/module'),
require('./plugins/tabs'),
],
Expand Down
34 changes: 34 additions & 0 deletions docs/.vuepress/plugins/dialog/Dialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<template>
<div>
<md-dialog :md-active.sync="showDialog">
<md-dialog-title>{{ title }}</md-dialog-title>

<md-dialog-content>
<slot></slot>
</md-dialog-content>

<md-dialog-actions>
<md-button class="md-primary" @click="showDialog = false">Close</md-button>
</md-dialog-actions>
</md-dialog>

<md-button class="md-primary md-raised" @click="showDialog = true">{{ button }}</md-button>
</div>
</template>

<script>
export default {
name: 'Dialog',
data: () => ({
showDialog: false
}),
props: {
title: { type: String, default: '' },
button: { type: String, default: '' },
},
};
</script>

<style lang="stylus">
</style>
5 changes: 5 additions & 0 deletions docs/.vuepress/plugins/dialog/enhanceApp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Dialog from './Dialog.vue';

export default ({ Vue }) => {
Vue.component('Dialog', Dialog);
};
21 changes: 21 additions & 0 deletions docs/.vuepress/plugins/dialog/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const container = require('markdown-it-container');
const path = require('path');

module.exports = (options, ctx) => ({
name: 'dialog',
enhanceAppFiles: path.resolve(__dirname, './enhanceApp.js'),
extendMarkdown: (md) => {
md.use(container, 'dialog', {
render: (tokens, idx) => {
const { nesting, info } = tokens[idx];

if (nesting === 1) {
const [, button, title] = info.match(/dialog "(.*?)" "(.*?)"/);
return `<Dialog button="${button}" title="${title}">\n`;
} else {
return `</Dialog>\n`;
}
},
});
},
});
5 changes: 2 additions & 3 deletions docs/.vuepress/plugins/gallery/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ module.exports = (options, ctx) => ({

md.use(container, 'item', {
render: (tokens, idx) => {
const { nesting, info } = tokens[idx];
const attributes = info.trim().slice('item '.length);
const { nesting } = tokens[idx];

if (nesting === 1) {
return `<GalleryItem ${attributes}>\n`;
return `<GalleryItem>\n`;
} else {
return `</GalleryItem>\n`;
}
Expand Down
43 changes: 33 additions & 10 deletions docs/plugins/virtual-tour.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,12 @@ Overrides the tooltip content (defaults to the node's `name` property).

#### `linkOffset`

- type: `{ yaw, pitch }`
- type: `{ yaw?, pitch?, depth? }`

Offset added to the final link position order to move the marker/arrow without affecting where the viewer is rotated before going to the next node.

`depth` is only used in 3D render mode to manage overlapping arrows. Note that it is automatically computed in GPS mode depending on the distance to the node, but can be overriden if necessary.

#### `arrowStyle` (3d mode only)

- type: `object`
Expand Down Expand Up @@ -385,21 +387,42 @@ Id of the initially loaded node. If empty the first node will be displayed. You

Enable the preloading of linked nodes, can be a function that returns true or false for each link.

#### `rotateSpeed`
#### `transitionOptions`

- type: `boolean | string | number`
- default: `20rpm`
- type: `object | function`
- default: `{ fade: 1500, rotateSpeed: '20deg' }`
- updatable: no

When a link is clicked, adds a animation to face it before actually changing the node. If `false` the viewer won't rotate at all and keep the current orientation.
Configuration of the transition between nodes. Can be a callback.

#### `transition`
::: dialog "See details" "VirtualTour transitionOptions"

- type: `boolean | number`
- default: `1500`
- updatable: no
`transitionOptions` can be defined as a static object or a function called before switching to a new node.

The default behaviour is to rotate the view to face the direction of the link then perform a fade-in transition to the next node.

**If defined as an object, the type is:**

```ts
{
/** @default 1500 */
fade?: boolean | number;
/** @default '20rpm' */
rotateSpeed?: boolean | string | number;
}
```

**If defined as a function, the signature is:**

Duration of the transition between nodes.
```ts
(toNode: Node, fromNode?: Node, fromLink?: Link) => ({
fade?: boolean | number;
rotateSpeed?: boolean | string | number;
rotateTo?: { yaw: number, pitch: number };
})
```

:::

#### `linksOnCompass`

Expand Down
6 changes: 4 additions & 2 deletions examples/plugin-virtual-tour.html
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,11 @@
renderMode: '3d',
startNodeId: nodes[1].id,
preload: true,
// transition : false,
// rotateSpeed : false,
arrowPosition: 'bottom',
transitionOptions: {
// fade: false,
// rotateSpeed: false,
},

// client mode
dataMode: 'client',
Expand Down
110 changes: 85 additions & 25 deletions packages/virtual-tour-plugin/src/VirtualTourPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import { AbstractDatasource } from './datasources/AbstractDataSource';
import { ClientSideDatasource } from './datasources/ClientSideDatasource';
import { ServerSideDatasource } from './datasources/ServerSideDatasource';
import { NodeChangedEvent, VirtualTourEvents } from './events';
import { GpsPosition, VirtualTourLink, VirtualTourNode, VirtualTourPluginConfig } from './model';
import { gpsToSpherical, setMeshColor } from './utils';
import { GpsPosition, VirtualTourLink, VirtualTourNode, VirtualTourPluginConfig, VirtualTourTransitionOptions } from './model';
import { gpsDistance, gpsToSpherical, setMeshColor } from './utils';

// https://discourse.threejs.org/t/updates-to-lighting-in-three-js-r155/53733
const LIGHT_INTENSITY = parseInt(REVISION) >= 155 ? Math.PI : 1;
Expand All @@ -35,8 +35,12 @@ const getConfig = utils.getConfigParser<VirtualTourPluginConfig>(
getNode: null,
startNodeId: null,
preload: false,
rotateSpeed: '20rpm',
transition: CONSTANTS.DEFAULT_TRANSITION,
transitionOptions: {
rotateSpeed: '20rpm',
fade: 1500,
},
rotateSpeed: null,
transition: null,
linksOnCompass: true,
markerStyle: DEFAULT_MARKER,
arrowStyle: DEFAULT_ARROW,
Expand Down Expand Up @@ -82,6 +86,25 @@ const getConfig = utils.getConfigParser<VirtualTourPluginConfig>(
}
return map;
},
transitionOptions(transitionOptions, { rawConfig }) {
if (typeof transitionOptions === 'object') {
if (!utils.isNil(rawConfig.transition)) {
transitionOptions.fade = rawConfig.transition;
}
if (!utils.isNil(rawConfig.rotateSpeed)) {
transitionOptions.rotateSpeed = rawConfig.rotateSpeed;
}
}
return transitionOptions;
},
transition(transition) {
utils.logWarn('VirtualTourPlugin: "transition" option is deprecated, use "transitionOptions.fade" instead');
return transition;
},
rotateSpeed(transition) {
utils.logWarn('VirtualTourPlugin: "rotateSpeed" option is deprecated, use "transitionOptions.rotateSpeed" instead');
return transition;
},
}
);

Expand Down Expand Up @@ -250,12 +273,12 @@ export class VirtualTourPlugin extends AbstractConfigurablePlugin<
} else if (e instanceof events.ClickEvent) {
const link = e.data.objects.find((o) => o.userData[LINK_DATA])?.userData[LINK_DATA];
if (link) {
this.setCurrentNode(link.nodeId, link);
this.setCurrentNode(link.nodeId, null, link);
}
} else if (e.type === 'select-marker') {
const link = (e as markersEvents.SelectMarkerEvent).marker.data?.[LINK_DATA];
if (link) {
this.setCurrentNode(link.nodeId, link);
this.setCurrentNode(link.nodeId, null, link);
}
} else if (e instanceof events.ObjectEnterEvent) {
if (e.userDataKey === LINK_DATA) {
Expand Down Expand Up @@ -335,7 +358,7 @@ export class VirtualTourPlugin extends AbstractConfigurablePlugin<
* Changes the current node
* @returns {Promise<boolean>} resolves false if the loading was aborted by another call
*/
setCurrentNode(nodeId: string, fromLink?: VirtualTourLink): Promise<boolean> {
setCurrentNode(nodeId: string, options?: VirtualTourTransitionOptions, fromLink?: VirtualTourLink): Promise<boolean> {
if (nodeId === this.state.currentNode?.id) {
return Promise.resolve(true);
}
Expand All @@ -347,30 +370,48 @@ export class VirtualTourPlugin extends AbstractConfigurablePlugin<
const fromNode = this.state.currentNode;
const fromLinkPosition = fromNode && fromLink ? this.__getLinkPosition(fromNode, fromLink) : null;

return Promise.all([
// if this node is already preloading, wait for it
Promise.resolve(this.state.preload[nodeId]).then(() => {
// if this node is already preloading, wait for it
return Promise.resolve(this.state.preload[nodeId])
.then(() => {
if (this.state.loadingNode !== nodeId) {
throw utils.getAbortError();
}

return this.datasource.loadNode(nodeId);
}),
Promise.resolve(fromLinkPosition ? this.config.rotateSpeed : false)
.then((speed) => {
if (speed) {
return this.viewer.animate({ ...fromLinkPosition, speed });
}
})
.then(() => {
this.viewer.loader.show();
}),
])
.then(([node]) => {
})
.then((node) => {
if (this.state.loadingNode !== nodeId) {
throw utils.getAbortError();
}

const transitionOptions: VirtualTourTransitionOptions = {
fade: (getConfig.defaults.transitionOptions as any).fade,
rotateSpeed: (getConfig.defaults.transitionOptions as any).rotateSpeed,
rotateTo: fromLinkPosition,
...(typeof this.config.transitionOptions === 'function'
? this.config.transitionOptions(node, fromNode, fromLink)
: this.config.transitionOptions
),
...options
};

if (transitionOptions.rotateTo && transitionOptions.rotateSpeed) {
return this.viewer.animate({
...transitionOptions.rotateTo,
speed: transitionOptions.rotateSpeed,
})
.then(() => ([node, transitionOptions] as [VirtualTourNode, VirtualTourTransitionOptions]));
} else {
return Promise.resolve([node, transitionOptions] as [VirtualTourNode, VirtualTourTransitionOptions]);
}
})
.then(([node, transitionOptions]) => {
if (this.state.loadingNode !== nodeId) {
throw utils.getAbortError();
}

this.viewer.loader.show();

this.state.currentNode = node;

if (this.state.currentTooltip) {
Expand All @@ -389,16 +430,16 @@ export class VirtualTourPlugin extends AbstractConfigurablePlugin<
if (this.map) {
this.map.minimize();
const center = this.__getNodeMapPosition(node);
if (typeof this.config.transition === 'number') {
setTimeout(() => this.map.setCenter(center), this.config.transition / 2);
if (typeof transitionOptions.fade === 'number') {
setTimeout(() => this.map.setCenter(center), transitionOptions.fade / 2);
} else {
this.map.setCenter(center);
}
}

return this.viewer
.setPanorama(node.panorama, {
transition: this.config.transition,
transition: transitionOptions.fade,
caption: node.caption,
description: node.description,
panoData: node.panoData,
Expand Down Expand Up @@ -458,6 +499,19 @@ export class VirtualTourPlugin extends AbstractConfigurablePlugin<
private __renderLinks(node: VirtualTourNode) {
const positions: Position[] = [];

let minDist = Number.POSITIVE_INFINITY;
let maxDist = Number.NEGATIVE_INFINITY;
const linksDist: Record<string, number> = {};

if (this.isGps) {
node.links.forEach((link) => {
const dist = gpsDistance(node.gps, link.gps);
linksDist[link.nodeId] = dist;
minDist = Math.min(dist, minDist);
maxDist = Math.max(dist, maxDist);
});
}

let i = 0;
node.links.forEach((link) => {
const position = this.__getLinkPosition(node, link);
Expand All @@ -474,6 +528,12 @@ export class VirtualTourPlugin extends AbstractConfigurablePlugin<
.sphericalCoordsToVector3({ yaw: position.yaw, pitch: 0 }, mesh.position)
.multiplyScalar(1 / CONSTANTS.SPHERE_RADIUS);

if (!utils.isNil(link.linkOffset?.depth)) {
mesh.position.multiplyScalar(link.linkOffset.depth);
} else if (this.isGps && minDist !== maxDist) {
mesh.position.multiplyScalar(MathUtils.mapLinear(linksDist[link.nodeId], minDist, maxDist, 0.5, 1.5));
}

const outlineMesh = new Mesh(ARROW_OUTLINE_GEOM, new MeshBasicMaterial({ side: BackSide }));
outlineMesh.position.copy(mesh.position);
outlineMesh.rotation.copy(mesh.rotation);
Expand Down
Loading

0 comments on commit 5408abb

Please sign in to comment.