spatial-controls
Version:
Configurable 3D movement controls.
1,907 lines (1,881 loc) • 78.1 kB
JavaScript
/**
* spatial-controls v6.1.2 build Fri Jun 28 2024
* https://github.com/vanruesc/spatial-controls
* Copyright 2017 Raoul van Rüschen
* @license Zlib
*/
// src/core/Action.ts
var Action = /* @__PURE__ */ ((Action2) => {
Action2[Action2["MOVE_FORWARD"] = 0] = "MOVE_FORWARD";
Action2[Action2["MOVE_LEFT"] = 1] = "MOVE_LEFT";
Action2[Action2["MOVE_BACKWARD"] = 2] = "MOVE_BACKWARD";
Action2[Action2["MOVE_RIGHT"] = 3] = "MOVE_RIGHT";
Action2[Action2["MOVE_DOWN"] = 4] = "MOVE_DOWN";
Action2[Action2["MOVE_UP"] = 5] = "MOVE_UP";
Action2[Action2["ZOOM_OUT"] = 6] = "ZOOM_OUT";
Action2[Action2["ZOOM_IN"] = 7] = "ZOOM_IN";
Action2[Action2["BOOST"] = 8] = "BOOST";
Action2[Action2["ROTATE"] = 9] = "ROTATE";
return Action2;
})(Action || {});
// src/core/ControlMode.ts
var ControlMode = /* @__PURE__ */ ((ControlMode2) => {
ControlMode2["FIRST_PERSON"] = "first-person";
ControlMode2["THIRD_PERSON"] = "third-person";
return ControlMode2;
})(ControlMode || {});
// src/core/Direction.ts
var Direction = /* @__PURE__ */ ((Direction2) => {
Direction2[Direction2["FORWARD"] = 0] = "FORWARD";
Direction2[Direction2["LEFT"] = 1] = "LEFT";
Direction2[Direction2["BACKWARD"] = 2] = "BACKWARD";
Direction2[Direction2["RIGHT"] = 3] = "RIGHT";
Direction2[Direction2["DOWN"] = 4] = "DOWN";
Direction2[Direction2["UP"] = 5] = "UP";
return Direction2;
})(Direction || {});
// src/core/RotationControls.ts
import { EventDispatcher as EventDispatcher8, Quaternion as Quaternion2, Vector2, Vector3 as Vector35 } from "three";
// src/input/PointerBehaviour.ts
var PointerBehaviour = /* @__PURE__ */ ((PointerBehaviour2) => {
PointerBehaviour2["DEFAULT"] = "default";
PointerBehaviour2["LOCK"] = "lock";
PointerBehaviour2["LOCK_HOLD"] = "lock-hold";
return PointerBehaviour2;
})(PointerBehaviour || {});
// src/strategies/RotationStrategy.ts
var RotationStrategy = class {
/**
* The controls.
*/
controls;
/**
* Constructs a new rotation strategy.
*
* @param controls - The controls.
*/
constructor(controls) {
this.controls = controls;
}
execute(flag, event) {
const behaviour = this.controls.settings.pointer.behaviour;
const isMouse = event.type === "mousedown" || event.type === "mouseup";
if (isMouse && behaviour !== "default" /* DEFAULT */) {
this.controls.setPointerLocked();
} else {
this.controls.setRotationEnabled(flag);
}
}
};
// src/strategies/ZoomStrategy.ts
var ZoomStrategy = class {
/**
* A rotation manager.
*/
rotationManager;
/**
* Indicates whether this zoom strategy should zoom in.
*/
zoomIn;
/**
* Constructs a new zoom strategy.
*
* @param rotationManager - A rotation manager.
* @param zoomIn - Whether this strategy should zoom in.
*/
constructor(rotationManager, zoomIn) {
this.rotationManager = rotationManager;
this.zoomIn = zoomIn;
}
execute(flag) {
if (flag) {
this.rotationManager.zoom(this.zoomIn ? -1 : 1);
}
}
};
// src/input/KeyCode.ts
var KeyCode = /* @__PURE__ */ ((KeyCode2) => {
KeyCode2["BACKSPACE"] = "Backspace";
KeyCode2["TAB"] = "Tab";
KeyCode2["ENTER"] = "Enter";
KeyCode2["SHIFT_LEFT"] = "ShiftLeft";
KeyCode2["SHIFT_RIGHT"] = "ShiftRight";
KeyCode2["CTRL_LEFT"] = "ControlLeft";
KeyCode2["CTRL_RIGHT"] = "ControlRight";
KeyCode2["ALT_LEFT"] = "AltLeft";
KeyCode2["ALT_RIGHT"] = "AltRight";
KeyCode2["PAUSE"] = "Pause";
KeyCode2["CAPS_LOCK"] = "CapsLock";
KeyCode2["ESCAPE"] = "Escape";
KeyCode2["SPACE"] = "Space";
KeyCode2["PAGE_UP"] = "PageUp";
KeyCode2["PAGE_DOWN"] = "PageDown";
KeyCode2["END"] = "End";
KeyCode2["HOME"] = "Home";
KeyCode2["ARROW_LEFT"] = "ArrowLeft";
KeyCode2["ARROW_UP"] = "ArrowUp";
KeyCode2["ARROW_RIGHT"] = "ArrowRight";
KeyCode2["ARROW_DOWN"] = "ArrowDown";
KeyCode2["INSERT"] = "Insert";
KeyCode2["DELETE"] = "Delete";
KeyCode2["DIGIT_0"] = "Digit0";
KeyCode2["DIGIT_1"] = "Digit1";
KeyCode2["DIGIT_2"] = "Digit2";
KeyCode2["DIGIT_3"] = "Digit3";
KeyCode2["DIGIT_4"] = "Digit4";
KeyCode2["DIGIT_5"] = "Digit5";
KeyCode2["DIGIT_6"] = "Digit6";
KeyCode2["DIGIT_7"] = "Digit7";
KeyCode2["DIGIT_8"] = "Digit8";
KeyCode2["DIGIT_9"] = "Digit9";
KeyCode2["KEY_A"] = "KeyA";
KeyCode2["KEY_B"] = "KeyB";
KeyCode2["KEY_C"] = "KeyC";
KeyCode2["KEY_D"] = "KeyD";
KeyCode2["KEY_E"] = "KeyE";
KeyCode2["KEY_F"] = "KeyF";
KeyCode2["KEY_G"] = "KeyG";
KeyCode2["KEY_H"] = "KeyH";
KeyCode2["KEY_I"] = "KeyI";
KeyCode2["KEY_J"] = "KeyJ";
KeyCode2["KEY_K"] = "KeyK";
KeyCode2["KEY_L"] = "KeyL";
KeyCode2["KEY_M"] = "KeyM";
KeyCode2["KEY_N"] = "KeyN";
KeyCode2["KEY_O"] = "KeyO";
KeyCode2["KEY_P"] = "KeyP";
KeyCode2["KEY_Q"] = "KeyQ";
KeyCode2["KEY_R"] = "KeyR";
KeyCode2["KEY_S"] = "KeyS";
KeyCode2["KEY_T"] = "KeyT";
KeyCode2["KEY_U"] = "KeyU";
KeyCode2["KEY_V"] = "KeyV";
KeyCode2["KEY_W"] = "KeyW";
KeyCode2["KEY_X"] = "KeyX";
KeyCode2["KEY_Y"] = "KeyY";
KeyCode2["KEY_Z"] = "KeyZ";
KeyCode2["OS_LEFT"] = "OSLeft";
KeyCode2["OS_RIGHT"] = "OSRight";
KeyCode2["META_LEFT"] = "MetaLeft";
KeyCode2["META_RIGHT"] = "MetaRight";
KeyCode2["MEDIA_SELECT"] = "MediaSelect";
KeyCode2["NUMPAD_0"] = "Numpad0";
KeyCode2["NUMPAD_1"] = "Numpad1";
KeyCode2["NUMPAD_2"] = "Numpad2";
KeyCode2["NUMPAD_3"] = "Numpad3";
KeyCode2["NUMPAD_4"] = "Numpad4";
KeyCode2["NUMPAD_5"] = "Numpad5";
KeyCode2["NUMPAD_6"] = "Numpad6";
KeyCode2["NUMPAD_7"] = "Numpad7";
KeyCode2["NUMPAD_8"] = "Numpad8";
KeyCode2["NUMPAD_9"] = "Numpad9";
KeyCode2["NUMPAD_MULTIPLY"] = "NumpadMultiply";
KeyCode2["NUMPAD_ADD"] = "NumpadAdd";
KeyCode2["NUMPAD_SUBTRACT"] = "NumpadSubtract";
KeyCode2["NUMPAD_DECIMAL"] = "NumpadDecimal";
KeyCode2["NUMPAD_DIVIDE"] = "NumpadDivide";
KeyCode2["F1"] = "F1";
KeyCode2["F2"] = "F2";
KeyCode2["F3"] = "F3";
KeyCode2["F4"] = "F4";
KeyCode2["F5"] = "F5";
KeyCode2["F6"] = "F6";
KeyCode2["F7"] = "F7";
KeyCode2["F8"] = "F8";
KeyCode2["F9"] = "F9";
KeyCode2["F10"] = "F10";
KeyCode2["F11"] = "F11";
KeyCode2["F12"] = "F12";
KeyCode2["NUM_LOCK"] = "NumLock";
KeyCode2["SCROLL_LOCK"] = "ScrollLock";
KeyCode2["SEMICOLON"] = "Semicolon";
KeyCode2["EQUAL"] = "Equal";
KeyCode2["COMMA"] = "Comma";
KeyCode2["MINUS"] = "Minus";
KeyCode2["PERIOD"] = "Period";
KeyCode2["SLASH"] = "Slash";
KeyCode2["BACKQUOTE"] = "Backquote";
KeyCode2["BRACKET_LEFT"] = "BracketLeft";
KeyCode2["BRACKET_RIGHT"] = "BracketRight";
KeyCode2["BACKSLASH"] = "Backslash";
return KeyCode2;
})(KeyCode || {});
// src/input/keyCodeLegacy.ts
var keyCodeLegacy = /* @__PURE__ */ new Map([
[8, "Backspace" /* BACKSPACE */],
[9, "Tab" /* TAB */],
[13, "Enter" /* ENTER */],
[16, "ShiftLeft" /* SHIFT_LEFT */],
[17, "ControlLeft" /* CTRL_LEFT */],
[18, "AltLeft" /* ALT_LEFT */],
[19, "Pause" /* PAUSE */],
[20, "CapsLock" /* CAPS_LOCK */],
[27, "Escape" /* ESCAPE */],
[32, "Space" /* SPACE */],
[33, "PageUp" /* PAGE_UP */],
[34, "PageDown" /* PAGE_DOWN */],
[35, "End" /* END */],
[36, "Home" /* HOME */],
[37, "ArrowLeft" /* ARROW_LEFT */],
[38, "ArrowUp" /* ARROW_UP */],
[39, "ArrowRight" /* ARROW_RIGHT */],
[40, "ArrowDown" /* ARROW_DOWN */],
[45, "Insert" /* INSERT */],
[46, "Delete" /* DELETE */],
[48, "Digit0" /* DIGIT_0 */],
[49, "Digit1" /* DIGIT_1 */],
[50, "Digit2" /* DIGIT_2 */],
[51, "Digit3" /* DIGIT_3 */],
[52, "Digit4" /* DIGIT_4 */],
[53, "Digit5" /* DIGIT_5 */],
[54, "Digit6" /* DIGIT_6 */],
[55, "Digit7" /* DIGIT_7 */],
[56, "Digit8" /* DIGIT_8 */],
[57, "Digit9" /* DIGIT_9 */],
[65, "KeyA" /* KEY_A */],
[66, "KeyB" /* KEY_B */],
[67, "KeyC" /* KEY_C */],
[68, "KeyD" /* KEY_D */],
[69, "KeyE" /* KEY_E */],
[70, "KeyF" /* KEY_F */],
[71, "KeyG" /* KEY_G */],
[72, "KeyH" /* KEY_H */],
[73, "KeyI" /* KEY_I */],
[74, "KeyJ" /* KEY_J */],
[75, "KeyK" /* KEY_K */],
[76, "KeyL" /* KEY_L */],
[77, "KeyM" /* KEY_M */],
[78, "KeyN" /* KEY_N */],
[79, "KeyO" /* KEY_O */],
[80, "KeyP" /* KEY_P */],
[81, "KeyQ" /* KEY_Q */],
[82, "KeyR" /* KEY_R */],
[83, "KeyS" /* KEY_S */],
[84, "KeyT" /* KEY_T */],
[85, "KeyU" /* KEY_U */],
[86, "KeyV" /* KEY_V */],
[87, "KeyW" /* KEY_W */],
[88, "KeyX" /* KEY_X */],
[89, "KeyY" /* KEY_Y */],
[90, "KeyZ" /* KEY_Z */],
[91, "MetaLeft" /* META_LEFT */],
[92, "MetaRight" /* META_RIGHT */],
[93, "MediaSelect" /* MEDIA_SELECT */],
[96, "Numpad0" /* NUMPAD_0 */],
[97, "Numpad1" /* NUMPAD_1 */],
[98, "Numpad2" /* NUMPAD_2 */],
[99, "Numpad3" /* NUMPAD_3 */],
[100, "Numpad4" /* NUMPAD_4 */],
[101, "Numpad5" /* NUMPAD_5 */],
[102, "Numpad6" /* NUMPAD_6 */],
[103, "Numpad7" /* NUMPAD_7 */],
[104, "Numpad8" /* NUMPAD_8 */],
[105, "Numpad9" /* NUMPAD_9 */],
[106, "NumpadMultiply" /* NUMPAD_MULTIPLY */],
[107, "NumpadAdd" /* NUMPAD_ADD */],
[109, "NumpadSubtract" /* NUMPAD_SUBTRACT */],
[110, "NumpadDecimal" /* NUMPAD_DECIMAL */],
[111, "NumpadDivide" /* NUMPAD_DIVIDE */],
[112, "F1" /* F1 */],
[113, "F2" /* F2 */],
[114, "F3" /* F3 */],
[115, "F4" /* F4 */],
[116, "F5" /* F5 */],
[117, "F6" /* F6 */],
[118, "F7" /* F7 */],
[119, "F8" /* F8 */],
[120, "F9" /* F9 */],
[121, "F10" /* F10 */],
[122, "F11" /* F11 */],
[123, "F12" /* F12 */],
[144, "NumLock" /* NUM_LOCK */],
[145, "ScrollLock" /* SCROLL_LOCK */],
[186, "Semicolon" /* SEMICOLON */],
[187, "Equal" /* EQUAL */],
[188, "Comma" /* COMMA */],
[189, "Minus" /* MINUS */],
[190, "Period" /* PERIOD */],
[191, "Slash" /* SLASH */],
[192, "Backquote" /* BACKQUOTE */],
[219, "BracketLeft" /* BRACKET_LEFT */],
[221, "BracketRight" /* BRACKET_RIGHT */],
[220, "Backslash" /* BACKSLASH */]
]);
// src/input/PointerType.ts
var PointerType = /* @__PURE__ */ ((PointerType2) => {
PointerType2["MOUSE"] = "mouse";
PointerType2["PEN"] = "pen";
PointerType2["TOUCH"] = "touch";
return PointerType2;
})(PointerType || {});
// src/managers/RotationManager.ts
import { EventDispatcher, Matrix4, Spherical, Vector3 } from "three";
// src/core/time.ts
var MILLISECONDS_TO_SECONDS = 1 / 1e3;
// src/math/ScalarDamper.ts
var ScalarDamper = class {
/**
* The maximum speed.
*/
maxSpeed;
/**
* The current velocity.
*/
velocity;
/**
* Constructs a new scalar damper.
*
* @param maxSpeed - The maximum speed at which the value can change.
*/
constructor(maxSpeed = Number.POSITIVE_INFINITY) {
this.maxSpeed = maxSpeed;
this.velocity = 0;
}
/**
* Resets the velocity.
*/
resetVelocity() {
this.velocity = 0;
}
/**
* Smooth interpolation with exponential velocity gain/decay.
*
* @param a - The start value.
* @param b - The target value.
* @param lambda - A smoothing factor.
* @param omega - See {@link ScalarDamper.calculateOmega}.
* @param exp - See {@link ScalarDamper.calculateExp}.
* @param dt - The delta time in seconds.
* @return The interpolated value.
*/
interpolate(a, b, lambda, omega, exp, dt) {
const maxChange = this.maxSpeed * Math.max(lambda, 1e-4);
const change = Math.min(Math.max(a - b, -maxChange), maxChange);
const c = a - change;
const velocity = this.velocity;
const t = (velocity + omega * change) * dt;
this.velocity = (velocity - omega * t) * exp;
let result = c + (change + t) * exp;
if (Math.abs(change) < 1e-6) {
result = b;
this.velocity = 0;
} else if (b - a > 0 === result > b) {
this.velocity = (result - b) / dt;
result = b;
}
return result;
}
/**
* Calculates the Omega coefficient which can be reused for interpolations during the same frame.
*
* @param lambda - A smoothing factor.
* @return Omega.
*/
static calculateOmega(lambda) {
return 2 / Math.max(lambda, 1e-4);
}
/**
* Calculates the exponentional factor which can be reused for interpolations during the same frame.
*
* @param omega - See {@link ScalarDamper.calculateOmega}.
* @param dt - The delta time in seconds.
* @return The exponentional interpolation factor.
*/
static calculateExp(omega, dt) {
const x2 = omega * dt;
const x22 = x2 * x2;
return 1 / (1 + x2 + 0.48 * x22 + 0.235 * x2 * x22);
}
};
// src/managers/RotationManager.ts
var TWO_PI = 2 * Math.PI;
var u = /* @__PURE__ */ new Vector3();
var v = /* @__PURE__ */ new Vector3();
var m = /* @__PURE__ */ new Matrix4();
var RotationManager = class _RotationManager extends EventDispatcher {
/**
* Triggers when the position or quaternion is changed.
*
* @event
*/
static EVENT_UPDATE = "update";
/**
* @see {@link position}
*/
_position;
/**
* @see {@link quaternion}
*/
_quaternion;
/**
* @see {@link target}
*/
_target;
/**
* The settings.
*/
settings;
/**
* The current spherical coordinates.
*/
spherical0;
/**
* The spherical target coordinates.
*/
spherical1;
/**
* Scalar dampers.
*/
scalarDampers;
/**
* A timestamp.
*/
timestamp;
/**
* A reusable update event.
*/
updateEvent;
/**
* Constructs a new rotation manager.
*
* @param position - The position.
* @param quaternion - The quaternion.
* @param target - The target.
* @param settings - The settings.
*/
constructor(position, quaternion, target, settings) {
super();
this._position = position;
this._quaternion = quaternion;
this._target = target;
this.settings = settings;
this.spherical0 = new Spherical();
this.spherical1 = new Spherical();
this.timestamp = 0;
this.updateEvent = { type: _RotationManager.EVENT_UPDATE };
this.scalarDampers = [
new ScalarDamper(),
new ScalarDamper(),
new ScalarDamper()
];
}
/**
* The position.
*/
get position() {
return this._position;
}
set position(value) {
this._position = value;
}
/**
* The quaternion.
*/
get quaternion() {
return this._quaternion;
}
set quaternion(value) {
this._quaternion = value;
}
/**
* The target.
*/
get target() {
return this._target;
}
set target(value) {
this._target = value;
}
/**
* The current radius.
*/
get radius() {
return this.spherical0.radius;
}
/**
* Resets the current velocity.
*/
resetVelocity() {
this.spherical1.copy(this.spherical0);
for (const scalarDamper of this.scalarDampers) {
scalarDamper.resetVelocity();
}
}
/**
* Restricts the spherical angles.
*
* @return This manager.
*/
restrictAngles() {
const s = this.spherical1;
const rotation = this.settings.rotation;
const thetaMin = rotation.minAzimuthalAngle;
const thetaMax = rotation.maxAzimuthalAngle;
const phiMin = rotation.minPolarAngle;
const phiMax = rotation.maxPolarAngle;
s.theta = Math.min(Math.max(s.theta, thetaMin), thetaMax);
s.phi = Math.min(Math.max(s.phi, phiMin), phiMax);
if (s.phi === 0 || s.phi === Math.PI) {
s.makeSafe();
}
return this;
}
/**
* Restricts the spherical radius.
*
* @return This manager.
*/
restrictRadius() {
const s = this.spherical1;
const zoom = this.settings.zoom;
const min = zoom.minDistance;
const max = zoom.maxDistance;
s.radius = Math.min(Math.max(s.radius, min), max);
return this;
}
/**
* Restricts the spherical system.
*
* @return This manager.
*/
restrictSpherical() {
return this.restrictRadius().restrictAngles();
}
/**
* Updates the spherical coordinates based on the position and target.
*
* @return This manager.
*/
updateSpherical() {
if (this.settings.general.mode === "third-person" /* THIRD_PERSON */) {
const pivotOffset = this.settings.rotation.pivotOffset;
v.subVectors(u.subVectors(this.position, pivotOffset), this.target);
this.spherical1.setFromVector3(v);
} else {
this.spherical1.setFromVector3(this.target);
}
this.restrictSpherical();
this.spherical0.copy(this.spherical1);
return this;
}
/**
* Updates the position based on the spherical coordinates.
*
* @return This manager.
*/
updatePosition() {
if (this.settings.general.mode === "third-person" /* THIRD_PERSON */) {
const pivotOffset = this.settings.rotation.pivotOffset;
this.position.setFromSpherical(this.spherical0).add(this.target).add(pivotOffset);
}
return this;
}
/**
* Updates the quaternion.
*
* @return This manager.
*/
updateQuaternion() {
const settings = this.settings;
const rotation = settings.rotation;
const target = this.target;
const up = u.copy(rotation.up);
const phi = this.spherical0.phi % TWO_PI;
if (phi < 0 && phi > -Math.PI || phi > Math.PI && phi < TWO_PI) {
up.negate();
}
if (settings.general.mode === "third-person" /* THIRD_PERSON */) {
m.lookAt(v.subVectors(this.position, target), rotation.pivotOffset, up);
} else {
m.lookAt(v.set(0, 0, 0), target.setFromSpherical(this.spherical0), up);
}
this.quaternion.setFromRotationMatrix(m);
this.dispatchEvent(this.updateEvent);
return this;
}
/**
* Adjusts the spherical system.
*
* @param theta - The angle to add to theta in radians.
* @param phi - The angle to add to phi in radians.
* @return This manager.
*/
adjustSpherical(theta, phi) {
const s = this.spherical1;
const settings = this.settings;
const rotation = settings.rotation;
const invertedY = rotation.invertedY;
const orbit = settings.general.mode === "third-person" /* THIRD_PERSON */;
const orbitXorInvertedY = (orbit || invertedY) && !(orbit && invertedY);
s.theta = rotation.invertedX ? s.theta + theta : s.theta - theta;
s.phi = orbitXorInvertedY ? s.phi - phi : s.phi + phi;
return this.restrictAngles();
}
/**
* Zooms in or out. Only applies in third person mode.
*
* @param sign - The zoom sign. Possible values are [-1, 0, 1].
* @return This manager.
*/
zoom(sign) {
const s = this.spherical1;
const settings = this.settings;
const zoom = settings.zoom;
if (zoom.enabled && settings.general.mode === "third-person" /* THIRD_PERSON */) {
const amount = sign * zoom.sensitivity;
s.radius = zoom.inverted ? s.radius - amount : s.radius + amount;
this.restrictRadius();
}
return this;
}
/**
* Looks at the given point.
*
* @param point - The target point.
* @return This manager.
*/
lookAt(point) {
if (this.settings.general.mode === "third-person" /* THIRD_PERSON */) {
this.target.copy(point).sub(this.settings.rotation.pivotOffset);
} else {
this.target.subVectors(point, this.position).normalize();
}
return this;
}
/**
* Returns the current view direction.
*
* @param view - A vector to store the direction in.
* @return The normalized view direction.
*/
getViewDirection(view) {
const settings = this.settings;
const orbit = settings.general.mode === "third-person" /* THIRD_PERSON */;
view.setFromSpherical(this.spherical0).normalize();
return orbit ? view.negate() : view;
}
update(timestamp) {
const s0 = this.spherical0;
const s1 = this.spherical1;
const equal = s0.radius === s1.radius && s0.theta === s1.theta && s0.phi === s1.phi;
if (!equal) {
const settings = this.settings;
const scalarDampers = this.scalarDampers;
const elapsed = (timestamp - this.timestamp) * MILLISECONDS_TO_SECONDS;
if (settings.rotation.damping > 0) {
const damping = settings.rotation.damping;
const omega = ScalarDamper.calculateOmega(damping);
const exp = ScalarDamper.calculateExp(omega, elapsed);
s0.theta = scalarDampers[0].interpolate(s0.theta, s1.theta, damping, omega, exp, elapsed);
s0.phi = scalarDampers[1].interpolate(s0.phi, s1.phi, damping, omega, exp, elapsed);
} else {
s0.theta = s1.theta;
s0.phi = s1.phi;
}
if (settings.zoom.damping > 0) {
const damping = settings.zoom.damping;
const omega = ScalarDamper.calculateOmega(damping);
const exp = ScalarDamper.calculateExp(omega, elapsed);
s0.radius = scalarDampers[2].interpolate(s0.radius, s1.radius, damping, omega, exp, elapsed);
} else {
s0.radius = s1.radius;
}
this.updatePosition().updateQuaternion();
} else {
if (Math.abs(s0.theta) >= TWO_PI) {
s0.theta %= TWO_PI;
s1.theta %= TWO_PI;
}
if (Math.abs(s0.phi) >= TWO_PI) {
s0.phi %= TWO_PI;
s1.phi %= TWO_PI;
}
}
this.timestamp = timestamp;
}
};
// src/settings/Settings.ts
import { EventDispatcher as EventDispatcher7 } from "three";
// src/input/PointerButton.ts
var PointerButton = /* @__PURE__ */ ((PointerButton2) => {
PointerButton2[PointerButton2["MAIN"] = 0] = "MAIN";
PointerButton2[PointerButton2["AUXILIARY"] = 1] = "AUXILIARY";
PointerButton2[PointerButton2["SECONDARY"] = 2] = "SECONDARY";
return PointerButton2;
})(PointerButton || {});
// src/settings/Bindings.ts
var Bindings = class _Bindings {
/**
* The default bindings.
*/
defaultActions;
/**
* A collection that maps keys to actions.
*/
actions;
/**
* Constructs new input bindings.
*/
constructor() {
this.defaultActions = /* @__PURE__ */ new Map();
this.actions = /* @__PURE__ */ new Map();
}
/**
* Resets the current bindings to match the default bindings.
*
* @return This instance.
*/
reset() {
this.actions = new Map(this.defaultActions);
return this;
}
/**
* Establishes default bindings and resets the current bindings.
*
* @param actions - A collection that maps keys to actions.
* @return This instance.
*/
setDefault(actions) {
this.defaultActions = actions;
return this.reset();
}
/**
* Clears the default bindings.
*
* @return This instance.
*/
clearDefault() {
this.defaultActions.clear();
return this;
}
/**
* Clears the current bindings.
*
* @return This instance.
*/
clear() {
this.actions.clear();
return this;
}
/**
* Copies the given bindings, including the default bindings.
*
* @param bindings - Bindings.
* @return This instance.
*/
copy(bindings) {
this.defaultActions = new Map(bindings.defaultActions);
this.actions = new Map(bindings.actions);
return this;
}
/**
* Clones these bindings.
*
* @return The cloned bindings.
*/
clone() {
const clone = new _Bindings();
return clone.copy(this);
}
/**
* Copies the given JSON data.
*
* @param json - The JSON data.
* @return This instance.
*/
fromJSON(json) {
if (json !== void 0) {
this.defaultActions = new Map(json.defaultActions);
this.actions = new Map(json.actions);
}
return this;
}
/**
* Checks if the given key is bound to an action.
*
* @param key - A key.
* @return Whether the given key is bound to an action.
*/
has(key) {
return this.actions.has(key);
}
/**
* Returns the action that is bound to the given key.
*
* @param key - A key.
* @return The action, or undefined if the key is not bound to any action.
*/
get(key) {
return this.actions.get(key);
}
/**
* Binds a key to an action.
*
* @param key - A key.
* @param action - An action.
* @return This instance.
*/
set(key, action) {
this.actions.set(key, action);
return this;
}
/**
* Unbinds a key.
*
* @param key - The key.
* @return Whether the binding existed.
*/
delete(key) {
return this.actions.delete(key);
}
toJSON() {
return {
defaultActions: [...this.defaultActions],
actions: [...this.actions]
};
}
};
// src/settings/GeneralSettings.ts
import { EventDispatcher as EventDispatcher2 } from "three";
var GeneralSettings = class _GeneralSettings extends EventDispatcher2 {
/**
* Triggers when the settings are changed.
*
* @event
*/
static EVENT_CHANGE = "change";
/**
* @see {@link mode}
*/
_mode;
/**
* @see {@link previousMode}
*/
_previousMode;
/**
* Constructs new general settings.
*/
constructor() {
super();
this._mode = "first-person" /* FIRST_PERSON */;
this._previousMode = this._mode;
}
/**
* The previous control mode.
*
* @internal
*/
get previousMode() {
return this._previousMode;
}
/**
* The control mode.
*/
get mode() {
return this._mode;
}
set mode(value) {
if (this._mode !== value) {
this._mode = value;
this.dispatchEvent({ type: _GeneralSettings.EVENT_CHANGE });
this._previousMode = value;
}
}
/**
* Copies the given general settings.
*
* @param settings - General settings.
* @return This instance.
*/
copy(settings) {
this.mode = settings.mode;
return this;
}
/**
* Clones this general settings instance.
*
* @return The cloned general settings.
*/
clone() {
const clone = new _GeneralSettings();
return clone.copy(this);
}
/**
* Copies the given JSON data.
*
* @param json - The JSON data.
* @return This instance.
*/
fromJSON(json) {
this.mode = json.mode;
return this;
}
toJSON() {
return {
mode: this.mode
};
}
};
// src/settings/PointerSettings.ts
import { EventDispatcher as EventDispatcher3 } from "three";
var PointerSettings = class _PointerSettings extends EventDispatcher3 {
/**
* Triggers when the settings are changed.
*
* @event
*/
static EVENT_CHANGE = "change";
/**
* @see {@link behaviour}
*/
_behaviour;
/**
* @see {@link sensitivity}
*/
_sensitivity;
/**
* Constructs new pointer settings.
*/
constructor() {
super();
this._behaviour = "default" /* DEFAULT */;
this._sensitivity = 1e-3;
}
/**
* The pointer behaviour.
*/
get behaviour() {
return this._behaviour;
}
set behaviour(value) {
this._behaviour = value;
this.dispatchEvent({ type: _PointerSettings.EVENT_CHANGE });
}
/**
* Sets the sensitivity.
*
* This sensitivity acts as a baseline scale for pointer movement deltas. Default is `1e-3`.
*/
get sensitivity() {
return this._sensitivity;
}
set sensitivity(value) {
this._sensitivity = value;
this.dispatchEvent({ type: _PointerSettings.EVENT_CHANGE });
}
/**
* Copies the given pointer settings.
*
* @param settings - Pointer settings.
* @return This instance.
*/
copy(settings) {
this.behaviour = settings.behaviour;
this.sensitivity = settings.sensitivity;
return this;
}
/**
* Clones this pointer settings instance.
*
* @return The cloned pointer settings.
*/
clone() {
const clone = new _PointerSettings();
return clone.copy(this);
}
/**
* Copies the given JSON data.
*
* @param json - The JSON data.
* @return This instance.
*/
fromJSON(json) {
this.behaviour = json.behaviour;
this.sensitivity = json.sensitivity;
return this;
}
toJSON() {
return {
behaviour: this.behaviour,
sensitivity: this.sensitivity
};
}
};
// src/settings/RotationSettings.ts
import { EventDispatcher as EventDispatcher4, Vector3 as Vector33 } from "three";
// src/core/axes.ts
import { Vector3 as Vector32 } from "three";
var x = new Vector32(1, 0, 0);
var y = new Vector32(0, 1, 0);
var z = new Vector32(0, 0, 1);
// src/settings/RotationSettings.ts
var RotationSettings = class _RotationSettings extends EventDispatcher4 {
/**
* Triggers when the settings are changed.
*
* @event
*/
static EVENT_CHANGE = "change";
/**
* @see {@link enabled}
*/
_enabled;
/**
* @see {@link up}
*/
_up;
/**
* @see {@link pivotOffset}
*/
_pivotOffset;
/**
* @see {@link minAzimuthalAngle}
*/
_minAzimuthalAngle;
/**
* @see {@link maxAzimuthalAngle}
*/
_maxAzimuthalAngle;
/**
* @see {@link minPolarAngle}
*/
_minPolarAngle;
/**
* @see {@link maxPolarAngle}
*/
_maxPolarAngle;
/**
* @see {@link invertedX}
*/
_invertedX;
/**
* @see {@link invertedY}
*/
_invertedY;
/**
* @see {@link sensitivityX}
*/
_sensitivityX;
/**
* @see {@link sensitivityY}
*/
_sensitivityY;
/**
* @see {@link damping}
*/
_damping;
/**
* Constructs new rotation settings.
*/
constructor() {
super();
this._enabled = true;
this._up = new Vector33();
this._up.copy(y);
this._pivotOffset = new Vector33();
this._minAzimuthalAngle = Number.NEGATIVE_INFINITY;
this._maxAzimuthalAngle = Number.POSITIVE_INFINITY;
this._minPolarAngle = 0;
this._maxPolarAngle = Math.PI;
this._invertedX = false;
this._invertedY = false;
this._sensitivityX = 1;
this._sensitivityY = 1;
this._damping = 0;
}
/**
* Indicates whether rotation is enabled.
*/
get enabled() {
return this._enabled;
}
set enabled(value) {
this._enabled = value;
this.dispatchEvent({ type: _RotationSettings.EVENT_CHANGE });
}
/**
* A normalized up vector.
*/
get up() {
return this._up;
}
set up(value) {
this._up = value;
this.dispatchEvent({ type: _RotationSettings.EVENT_CHANGE });
}
/**
* The pivot offset.
*/
get pivotOffset() {
return this._pivotOffset;
}
set pivotOffset(value) {
this._pivotOffset = value;
this.dispatchEvent({ type: _RotationSettings.EVENT_CHANGE });
}
/**
* The minimum azimuthal angle in radians. Range: [-Math.PI, Math.PI].
*/
get minAzimuthalAngle() {
return this._minAzimuthalAngle;
}
set minAzimuthalAngle(value) {
this._minAzimuthalAngle = value;
this.dispatchEvent({ type: _RotationSettings.EVENT_CHANGE });
}
/**
* The maximum azimuthal angle in radians. Range: [-Math.PI, Math.PI].
*/
get maxAzimuthalAngle() {
return this._maxAzimuthalAngle;
}
set maxAzimuthalAngle(value) {
this._maxAzimuthalAngle = value;
this.dispatchEvent({ type: _RotationSettings.EVENT_CHANGE });
}
/**
* The minimum polar angle in radians. Range: [0, Math.PI].
*/
get minPolarAngle() {
return this._minPolarAngle;
}
set minPolarAngle(value) {
this._minPolarAngle = value;
this.dispatchEvent({ type: _RotationSettings.EVENT_CHANGE });
}
/**
* The maximum polar angle in radians. Range: [0, Math.PI].
*/
get maxPolarAngle() {
return this._maxPolarAngle;
}
set maxPolarAngle(value) {
this._maxPolarAngle = value;
this.dispatchEvent({ type: _RotationSettings.EVENT_CHANGE });
}
/**
* Indicates whether the horizontal rotation is inverted.
*/
get invertedX() {
return this._invertedX;
}
set invertedX(value) {
this._invertedX = value;
this.dispatchEvent({ type: _RotationSettings.EVENT_CHANGE });
}
/**
* Indicates whether the vertical rotation is inverted.
*/
get invertedY() {
return this._invertedY;
}
set invertedY(value) {
this._invertedY = value;
this.dispatchEvent({ type: _RotationSettings.EVENT_CHANGE });
}
/**
* The horizontal rotation sensitivity.
*/
get sensitivityX() {
return this._sensitivityX;
}
set sensitivityX(value) {
this._sensitivityX = value;
this.dispatchEvent({ type: _RotationSettings.EVENT_CHANGE });
}
/**
* The vertical rotation sensitivity.
*/
get sensitivityY() {
return this._sensitivityY;
}
set sensitivityY(value) {
this._sensitivityY = value;
this.dispatchEvent({ type: _RotationSettings.EVENT_CHANGE });
}
/**
* Sets the horizontal and vertical rotation sensitivity.
*/
set sensitivity(value) {
this._sensitivityX = this._sensitivityY = value;
this.dispatchEvent({ type: _RotationSettings.EVENT_CHANGE });
}
/**
* The damping factor.
*/
get damping() {
return this._damping;
}
set damping(value) {
this._damping = value;
this.dispatchEvent({ type: _RotationSettings.EVENT_CHANGE });
}
/**
* Copies the given rotation settings.
*
* @param settings - Rotation settings.
* @return This instance.
*/
copy(settings) {
this.up.copy(settings.up);
this.pivotOffset.copy(settings.pivotOffset);
this.minAzimuthalAngle = settings.minAzimuthalAngle;
this.maxAzimuthalAngle = settings.maxAzimuthalAngle;
this.minPolarAngle = settings.minPolarAngle;
this.maxPolarAngle = settings.maxPolarAngle;
this.invertedX = settings.invertedX;
this.invertedY = settings.invertedY;
this.sensitivityX = settings.sensitivityX;
this.sensitivityY = settings.sensitivityY;
this.damping = settings.damping;
return this;
}
/**
* Clones this rotation settings instance.
*
* @return The cloned rotation settings.
*/
clone() {
const clone = new _RotationSettings();
return clone.copy(this);
}
/**
* Copies the given JSON data.
*
* @param json - The JSON data.
* @return This instance.
*/
fromJSON(json) {
this.enabled = json.enabled;
this.up.copy(json.up);
this.pivotOffset.copy(json.pivotOffset);
this.minAzimuthalAngle = json.minAzimuthalAngle !== null ? json.minAzimuthalAngle : Number.NEGATIVE_INFINITY;
this.maxAzimuthalAngle = json.maxAzimuthalAngle !== null ? json.maxAzimuthalAngle : Number.POSITIVE_INFINITY;
this.minPolarAngle = json.minPolarAngle !== null ? json.minPolarAngle : Number.NEGATIVE_INFINITY;
this.maxPolarAngle = json.maxPolarAngle !== null ? json.maxPolarAngle : Number.POSITIVE_INFINITY;
this.invertedX = json.invertedX;
this.invertedY = json.invertedY;
this.sensitivityX = json.sensitivityX;
this.sensitivityY = json.sensitivityY;
this.damping = json.damping;
return this;
}
toJSON() {
return {
enabled: this.enabled,
up: this.up,
pivotOffset: this.pivotOffset,
minAzimuthalAngle: this.minAzimuthalAngle,
maxAzimuthalAngle: this.maxAzimuthalAngle,
minPolarAngle: this.minPolarAngle,
maxPolarAngle: this.maxPolarAngle,
invertedX: this.invertedX,
invertedY: this.invertedY,
sensitivityX: this.sensitivityX,
sensitivityY: this.sensitivityY,
damping: this.damping
};
}
};
// src/settings/TranslationSettings.ts
import { EventDispatcher as EventDispatcher5, Vector3 as Vector34 } from "three";
var TranslationSettings = class _TranslationSettings extends EventDispatcher5 {
/**
* Triggers when the settings are changed.
*
* @event
*/
static EVENT_CHANGE = "change";
/**
* @see {@link enabled}
*/
_enabled;
/**
* @see {@link sensitivity}
*/
_sensitivity;
/**
* @see {@link boostMultiplier}
*/
_boostMultiplier;
/**
* @see {@link axisModifier}
*/
_axisWeights;
/**
* @see {@link damping}
*/
_damping;
/**
* Constructs new translation settings.
*/
constructor() {
super();
this._enabled = true;
this._sensitivity = 1;
this._boostMultiplier = 2;
this._axisWeights = new Vector34(1, 1, 1);
this._damping = 0;
}
/**
* Indicates whether positional translation is enabled.
*/
get enabled() {
return this._enabled;
}
set enabled(value) {
this._enabled = value;
this.dispatchEvent({ type: _TranslationSettings.EVENT_CHANGE });
}
/**
* The translation sensitivity.
*/
get sensitivity() {
return this._sensitivity;
}
set sensitivity(value) {
this._sensitivity = value;
this.dispatchEvent({ type: _TranslationSettings.EVENT_CHANGE });
}
/**
* The translation boost multiplier.
*/
get boostMultiplier() {
return this._boostMultiplier;
}
set boostMultiplier(value) {
this._boostMultiplier = Math.max(value, 1);
this.dispatchEvent({ type: _TranslationSettings.EVENT_CHANGE });
}
/**
* Weights that influence movement along each axis.
*/
get axisWeights() {
return this._axisWeights;
}
set axisWeights(value) {
this._axisWeights = value;
this.dispatchEvent({ type: _TranslationSettings.EVENT_CHANGE });
}
/**
* The damping factor. Range is [0.0, +Infinity]. Set to 0 to disable.
*/
get damping() {
return this._damping;
}
set damping(value) {
this._damping = value;
this.dispatchEvent({ type: _TranslationSettings.EVENT_CHANGE });
}
/**
* Copies the given translation settings.
*
* @param settings - Translation settings.
* @return This instance.
*/
copy(settings) {
this.enabled = settings.enabled;
this.sensitivity = settings.sensitivity;
this.boostMultiplier = settings.boostMultiplier;
this.damping = settings.damping;
return this;
}
/**
* Clones this translation settings instance.
*
* @return The cloned translation settings.
*/
clone() {
const clone = new _TranslationSettings();
return clone.copy(this);
}
/**
* Copies the given JSON data.
*
* @param json - The JSON data.
* @return This instance.
*/
fromJSON(json) {
this.enabled = json.enabled;
this.sensitivity = json.sensitivity;
this.boostMultiplier = json.boostMultiplier;
this.damping = json.damping;
if (json.axisWeights !== void 0) {
this.axisWeights.copy(json.axisWeights);
}
return this;
}
toJSON() {
return {
enabled: this.enabled,
sensitivity: this.sensitivity,
boostMultiplier: this.boostMultiplier,
axisWeights: this.axisWeights,
damping: this.damping
};
}
};
// src/settings/ZoomSettings.ts
import { EventDispatcher as EventDispatcher6 } from "three";
var ZoomSettings = class _ZoomSettings extends EventDispatcher6 {
/**
* Triggers when the settings are changed.
*
* @event
*/
static EVENT_CHANGE = "change";
/**
* @see {@link enabled}
*/
_enabled;
/**
* @see {@link inverted}
*/
_inverted;
/**
* @see {@link minDistance}
*/
_minDistance;
/**
* @see {@link maxDistance}
*/
_maxDistance;
/**
* @see {@link sensitivity}
*/
_sensitivity;
/**
* @see {@link damping}
*/
_damping;
/**
* Constructs new zoom settings.
*/
constructor() {
super();
this._enabled = true;
this._inverted = false;
this._minDistance = 1e-6;
this._maxDistance = Number.POSITIVE_INFINITY;
this._sensitivity = 1;
this._damping = 0;
}
/**
* Indicates whether zooming is enabled.
*/
get enabled() {
return this._enabled;
}
set enabled(value) {
this._enabled = value;
this.dispatchEvent({ type: _ZoomSettings.EVENT_CHANGE });
}
/**
* Indicates whether the zoom controls should be inverted.
*/
get inverted() {
return this._inverted;
}
set inverted(value) {
this._inverted = value;
this.dispatchEvent({ type: _ZoomSettings.EVENT_CHANGE });
}
/**
* The minimum zoom distance.
*/
get minDistance() {
return this._minDistance;
}
set minDistance(value) {
this._minDistance = Math.min(
Math.max(value, 1e-6),
Number.POSITIVE_INFINITY
);
this.dispatchEvent({ type: _ZoomSettings.EVENT_CHANGE });
}
/**
* The maximum zoom distance.
*/
get maxDistance() {
return this._maxDistance;
}
set maxDistance(value) {
this._maxDistance = Math.min(
Math.max(value, this._minDistance),
Number.POSITIVE_INFINITY
);
this.dispatchEvent({ type: _ZoomSettings.EVENT_CHANGE });
}
/**
* Sets the minimum and maximum zoom distance.
*
* @param min - The minimum distance.
* @param max - The maximum distance.
*/
setRange(min, max) {
this._minDistance = min;
this._maxDistance = max;
this.dispatchEvent({ type: _ZoomSettings.EVENT_CHANGE });
}
/**
* The zoom sensitivity.
*/
get sensitivity() {
return this._sensitivity;
}
set sensitivity(value) {
this._sensitivity = value;
this.dispatchEvent({ type: _ZoomSettings.EVENT_CHANGE });
}
/**
* The damping factor.
*/
get damping() {
return this._damping;
}
set damping(value) {
this._damping = value;
this.dispatchEvent({ type: _ZoomSettings.EVENT_CHANGE });
}
/**
* Copies the given zoom settings.
*
* @param settings - Zoom settings.
* @return This instance.
*/
copy(settings) {
this.enabled = settings.enabled;
this.inverted = settings.inverted;
this.minDistance = settings.minDistance;
this.maxDistance = settings.maxDistance;
this.sensitivity = settings.sensitivity;
this.damping = settings.damping;
return this;
}
/**
* Clones this zoom settings instance.
*
* @return The cloned zoom settings.
*/
clone() {
const clone = new _ZoomSettings();
return clone.copy(this);
}
/**
* Copies the given JSON data.
*
* @param json - The JSON data.
* @return This instance.
*/
fromJSON(json) {
this.enabled = json.enabled;
this.inverted = json.inverted;
this.minDistance = json.minDistance;
this.maxDistance = json.maxDistance || Number.POSITIVE_INFINITY;
this.sensitivity = json.sensitivity;
this.damping = json.damping;
return this;
}
toJSON() {
return {
enabled: this.enabled,
inverted: this.inverted,
minDistance: this.minDistance,
maxDistance: this.maxDistance,
sensitivity: this.sensitivity,
damping: this.damping
};
}
};
// src/settings/Settings.ts
var Settings = class _Settings extends EventDispatcher7 {
/**
* Triggers when the settings are changed.
*
* @event
*/
static EVENT_CHANGE = "change";
/**
* Key bindings.
*
* This collection maps {@linkplain KeyCode key codes} to {@linkplain Action actions}.
*/
keyBindings;
/**
* Pointer bindings.
*
* This collection maps {@linkplain PointerButton pointer buttons} to {@linkplain Action actions}.
*/
pointerBindings;
/**
* General settings.
*/
general;
/**
* Pointer settings.
*/
pointer;
/**
* Rotation settings.
*/
rotation;
/**
* Translation settings.
*/
translation;
/**
* Zoom settings.
*/
zoom;
/**
* Constructs new settings.
*/
constructor() {
super();
this.keyBindings = new Bindings();
this.keyBindings.setDefault(/* @__PURE__ */ new Map([
["KeyW" /* KEY_W */, 0 /* MOVE_FORWARD */],
["KeyA" /* KEY_A */, 1 /* MOVE_LEFT */],
["KeyS" /* KEY_S */, 2 /* MOVE_BACKWARD */],
["KeyD" /* KEY_D */, 3 /* MOVE_RIGHT */],
["ArrowUp" /* ARROW_UP */, 0 /* MOVE_FORWARD */],
["ArrowLeft" /* ARROW_LEFT */, 1 /* MOVE_LEFT */],
["ArrowDown" /* ARROW_DOWN */, 2 /* MOVE_BACKWARD */],
["ArrowRight" /* ARROW_RIGHT */, 3 /* MOVE_RIGHT */],
["KeyX" /* KEY_X */, 4 /* MOVE_DOWN */],
["Space" /* SPACE */, 5 /* MOVE_UP */],
["PageDown" /* PAGE_DOWN */, 6 /* ZOOM_OUT */],
["PageUp" /* PAGE_UP */, 7 /* ZOOM_IN */],
["ShiftLeft" /* SHIFT_LEFT */, 8 /* BOOST */]
]));
this.pointerBindings = new Bindings();
this.pointerBindings.setDefault(/* @__PURE__ */ new Map([
[0 /* MAIN */, 9 /* ROTATE */]
]));
this.general = new GeneralSettings();
this.pointer = new PointerSettings();
this.rotation = new RotationSettings();
this.translation = new TranslationSettings();
this.zoom = new ZoomSettings();
this.general.addEventListener(_Settings.EVENT_CHANGE, (e) => this.dispatchEvent(e));
this.pointer.addEventListener(_Settings.EVENT_CHANGE, (e) => this.dispatchEvent(e));
this.rotation.addEventListener(_Settings.EVENT_CHANGE, (e) => this.dispatchEvent(e));
this.translation.addEventListener(_Settings.EVENT_CHANGE, (e) => this.dispatchEvent(e));
this.zoom.addEventListener(_Settings.EVENT_CHANGE, (e) => this.dispatchEvent(e));
}
/**
* Copies the given settings.
*
* @param settings - Settings.
* @return This instance.
*/
copy(settings) {
this.keyBindings.copy(settings.keyBindings);
this.pointerBindings.copy(settings.pointerBindings);
this.general.copy(settings.general);
this.pointer.copy(settings.pointer);
this.rotation.copy(settings.rotation);
this.translation.copy(settings.translation);
this.zoom.copy(settings.zoom);
this.dispatchEvent({ type: _Settings.EVENT_CHANGE });
return this;
}
/**
* Clones these settings.
*
* @return The cloned settings.
*/
clone() {
const clone = new _Settings();
return clone.copy(this);
}
/**
* Copies the given JSON data.
*
* @param json - The JSON data string.
* @return This instance.
*/
fromJSON(json) {
const settings = JSON.parse(json);
this.keyBindings.fromJSON(settings.keyBindings);
this.pointerBindings.fromJSON(settings.pointerBindings);
this.general.fromJSON(settings.general);
this.pointer.fromJSON(settings.pointer);
this.rotation.fromJSON(settings.rotation);
this.translation.fromJSON(settings.translation);
this.zoom.fromJSON(settings.zoom);
this.dispatchEvent({ type: _Settings.EVENT_CHANGE });
return this;
}
/**
* Exports these settings as a data blob.
*
* @return The settings blob.
*/
toBlob() {
return new Blob([JSON.stringify(this)], {
type: "text/json"
});
}
toJSON() {
return {
keyBindings: this.keyBindings,
pointerBindings: this.pointerBindings,
general: this.general,
pointer: this.pointer,
rotation: this.rotation,
translation: this.translation,
zoom: this.zoom
};
}
};
// src/core/RotationControls.ts
var v2 = /* @__PURE__ */ new Vector35();
var p = /* @__PURE__ */ new Vector2();
var RotationControls = class _RotationControls extends EventDispatcher8 {
/**
* Triggers when the quaternion is changed.
*
* @event
*/
static EVENT_UPDATE = "update";
/**
* @see {@link domElement}
*/
_domElement;
/**
* A rotation manager.
*/
rotationManager;
/**
* A map that links actions to specific strategies.
*/
strategies;
/**
* Indicates whether the user is currently holding the pointer button down.
*/
dragging;
/**
* @see {@link enabled}
*/
_enabled;
/**
* The control settings.
*/
settings;
/**
* Constructs new controls.
*
* @param position - A position.
* @param quaternion - A quaternion.
* @param target - A target.
* @param settings - The settings.
*/
constructor(position = new Vector35(), quaternion = new Quaternion2(), target = new Vector35(), settings = new Settings()) {
super();
this._domElement = null;
this._enabled = false;
this.dragging = false;
this.settings = settings;
settings.addEventListener("change", (e) => this.handleEvent(e));
this.rotationManager = new RotationManager(position, quaternion, target, settings);
this.rotationManager.addEventListener(_RotationControls.EVENT_UPDATE, (e) => this.dispatchEvent(e));
this.strategies = /* @__PURE__ */ new Map([
[6 /* ZOOM_OUT */, new ZoomStrategy(this.rotationManager, false)],
[7 /* ZOOM_IN */, new ZoomStrategy(this.rotationManager, true)],
[9 /* ROTATE */, new RotationStrategy(this)]
]);
}
/**
* A DOM element. Acts as the primary event target.
*/
get domElement() {
return this._domElement;
}
set domElement(value) {
this._domElement = value;
const enabled = this.enabled;
this.dispose();
this.enabled = enabled;
}
/**
* The position.
*/
get position() {
return this.rotationManager.position;
}
set position(value) {
this.rotationManager.position = value;
}
/**
* The quaternion.
*/
get quaternion() {
return this.rotationManager.quaternion;
}
set quaternion(value) {
this.rotationManager.quaternion = value;
}
/**
* The target.
*/
get target() {
return this.rotationManager.target;
}
set target(value) {
this.rotationManager.target = value;
}
/**
* Looks at the given point.
*
* @param x - The X-coordinate, or a point.
* @param y - The Y-coordinate.
* @param z - The Z-coordinate.
* @return This instance.
*/
lookAt(x2, y2, z2) {
if (x2 instanceof Vector35) {
this.rotationManager.lookAt(x2);
} else {
this.rotationManager.lookAt(v2.set(x2, y2, z2));
}
return this;
}
/**
* Returns the current view direction.
*
* @param view - A vector to store the direction in.
* @return The normalized view direction.
*/
getViewDirection(view) {
return this.rotationManager.getViewDirection(view);
}
/**
* Indicates whether the controls are enabled.
*
* Event listeners will be registered or unregistered depending on this flag.
*/
get enabled() {
return this._enabled;
}
set enabled(value) {
if (this.domElement === null || typeof document === "undefined") {
return;
}
const domElement = this.domElement;
if (value && !this._enabled) {
domElement.style.touchAction = "none";
document.addEventListener("pointerlockchange", this);
document.addEventListener("pointerlockerror", this);
document.addEventListener("visibilitychange", this);
document.body.addEventListener("keyup", this);
document.body.addEventListener("keydown", this);
domElement.addEventListener("mousedown", this);
domElement.addEventListener("mouseup", this);
domElement.addEventListener("pointerdown", this);
domElement.addEventListener("pointerup", this);
domElement.addEventListener("pointercancel", this);
domElement.addEventListener("wheel", this, { passive: true });
} else if (!value && this._enabled) {
domElement.style.touchAction = "";
document.removeEven