dragAndDropAvatar_DragAndDropAvatar.js
import { computeRelativeElevationFromGround } from '../utils';
import { CameraManager } from '../CameraManager';
import './style.css';
import * as THREE from 'three';
import { ScriptBase } from '@ud-viz/game_browser';
import { Command, Object3D } from '@ud-viz/game_shared';
import { constant } from '@ud-viz/game_shared_template';
import { ControllerNativeCommandManager } from '../ControllerNativeCommandManager';
/**
@class Alows users to drag and drop an avatar in a 3D
environment and control its position and camera view.
*/
export class DragAndDropAvatar extends ScriptBase {
init() {
/** @type {ControllerNativeCommandManager} */
this.controllerNativeCommandManager = this.context.findExternalScriptWithID(
ControllerNativeCommandManager.ID_SCRIPT
);
/**
* camera manager
*
@type {CameraManager} */
this.cameraManager = this.context.findExternalScriptWithID(
CameraManager.ID_SCRIPT
);
if (!this.cameraManager)
throw new Error(
'this script is dependent of CameraManager script template'
);
/** @type {Object3D} */
this.avatar = null; // no avatar for now
/** @type {THREE.Vector3} */
this.itownsCameraPosition = new THREE.Vector3();
/**
* record where was camera quaternion
*
* @type {THREE.Quaternion}
*/
this.itownsCameraQuaternion = new THREE.Quaternion();
/** @type {HTMLDivElement} */
this.leaveAvatarModeButton = document.createElement('button');
this.leaveAvatarModeButton.innerText = 'Leave avatar mode';
this.leaveAvatarModeButton.classList.add('leave_avatar_mode');
this.leaveAvatarModeButton.onclick = () => {
if (!this.avatar) return;
this.context.sendCommandsToGameContext([
new Command({
type: constant.COMMAND.REMOVE_AVATAR,
}),
]);
};
/** @type {HTMLDivElement} */
this.dragAndDropElement = document.createElement('div');
this.dragAndDropElement.classList.add('drag_and_drop_avatar');
this.dragAndDropElement.innerText = 'Drag And Drop Avatar';
this.dragAndDropElement.draggable = true;
/** @type {HTMLElement} */
this.domElement = this.context.userData.dragAndDropAvatarDomElement;
// append drag and drop element
this.appendToHtml(this.dragAndDropElement);
// drag and drop behavior
this.context.frame3D.domElement.ondragend = (event) => {
if (event.target != this.dragAndDropElement) return;
// compute where the avatar should be teleported
const worldPosition = new THREE.Vector3();
this.context.frame3D.itownsView.getPickingPositionFromDepth(
new THREE.Vector2(event.offsetX, event.offsetY),
worldPosition
);
// add an avatar
this.context.sendCommandsToGameContext([
new Command({
type: constant.COMMAND.ADD_AVATAR,
data: worldPosition,
}),
]);
};
}
appendToHtml(el) {
if (this.domElement) {
this.domElement.appendChild(el);
} else {
this.context.frame3D.domElementUI.appendChild(el);
}
}
/**
* It computes the elevation of the avatar and sends it to the game context
*/
tick() {
if (this.avatar) {
// send Z_Update to game context
this.context.sendCommandsToGameContext([
new Command({
type: constant.COMMAND.UPDATE_TRANSFORM,
data: {
object3DUUID: this.avatar.uuid,
position: {
z: computeRelativeElevationFromGround(
this.avatar,
this.context.frame3D.itownsView.tileLayer,
this.variables.update_z_crs
),
},
},
}),
]);
}
}
/**
* Update state of this based on there is an avatar or not
*
* @param {Object3D} avatar - avatar game object3D
*/
setAvatar(avatar) {
this.avatar = avatar;
if (this.avatar) {
// disable itowns controls
this.context.frame3D.itownsView.controls.enabled = false;
this.dragAndDropElement.remove();
// record where was the camera
this.itownsCameraPosition.copy(this.context.frame3D.camera.position);
this.itownsCameraQuaternion.copy(this.context.frame3D.camera.quaternion);
// traveling to focus avatar
this.cameraManager
.moveToObject3D(
this.avatar,
this.variables.camera_duration,
this.variables.camera_distance,
this.variables.camera_offset,
this.variables.camera_angle
)
.then((movementSucceed) => {
if (!movementSucceed) throw new Error('camera manager error');
this.cameraManager.followObject3D(
this.avatar,
this.variables.camera_distance,
this.variables.camera_offset,
this.variables.camera_angle
);
this.controllerNativeCommandManager.controls(
this.avatar.uuid,
ControllerNativeCommandManager.MODE[2]
);
// add ui to switch back to planar controls
this.appendToHtml(this.leaveAvatarModeButton);
});
} else {
this.leaveAvatarModeButton.remove();
this.controllerNativeCommandManager.removeControls();
this.cameraManager.stopFollowObject3D();
this.cameraManager
.moveToTransform(
this.itownsCameraPosition,
this.itownsCameraQuaternion,
this.variables.camera_duration
)
.then((movementSucceed) => {
if (!movementSucceed) throw new Error('camera manager error');
this.appendToHtml(this.dragAndDropElement);
this.context.frame3D.itownsView.controls.enabled = true;
});
}
}
onGameObjectRemoved(goRemoved) {
if (goRemoved.name === constant.NAME.AVATAR) {
this.setAvatar(null);
}
}
onNewGameObject(newGO) {
// check if this is the avatar
if (newGO.name === constant.NAME.AVATAR) {
this.setAvatar(newGO);
}
}
static get ID_SCRIPT() {
return 'drag_and_drop_avatar_id';
}
/**
* @typedef DragAndDropAvatarVariables
* @property {number} camera_duration - time for camera movement in ms
* @property {{x:number,y:number,z:number}} camera_offset - offset to positioned camera behind object3D
* @property {number} camera_angle - angle on x to positioned camera behind object3D
* @property {number} camera_distance - distance to positioned camera behind object3D
* @property {string} update_z_crs - projection used to update z elevation of avatar
*/
/**
* @returns {DragAndDropAvatarVariables} - default variables
*/
static get DEFAULT_VARIABLES() {
return {
camera_duration: 2000,
camera_offset: { x: 0, y: 0, z: 2 },
camera_angle: 0,
camera_distance: 7,
update_z_crs: 'EPSG:3946', // the one of lyon by default /!\ must have been define before
};
}
}