import {Clock, Vector3} from "three";
import {Vector} from "../../core/geometry/Vector.js";
import {LEFT, RIGHT} from "../../constants.js";
import {Position} from "../../core/geometry/General.js";
import {bind} from "../../utils.js";
const STATUS_REST = 0;
const STATUS_TRIGGERED = 1;
/**
* @class
*
* @classdesc
* Makes sure that an given solid in the geometry follows a VR controller (living in the tangent space).
* The position of the underlying shape should be given by an isometry of the geometry
*/
export class ShootVRControls {
/**
* Constructor
* @param {Group} controller - the group representing the controller
* @param {VRCamera} camera - the camera (giving the position of the observer)
* @param {Solid[]} solids - the solid following the controller.
* @param {number} speed - speed of the bullet
* The position of the underlying shape should be given by an isometry.
*/
constructor(controller, camera, solids, speed) {
this.controller = controller;
this.camera = camera;
this.solids = solids;
this.speed = speed;
/**
* Status of the gun
* - STATUS_REST: at rest
* - STATUS_TRIGGERED: the user pressed the button, the the bullet has not been launched
* @type {number}
* @private
*/
this._status = STATUS_REST;
/**
* The id of the next solid to shoot
* @type {number}
* @private
*/
this._nextBullet = 0;
/**
* Clock to update the position of the bullets
* @type {Clock}
* @private
*/
this._clock = new Clock();
const _onSelectStart = bind(this, this.onSelectStart);
const _onSelectEnd = bind(this, this.onSelectEnd);
this.controller.addEventListener('selectstart', _onSelectStart);
this.controller.addEventListener('selectend', _onSelectEnd);
}
/**
* Event handler when the user starts selecting
*/
onSelectStart() {
if (this._status === STATUS_REST) {
this._status = STATUS_TRIGGERED;
}
}
/**
* Event handler when the user stops selecting
*/
onSelectEnd() {
}
/**
* Shoot the next bullet
* @param {Position} position - initial position of the bullet
*/
shoot(position) {
const bullet = this.solids[this._nextBullet];
bullet.bulletData = {
time: this._clock.getElapsedTime(),
position: position
}
bullet.isRendered = true;
this._nextBullet = (this._nextBullet + 1) % this.solids.length;
}
/**
* Update the position of the given bullet
* @param {number} index - the index of the bullet
*/
updateBullet(index) {
const bullet = this.solids[index];
// no bulletData means the bullet has not been shot yet
if (bullet.hasOwnProperty('bulletData')) {
const delta = this._clock.getElapsedTime() - bullet.bulletData.time;
const aux = bullet.bulletData.position.clone().flow(new Vector(0, 0, -this.speed * delta));
bullet.isom.copy(aux.boost);
bullet.updateData();
}
}
/**
* Update the position of all bullets
*/
updateAllBullets() {
for (let i = 0; i < this.solids.length; i++) {
this.updateBullet(i);
}
}
/**
* @param {WebXRManager} webXRManager - the WebXRManager used by Three.js
*
*/
update(webXRManager) {
if (this._status === STATUS_TRIGGERED) {
const controllerPosition = new Vector().setFromMatrixPosition(this.controller.matrixWorld);
let cameraPosition = new Vector();
if (this.camera.isStereoOn) {
// If XR is enable, we get the position of the left and right camera.
// Note that when XR is on, then main Three.js Camera is shifted to coincide with the right eye.
// Do its position is NOT the midpoint between the eyes of the observer.
// Thus we take here the midpoint between the two VR cameras.
// Those can only be accessed using the WebXRManager.
const camerasVR = webXRManager.getCamera(this.camera.threeCamera).cameras;
const newThreePositionL = new Vector3().setFromMatrixPosition(camerasVR[LEFT].matrixWorld);
const newThreePositionR = new Vector3().setFromMatrixPosition(camerasVR[RIGHT].matrixWorld);
cameraPosition.lerpVectors(newThreePositionL, newThreePositionR, 0.5);
} else {
cameraPosition.setFromMatrixPosition(this.camera.matrix);
}
const relativeControllerPosition = controllerPosition.clone().sub(cameraPosition);
const relativeControllerMatrixWorld = this.controller.matrixWorld.clone().setPosition(relativeControllerPosition);
const position = this.camera.position.clone().fakeDiffExpMap(relativeControllerMatrixWorld);
this.shoot(position.globalPosition);
this._status = STATUS_REST;
}
this.updateAllBullets();
}
}