//<pre>

package actor;

import java.text.DecimalFormat;

import render.Matrix;

/**
 * Representation of 3D-point/vector.
 * NOTE: Unlike Vec3D, Point3D is not immutable.  
 * To prevent unwanted modifications of local data,
 * users of this class should return copies of these 
 * objects when needed.
 * 
 * TODO: test..
*/
public class Point3D implements Cloneable 
{  
   public double x,y,z;

   public Point3D() 
   {
     this.x=0d;
     this.y=0d;
     this.z=0d;
   }
   
   public Point3D(double x, double y, double z) 
   {
     this.x=x;
     this.y=y;
     this.z=z;
   }
   
   public Point3D(Point3D src) 
   {
     this.x=src.x;
     this.y=src.y;
     this.z=src.z;  
   }
   
   public Point3D set(double x, double y, double z) 
   {
     this.x=x;
     this.y=y;
     this.z=z;
     return this;
   }
   
   public Point3D setX(double x)
   {
     this.x = x;
     return this;
   }
   
   public Point3D setY(double y)
   {
     this.y = y;
     return this;
   }
   
   public Point3D setZ(double z)
   {
     this.z = z;
     return this;
   }
      
   public static double distance2D(Point3D a, Point3D b)
   {
     double X1 = a.x;
     double Y1 = a.y;
     X1 -= b.x; Y1 -= b.y;
     return Math.sqrt(X1 * X1 + Y1 * Y1); 
   }
   
   public double distance2D(Point3D a)
   {
     double X1 = a.x;
     double Y1 = a.y;
     X1 -= this.x; Y1 -= this.y;
     return Math.sqrt(X1 * X1 + Y1 * Y1); 
   }
   
   public static double dot(Point3D thisVec, Point3D otherVec) 
   {
     double sum = 0;
     sum += thisVec.x * otherVec.x;
     sum += thisVec.y * otherVec.y;
     sum += thisVec.z * otherVec.z;
     return sum;
   }
   
   public double dot(Point3D otherVec) 
   {
     double sum = 0;
     sum += x * otherVec.x;
     sum += y * otherVec.y;
     sum += z * otherVec.z;
     return sum;
   }
   
   public double norm() 
   {
     return Math.sqrt(dot(this));
   }


   public Point3D normalize() 
   {
     double s = norm();
     if (s==0) return this;
     this.x /= s;
     this.y /= s;
     this.z /= s;
     return this;
   }

   public static Point3D cross(Point3D thisVec, Point3D otherVec) 
   {
     Point3D answer = new Point3D(0,0,0);
     answer.x = otherVec.y * thisVec.z - otherVec.z * thisVec.y;
     answer.y = otherVec.z * thisVec.x - otherVec.x * thisVec.z;
     answer.z = otherVec.x * thisVec.y - otherVec.y * thisVec.x;
     return answer;
   }
   
   public Point3D cross(Point3D otherVec) 
   {
     this.x = otherVec.y * this.z - otherVec.z * this.y;
     this.y = otherVec.z * this.x - otherVec.x * this.z;
     this.z = otherVec.x * this.y - otherVec.y * this.x;
     return this;
   }
   
   public Point3D add(Point3D other) 
   {
     this.add(other.getX(), other.getY(), other.getZ());
     return this;
   }
   
   public Point3D subtract(Point3D other)
   {
     this.add(-other.getX(), -other.getY(), -other.getZ());
     return this;
   }
   
   public static Point3D subtract(Point3D one, Point3D other)
   {
     return new Point3D(-other.getX(), -other.getY(), -other.getZ()).add(one);
   }

   public double[] toArray() 
   {
     return new double[] { x,y,z };
   } 

   public double getX() { return x; }
   public double getY() { return y; }
   public double getZ() { return z; }

   public Point3D add(double xa, double ya, double za)
   {
     this.x += xa ;
     this.y += ya ;
     this.z += za ;
     return this;
   }   
   
   public Point3D multiply(double d)
   {
     this.x *= d;
     this.y *= d;
     this.z *= d;
     return this;
   }
   
   public static Point3D add(Point3D a, Point3D b)
   {
     return new Point3D(a).add(b);
   }
   
   public static Point3D multiply(double d, Point3D p)
   {
     return new Point3D(p.x * d, p.y *d, p.z * d);
   }

   public static Point3D lerp(double t, Point3D a, Point3D b)
   {
     return lerp(t, a, b, new Point3D());
   }

   public static Point3D lerp(double t, Point3D a, Point3D b, Point3D dst)
   {
     dst.x = lerp(t, a.x, b.x);
     dst.y = lerp(t, a.y, b.y);
     dst.z = lerp(t, a.z, b.z);
     return dst;
   }
   
   public static double lerp(double t, double a, double b)
   {
     return a + t * (b - a);
   }
   
   public static Point3D[] transformPoints(Matrix m, Point3D[] pts)
   {
     Point3D[] returnPts = new Point3D[pts.length];
     for(int i = 0; i < pts.length; i++)
       returnPts[i] = Point3D.matrixTransform(m, pts[i]);    
     return returnPts;
   }
   
   
   public static Point3D matrixTransform(Matrix m, Point3D p)
   {
     double[] v = new double[4];
     transformVertex(m, p.x, p.y, p.z, 1, v);
     return new Point3D(v[0], v[1], v[2]);
   }
   
   public static Point3D matrixTransform(Matrix m, double x, double y, double z)
   {
     double[] v = new double[4];
     transformVertex(m, x, y, z, 1, v);
     return new Point3D(v[0], v[1], v[2]);
   }   
   
   public static Point3D createDirectionVector(Matrix rotationMatrix)     
   {
    return matrixTransform(rotationMatrix, 0, 0, 1);
   }

   private static double[] transformVertex(
    Matrix m,
    double x,
    double y,
    double z,
    double w,
    double[] v)
   {
     if (w == 0)
       for (int j = 0; j < 3; j++)
         v[j] = m.get(j, 0) * x + m.get(j, 1) * y + m.get(j, 2) * z;
     else
       for (int j = 0; j < 3; j++)
         v[j] =
         m.get(j, 0) * x + m.get(j, 1) * y + m.get(j, 2) * z + m.get(j, 3);
     return v;
   }
   
   public double getXRotation()
   {
     Point3D YZPlaneVector = new Point3D(0.0, y, z).normalize();
     double derivedRotationAngle = Math.atan2(YZPlaneVector.y, YZPlaneVector.z);
     if(YZPlaneVector.y < 0)
       derivedRotationAngle = (2*Math.PI) + derivedRotationAngle;     
     return(derivedRotationAngle);     
   }
      
   public double getYRotation()
   {
     Point3D XZPlaneVector = new Point3D(x, 0.0, z).normalize();
     double derivedRotationAngle = Math.atan2(XZPlaneVector.x, XZPlaneVector.z);
     if(XZPlaneVector.x < 0)
       derivedRotationAngle = (2*Math.PI) + derivedRotationAngle;     
     return(derivedRotationAngle);     
   }
   
   public double getZRotation()
   {
     Point3D XYPlaneVector = new Point3D(x, y, 0.0).normalize();
     double derivedRotationAngle = Math.atan2(XYPlaneVector.x, XYPlaneVector.y);
     if(XYPlaneVector.x < 0)
       derivedRotationAngle = (2*Math.PI) + derivedRotationAngle;     
     return(derivedRotationAngle);     
   }
   
   
   /**
    * Creates and returns a copy of this object. 
    * so that both (x.clone() != x) and (x.clone().getClass()
    * == x.getClass()) will be <tt>true</tt>.
    * @return a clone of this instance.
    * @see java.lang.Cloneable
    */
   public Object clone() 
   {
     Object o = null;
     try
     {
       o = super.clone();
     }
     catch (CloneNotSupportedException e)
     {
       e.printStackTrace();
     }
     return o;
   }

   private static DecimalFormat dF = new DecimalFormat("+0.000;-0.000");
   
   public String toString()
  {
    return (
      "["
        + dF.format(getX())
        + ","
        + dF.format(getY())
        + ","
        + dF.format(getZ())
        + "]");
  }  
  
}// end