// Cone object. By John Haggerty (Slime).
// http://www.slimeland.com/
// Created January 21, 2003

// requires objects.js.

function Cone(end1,rad1,end2,rad2,open)
{
	this.end1 = end1;
	this.rad1 = rad1;
	this.end2 = end2;
	this.rad2 = rad2;
	this.open = open;
	this.setupdefaultmodifiers();
}
Cone.prototype = new Obj();
Cone.prototype.copy = function()
{
	return this.copymodifiers(new Cone(this.end1.copy(),this.rad1,this.end2.copy(),this.rad2,this.open));
}
Cone.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.rad1squared = this.rad1*this.rad1;
	this.rad2squared = this.rad2*this.rad2;
	this.raddiffperunit = (this.rad2-this.rad1)/Vector.add(this.end2,this.negend1).length();

	var v1=new Vector(0,0,0),v2=new Vector(0,0,0), cosines,sines, cap1boundv1,cap1boundv2, cap2boundv1,cap2boundv2;
	cosines = new Vector(Vector.dot(Vector.X,this.normaxis),Vector.dot(Vector.Y,this.normaxis),Vector.dot(Vector.Z,this.normaxis));
	sines = new Vector(Math.sqrt(1-cosines.x*cosines.x),Math.sqrt(1-cosines.y*cosines.y),Math.sqrt(1-cosines.z*cosines.z));
	cap1boundv2 = Vector.scalar(sines,this.rad1);
	cap2boundv2 = Vector.scalar(sines,this.rad2);
	cap1boundv1 = cap1boundv2.neg();
	cap2boundv1 = cap2boundv2.neg();
	cap1boundv1 = Vector.add(cap1boundv1,this.end1);
	cap1boundv2 = Vector.add(cap1boundv2,this.end1);
	cap2boundv1 = Vector.add(cap2boundv1,this.end2);
	cap2boundv2 = Vector.add(cap2boundv2,this.end2);
	if (cap1boundv1.x < cap2boundv1.x) v1.x = cap1boundv1.x; else v1.x = cap2boundv1.x;
	if (cap1boundv1.y < cap2boundv1.y) v1.y = cap1boundv1.y; else v1.y = cap2boundv1.y;
	if (cap1boundv1.z < cap2boundv1.z) v1.z = cap1boundv1.z; else v1.z = cap2boundv1.z;
	if (cap1boundv2.x > cap2boundv2.x) v2.x = cap1boundv2.x; else v2.x = cap2boundv2.x;
	if (cap1boundv2.y > cap2boundv2.y) v2.y = cap1boundv2.y; else v2.y = cap2boundv2.y;
	if (cap1boundv2.z > cap2boundv2.z) v2.z = cap1boundv2.z; else v2.z = cap2boundv2.z;
	this.boundedby = new Box(v1,v2, true);

	this.generalLowLevelObjectInitialization();
}
Cone.prototype.findIntersectionsUntransformed = function(ray)
{
	var isects = new Array();
	
	var raystartminusthisend1 = Vector.add(ray.start,this.negend1);
	var a,b,c,t,nearestpointonaxis,pos,dirdotnorm,raystartminusend1dotnorm,dirdotnormtimesraddiff,u,v,w,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.rad1squared)
			{
				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.rad2squared)
			{
				isects[isects.length] = new Intersection(t,ray,this);
				isects[isects.length-1].data = 2;
			}
		}
	}
	// cone
	if (isects.length < 2)
	{
		dirdotnorm = Vector.dot(ray.dir,this.normaxis);
		raystartminusend1dotnorm = Vector.dot(raystartminusthisend1,this.normaxis);
		
		u = Vector.add(ray.dir,Vector.scalar(this.normaxis,-dirdotnorm));
		v = Vector.add(raystartminusthisend1,Vector.scalar(this.normaxis,-raystartminusend1dotnorm));
		// (w is not a vector)
		w = raystartminusend1dotnorm*this.raddiffperunit + this.rad1;
		
		dirdotnormtimesraddiff = dirdotnorm*this.raddiffperunit;
	
		a = Vector.dot(u,u) - dirdotnormtimesraddiff*dirdotnormtimesraddiff;
		if (a != 0) // a=b=c=0 means that the ray is travelling right along the cone's side, I think
		{
			b = 2*(Vector.dot(u,v) - w*dirdotnormtimesraddiff);
			c = Vector.dot(v,v) - w*w;
		
			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;
};
Cone.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 disttoend1 = Vector.dot(Vector.add(pos,this.negend1),this.normaxis);
	var nearestpointonaxis = Vector.add(Vector.scalar(this.normaxis,disttoend1),this.end1);
	var radhere = (disttoend1*this.raddiffperunit + this.rad1squared);
	return (Vector.add(pos,nearestpointonaxis.neg()).lengthsquared() < radhere*radhere);
}
Cone.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);
		var simplenorm = Vector.add(pos,nearestpointonaxis.neg());
		return Vector.add(simplenorm,Vector.scalar(this.normaxis,-this.raddiffperunit*simplenorm.length()));
	}
}