Source: mesh/math/vector3.js

import {_Math} from './Math';
import {Matrix4} from './Matrix4';
import {Quaternion} from './Quaternion';

/**
 *
 * @param {*} x
 * @param {*} y
 * @param {*} z
 */
function Vector3( x, y, z ) {
  this.x = x || 0;
  this.y = y || 0;
  this.z = z || 0;
}

Vector3.prototype = {

  constructor: Vector3,

  isVector3: true,

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

    return this;
  },

  setScalar: function( scalar ) {
    this.x = scalar;
    this.y = scalar;
    this.z = 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;
  },

  setComponent: function( index, value ) {
    switch ( index ) {
    case 0: this.x = value; break;
    case 1: this.y = value; break;
    case 2: this.z = 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;
    default: throw new Error( 'index is out of range: ' + index );
    }
  },

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

  copy: function( v ) {
    this.x = v.x;
    this.y = v.y;
    this.z = v.z;

    return this;
  },

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

    return this;
  },

  addScalar: function( s ) {
    this.x += s;
    this.y += s;
    this.z += 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;

    return this;
  },

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

    return this;
  },

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

    return this;
  },

  subScalar: function( s ) {
    this.x -= s;
    this.y -= s;
    this.z -= 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;

    return this;
  },

  multiply: function( v ) {
    this.x *= v.x;
    this.y *= v.y;
    this.z *= v.z;

    return this;
  },

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

    return this;
  },

  multiplyVectors: function( a, b ) {
    this.x = a.x * b.x;
    this.y = a.y * b.y;
    this.z = a.z * b.z;

    return this;
  },

  applyEuler: function() {
    let quaternion;

    return function applyEuler( euler ) {
      if ( quaternion === undefined ) quaternion = new Quaternion();

      return this.applyQuaternion( quaternion.setFromEuler( euler ) );
    };
  }(),

  applyAxisAngle: function() {
    let quaternion;

    return function applyAxisAngle( axis, angle ) {
      if ( quaternion === undefined ) quaternion = new Quaternion();

      return this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) );
    };
  }(),

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

    this.x = e[0] * x + e[3] * y + e[6] * z;
    this.y = e[1] * x + e[4] * y + e[7] * z;
    this.z = e[2] * x + e[5] * y + e[8] * z;

    return this;
  },

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

    this.x = e[0] * x + e[4] * y + e[8] * z + e[12];
    this.y = e[1] * x + e[5] * y + e[9] * z + e[13];
    this.z = e[2] * x + e[6] * y + e[10] * z + e[14];
    const w = e[3] * x + e[7] * y + e[11] * z + e[15];

    return this.divideScalar( w );
  },

  applyQuaternion: function( q ) {
    const x = this.x;
    const y = this.y;
    const z = this.z;
    const qx = q.x;
    const qy = q.y;
    const qz = q.z;
    const qw = q.w;

    // calculate quat * vector

    let ix = qw * x + qy * z - qz * y;
    let iy = qw * y + qz * x - qx * z;
    let iz = qw * z + qx * y - qy * x;
    let 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;
  },

  project: function() {
    let matrix;

    return function project( camera ) {
      if ( matrix === undefined ) matrix = new Matrix4();

      /* eslint max-len: 0 */
      matrix.multiplyMatrices( camera.projectionMatrix, matrix.getInverse( camera.matrixWorld ) );
      return this.applyMatrix4( matrix );
    };
  }(),

  unproject: function() {
    let matrix;

    return function unproject( camera ) {
      if ( matrix === undefined ) matrix = new Matrix4();

      matrix.multiplyMatrices( camera.matrixWorld, matrix.getInverse( camera.projectionMatrix ) );
      return this.applyMatrix4( matrix );
    };
  }(),

  transformDirection: function( m ) {
    // input: THREE.Matrix4 affine matrix
    // vector interpreted as a direction

    const x = this.x;
    const y = this.y;
    const 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();
  },

  divide: function( v ) {
    this.x /= v.x;
    this.y /= v.y;
    this.z /= v.z;

    return this;
  },

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

  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 );

    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 );

    return this;
  },

  clamp: function( min, max ) {
    // This function assumes min < max, if this assumption isn't true it will not operate correctly

    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;
  },

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

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

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

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

  clampLength: function( min, max ) {
    let length = this.length();

    return this.multiplyScalar( Math.max( min, Math.min( max, length ) ) / length );
  },

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

    return this;
  },

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

    return this;
  },

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

    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 );

    return this;
  },

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

    return this;
  },

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

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

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

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

  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;

    return this;
  },

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

  cross: function( v, w ) {
    if ( w !== undefined ) {
      console.warn( 'THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' );
      return this.crossVectors( v, w );
    }

    const x = this.x;
    const y = this.y;
    const 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;
  },

  crossVectors: function( a, b ) {
    const ax = a.x;
    const ay = a.y;
    const az = a.z;
    const bx = b.x;
    const by = b.y;
    const bz = b.z;

    this.x = ay * bz - az * by;
    this.y = az * bx - ax * bz;
    this.z = ax * by - ay * bx;

    return this;
  },

  projectOnVector: function( vector ) {
    let scalar = vector.dot( this ) / vector.lengthSq();

    return this.copy( vector ).multiplyScalar( scalar );
  },

  projectOnPlane: function() {
    let v1;

    return function projectOnPlane( planeNormal ) {
      if ( v1 === undefined ) v1 = new Vector3();

      v1.copy( this ).projectOnVector( planeNormal );

      return this.sub( v1 );
    };
  }(),

  reflect: function() {
    // reflect incident vector off plane orthogonal to normal
    // normal is assumed to have unit length

    let v1;

    return function reflect( normal ) {
      if ( v1 === undefined ) v1 = new Vector3();

      return this.sub( v1.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) );
    };
  }(),

  angleTo: function( v ) {
    let theta = this.dot( v ) / ( Math.sqrt( this.lengthSq() * v.lengthSq() ) );

    // clamp, to handle numerical problems

    return Math.acos( _Math.clamp( theta, - 1, 1 ) );
  },

  distanceTo: function( v ) {
    return Math.sqrt( this.distanceToSquared( v ) );
  },

  distanceToSquared: function( v ) {
    const dx = this.x - v.x;
    const dy = this.y - v.y;
    const dz = this.z - v.z;

    return dx * dx + dy * dy + dz * dz;
  },

  distanceToManhattan: function( v ) {
    return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z );
  },

  setFromSpherical: function( s ) {
    let sinPhiRadius = Math.sin( s.phi ) * s.radius;

    this.x = sinPhiRadius * Math.sin( s.theta );
    this.y = Math.cos( s.phi ) * s.radius;
    this.z = sinPhiRadius * Math.cos( s.theta );

    return this;
  },

  setFromCylindrical: function( c ) {
    this.x = c.radius * Math.sin( c.theta );
    this.y = c.y;
    this.z = c.radius * Math.cos( c.theta );

    return this;
  },

  setFromMatrixPosition: function( m ) {
    return this.setFromMatrixColumn( m, 3 );
  },

  setFromMatrixScale: function( m ) {
    let sx = this.setFromMatrixColumn( m, 0 ).length();
    let sy = this.setFromMatrixColumn( m, 1 ).length();
    let sz = this.setFromMatrixColumn( m, 2 ).length();

    this.x = sx;
    this.y = sy;
    this.z = sz;

    return this;
  },

  setFromMatrixColumn: function( m, index ) {
    if ( typeof m === 'number' ) {
      console.warn( 'THREE.Vector3: setFromMatrixColumn now expects ( matrix, index ).' );
      let temp = m;
      m = index;
      index = temp;
    }

    return this.fromArray( m.elements, index * 4 );
  },

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

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

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

    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;

    return array;
  },

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

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

    return this;
  },

};


export {Vector3};