//
// 3-dimensional vector corresponding to Point3D class in sandy
//

import flash.geom.Vector3D;

class Point3D {
  public static inline var EPSILON = 1.0e-10;

  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() );
    }
    // note: mirror image of the real world coordinate system
    return( new Point3D( Std.parseFloat( sa[0] ), Std.parseFloat( sa[1] ),
                         -Std.parseFloat( sa[2] ) ) );
  }

  static public function getAdd( p0:Point3D,
                                 p1:Point3D ):Point3D {
    var ret:Point3D = p0.clone();
    ret.add( p1 );
    return( ret );
  }

  static public function getSub( p0:Point3D,
                                 p1:Point3D ):Point3D {
    var ret:Point3D = p0.clone();
    ret.sub( p1 );
    return( ret );
  }

  static public function getMultiply( p:Point3D,
                                      f:Float ):Point3D {
    var ret:Point3D = p.clone();
    ret.multiply( f );
    return( ret );
  }

  static public function getCross( p0:Point3D,
                                   p1:Point3D ):Point3D {
    var ret:Point3D = p0.clone();
    ret.cross( p1 );
    return( ret );
  }

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

  public var x( __getx, __setx ):Float;
  public var y( __gety, __sety ):Float;
  public var z( __getz, __setz ):Float;

  public function new( ?vx:Float = 0.0,
                       ?vy:Float = 0.0,
                       ?vz:Float = 0.0 ) {
    x = vx;
    y = vy;
    z = vz;
  }

  public function clear():Void {
    nullify();
  }

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

  public function clone():Point3D {
    var ret:Point3D = new Point3D();
    ret.x = x;
    ret.y = y;
    ret.z = z;
    return( ret );
  }

  public function __getx():Float { return( x ); }
  public function __gety():Float { return( y ); }
  public function __getz():Float { return( z ); }
  public function __setx( v:Float ):Float {
    x = v;
    return( x );
  }
  public function __sety( v:Float ):Float {
    y = v;
    return( y );
  }
  public function __setz( v:Float ):Float {
    z = v;
    return( z );
  }

  public function add( p:Point3D ):Point3D {
    x += p.x;
    y += p.y;
    z += p.z;
    return( this );
  }

  public function sub( p:Point3D ):Point3D {
    x -= p.x;
    y -= p.y;
    z -= p.z;
    return( this );
  }

  public function multiply( f:Float ):Point3D {
    x *= f;
    y *= f;
    z *= f;
    return( this );
  }

  public function dot( p:Point3D ):Float {
    return( x * p.x + y * p.y + z * p.z );
  }

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

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

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

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

  public function getAngle( p:Point3D ):Float {
    return ( Math.acos( dot( p ) / Math.sqrt( sqabs() * p.sqabs() ) ) );
  }

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

  public function toVector3D():Vector3D {
    return( new Vector3D( x, y, z, 1.0 ) );
  }

  public function toString():String {
    var myz:Float = -z;
    return( x + " " + y + " " + myz );
  }
}
