// --------------------------------------------------------------------
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// --------------------------------------------------------------------

package tinylib;

/**
  Point3D: 3-dimensional position of vector corresponding

  This class is derived from Sandy Point3D.
**/

class Point3D {
  /**
    A small value. If the `norm()` is smaller than this value, `normalize()`
    function do `clear()` instead of normalization.
  **/
  public static inline var EPSILON = 1.0e-10;

  /**
    Generate Point3D from String.

    The string should have at least three space-separated float values.
    For example, "1.0 2.0 3.0" and " 1.0 2.0 3.0   " are valid.
  **/
  static public function fromString( s:String ):Point3D {
    var v0:EReg = ~/^\s*/;
    var v1:EReg = ~/\s*$/;
    var v2:EReg = ~/\s+/g;
    var mystr:String = v0.replace( s, "" );
    mystr = v1.replace( mystr, "" );
    mystr = v2.replace( mystr, " " );
    var sa:Array< String > = mystr.split( " " );
    if ( sa.length < 3 ) {
      trace( "position must have three numbers" );
      return( new Point3D() );
    }
    return( new Point3D( Std.parseFloat( sa[0] ), Std.parseFloat( sa[1] ),
                         Std.parseFloat( sa[2] ) ) );
  }

  /**
    Generate Point3D from String. See also `fromString()` function.

    The only difference of this function from `fromString()` is
    z-value is inverted in this function.
  **/
  static public function fromStringInverted( s:String ):Point3D {
    var ret = Point3D.fromString( s );
    ret.z = - ret.z;
    // note: mirror image of the real world coordinate system
    return( ret );
  }

  /**
    returns new Point3D of `p0` + `p1`
  **/
  static public function getAdd( p0:Point3D,
                                 p1:Point3D ):Point3D {
    var ret:Point3D = p0.clone();
    ret.add( p1 );
    return( ret );
  }

  /**
    returns new Point3D of `p0` - `p1`
  **/
  static public function getSub( p0:Point3D,
                                 p1:Point3D ):Point3D {
    var ret:Point3D = p0.clone();
    ret.sub( p1 );
    return( ret );
  }

  /**
    returns new Point3D of `f` * `p`
  **/
  static public function getMultiply( p:Point3D,
                                      f:Float ):Point3D {
    var ret:Point3D = p.clone();
    ret.multiply( f );
    return( ret );
  }

  /**
    returns new Point3D of vector product `p0` * `p1`
  **/
  static public function getCross( p0:Point3D,
                                   p1:Point3D ):Point3D {
    var ret:Point3D = p0.clone();
    ret.cross( p1 );
    return( ret );
  }

  /**
    returns middle point of `p0` and `p1`.
    Default of the optional values are:

    - c: false
    - r: 0.0
    - o: null

    If `c` is false (default), returns ( `p0` + `p1` ) / 2.

    If `c` is true and `o` == null (default), returns
    `r` * ( `p0` + `p1` ) / | `p0` + `p1` |.

    If `c` is true and `o` != null, returns
    `r` * ( `p0` + `p1` - 2`o`) / | `p0` + `p1` - 2`o`| + `o`. `o` is used as
    the origin.
  **/
  static public function getMiddle( p0:Point3D,
                                    p1:Point3D,
                                    ?c:Bool = false,
                                    ?r:Float = 0.0,
                                    ?o:Point3D = null ):Point3D {
    if ( !c ) {
      var ret:Point3D = p0.clone().add( p1 );
      ret.multiply( 0.5 );
      return( ret );
    }
    if ( o == null ) {
      var ret:Point3D = Point3D.getAdd( p0, p1 );
      ret.normalize();
      ret.multiply( r );
      return( ret );
    }
    var ret:Point3D = Point3D.getAdd( p0, p1 );
    ret.sub( o );
    ret.sub( o );
    ret.normalize();
    ret.multiply( r );
    ret.add( o );
    return( ret );
  }

  // ###################################################################
  /**
    x component
  **/
  @:isVar public var x( get, set ):Float;
    /**
      getter of `x`
    **/
    public function get_x():Float { return( x ); }
    /**
      setter of `x`
    **/
    public function set_x( v:Float ):Float {
      x = v;
      return( x );
    }
  /**
    y component
  **/
  @:isVar public var y( get, set ):Float;
    /**
      getter of `y`
    **/
    public function get_y():Float { return( y ); }
    /**
      setter of `y`
    **/
    public function set_y( v:Float ):Float {
      y = v;
      return( y );
    }
  /**
    z component
  **/
  @:isVar public var z( get, set ):Float;
    /**
      getter of `z`
    **/
    public function get_z():Float { return( z ); }
    /**
      setter of `z`
    **/
    public function set_z( v:Float ):Float {
      z = v;
      return( z );
    }

  // ###################################################################

  /**
    Constructor. Initial value can be assigned by `vx`, `vy`, and `vz`.
  **/
  public function new( ?vx:Float = 0.0,
                       ?vy:Float = 0.0,
                       ?vz:Float = 0.0 ) {
    x = vx;
    y = vy;
    z = vz;
  }

  /**
    alias of `nullify`
  **/
  public function clear():Void {
    nullify();
  }

  /**
    x = y = z = 0.0
  **/
  public function nullify():Void {
    x = y = z = 0.0;
  }

  /**
    returns a copy of this instance
  **/
  public function clone():Point3D {
    var ret:Point3D = new Point3D();
    ret.x = x;
    ret.y = y;
    ret.z = z;
    return( ret );
  }

  /**
    add `p` to `this` instance and return `this`
  **/
  public function add( p:Point3D ):Point3D {
    x += p.x;
    y += p.y;
    z += p.z;
    return( this );
  }

  /**
    subtract `p` from `this` instance and return `this`
  **/
  public function sub( p:Point3D ):Point3D {
    x -= p.x;
    y -= p.y;
    z -= p.z;
    return( this );
  }

  /**
    multiply float value `f` to `this` instance and return `this`
  **/
  public function multiply( f:Float ):Point3D {
    x *= f;
    y *= f;
    z *= f;
    return( this );
  }

  /**
    returns dot product of `this` and `p`
  **/
  public function dot( p:Point3D ):Float {
    return( x * p.x + y * p.y + z * p.z );
  }

  /**
    calculate vector product of `this` and `p` and replace `this`.
    return value is `this`.
  **/
  public function cross( p:Point3D ):Point3D {
    var pc:Point3D = clone();
    x = pc.y * p.z - pc.z * p.y;
    y = pc.z * p.x - pc.x * p.z;
    z = pc.x * p.y - pc.y * p.x;
    return( this );
  }

  /**
    returns x*x + y*y + z*z
  **/
  public function sqabs():Float {
    return( x * x + y * y + z * z );
  }

  /**
    returns norm
  **/
  public function norm():Float {
    return( Math.sqrt( sqabs() ) );
  }

  /**
    normalize `this`
  **/
  public function normalize():Point3D {
    var n:Float = norm();
    if ( n < Point3D.EPSILON ) {
      clear();
    } else {
      multiply( 1.0 / n );
    }
    return( this );
  }

  /**
    returns angle between `this` and `p`.

    The vlaue is calculated as acos( `this` * `p` / ( norm() * p.norm() ) ).
  **/
  public function getAngle( p:Point3D ):Float {
    return ( Math.acos( dot( p ) / Math.sqrt( sqabs() * p.sqabs() ) ) );
  }

  /**
    remove a vector parallel to `p` from `this` instance.
  **/
  public function removeParallel( p:Point3D ):Point3D {
    var ret:Point3D = this.clone();
    ret.normalize();
    var pc:Point3D = p.clone();
    pc.normalize();
    var norm:Float = ret.dot( pc );
    pc.multiply( norm );
    ret.sub( pc );
    this.x = ret.x;
    this.y = ret.y;
    this.z = ret.z;
    return( ret );
  }

  #if flash
  /**
    convert Point3D (x,y,z) to flash.geom.Vector3D (x,y,z,1.0)
  **/
  public function toVector3D():flash.geom.Vector3D {
    return( new flash.geom.Vector3D( x, y, z, 1.0 ) );
  }
  #end

  /**
    convert to space-separated string
  **/
  public function toString():String {
    var myz:Float = -z;
    return( x + " " + y + " " + myz );
  }
}
