UNPKG

yuka

Version:

JavaScript Game AI library

2,213 lines (1,622 loc) 460 kB
/** * The MIT License * * Copyright © 2022 Yuka authors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.YUKA = {})); })(this, (function (exports) { 'use strict'; /** * Class for representing a telegram, an envelope which contains a message * and certain metadata like sender and receiver. Part of the messaging system * for game entities. * * @author {@link https://github.com/Mugen87|Mugen87} */ class Telegram { /** * Constructs a new telegram object. * * @param {GameEntity} sender - The sender. * @param {GameEntity} receiver - The receiver. * @param {String} message - The actual message. * @param {Number} delay - A time value in millisecond used to delay the message dispatching. * @param {Object} data - An object for custom data. */ constructor( sender, receiver, message, delay, data ) { /** * The sender. * @type {GameEntity} */ this.sender = sender; /** * The receiver. * @type {GameEntity} */ this.receiver = receiver; /** * The actual message. * @type {String} */ this.message = message; /** * A time value in millisecond used to delay the message dispatching. * @type {Number} */ this.delay = delay; /** * An object for custom data. * @type {Object} */ this.data = data; } /** * Transforms this instance into a JSON object. * * @return {Object} The JSON object. */ toJSON() { return { type: this.constructor.name, sender: this.sender.uuid, receiver: this.receiver.uuid, message: this.message, delay: this.delay, data: this.data }; } /** * Restores this instance from the given JSON object. * * @param {Object} json - The JSON object. * @return {Telegram} A reference to this telegram. */ fromJSON( json ) { this.sender = json.sender; this.receiver = json.receiver; this.message = json.message; this.delay = json.delay; this.data = json.data; return this; } /** * Restores UUIDs with references to GameEntity objects. * * @param {Map<String,GameEntity>} entities - Maps game entities to UUIDs. * @return {Telegram} A reference to this telegram. */ resolveReferences( entities ) { this.sender = entities.get( this.sender ); this.receiver = entities.get( this.receiver ); return this; } } /* istanbul ignore next */ /** * Class with a logger interface. Messages are only logged to console if * their log level is smaller or equal than the current log level. * * @author {@link https://github.com/Mugen87|Mugen87} */ class Logger { /** * Sets the log level for the logger. Allow values are: *LOG*, * *WARN*, *ERROR*, *SILENT*. The default level is *WARN*. The constants * are accessible over the *Logger.LEVEL* namespace. * * @param {Number} level - The log level. */ static setLevel( level ) { currentLevel = level; } /** * Logs a message with the level *LOG*. * * @param {...Any} args - The arguments to log. */ static log( ...args ) { if ( currentLevel <= Logger.LEVEL.LOG ) console.log( ...args ); } /** * Logs a message with the level *WARN*. * * @param {...Any} args - The arguments to log. */ static warn( ...args ) { if ( currentLevel <= Logger.LEVEL.WARN ) console.warn( ...args ); } /** * Logs a message with the level *ERROR*. * * @param {...Any} args - The arguments to log. */ static error( ...args ) { if ( currentLevel <= Logger.LEVEL.ERROR ) console.error( ...args ); } } Logger.LEVEL = Object.freeze( { LOG: 0, WARN: 1, ERROR: 2, SILENT: 3 } ); let currentLevel = Logger.LEVEL.WARN; /** * This class is the core of the messaging system for game entities and used by the * {@link EntityManager}. The implementation can directly dispatch messages or use a * delayed delivery for deferred communication. This can be useful if a game entity * wants to inform itself about a particular event in the future. * * @author {@link https://github.com/Mugen87|Mugen87} */ class MessageDispatcher { /** * Constructs a new message dispatcher. */ constructor() { /** * A list of delayed telegrams. * @type {Array<Telegram>} * @readonly */ this.delayedTelegrams = new Array(); } /** * Delivers the message to the receiver. * * @param {Telegram} telegram - The telegram to deliver. * @return {MessageDispatcher} A reference to this message dispatcher. */ deliver( telegram ) { const receiver = telegram.receiver; if ( receiver.handleMessage( telegram ) === false ) { Logger.warn( 'YUKA.MessageDispatcher: Message not handled by receiver: %o', receiver ); } return this; } /** * Receives the raw telegram data and decides how to dispatch the telegram (with or without delay). * * @param {GameEntity} sender - The sender. * @param {GameEntity} receiver - The receiver. * @param {String} message - The actual message. * @param {Number} delay - A time value in millisecond used to delay the message dispatching. * @param {Object} data - An object for custom data. * @return {MessageDispatcher} A reference to this message dispatcher. */ dispatch( sender, receiver, message, delay, data ) { const telegram = new Telegram( sender, receiver, message, delay, data ); if ( delay <= 0 ) { this.deliver( telegram ); } else { this.delayedTelegrams.push( telegram ); } return this; } /** * Used to process delayed messages. * * @param {Number} delta - The time delta. * @return {MessageDispatcher} A reference to this message dispatcher. */ dispatchDelayedMessages( delta ) { let i = this.delayedTelegrams.length; while ( i -- ) { const telegram = this.delayedTelegrams[ i ]; telegram.delay -= delta; if ( telegram.delay <= 0 ) { this.deliver( telegram ); this.delayedTelegrams.pop(); } } return this; } /** * Clears the internal state of this message dispatcher. * * @return {MessageDispatcher} A reference to this message dispatcher. */ clear() { this.delayedTelegrams.length = 0; return this; } /** * Transforms this instance into a JSON object. * * @return {Object} The JSON object. */ toJSON() { const data = { type: this.constructor.name, delayedTelegrams: new Array() }; // delayed telegrams for ( let i = 0, l = this.delayedTelegrams.length; i < l; i ++ ) { const delayedTelegram = this.delayedTelegrams[ i ]; data.delayedTelegrams.push( delayedTelegram.toJSON() ); } return data; } /** * Restores this instance from the given JSON object. * * @param {Object} json - The JSON object. * @return {MessageDispatcher} A reference to this message dispatcher. */ fromJSON( json ) { this.clear(); const telegramsJSON = json.delayedTelegrams; for ( let i = 0, l = telegramsJSON.length; i < l; i ++ ) { const telegramJSON = telegramsJSON[ i ]; const telegram = new Telegram().fromJSON( telegramJSON ); this.delayedTelegrams.push( telegram ); } return this; } /** * Restores UUIDs with references to GameEntity objects. * * @param {Map<String,GameEntity>} entities - Maps game entities to UUIDs. * @return {MessageDispatcher} A reference to this message dispatcher. */ resolveReferences( entities ) { const delayedTelegrams = this.delayedTelegrams; for ( let i = 0, l = delayedTelegrams.length; i < l; i ++ ) { const delayedTelegram = delayedTelegrams[ i ]; delayedTelegram.resolveReferences( entities ); } return this; } } const lut = new Array(); for ( let i = 0; i < 256; i ++ ) { lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 ); } /** * Class with various math helpers. * * @author {@link https://github.com/Mugen87|Mugen87} */ class MathUtils { /** * Computes the signed area of a rectangle defined by three points. * This method can also be used to calculate the area of a triangle. * * @param {Vector3} a - The first point in 3D space. * @param {Vector3} b - The second point in 3D space. * @param {Vector3} c - The third point in 3D space. * @return {Number} The signed area. */ static area( a, b, c ) { return ( ( c.x - a.x ) * ( b.z - a.z ) ) - ( ( b.x - a.x ) * ( c.z - a.z ) ); } /** * Returns the indices of the maximum values of the given array. * * @param {Array<Number>} array - The input array. * @return {Array<Number>} Array of indices into the array. */ static argmax( array ) { const max = Math.max( ...array ); const indices = []; for ( let i = 0, l = array.length; i < l; i ++ ) { if ( array[ i ] === max ) indices.push( i ); } return indices; } /** * Returns a random sample from a given array. * * @param {Array<Any>} array - The array that is used to generate the random sample. * @param {Array<Number>} probabilities - The probabilities associated with each entry. If not given, the sample assumes a uniform distribution over all entries. * @return {Any} The random sample value. */ static choice( array, probabilities = null ) { const random = Math.random(); if ( probabilities === null ) { return array[ Math.floor( Math.random() * array.length ) ]; } else { let probability = 0; const index = array.map( ( value, index ) => { probability += probabilities[ index ]; return probability; } ).findIndex( ( probability ) => probability >= random ); return array[ index ]; } } /** * Ensures the given scalar value is within a given min/max range. * * @param {Number} value - The value to clamp. * @param {Number} min - The min value. * @param {Number} max - The max value. * @return {Number} The clamped value. */ static clamp( value, min, max ) { return Math.max( min, Math.min( max, value ) ); } /** * Computes a RFC4122 Version 4 complied Universally Unique Identifier (UUID). * * @return {String} The UUID. */ static generateUUID() { // https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript/21963136#21963136 const d0 = Math.random() * 0xffffffff | 0; const d1 = Math.random() * 0xffffffff | 0; const d2 = Math.random() * 0xffffffff | 0; const d3 = Math.random() * 0xffffffff | 0; const uuid = lut[ d0 & 0xff ] + lut[ d0 >> 8 & 0xff ] + lut[ d0 >> 16 & 0xff ] + lut[ d0 >> 24 & 0xff ] + '-' + lut[ d1 & 0xff ] + lut[ d1 >> 8 & 0xff ] + '-' + lut[ d1 >> 16 & 0x0f | 0x40 ] + lut[ d1 >> 24 & 0xff ] + '-' + lut[ d2 & 0x3f | 0x80 ] + lut[ d2 >> 8 & 0xff ] + '-' + lut[ d2 >> 16 & 0xff ] + lut[ d2 >> 24 & 0xff ] + lut[ d3 & 0xff ] + lut[ d3 >> 8 & 0xff ] + lut[ d3 >> 16 & 0xff ] + lut[ d3 >> 24 & 0xff ]; return uuid.toUpperCase(); } /** * Computes a random float value within a given min/max range. * * @param {Number} min - The min value. * @param {Number} max - The max value. * @return {Number} The random float value. */ static randFloat( min, max ) { return min + Math.random() * ( max - min ); } /** * Computes a random integer value within a given min/max range. * * @param {Number} min - The min value. * @param {Number} max - The max value. * @return {Number} The random integer value. */ static randInt( min, max ) { return min + Math.floor( Math.random() * ( max - min + 1 ) ); } } /** * Class representing a 3D vector. * * @author {@link https://github.com/Mugen87|Mugen87} */ class Vector3 { /** * Constructs a new 3D vector with the given values. * * @param {Number} x - The x component. * @param {Number} y - The y component. * @param {Number} z - The z component. */ constructor( x = 0, y = 0, z = 0 ) { /** * The x component. * @type {Number} */ this.x = x; /** * The y component. * @type {Number} */ this.y = y; /** * The z component. * @type {Number} */ this.z = z; } /** * Sets the given values to this 3D vector. * * @param {Number} x - The x component. * @param {Number} y - The y component. * @param {Number} z - The z component. * @return {Vector3} A reference to this vector. */ set( x, y, z ) { this.x = x; this.y = y; this.z = z; return this; } /** * Copies all values from the given 3D vector to this 3D vector. * * @param {Vector3} v - The vector to copy. * @return {Vector3} A reference to this vector. */ copy( v ) { this.x = v.x; this.y = v.y; this.z = v.z; return this; } /** * Creates a new 3D vector and copies all values from this 3D vector. * * @return {Vector3} A new 3D vector. */ clone() { return new this.constructor().copy( this ); } /** * Adds the given 3D vector to this 3D vector. * * @param {Vector3} v - The vector to add. * @return {Vector3} A reference to this vector. */ add( v ) { this.x += v.x; this.y += v.y; this.z += v.z; return this; } /** * Adds the given scalar to this 3D vector. * * @param {Number} s - The scalar to add. * @return {Vector3} A reference to this vector. */ addScalar( s ) { this.x += s; this.y += s; this.z += s; return this; } /** * Adds two given 3D vectors and stores the result in this 3D vector. * * @param {Vector3} a - The first vector of the operation. * @param {Vector3} b - The second vector of the operation. * @return {Vector3} A reference to this vector. */ addVectors( a, b ) { this.x = a.x + b.x; this.y = a.y + b.y; this.z = a.z + b.z; return this; } /** * Subtracts the given 3D vector from this 3D vector. * * @param {Vector3} v - The vector to substract. * @return {Vector3} A reference to this vector. */ sub( v ) { this.x -= v.x; this.y -= v.y; this.z -= v.z; return this; } /** * Subtracts the given scalar from this 3D vector. * * @param {Number} s - The scalar to substract. * @return {Vector3} A reference to this vector. */ subScalar( s ) { this.x -= s; this.y -= s; this.z -= s; return this; } /** * Subtracts two given 3D vectors and stores the result in this 3D vector. * * @param {Vector3} a - The first vector of the operation. * @param {Vector3} b - The second vector of the operation. * @return {Vector3} A reference to this vector. */ subVectors( a, b ) { this.x = a.x - b.x; this.y = a.y - b.y; this.z = a.z - b.z; return this; } /** * Multiplies the given 3D vector with this 3D vector. * * @param {Vector3} v - The vector to multiply. * @return {Vector3} A reference to this vector. */ multiply( v ) { this.x *= v.x; this.y *= v.y; this.z *= v.z; return this; } /** * Multiplies the given scalar with this 3D vector. * * @param {Number} s - The scalar to multiply. * @return {Vector3} A reference to this vector. */ multiplyScalar( s ) { this.x *= s; this.y *= s; this.z *= s; return this; } /** * Multiplies two given 3D vectors and stores the result in this 3D vector. * * @param {Vector3} a - The first vector of the operation. * @param {Vector3} b - The second vector of the operation. * @return {Vector3} A reference to this vector. */ multiplyVectors( a, b ) { this.x = a.x * b.x; this.y = a.y * b.y; this.z = a.z * b.z; return this; } /** * Divides the given 3D vector through this 3D vector. * * @param {Vector3} v - The vector to divide. * @return {Vector3} A reference to this vector. */ divide( v ) { this.x /= v.x; this.y /= v.y; this.z /= v.z; return this; } /** * Divides the given scalar through this 3D vector. * * @param {Number} s - The scalar to multiply. * @return {Vector3} A reference to this vector. */ divideScalar( s ) { this.x /= s; this.y /= s; this.z /= s; return this; } /** * Divides two given 3D vectors and stores the result in this 3D vector. * * @param {Vector3} a - The first vector of the operation. * @param {Vector3} b - The second vector of the operation. * @return {Vector3} A reference to this vector. */ divideVectors( a, b ) { this.x = a.x / b.x; this.y = a.y / b.y; this.z = a.z / b.z; return this; } /** * Reflects this vector along the given normal. * * @param {Vector3} normal - The normal vector. * @return {Vector3} A reference to this vector. */ reflect( normal ) { // solve r = v - 2( v * n ) * n return this.sub( v1$4.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); } /** * Ensures this 3D vector lies in the given min/max range. * * @param {Vector3} min - The min range. * @param {Vector3} max - The max range. * @return {Vector3} A reference to this vector. */ clamp( min, max ) { this.x = Math.max( min.x, Math.min( max.x, this.x ) ); this.y = Math.max( min.y, Math.min( max.y, this.y ) ); this.z = Math.max( min.z, Math.min( max.z, this.z ) ); return this; } /** * Compares each vector component of this 3D vector and the * given one and stores the minimum value in this instance. * * @param {Vector3} v - The 3D vector to check. * @return {Vector3} A reference to this vector. */ min( v ) { this.x = Math.min( this.x, v.x ); this.y = Math.min( this.y, v.y ); this.z = Math.min( this.z, v.z ); return this; } /** * Compares each vector component of this 3D vector and the * given one and stores the maximum value in this instance. * * @param {Vector3} v - The 3D vector to check. * @return {Vector3} A reference to this vector. */ max( v ) { this.x = Math.max( this.x, v.x ); this.y = Math.max( this.y, v.y ); this.z = Math.max( this.z, v.z ); return this; } /** * Computes the dot product of this and the given 3D vector. * * @param {Vector3} v - The given 3D vector. * @return {Number} The results of the dor product. */ dot( v ) { return ( this.x * v.x ) + ( this.y * v.y ) + ( this.z * v.z ); } /** * Computes the cross product of this and the given 3D vector and * stores the result in this 3D vector. * * @param {Vector3} v - A 3D vector. * @return {Vector3} A reference to this vector. */ cross( v ) { const x = this.x, y = this.y, z = this.z; this.x = ( y * v.z ) - ( z * v.y ); this.y = ( z * v.x ) - ( x * v.z ); this.z = ( x * v.y ) - ( y * v.x ); return this; } /** * Computes the cross product of the two given 3D vectors and * stores the result in this 3D vector. * * @param {Vector3} a - The first 3D vector. * @param {Vector3} b - The second 3D vector. * @return {Vector3} A reference to this vector. */ crossVectors( a, b ) { const ax = a.x, ay = a.y, az = a.z; const bx = b.x, by = b.y, bz = b.z; this.x = ( ay * bz ) - ( az * by ); this.y = ( az * bx ) - ( ax * bz ); this.z = ( ax * by ) - ( ay * bx ); return this; } /** * Computes the angle between this and the given vector. * * @param {Vector3} v - A 3D vector. * @return {Number} The angle in radians. */ angleTo( v ) { const denominator = Math.sqrt( this.squaredLength() * v.squaredLength() ); if ( denominator === 0 ) return 0; const theta = this.dot( v ) / denominator; // clamp, to handle numerical problems return Math.acos( MathUtils.clamp( theta, - 1, 1 ) ); } /** * Computes the length of this 3D vector. * * @return {Number} The length of this 3D vector. */ length() { return Math.sqrt( this.squaredLength() ); } /** * Computes the squared length of this 3D vector. * Calling this method is faster than calling {@link Vector3#length}, * since it avoids computing a square root. * * @return {Number} The squared length of this 3D vector. */ squaredLength() { return this.dot( this ); } /** * Computes the manhattan length of this 3D vector. * * @return {Number} The manhattan length of this 3D vector. */ manhattanLength() { return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); } /** * Computes the euclidean distance between this 3D vector and the given one. * * @param {Vector3} v - A 3D vector. * @return {Number} The euclidean distance between two 3D vectors. */ distanceTo( v ) { return Math.sqrt( this.squaredDistanceTo( v ) ); } /** * Computes the squared euclidean distance between this 3D vector and the given one. * Calling this method is faster than calling {@link Vector3#distanceTo}, * since it avoids computing a square root. * * @param {Vector3} v - A 3D vector. * @return {Number} The squared euclidean distance between two 3D vectors. */ squaredDistanceTo( v ) { const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; return ( dx * dx ) + ( dy * dy ) + ( dz * dz ); } /** * Computes the manhattan distance between this 3D vector and the given one. * * @param {Vector3} v - A 3D vector. * @return {Number} The manhattan distance between two 3D vectors. */ manhattanDistanceTo( v ) { const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; return Math.abs( dx ) + Math.abs( dy ) + Math.abs( dz ); } /** * Normalizes this 3D vector. * * @return {Vector3} A reference to this vector. */ normalize() { return this.divideScalar( this.length() || 1 ); } /** * Multiplies the given 4x4 matrix with this 3D vector * * @param {Matrix4} m - A 4x4 matrix. * @return {Vector3} A reference to this vector. */ applyMatrix4( m ) { const x = this.x, y = this.y, z = this.z; const e = m.elements; const w = 1 / ( ( e[ 3 ] * x ) + ( e[ 7 ] * y ) + ( e[ 11 ] * z ) + e[ 15 ] ); this.x = ( ( e[ 0 ] * x ) + ( e[ 4 ] * y ) + ( e[ 8 ] * z ) + e[ 12 ] ) * w; this.y = ( ( e[ 1 ] * x ) + ( e[ 5 ] * y ) + ( e[ 9 ] * z ) + e[ 13 ] ) * w; this.z = ( ( e[ 2 ] * x ) + ( e[ 6 ] * y ) + ( e[ 10 ] * z ) + e[ 14 ] ) * w; return this; } /** * Multiplies the given quaternion with this 3D vector. * * @param {Quaternion} q - A quaternion. * @return {Vector3} A reference to this vector. */ applyRotation( q ) { const x = this.x, y = this.y, z = this.z; const qx = q.x, qy = q.y, qz = q.z, qw = q.w; // calculate quat * vector const ix = qw * x + qy * z - qz * y; const iy = qw * y + qz * x - qx * z; const iz = qw * z + qx * y - qy * x; const iw = - qx * x - qy * y - qz * z; // calculate result * inverse quat this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy; this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz; this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx; return this; } /** * Extracts the position portion of the given 4x4 matrix and stores it in this 3D vector. * * @param {Matrix4} m - A 4x4 matrix. * @return {Vector3} A reference to this vector. */ extractPositionFromMatrix( m ) { const e = m.elements; this.x = e[ 12 ]; this.y = e[ 13 ]; this.z = e[ 14 ]; return this; } /** * Transform this direction vector by the given 4x4 matrix. * * @param {Matrix4} m - A 4x4 matrix. * @return {Vector3} A reference to this vector. */ transformDirection( m ) { const x = this.x, y = this.y, z = this.z; const e = m.elements; this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; return this.normalize(); } /** * Sets the components of this 3D vector from a column of a 3x3 matrix. * * @param {Matrix3} m - A 3x3 matrix. * @param {Number} i - The index of the column. * @return {Vector3} A reference to this vector. */ fromMatrix3Column( m, i ) { return this.fromArray( m.elements, i * 3 ); } /** * Sets the components of this 3D vector from a column of a 4x4 matrix. * * @param {Matrix3} m - A 4x4 matrix. * @param {Number} i - The index of the column. * @return {Vector3} A reference to this vector. */ fromMatrix4Column( m, i ) { return this.fromArray( m.elements, i * 4 ); } /** * Sets the components of this 3D vector from a spherical coordinate. * * @param {Number} radius - The radius. * @param {Number} phi - The polar or inclination angle in radians. Should be in the range of (−π/2, +π/2]. * @param {Number} theta - The azimuthal angle in radians. Should be in the range of (−π, +π]. * @return {Vector3} A reference to this vector. */ fromSpherical( radius, phi, theta ) { const sinPhiRadius = Math.sin( phi ) * radius; this.x = sinPhiRadius * Math.sin( theta ); this.y = Math.cos( phi ) * radius; this.z = sinPhiRadius * Math.cos( theta ); return this; } /** * Sets the components of this 3D vector from an array. * * @param {Array<Number>} array - An array. * @param {Number} offset - An optional offset. * @return {Vector3} A reference to this vector. */ fromArray( array, offset = 0 ) { this.x = array[ offset + 0 ]; this.y = array[ offset + 1 ]; this.z = array[ offset + 2 ]; return this; } /** * Copies all values of this 3D vector to the given array. * * @param {Array<Number>} array - An array. * @param {Number} offset - An optional offset. * @return {Array<Number>} The array with the 3D vector components. */ toArray( array, offset = 0 ) { array[ offset + 0 ] = this.x; array[ offset + 1 ] = this.y; array[ offset + 2 ] = this.z; return array; } /** * Returns true if the given 3D vector is deep equal with this 3D vector. * * @param {Vector3} v - The 3D vector to test. * @return {Boolean} The result of the equality test. */ equals( v ) { return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); } } const v1$4 = new Vector3(); const WorldUp = new Vector3( 0, 1, 0 ); const localRight = new Vector3(); const worldRight = new Vector3(); const perpWorldUp = new Vector3(); const temp = new Vector3(); const colVal = [ 2, 2, 1 ]; const rowVal = [ 1, 0, 0 ]; /** * Class representing a 3x3 matrix. The elements of the matrix * are stored in column-major order. * * @author {@link https://github.com/Mugen87|Mugen87} */ class Matrix3 { /** * Constructs a new 3x3 identity matrix. */ constructor() { /** * The elements of the matrix in column-major order. * @type {Array<Number>} */ this.elements = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]; } /** * Sets the given values to this matrix. The arguments are in row-major order. * * @param {Number} n11 - An element of the matrix. * @param {Number} n12 - An element of the matrix. * @param {Number} n13 - An element of the matrix. * @param {Number} n21 - An element of the matrix. * @param {Number} n22 - An element of the matrix. * @param {Number} n23 - An element of the matrix. * @param {Number} n31 - An element of the matrix. * @param {Number} n32 - An element of the matrix. * @param {Number} n33 - An element of the matrix. * @return {Matrix3} A reference to this matrix. */ set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { const e = this.elements; e[ 0 ] = n11; e[ 3 ] = n12; e[ 6 ] = n13; e[ 1 ] = n21; e[ 4 ] = n22; e[ 7 ] = n23; e[ 2 ] = n31; e[ 5 ] = n32; e[ 8 ] = n33; return this; } /** * Copies all values from the given matrix to this matrix. * * @param {Matrix3} m - The matrix to copy. * @return {Matrix3} A reference to this matrix. */ copy( m ) { const e = this.elements; const me = m.elements; e[ 0 ] = me[ 0 ]; e[ 1 ] = me[ 1 ]; e[ 2 ] = me[ 2 ]; e[ 3 ] = me[ 3 ]; e[ 4 ] = me[ 4 ]; e[ 5 ] = me[ 5 ]; e[ 6 ] = me[ 6 ]; e[ 7 ] = me[ 7 ]; e[ 8 ] = me[ 8 ]; return this; } /** * Creates a new matrix and copies all values from this matrix. * * @return {Matrix3} A new matrix. */ clone() { return new this.constructor().copy( this ); } /** * Transforms this matrix to an identity matrix. * * @return {Matrix3} A reference to this matrix. */ identity() { this.set( 1, 0, 0, 0, 1, 0, 0, 0, 1 ); return this; } /** * Multiplies this matrix with the given matrix. * * @param {Matrix3} m - The matrix to multiply. * @return {Matrix3} A reference to this matrix. */ multiply( m ) { return this.multiplyMatrices( this, m ); } /** * Multiplies this matrix with the given matrix. * So the order of the multiplication is switched compared to {@link Matrix3#multiply}. * * @param {Matrix3} m - The matrix to multiply. * @return {Matrix3} A reference to this matrix. */ premultiply( m ) { return this.multiplyMatrices( m, this ); } /** * Multiplies two given matrices and stores the result in this matrix. * * @param {Matrix3} a - The first matrix of the operation. * @param {Matrix3} b - The second matrix of the operation. * @return {Matrix3} A reference to this matrix. */ multiplyMatrices( a, b ) { const ae = a.elements; const be = b.elements; const e = this.elements; const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ]; const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ]; const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ]; const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ]; const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ]; const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ]; e[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31; e[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32; e[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33; e[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31; e[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32; e[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33; e[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31; e[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32; e[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33; return this; } /** * Multiplies the given scalar with this matrix. * * @param {Number} s - The scalar to multiply. * @return {Matrix3} A reference to this matrix. */ multiplyScalar( s ) { const e = this.elements; e[ 0 ] *= s; e[ 3 ] *= s; e[ 6 ] *= s; e[ 1 ] *= s; e[ 4 ] *= s; e[ 7 ] *= s; e[ 2 ] *= s; e[ 5 ] *= s; e[ 8 ] *= s; return this; } /** * Extracts the basis vectors and stores them to the given vectors. * * @param {Vector3} xAxis - The first result vector for the x-axis. * @param {Vector3} yAxis - The second result vector for the y-axis. * @param {Vector3} zAxis - The third result vector for the z-axis. * @return {Matrix3} A reference to this matrix. */ extractBasis( xAxis, yAxis, zAxis ) { xAxis.fromMatrix3Column( this, 0 ); yAxis.fromMatrix3Column( this, 1 ); zAxis.fromMatrix3Column( this, 2 ); return this; } /** * Makes a basis from the given vectors. * * @param {Vector3} xAxis - The first basis vector for the x-axis. * @param {Vector3} yAxis - The second basis vector for the y-axis. * @param {Vector3} zAxis - The third basis vector for the z-axis. * @return {Matrix3} A reference to this matrix. */ makeBasis( xAxis, yAxis, zAxis ) { this.set( xAxis.x, yAxis.x, zAxis.x, xAxis.y, yAxis.y, zAxis.y, xAxis.z, yAxis.z, zAxis.z ); return this; } /** * Creates a rotation matrix that orients an object to face towards a specified target direction. * * @param {Vector3} localForward - Specifies the forward direction in the local space of the object. * @param {Vector3} targetDirection - Specifies the desired world space direction the object should look at. * @param {Vector3} localUp - Specifies the up direction in the local space of the object. * @return {Matrix3} A reference to this matrix. */ lookAt( localForward, targetDirection, localUp ) { localRight.crossVectors( localUp, localForward ).normalize(); // orthonormal linear basis A { localRight, localUp, localForward } for the object local space worldRight.crossVectors( WorldUp, targetDirection ).normalize(); if ( worldRight.squaredLength() === 0 ) { // handle case when it's not possible to build a basis from targetDirection and worldUp // slightly shift targetDirection in order to avoid collinearity temp.copy( targetDirection ).addScalar( Number.EPSILON ); worldRight.crossVectors( WorldUp, temp ).normalize(); } perpWorldUp.crossVectors( targetDirection, worldRight ).normalize(); // orthonormal linear basis B { worldRight, perpWorldUp, targetDirection } for the desired target orientation m1.makeBasis( worldRight, perpWorldUp, targetDirection ); m2.makeBasis( localRight, localUp, localForward ); // construct a matrix that maps basis A to B this.multiplyMatrices( m1, m2.transpose() ); return this; } /** * Transposes this matrix. * * @return {Matrix3} A reference to this matrix. */ transpose() { const e = this.elements; let t; t = e[ 1 ]; e[ 1 ] = e[ 3 ]; e[ 3 ] = t; t = e[ 2 ]; e[ 2 ] = e[ 6 ]; e[ 6 ] = t; t = e[ 5 ]; e[ 5 ] = e[ 7 ]; e[ 7 ] = t; return this; } /** * Computes the element index according to the given column and row. * * @param {Number} column - Index of the column. * @param {Number} row - Index of the row. * @return {Number} The index of the element at the provided row and column. */ getElementIndex( column, row ) { return column * 3 + row; } /** * Computes the frobenius norm. It's the squareroot of the sum of all * squared matrix elements. * * @return {Number} The frobenius norm. */ frobeniusNorm() { const e = this.elements; let norm = 0; for ( let i = 0; i < 9; i ++ ) { norm += e[ i ] * e[ i ]; } return Math.sqrt( norm ); } /** * Computes the "off-diagonal" frobenius norm. Assumes the matrix is symmetric. * * @return {Number} The "off-diagonal" frobenius norm. */ offDiagonalFrobeniusNorm() { const e = this.elements; let norm = 0; for ( let i = 0; i < 3; i ++ ) { const t = e[ this.getElementIndex( colVal[ i ], rowVal[ i ] ) ]; norm += 2 * t * t; // multiply the result by two since the matrix is symetric } return Math.sqrt( norm ); } /** * Computes the eigenvectors and eigenvalues. * * Reference: https://github.com/AnalyticalGraphicsInc/cesium/blob/411a1afbd36b72df64d7362de6aa934730447234/Source/Core/Matrix3.js#L1141 (Apache License 2.0) * * The values along the diagonal of the diagonal matrix are the eigenvalues. * The columns of the unitary matrix are the corresponding eigenvectors. * * @param {Object} result - An object with unitary and diagonal properties which are matrices onto which to store the result. * @return {Object} An object with unitary and diagonal properties which are matrices onto which to store the result. */ eigenDecomposition( result ) { let count = 0; let sweep = 0; const maxSweeps = 10; result.unitary.identity(); result.diagonal.copy( this ); const unitaryMatrix = result.unitary; const diagonalMatrix = result.diagonal; const epsilon = Number.EPSILON * diagonalMatrix.frobeniusNorm(); while ( sweep < maxSweeps && diagonalMatrix.offDiagonalFrobeniusNorm() > epsilon ) { diagonalMatrix.shurDecomposition( m1 ); m2.copy( m1 ).transpose(); diagonalMatrix.multiply( m1 ); diagonalMatrix.premultiply( m2 ); unitaryMatrix.multiply( m1 ); if ( ++ count > 2 ) { sweep ++; count = 0; } } return result; } /** * Finds the largest off-diagonal term and then creates a matrix * which can be used to help reduce it. * * @param {Matrix3} result - The result matrix. * @return {Matrix3} The result matrix. */ shurDecomposition( result ) { let maxDiagonal = 0; let rotAxis = 1; // find pivot (rotAxis) based on largest off-diagonal term const e = this.elements; for ( let i = 0; i < 3; i ++ ) { const t = Math.abs( e[ this.getElementIndex( colVal[ i ], rowVal[ i ] ) ] ); if ( t > maxDiagonal ) { maxDiagonal = t; rotAxis = i; } } let c = 1; let s = 0; const p = rowVal[ rotAxis ]; const q = colVal[ rotAxis ]; if ( Math.abs( e[ this.getElementIndex( q, p ) ] ) > Number.EPSILON ) { const qq = e[ this.getElementIndex( q, q ) ]; const pp = e[ this.getElementIndex( p, p ) ]; const qp = e[ this.getElementIndex( q, p ) ]; const tau = ( qq - pp ) / 2 / qp; let t; if ( tau < 0 ) { t = - 1 / ( - tau + Math.sqrt( 1 + tau * tau ) ); } else { t = 1 / ( tau + Math.sqrt( 1.0 + tau * tau ) ); } c = 1.0 / Math.sqrt( 1.0 + t * t ); s = t * c; } result.identity(); result.elements[ this.getElementIndex( p, p ) ] = c; result.elements[ this.getElementIndex( q, q ) ] = c; result.elements[ this.getElementIndex( q, p ) ] = s; result.elements[ this.getElementIndex( p, q ) ] = - s; return result; } /** * Creates a rotation matrix from the given quaternion. * * @param {Quaternion} q - A quaternion representing a rotation. * @return {Matrix3} A reference to this matrix. */ fromQuaternion( q ) { const e = this.elements; const x = q.x, y = q.y, z = q.z, w = q.w; const x2 = x + x, y2 = y + y, z2 = z + z; const xx = x * x2, xy = x * y2, xz = x * z2; const yy = y * y2, yz = y * z2, zz = z * z2; const wx = w * x2, wy = w * y2, wz = w * z2; e[ 0 ] = 1 - ( yy + zz ); e[ 3 ] = xy - wz; e[ 6 ] = xz + wy; e[ 1 ] = xy + wz; e[ 4 ] = 1 - ( xx + zz ); e[ 7 ] = yz - wx; e[ 2 ] = xz - wy; e[ 5 ] = yz + wx; e[ 8 ] = 1 - ( xx + yy ); return this; } /** * Sets the elements of this matrix by extracting the upper-left 3x3 portion * from a 4x4 matrix. * * @param {Matrix4} m - A 4x4 matrix. * @return {Matrix3} A reference to this matrix. */ fromMatrix4( m ) { const e = this.elements; const me = m.elements; e[ 0 ] = me[ 0 ]; e[ 1 ] = me[ 1 ]; e[ 2 ] = me[ 2 ]; e[ 3 ] = me[ 4 ]; e[ 4 ] = me[ 5 ]; e[ 5 ] = me[ 6 ]; e[ 6 ] = me[ 8 ]; e[ 7 ] = me[ 9 ]; e[ 8 ] = me[ 10 ]; return this; } /** * Sets the elements of this matrix from an array. * * @param {Array<Number>} array - An array. * @param {Number} offset - An optional offset. * @return {Matrix3} A reference to this matrix. */ fromArray( array, offset = 0 ) { const e = this.elements; for ( let i = 0; i < 9; i ++ ) { e[ i ] = array[ i + offset ]; } return this; } /** * Copies all elements of this matrix to the given array. * * @param {Array<Number>} array - An array. * @param {Number} offset - An optional offset. * @return {Array<Number>} The array with the elements of the matrix. */ toArray( array, offset = 0 ) { const e = this.elements; array[ offset + 0 ] = e[ 0 ]; array[ offset + 1 ] = e[ 1 ]; array[ offset + 2 ] = e[ 2 ]; array[ offset + 3 ] = e[ 3 ]; array[ offset + 4 ] = e[ 4 ]; array[ offset + 5 ] = e[ 5 ]; array[ offset + 6 ] = e[ 6 ]; array[ offset + 7 ] = e[ 7 ]; array[ offset + 8 ] = e[ 8 ]; return array; } /** * Returns true if the given matrix is deep equal with this matrix. * * @param {Matrix3} m - The matrix to test. * @return {Boolean} The result of the equality test. */ equals( m ) { const e = this.elements; const me = m.elements; for ( let i = 0; i < 9; i ++ ) { if ( e[ i ] !== me[ i ] ) return false; } return true; } } const m1 = new Matrix3(); const m2 = new Matrix3(); const matrix$1 = new Matrix3(); const vector$1 = new Vector3(); /** * Class representing a quaternion. * * @author {@link https://github.com/Mugen87|Mugen87} */ class Quaternion { /** * Constructs a new quaternion with the given values. * * @param {Number} x - The x component. * @param {Number} y - The y component. * @param {Number} z - The z component. * @param {Number} w - The w component. */ constructor( x = 0, y = 0, z = 0, w = 1 ) { /** * The x component. * @type {Number} */ this.x = x; /** * The y component. * @type {Number} */ this.y = y; /** * The z component. * @type {Number} */ this.z = z; /** * The w component. * @type {Number} */ this.w = w; } /** * Sets the given values to this quaternion. * * @param {Number} x - The x component. * @param {Number} y - The y component. * @param {Number} z - The z component. * @param {Number} w - The w component. * @return {Quaternion} A reference to this quaternion. */ set( x, y, z, w ) { this.x = x; this.y = y; this.z = z; this.w = w; return this; } /** * Copies all values from the given quaternion to this quaternion. * * @param {Quaternion} q - The quaternion to copy. * @return {Quaternion} A reference to this quaternion. */ copy( q ) { this.x = q.x; this.y = q.y; this.z = q.z; this.w = q.w; return this; } /** * Creates a new quaternion and copies all values from this quaternion. * * @return {Quaternion} A new quaternion. */ clone() { return new this.constructor().copy( this ); } /** * Computes the inverse of this quaternion. * * @return {Quaternion} A reference to this quaternion. */ inverse() { return this.conjugate().normalize(); } /** * Computes the conjugate of this quaternion. * * @return {Quaternion} A reference to this quaternion. */ conjugate() { this.x *= - 1; this.y *= - 1; this.z *= - 1; return this; } /** * Computes the dot product of this and the given quaternion. * * @param {Quaternion} q - The given quaternion. * @return {Quaternion} A reference to this quaternion. */ dot( q ) { return ( this.x * q.x ) + ( this.y * q.y ) + ( this.z * q.z ) + ( this.w * q.w ); } /** * Computes the length of this quaternion. * * @return {Number} The length of this quaternion. */ length() { return Math.sqrt( this.squaredLength() ); } /** * Computes the squared length of this quaternion. * * @return {Number} The squared length of this quaternion. */ squaredLength() { return this.dot( this ); } /** * Normalizes this quaternion. * * @return {Quaternion} A reference to this quaternion. */ normalize() { let l = this.length(); if ( l === 0 ) { this.x = 0; this.y = 0; this.z = 0; this.w = 1; } else { l = 1 / l; this.x = this.x * l; this.y = this.y * l; this.z = this.z * l; this.w = this.w * l; } return this; } /** * Multiplies this quaternion with the given quaternion. * * @param {Quaternion} q - The quaternion to multiply. * @return {Quaternion} A reference to this quaternion. */ multiply( q ) { return this.multiplyQuaternions( this, q ); } /** * Multiplies the given quaternion with this quaternion. * So the order of the multiplication is switched compared to {@link Quaternion#multiply}. * * @param {Quaternion} q - The quaternion to multiply. * @return {Quaternion} A reference to this quaternion. */ premultiply( q ) { return this.multiplyQuaternions( q, this ); } /** * Multiplies two given quaternions and stores the result in this quaternion. * * @param {Quaternion} a - The first quaternion of the operation. * @param {Quaternion} b - The second quaternion of the operation. * @return {Quaternion} A reference to this quaternion. */ multiplyQuaternions( a, b ) { const qax = a.x, qay = a.y, qaz = a.z, qaw = a.w; const qbx = b.x, qby = b.y, qbz = b.z, qbw = b.w; this.x = ( qax * qbw ) + ( qaw * qbx ) + ( qay * qbz ) - ( qaz * qby ); this.y = ( qay * qbw ) + ( qaw * qby ) + ( qaz * qbx ) - ( qax * qbz ); this.z = ( qaz * qbw ) + ( qaw * qbz ) + ( qax * qby ) - ( qay * qbx ); this.w = ( qaw * qbw ) - ( qax * qbx ) - ( qay * qby ) - ( qaz * qbz ); return this; } /** * Computes the shortest angle between two rotation defined by this quaternion and the given one. * * @param {Quaternion} q - The given quaternion. * @return {Number} The angle in radians. */ angleTo( q ) { return 2 * Math.acos( Math.abs( MathUtils.clamp( this.dot( q ), - 1, 1 ) ) ); } /** * Transforms this rotation defined by this quaternion towards the target rotation * defined by the given quaternion by the given angular step. The rotation will not overshoot. * * @param {Quaternion} q - The target rotation. * @param {Number} step - The maximum step in radians. * @param {Number} tolerance - A tolerance value in radians to tweak the result * when both rotations are considered to be equal. * @return {Boolean} Whether the given quaternion already represents the target rotation. */ rotateTo( q, step, tolerance = 0.0001 ) { const angle = this.angleTo( q ); if ( angle < tolerance ) return true; const t = Math.min( 1, step / angle ); this.slerp( q, t ); return false; } /** * Creates a quaternion that orients an object to face towards a specified target direction. * * @param {Vector3} localForward - Specifies the forward direction in the local space of the object. * @param {Vector3} targetDirection - Specifies the desired world space direction the object should look at. * @param {Vector3} localUp - Specifies the up direction in the local space of the object. * @return {Quaternion} A reference to this quaternion. */ lookAt( localForward, targetDirection, localUp ) { matrix$1.lookAt( localForward, targetDirection, localUp ); this.fromMatrix3( matrix$1 ); } /** * Spherically interpolates between this quaternion and the given quaternion by t. * The parameter t is clamped to the range [0, 1]. * * @param {Quaternion} q - The target rotation. * @param {Number} t - The interpolation parameter. * @return {Quaternion} A reference to this quaternion. */ slerp( q, t ) { if ( t === 0 ) return this; if ( t === 1 ) return this.copy( q ); const x = this.x, y = this.y, z = this.z, w = this.w; let cosHalfTheta = w * q.w + x * q.x + y * q.y + z * q.z; if ( cosHalfTheta < 0 ) { this.w = - q.w; this.x = - q.x; this.y = - q.y; this.z = - q.z; cosHalfTheta = - cosHalfTheta; } else { this.copy( q ); } if ( cosHalfTheta >= 1.0 ) { this.w = w; this.x = x; this.y = y; this.z = z; return this; } const sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta ); if ( Math.abs( sinHalfTheta ) < 0.001 ) { this.w = 0.5 * ( w + this.w ); this.x = 0.5 * ( x + this.x ); this.y = 0.5 * ( y + this.y ); this.z = 0.5 * ( z + this.z ); return this; } const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta ); const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta; const ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; this.w = ( w * ratioA ) + ( this.w * ratioB ); this.x = ( x * ratioA ) + ( this.x * ratioB ); this.y = ( y * ratioA ) + ( this.y * ratioB ); this.z = ( z * ratioA ) + ( this.z * ratioB ); return this; } /** * Extracts the rotation of the given 4x4 matrix and stores it in this quaternion. * * @param {Matrix4} m - A 4x4 matrix. * @return {Quaternion} A reference to this quaternion. */ extractRotationFromMatrix( m ) { const e = matrix$1.elements; const me = m.elements; // remove scaling from the 3x3 portion const sx = 1 / vector$1.fromMatrix4Column( m, 0 ).length(); const sy = 1 / vector$1.fromMatrix4Column( m, 1 ).length(); const sz = 1 / vector$1.fromMatrix4Column( m, 2 ).length(); e[ 0 ] = me[ 0 ] * sx; e[ 1 ] = me[ 1 ] * sx; e[ 2 ] = me[ 2 ] * sx; e[ 3 ] = me[ 4 ] * sy; e[ 4 ] = me[ 5 ] * sy; e[ 5 ] = me[ 6 ] * sy; e[ 6 ] = me[ 8 ] * sz; e[ 7 ] = me[ 9 ] * sz; e[ 8 ] = me[ 10 ] * sz; this.fromMatrix3( matrix$1 ); return this; } /** * Sets the components of this quaternion from the given euler angle (YXZ order). * * @param {Number} x - Rotation around x axis in radians. * @param {Number} y - Rotation around y axis in radians. * @param {Number} z - Rotation around z axis in radians. * @return {Quaternion} A reference to this quaternion. */ fromEuler( x, y, z ) { // from 3D Math Primer for Graphics and Game Development // 8.7.5 Converting Euler Angles to a Quaternion // assuming YXZ (head/pitch/bank or yaw/pitch/roll) order const c1 =