js/Mobilizing/renderer/3D/three/input/SpaceNavigator.js
/**
* https://github.com/archilogic-com/aframe-space-navigator-controls/blob/master/src/aframe-space-navigator-controls.js
*/
/* eslint-disable no-unused-vars */
import Component from '../../../../core/Component';
import Gamepad from '../../../../input/Gamepad';
import Vector3 from "../types/Vector3";
import Euler from "../types/Euler";
import Quaternion from '../types/Quaternion';
import Node from "../shape/3D/primitive/Node";
import Time from "../../../../time/Time";
import * as math from "../../../../core/util/Math";
const MAX_DELTA = 200, // ms
ROTATION_EPS = 0.000,
DEFAULT_FOV = 60;
export default class SpaceNavigator extends Component {
constructor({
rotationSensitivity = 0.05,
movementEasing = 3,
movementAcceleration = 700,
fovSensitivity = 0.01,
fovEasing = 3,
fovAcceleration = 5,
invertScroll = false
} = {}) {
super(...arguments);
this.rotationSensitivity = rotationSensitivity;
this.movementEasing = movementEasing;
this.movementAcceleration = movementAcceleration;
this.fovSensitivity = fovSensitivity;
this.fovEasing = fovEasing;
this.fovAcceleration = fovAcceleration;
this.invertScroll = invertScroll;
this.data = {
// Enable/disable features
enabled: true,
movementEnabled: true,
lookEnabled: true,
rollEnabled: true,
invertPitch: false,
fovEnabled: true,
fovMin: 2,
fovMax: 115,
// Constants
rotationSensitivity,
movementEasing,
movementAcceleration,
fovSensitivity,
fovEasing,
fovAcceleration,
invertScroll
}
this.connected = false;
}
setup() {
if (!this._setupDone) {
this.spaceNavigator = null;
this.gamepad = new Gamepad();
this.gamepad.setup();
this.gamepad.on();
this.context.addComponent(this.gamepad);
//console.log(this.gamepad);
this.gamepad.events.on("connected", (gamepad) => {
const gamepadName = gamepad.id.toLowerCase();
if (gamepadName.indexOf("spacenavigator") >= 0 ||
gamepadName.indexOf("space navigator") >= 0) {
this.spaceNavigator = gamepad;
console.log("found SpaceNavigator", this.spaceNavigator);
this.events.trigger("connected");
this.connected = true;
}
});
this.gamepad.events.on("disconnected", () => {
this.events.trigger("disconnected");
this.spaceNavigator = null;
this.connected = false;
});
// Movement
this.position = new Vector3(0, 0, 0);
this.movement = new Vector3(0, 0, 0);
this.movementVelocity = new Vector3(0, 0, 0);
this.movementDirection = new Vector3(0, 0, 0);
// Rotation
this.rotation = new Euler(0, 0, 0, 'YXZ');
this.pitch = new Node();
this.roll = new Node();
this.yaw = new Node();
this.yaw.transform.setLocalPositionY = 10;
this.yaw.transform.addChild(this.pitch.transform);
this.initialRotation = new Vector3();
this.prevInitialRotation = new Vector3();
this.prevFinalRotation = new Vector3();
this.tCurrent = 0;
this.tLastLocalActivity = 0;
this.tLastExternalActivity = 0;
// FOV
this.fov = DEFAULT_FOV;
this.fovVelocity = 0;
//time delta
this.time = new Time();
this.time.setup();
this.time.on();
this.context.addComponent(this.time);
super.setup();
}
}
/*******************************************************************
* Movement
*/
updatePosition(dt) {
const data = this.data;
const acceleration = data.movementAcceleration;
const easing = data.movementEasing;
const velocity = this.movementVelocity;
const spaceNavigator = this.spaceNavigator;
// If data has changed or FPS is too low
// we reset the velocity
if (dt > MAX_DELTA) {
velocity.x = 0;
velocity.y = 0;
velocity.z = 0;
return;
}
velocity.z -= velocity.z * easing * dt / 1000;
velocity.x -= velocity.x * easing * dt / 1000;
velocity.y -= velocity.y * easing * dt / 1000;
if (data.enabled && data.movementEnabled && spaceNavigator) {
// 3dconnexion space navigator position axes
// "right handed coordinate system"
// 0: - left / + right (pos: X axis pointing to the right)
// 1: - backwards / + forward (pos: Z axis pointing forwards)
// 2: - up / + down (pos: Y axis pointing down)
const axes = spaceNavigator.axes;
const xDelta = axes[0];
const yDelta = axes[2];
const zDelta = axes[1];
velocity.x += xDelta * acceleration * dt / 1000;
velocity.z += zDelta * acceleration * dt / 1000;
velocity.y -= yDelta * acceleration * dt / 1000;
}
const movementVector = this.getMovementVector(dt);
this.movement.copy(movementVector)
this.position.add(movementVector)
//this.position.add(velocity);
/* if (el) {
el.object3D.position.copy(this.position)
el.setAttribute('position', {
x: this.position.x,
y: this.position.y,
z: this.position.z
});
} */
//console.log("this.position", this.position);
}
/**
*
* @param {*} dt
* @returns
* @private
*/
getMovementVector(dt) {
const euler = new Euler(0, 0, 0, 'YXZ');
const rotation = new Vector3();
const direction = this.movementDirection;
const velocity = this.movementVelocity;
//rotation.set(math.radToDeg(this.rotation.x), math.radToDeg(this.rotation.y), math.radToDeg(this.rotation.z));
rotation.set(0, 0, 1);
direction.copy(velocity)
direction.multiplyScalar(dt / 1000)
euler.set(math.degToRad(rotation.x), math.degToRad(rotation.y), math.degToRad(rotation.z));
direction.applyEuler(euler);
return direction;
}
/*******************************************************************
* Rotation
*/
updateRotation() {
const rotationEps = 0.0001;
const debounce = 500;
const spaceNavigator = this.spaceNavigator;
if (!this.data.lookEnabled || !spaceNavigator) {
return;
}
this.tCurrent = Date.now();
this.initialRotation.set(
math.radToDeg(this.rotation.x),
math.radToDeg(this.rotation.y),
math.radToDeg(this.rotation.z),
)
// If initial rotation for this frame is different from last frame, and
// doesn't match last spaceNavigator state, assume an external component is
// active on this element.
if (this.initialRotation.distanceToSquared(this.prevInitialRotation) > rotationEps
&& this.initialRotation.distanceToSquared(this.prevFinalRotation) > rotationEps) {
this.prevInitialRotation.copy(this.initialRotation);
this.tLastExternalActivity = this.tCurrent;
return;
}
this.prevInitialRotation.copy(this.initialRotation);
// If external controls have been active in last 500ms, wait.
if (this.tCurrent - this.tLastExternalActivity < debounce) {
return;
}
/*
* 3dconnexion space navigator rotation axes
*
* "right handed coordinate system"
* 3: - pitch down / + pitch up (rot: X axis clock wise)
* 4: - roll right / + roll left (rot: Z axis clock wise)
* 5: - yaw right / + yaw left (rot: Y axis clock wise)
*/
const delta = new Vector3(
spaceNavigator.axes[3],
spaceNavigator.axes[5],
spaceNavigator.axes[4]);
if (delta.x < ROTATION_EPS && delta.x > -ROTATION_EPS) {
delta.z = 0;
}
if (delta.y < ROTATION_EPS && delta.y > -ROTATION_EPS) {
delta.y = 0;
}
if (delta.z < ROTATION_EPS && delta.z > -ROTATION_EPS) {
delta.x = 0;
}
if (this.data.invertPitch) {
delta.x *= -delta.x;
}
// If external controls have been active more recently than spaceNavigator,
// and spaceNavigator hasn't moved, don't overwrite the existing rotation.
if (this.tLastExternalActivity > this.tLastLocalActivity && !delta.lengthSq()) {
return;
}
delta.multiplyScalar(this.data.rotationSensitivity);
/* this.pitch.rotation.x += delta.x;
this.yaw.rotation.y -= delta.y;
this.roll.rotation.z += delta.z; */
this.pitch.transform.setLocalRotationX(this.pitch.transform.getLocalRotationX() + delta.x);
this.yaw.transform.setLocalRotationY(this.yaw.transform.getLocalRotationY() - delta.y);
this.roll.transform.setLocalRotationZ(this.roll.transform.getLocalRotationZ() + delta.z);
this.rotation.set(
this.pitch.transform.getLocalRotationX(),
this.yaw.transform.getLocalRotationY(),
this.data.rollEnabled ? this.roll.transform.getLocalRotationZ() : 0
);
//console.log(this.rotation);
/* if (this.el) {
this.el.setAttribute('rotation', {
x: this.pitch.rotation.x * RAD_TO_DEG,
y: this.yaw.rotation.y * RAD_TO_DEG,
z: this.data.rollEnabled ? this.roll.rotation.z * RAD_TO_DEG : 0
})
prevFinalRotation.copy(this.el.getAttribute('rotation'))
} else { */
this.prevFinalRotation.set(
math.radToDeg(this.rotation.x),
math.radToDeg(this.rotation.y),
math.radToDeg(this.rotation.z)
)
//}
this.tLastLocalActivity = this.tCurrent;
}
getPosition(scaleFactor) {
if (this.spaceNavigator) {
if (!scaleFactor) {
scaleFactor = 1;
}
const pos = new Vector3().copy(this.position);
pos.multiplyScalar(scaleFactor);
return pos;
}
return null;
}
getRotation() {
if (this.spaceNavigator) {
return this.rotation;
}
return null;
}
getQuaternion() {
if (this.spaceNavigator) {
const quat = new Quaternion().setFromEuler(this.rotation);
return quat;
}
return null;
}
update() {
if (this.spaceNavigator) {
const updatedSpaceNavigator = this.gamepad.getGamePadFromIndex(this.spaceNavigator.index);
this.spaceNavigator = updatedSpaceNavigator;
if (this.spaceNavigator.connected) {
const dt = this.time.getDelta();
this.updatePosition(dt);
this.updateRotation(dt);
}
}
}
}