Source: mesh/math/vector4.js


/**
 *
 * @param {*} x
 * @param {*} y
 * @param {*} z
 * @param {*} w
 */
function Vector4( x, y, z, w ) {
  this.x = x || 0;
  this.y = y || 0;
  this.z = z || 0;
  this.w = ( w !== undefined ) ? w : 1;
}

Vector4.prototype = {

  constructor: Vector4,

  isVector4: true,

  set: function( x, y, z, w ) {
    this.x = x;
    this.y = y;
    this.z = z;
    this.w = w;

    return this;
  },

  setScalar: function( scalar ) {
    this.x = scalar;
    this.y = scalar;
    this.z = scalar;
    this.w = scalar;

    return this;
  },

  setX: function( x ) {
    this.x = x;

    return this;
  },

  setY: function( y ) {
    this.y = y;

    return this;
  },

  setZ: function( z ) {
    this.z = z;

    return this;
  },

  setW: function( w ) {
    this.w = w;

    return this;
  },

  setComponent: function( index, value ) {
    switch ( index ) {
    case 0: this.x = value; break;
    case 1: this.y = value; break;
    case 2: this.z = value; break;
    case 3: this.w = value; break;
    default: throw new Error( 'index is out of range: ' + index );
    }

    return this;
  },

  getComponent: function( index ) {
    switch ( index ) {
    case 0: return this.x;
    case 1: return this.y;
    case 2: return this.z;
    case 3: return this.w;
    default: throw new Error( 'index is out of range: ' + index );
    }
  },

  clone: function() {
    return new this.constructor( this.x, this.y, this.z, this.w );
  },

  copy: function( v ) {
    this.x = v.x;
    this.y = v.y;
    this.z = v.z;
    this.w = ( v.w !== undefined ) ? v.w : 1;

    return this;
  },

  add: function( v ) {
    this.x += v.x;
    this.y += v.y;
    this.z += v.z;
    this.w += v.w;

    return this;
  },

  addScalar: function( s ) {
    this.x += s;
    this.y += s;
    this.z += s;
    this.w += s;

    return this;
  },

  addVectors: function( a, b ) {
    this.x = a.x + b.x;
    this.y = a.y + b.y;
    this.z = a.z + b.z;
    this.w = a.w + b.w;

    return this;
  },

  addScaledVector: function( v, s ) {
    this.x += v.x * s;
    this.y += v.y * s;
    this.z += v.z * s;
    this.w += v.w * s;

    return this;
  },

  sub: function( v ) {
    this.x -= v.x;
    this.y -= v.y;
    this.z -= v.z;
    this.w -= v.w;

    return this;
  },

  subScalar: function( s ) {
    this.x -= s;
    this.y -= s;
    this.z -= s;
    this.w -= s;

    return this;
  },

  subVectors: function( a, b ) {
    this.x = a.x - b.x;
    this.y = a.y - b.y;
    this.z = a.z - b.z;
    this.w = a.w - b.w;

    return this;
  },

  multiplyScalar: function( scalar ) {
    if ( isFinite( scalar ) ) {
      this.x *= scalar;
      this.y *= scalar;
      this.z *= scalar;
      this.w *= scalar;
    } else {
      this.x = 0;
      this.y = 0;
      this.z = 0;
      this.w = 0;
    }

    return this;
  },

  applyMatrix4: function( m ) {
    const x = this.x;
    const y = this.y;
    const z = this.z;
    const w = this.w;
    const e = m.elements;

    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;
    this.w = e[3] * x + e[7] * y + e[11] * z + e[15] * w;

    return this;
  },

  divideScalar: function( scalar ) {
    return this.multiplyScalar( 1 / scalar );
  },

  setAxisAngleFromQuaternion: function( q ) {
    // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm

    // q is assumed to be normalized

    this.w = 2 * Math.acos( q.w );

    let s = Math.sqrt( 1 - q.w * q.w );

    if ( s < 0.0001 ) {
      this.x = 1;
      this.y = 0;
      this.z = 0;
    } else {
      this.x = q.x / s;
      this.y = q.y / s;
      this.z = q.z / s;
    }

    return this;
  },

  setAxisAngleFromRotationMatrix: function( m ) {
    // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm

    // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)

    let angle;
    let x;
    let y;
    let z; // variables for result
    const epsilon = 0.01; // margin to allow for rounding errors
    const epsilon2 = 0.1; // margin to distinguish between 0 and 180 degrees

    const te = m.elements;

    const m11 = te[0];
    const m12 = te[4];
    const m13 = te[8];
    const m21 = te[1];
    const m22 = te[5];
    const m23 = te[9];
    const m31 = te[2];
    const m32 = te[6];
    const m33 = te[10];

    if ( ( Math.abs( m12 - m21 ) < epsilon ) &&
         ( Math.abs( m13 - m31 ) < epsilon ) &&
         ( Math.abs( m23 - m32 ) < epsilon ) ) {
      // singularity found
      // first check for identity matrix which must have +1 for all terms
      // in leading diagonal and zero in other terms

      if ( ( Math.abs( m12 + m21 ) < epsilon2 ) &&
           ( Math.abs( m13 + m31 ) < epsilon2 ) &&
           ( Math.abs( m23 + m32 ) < epsilon2 ) &&
           ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) {
        // this singularity is identity matrix so angle = 0

        this.set( 1, 0, 0, 0 );

        return this; // zero angle, arbitrary axis
      }

      // otherwise this singularity is angle = 180

      angle = Math.PI;

      let xx = ( m11 + 1 ) / 2;
      let yy = ( m22 + 1 ) / 2;
      let zz = ( m33 + 1 ) / 2;
      let xy = ( m12 + m21 ) / 4;
      let xz = ( m13 + m31 ) / 4;
      let yz = ( m23 + m32 ) / 4;

      if ( ( xx > yy ) && ( xx > zz ) ) {
        // m11 is the largest diagonal term

        if ( xx < epsilon ) {
          x = 0;
          y = 0.707106781;
          z = 0.707106781;
        } else {
          x = Math.sqrt( xx );
          y = xy / x;
          z = xz / x;
        }
      } else if ( yy > zz ) {
        // m22 is the largest diagonal term

        if ( yy < epsilon ) {
          x = 0.707106781;
          y = 0;
          z = 0.707106781;
        } else {
          y = Math.sqrt( yy );
          x = xy / y;
          z = yz / y;
        }
      } else {
        // m33 is the largest diagonal term so base result on this

        if ( zz < epsilon ) {
          x = 0.707106781;
          y = 0.707106781;
          z = 0;
        } else {
          z = Math.sqrt( zz );
          x = xz / z;
          y = yz / z;
        }
      }

      this.set( x, y, z, angle );

      return this; // return 180 deg rotation
    }

    let s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) +
                       ( m13 - m31 ) * ( m13 - m31 ) +
                       ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize

    if ( Math.abs( s ) < 0.001 ) s = 1;

    this.x = ( m32 - m23 ) / s;
    this.y = ( m13 - m31 ) / s;
    this.z = ( m21 - m12 ) / s;
    this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 );

    return this;
  },

  min: function( 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 );
    this.w = Math.min( this.w, v.w );

    return this;
  },

  max: function( 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 );
    this.w = Math.max( this.w, v.w );

    return this;
  },

  clamp: function( 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 ) );
    this.w = Math.max( min.w, Math.min( max.w, this.w ) );

    return this;
  },

  clampScalar: function() {
    let min;
    let max;

    return function clampScalar( minVal, maxVal ) {
      if ( min === undefined ) {
        min = new Vector4();
        max = new Vector4();
      }

      min.set( minVal, minVal, minVal, minVal );
      max.set( maxVal, maxVal, maxVal, maxVal );

      return this.clamp( min, max );
    };
  }(),

  floor: function() {
    this.x = Math.floor( this.x );
    this.y = Math.floor( this.y );
    this.z = Math.floor( this.z );
    this.w = Math.floor( this.w );

    return this;
  },

  ceil: function() {
    this.x = Math.ceil( this.x );
    this.y = Math.ceil( this.y );
    this.z = Math.ceil( this.z );
    this.w = Math.ceil( this.w );

    return this;
  },

  round: function() {
    this.x = Math.round( this.x );
    this.y = Math.round( this.y );
    this.z = Math.round( this.z );
    this.w = Math.round( this.w );

    return this;
  },

  roundToZero: function() {
    this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );
    this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );
    this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z );
    this.w = ( this.w < 0 ) ? Math.ceil( this.w ) : Math.floor( this.w );

    return this;
  },

  negate: function() {
    this.x = - this.x;
    this.y = - this.y;
    this.z = - this.z;
    this.w = - this.w;

    return this;
  },

  dot: function( v ) {
    return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w;
  },

  lengthSq: function() {
    /* eslint max-len: 0*/
    return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
  },

  length: function() {
    return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w );
  },

  lengthManhattan: function() {
    return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w );
  },

  normalize: function() {
    return this.divideScalar( this.length() );
  },

  setLength: function( length ) {
    return this.multiplyScalar( length / this.length() );
  },

  lerp: function( v, alpha ) {
    this.x += ( v.x - this.x ) * alpha;
    this.y += ( v.y - this.y ) * alpha;
    this.z += ( v.z - this.z ) * alpha;
    this.w += ( v.w - this.w ) * alpha;

    return this;
  },

  lerpVectors: function( v1, v2, alpha ) {
    return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 );
  },

  equals: function( v ) {
    return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) );
  },

  fromArray: function( array, offset ) {
    if ( offset === undefined ) offset = 0;

    this.x = array[offset];
    this.y = array[offset + 1];
    this.z = array[offset + 2];
    this.w = array[offset + 3];

    return this;
  },

  toArray: function( array, offset ) {
    if ( array === undefined ) array = [];
    if ( offset === undefined ) offset = 0;

    array[offset] = this.x;
    array[offset + 1] = this.y;
    array[offset + 2] = this.z;
    array[offset + 3] = this.w;

    return array;
  },

  fromBufferAttribute: function( attribute, index, offset ) {
    if ( offset !== undefined ) {
      console.warn( 'THREE.Vector4: offset has been removed from .fromBufferAttribute().' );
    }

    this.x = attribute.getX( index );
    this.y = attribute.getY( index );
    this.z = attribute.getZ( index );
    this.w = attribute.getW( index );

    return this;
  },

};


export {Vector4};