public class Vector3D {
    private double vec_[] = new double[4];
    static private Vector3D temp_ = new Vector3D(),temp2_ = new Vector3D();
    
    Vector3D(double x, double y, double z, double w) {
        vec_[0] = x;
        vec_[1] = y;
        vec_[2] = z;
        vec_[3] = w;
    }
    Vector3D(double x, double y, double z) {
        this(x,y,z,1.);
    }
    Vector3D(boolean w1) {
        this(.0,0.,.0,w1?1.:0.);
    }
	Vector3D() {
		this(false);
	}
    Vector3D(Vector3D other) {
        copy(other);
    }
    double[] vector() {
        return vec_;
    }
    double get(int i) {
        return vec_[i];
    }
    double set(int i, double v) {
        return vec_[i] = v;
    }
	Vector3D set(double x, double y, double z, double w) {
		vec_[0] = x;
		vec_[1] = y;
		vec_[2] = z;
		vec_[3] = w;
		return this;
	}
	Vector3D set(double x, double y, double z) {
		set(x,y,z,0);
		return this;
	}
    double x() {
        return vec_[0];
    }
    double y() {
        return vec_[1];
    }
    double z() {
        return vec_[2];
    }
    double w() {
        return vec_[3];
    }
	Vector3D zero() {
		for(int i=0; i<4; ++i)
			vec_[i] = 0.;
		return this;
	}
    Vector3D copy(Vector3D other) {
        for(int i = 0; i<4; ++i) 
            vec_[i] = other.vec_[i];
        return this;
    }
    Vector3D reverse() {
        for(int i = 0; i<4; ++i) 
            vec_[i] = -vec_[i];
        return this;
    }
    Vector3D add(Vector3D other, Vector3D out) {
        for(int i = 0; i<4; ++i) 
            out.vec_[i] = vec_[i] + other.vec_[i];
        return out;
    }
    Vector3D add(Vector3D other) {
        temp_.copy(this);
        return temp_.add(other,this);
    }
    Vector3D minus(Vector3D other, Vector3D out) {
        for(int i = 0; i<4; ++i) 
            out.vec_[i] = vec_[i] - other.vec_[i];
        return out;
    }
    Vector3D minus(Vector3D other) {
        temp_.copy(this);
        return temp_.minus(other,this);
    }
    Vector3D times(Vector3D other, Vector3D out) {
        for(int i = 0; i<4; ++i) 
            out.vec_[i] = vec_[i] * other.vec_[i];
        return out;
    }
    Vector3D lerp(Vector3D other, double t, Vector3D out) {
        // maybe the syntax should be 
		// Vector3D lerp(Vector3D a, Vector3D b, double t) {
        // add(a,temp_.minus(b,a).times(t));
        // dest always the object
        // i guess either is "natural" in a way
        return other.minus(this,temp_).times(t).add(this,out);
        /*
        System.out.println("lerp "+this+" ("+t+") "+other+":");
        other.minus(this,temp_);
        System.out.println("- "+temp_);
        temp_.times(t);
        System.out.println("* "+temp_);
        temp_.add(this,out); 
        System.out.println("+ "+out);
        return out;
        */
    }
    Vector3D times(double a,Vector3D out) {
        for(int i = 0; i<4; ++i) 
            out.vec_[i] = vec_[i] * a;
        return out;
    }
    Vector3D times(double a) {
        return times(a,this);
    }
    Vector3D divide(double a) {
        return times(1./a);
    }
	Vector3D divide(double a,Vector3D out) {
		return times(1./a,out);
	}
    double dot(Vector3D other) {
        double ret = 0.;
        for(int i = 0; i<4; ++i) 
            ret += vec_[i] * other.vec_[i];
        return ret;
    }
	Vector3D cross(Vector3D v, Vector3D out) {
		out.vec_[0] = y()*v.z() - z()*v.y();
		out.vec_[1] = z()*v.x() - x()*v.z();
		out.vec_[2] = x()*v.y() - y()*v.x();
		out.vec_[3] = 0; //?
		return out;
	}
	double sum() {
		return vec_[0]+vec_[1]+vec_[2]+vec_[3];
	}
    private double tempsize() {
        return Math.sqrt(temp_.sum());
    }
    double dist(Vector3D other) {
        minus(other,temp2_);
        return Math.sqrt(temp2_.dot(temp2_));
	}
    double magn() {
        return Math.sqrt(dot(this));
    }
	Vector3D normalize() {
		divide(magn());
		return this;
	}

    public String toString() {
        StringBuffer buf = new StringBuffer(100);
        buf.append("{");
        for(int i = 0; i<4; ++i) {
            buf.append(vec_[i]);
            if(i!=3)
                buf.append(", ");
        }
        buf.append("}");
        return buf.toString();
    }
	/* ccw test: CCW, CW, or co-linear, adapted from graphviz from ? */
	enum Wise {CCW, CW, ON};
	static Wise wiseness(Vector3D a, Vector3D b, Vector3D c) {
		double d = (a.y() - b.y())*(c.x() - b.x()) -
			   (c.y() - b.y())*(a.x() - b.x());
		return (d > 1e-8) ? Wise.CCW :((d < -1e-8) ? Wise.CW : Wise.ON);
	}
}
