playcanvas
Version:
PlayCanvas WebGL game engine
214 lines (211 loc) • 6.21 kB
JavaScript
import { EventHandler } from '../../core/event-handler.js';
import { Pose } from './pose.js';
/** @import { HandleEventCallback } from '../../core/event-handler.js' */ /**
* Represents an input delta.
*
* @category Input Source
* @alpha
*/ class InputDelta {
/**
* @param {number | number[]} arg - The size of the delta or an array of initial values.
*/ constructor(arg){
if (Array.isArray(arg)) {
this._value = arg.slice();
} else {
this._value = new Array(+arg).fill(0);
}
}
/**
* Adds another InputDelta instance to this one.
*
* @param {InputDelta} other - The other InputDelta instance to add.
* @returns {InputDelta} Self for chaining.
*/ add(other) {
for(let i = 0; i < this._value.length; i++){
this._value[i] += other._value[i] || 0;
}
return this;
}
/**
* Appends offsets to the current delta values.
*
* @param {number[]} offsets - The offsets.
* @returns {InputDelta} Self for chaining.
*/ append(offsets) {
for(let i = 0; i < this._value.length; i++){
this._value[i] += offsets[i] || 0;
}
return this;
}
/**
* Copies the values from another InputDelta instance to this one.
*
* @param {InputDelta} other - The other InputDelta instance to copy from.
* @returns {InputDelta} Self for chaining.
*/ copy(other) {
for(let i = 0; i < this._value.length; i++){
this._value[i] = other._value[i] || 0;
}
return this;
}
/**
* The magnitude of the delta, calculated as the square root of the sum of squares
* of the values.
*
* @returns {number} - The magnitude of the delta.
*/ length() {
let sum = 0;
for (const value of this._value){
sum += value * value;
}
return Math.sqrt(sum);
}
/**
* Returns the current value of the delta and resets it to zero.
*
* @returns {number[]} - The current value of the delta.
*/ read() {
const value = this._value.slice();
this._value.fill(0);
return value;
}
}
/**
* Represents an input frame, which contains a map of input deltas.
*
* @category Input Source
* @alpha
*
* @template {Record<string, number[]>} T - The shape of the input frame.
*/ class InputFrame {
/**
* @param {T} data - The input frame data, where each key corresponds to an input delta.
*/ constructor(data){
/**
* @type {{ [K in keyof T]: InputDelta }}
*/ this.deltas = /** @type {{ [K in keyof T]: InputDelta }} */ {};
for(const name in data){
this.deltas[name] = new InputDelta(data[name]);
}
}
/**
* Returns the current frame state and resets the deltas to zero.
*
* @returns {{ [K in keyof T]: number[] }} - The flushed input frame with current deltas.
*/ read() {
const frame = /** @type {{ [K in keyof T]: number[] }} */ {};
for(const name in this.deltas){
frame[name] = this.deltas[name].read();
}
return frame;
}
}
/**
* The base class for all input devices.
*
* @category Input Source
* @alpha
*
* @template {Record<string, number[]>} T - The shape of the input source.
* @augments {InputFrame<T>}
*/ class InputSource extends InputFrame {
/**
* Adds an event listener for the specified event.
*
* @param {string} event - The event name to listen for.
* @param {HandleEventCallback} callback - The callback function to execute when the event is
* triggered.
*/ on(event, callback) {
this._events.on(event, callback);
}
/**
* Removes an event listener for the specified event.
*
* @param {string} event - The event name to stop listening for.
* @param {HandleEventCallback} callback - The callback function to remove.
*/ off(event, callback) {
this._events.off(event, callback);
}
/**
* Fires an event with the given name and arguments.
*
* @param {string} event - The event name to fire.
* @param {...any} args - The arguments to pass to the event listeners.
*/ fire(event, ...args) {
this._events.fire(event, ...args);
}
/**
* @param {HTMLElement} element - The element.
*/ attach(element) {
if (this._element) {
this.detach();
}
this._element = element;
}
detach() {
if (!this._element) {
return;
}
this._element = null;
this.read();
}
destroy() {
this.detach();
this._events.off();
}
constructor(...args){
super(...args), /**
* @type {HTMLElement | null}
* @protected
*/ this._element = null, /**
* @type {EventHandler}
* @private
*/ this._events = new EventHandler();
}
}
/**
* The base class for all input consumers, which are used to process input frames.
*
* @category Input Consumer
* @alpha
*/ class InputConsumer {
/**
* @param {InputFrame} frame - The input frame.
* @param {number} dt - The delta time.
* @returns {any} - The controller pose.
*/ update(frame, dt) {
// discard frame by default
frame.read();
}
}
/**
* The base class for all input controllers.
*
* @category Input Consumer
* @alpha
*/ class InputController extends InputConsumer {
/**
* @param {Pose} pose - The initial pose of the controller.
* @param {boolean} [smooth] - Whether to smooth the transition.
*/ attach(pose, smooth = true) {}
detach() {}
/**
* @param {InputFrame} frame - The input frame.
* @param {number} dt - The delta time.
* @returns {Pose} - The controller pose.
* @override
*/ update(frame, dt) {
super.update(frame, dt);
return this._pose;
}
destroy() {
this.detach();
}
constructor(...args){
super(...args), /**
* @type {Pose}
* @protected
*/ this._pose = new Pose();
}
}
export { InputConsumer, InputController, InputDelta, InputFrame, InputSource };