// Cylinder object. By John Haggerty (Slime).
// http://www.slimeland.com/
// Created January 21, 2003

// requires objects.js.

function Cylinder(end1,end2,rad,open)
{
	this.end1 = end1;
	this.end2 = end2;
	this.rad = rad;
	this.open = open;
	this.setupdefaultmodifiers();
}
Cylinder.prototype = new Obj();
Cylinder.prototype.copy = function()
{
	return this.copymodifiers(new Cylinder(this.end1.copy(),this.end2.copy(),this.rad,this.open));
}
Cylinder.prototype.initialize = function()
{
	this.negend1 = this.end1.neg();
	this.negend2 = this.end2.neg();
	this.normaxis = Vector.normalize(Vector.add(this.end2,this.negend1));
	this.negnormaxis = this.normaxis.neg();
	this.radsquared = this.rad*this.rad;

	var v1 = this.end1.copy(), v2 = this.end2.copy(), cosines,capbound;
	if (v1.x > v2.x) {
		var temp = v1.x;
		v1.x = v2.x;
		v2.x = temp;
	}
	if (v1.y > v2.y) {
		var temp = v1.y;
		v1.y = v2.y;
		v2.y = temp;
	}
	if (v1.z > v2.z) {
		var temp = v1.z;
		v1.z = v2.z;
		v2.z = temp;
	}
	cosines = new Vector(Vector.dot(Vector.X,this.normaxis),Vector.dot(Vector.Y,this.normaxis),Vector.dot(Vector.Z,this.normaxis));
	capbound = Vector.scalar(new Vector(Math.sqrt(1-cosines.x*cosines.x),Math.sqrt(1-cosines.y*cosines.y),Math.sqrt(1-cosines.z*cosines.z)),this.rad);
	this.boundedby = new Box(Vector.add(capbound.neg(),v1),Vector.add(capbound,v2), true);

	this.generalLowLevelObjectInitialization();
}
Cylinder.prototype.findIntersectionsUntransformed = function(ray)
{
	var isects = new Array();
	
	var raystartminusthisend1 = Vector.add(ray.start,this.negend1);
	var a,b,c,t,nearestpointonaxis,pos,u,v,discriminant;

	// caps
	if (!this.open)
	{
		a = Vector.dot(ray.dir,this.normaxis);
		if (a != 0) {
			b = Vector.dot(this.normaxis,raystartminusthisend1);
			t = -b/a;
			if (Vector.add(Vector.add(ray.start,Vector.scalar(ray.dir,t)),this.negend1).lengthsquared() <= this.radsquared)
			{
				isects[0] = new Intersection(t,ray,this);
				isects[0].data = 1; // remember that this is on a cap of the cylinder for normal calculation
			}
			
			b = Vector.dot(this.negnormaxis,Vector.add(ray.start,this.negend2));
			t = b/a;
			if (Vector.add(Vector.add(ray.start,Vector.scalar(ray.dir,t)),this.negend2).lengthsquared() <= this.radsquared)
			{
				isects[isects.length] = new Intersection(t,ray,this);
				isects[isects.length-1].data = 2;
			}
		}
	}
	// cylinder
	if (isects.length < 2)
	{
		u = Vector.add(ray.dir,Vector.scalar(this.normaxis,-Vector.dot(ray.dir,this.normaxis)));
		v = Vector.add(raystartminusthisend1,Vector.scalar(this.normaxis,-Vector.dot(raystartminusthisend1,this.normaxis)));
	
		a = Vector.dot(u,u);
		if (a != 0) // a=b=c=0 means that the ray is travelling right along the cylinder's side. (not positive c would be zero, but i think so; it would make sense mathematically: infinite solutions.)
		{
			b = 2*Vector.dot(u,v);
			c = Vector.dot(v,v) - this.radsquared;
		
			discriminant = b*b-4*a*c;
			if (discriminant == 0) {
				t = -b/(2*a);
				pos = Vector.add(ray.start,Vector.scalar(ray.dir,t));
				if ((Vector.dot(this.normaxis,   Vector.add(pos,this.negend1)) > 0) && 
				    (Vector.dot(this.negnormaxis,Vector.add(pos,this.negend2)) > 0))
					isects[isects.length] = new Intersection(t,ray,this);
			}
			else if (discriminant > 0)
			{
				var sqrtdiscriminant = Math.sqrt(discriminant);
				var oneovertwoa = 1/(2*a);
			
				t = (-sqrtdiscriminant-b)*oneovertwoa;
				pos = Vector.add(ray.start,Vector.scalar(ray.dir,t));
				if ((Vector.dot(this.normaxis,   Vector.add(pos,this.negend1)) > 0) && 
				    (Vector.dot(this.negnormaxis,Vector.add(pos,this.negend2)) > 0))
					isects[isects.length] = new Intersection(t,ray,this);
				if (isects.length < 2) {
					t = ( sqrtdiscriminant-b)*oneovertwoa;
					pos = Vector.add(ray.start,Vector.scalar(ray.dir,t));
					if ((Vector.dot(this.normaxis,   Vector.add(pos,this.negend1)) > 0) && 
					    (Vector.dot(this.negnormaxis,Vector.add(pos,this.negend2)) > 0))
						isects[isects.length] = new Intersection(t,ray,this);
				}
			}
		}
	}
	// sort
	if (isects.length > 1 && isects[1].depth < isects[0].depth) {
		var temp = isects[1];
		isects[1] = isects[0];
		isects[0] = temp;
	}
	return isects;
};
Cylinder.prototype.isPointInsideUntransformed = function(pos) {
	// test inside of caps (similar to plane test)
	if (Vector.dot(this.normaxis,   Vector.add(pos,this.negend1)) < 0) return false;
	if (Vector.dot(this.negnormaxis,Vector.add(pos,this.negend2)) < 0) return false;
	// find nearest point to this on the axis and test what the distance to it is
	var nearestpointonaxis = Vector.add(Vector.scalar(this.normaxis,Vector.dot(Vector.add(pos,this.negend1),this.normaxis)),this.end1);
	return (Vector.add(pos,nearestpointonaxis.neg()).lengthsquared() < this.radsquared);
}
Cylinder.prototype.getNormalAtUntransformed = function(pos,isect)
{
	if (isect.data) {
		if (isect.data == 1) return this.negnormaxis;
		return this.normaxis;
	}
	else {
		var nearestpointonaxis = Vector.add(Vector.scalar(this.normaxis,Vector.dot(Vector.add(pos,this.negend1),this.normaxis)),this.end1);
		return Vector.add(pos,nearestpointonaxis.neg());
	}
}