// Obj object, for other raytraced objects to inherit from. By John Haggerty (Slime).
// http://www.slimeland.com/
// Created January 21, 2003

// requires vector.js, texture.js, ray.js, transformation.js.

function Obj()
{
	// all 3d objects are "subclasses" of this
}
Obj.prototype.getColorAt = function(isect, objs,lights, tracelevel,effect) {
	var pos = isect.getpos(), dir = isect.ray.dir;
	
	var colorhere;
	for (var a=0; a < isect.objectstack.length; a++) {
		if (a == isect.objectstack.length-1 || isect.objectstack[a].textureoverridesparent) {
			colorhere = isect.objectstack[a].texture.colorAt(pos);
			break;
		}
	}
	var toreturn = Color.scalar(colorhere,this.texture.finish.ambient);
	var normal = this.getNormalAt(pos,isect);
	var reflectiondir;
	var reflectionray;
	if (Vector.dot(dir.neg(),normal) < 0) normal = normal.neg();
	if (this.texture.finish.reflection != 0 || this.texture.finish.specular != 0)
		reflectiondir = Vector.add(dir.neg(), Vector.scalar(Vector.add(Vector.scalar(normal,Vector.dot(normal,dir.neg())),dir),2));
	if (this.texture.finish.reflection != 0 && tracelevel < maxtracelevel && effect >= bailout)
	{
		reflectionray = new Ray(pos,reflectiondir);
		toreturn = Color.add(toreturn,Color.scalar(reflectionray.traceforcolor(objs,lights, tracelevel+1,effect*this.texture.finish.reflection),this.texture.finish.reflection));
	}
	if (this.texture.finish.diffuse != 0 || this.texture.finish.specular != 0)
	{
		for (var a=0; a < lights.length; a++)
		{
			lightdir = Vector.normalize(Vector.add(lights[a].pos,pos.neg()));
			cosangle = Vector.dot(normal,lightdir);
			if (cosangle > 0)
			{
				var incominglightcolor = lights[a].getLightColorAt(objs, pos); // (tests for shadow)
				if (incominglightcolor.red != 0 || incominglightcolor.green != 0 || incominglightcolor.blue != 0)
				{
					toreturn = Color.add(toreturn, Color.scalar(Color.mult(colorhere,incominglightcolor),cosangle*this.texture.finish.diffuse));
					if (this.texture.finish.specular != 0)
					{
						var specular = Vector.dot(reflectiondir,lightdir);
						if (specular > 0)
						{
							specular = Math.pow(specular,this.texture.finish.glossiness);
							toreturn = Color.add(toreturn, Color.scalar(incominglightcolor, specular*this.texture.finish.specular));
						}
					}
				}
			}
		}
	}
	return toreturn;
}
Obj.numericalorder = function(a,b) {return a-b}; // used for sorting
Obj.prototype.correctBounds = function() { // changes the bounding box to take transformations into account
	var v1 = this.boundedby.v1;
	var v2 = this.boundedby.v2;
	var corners = new Array(v1,v2, new Vector(v1.x,v1.y,v2.z), new Vector(v1.x,v2.y,v1.z), new Vector(v2.x,v1.y,v1.z), new Vector(v1.x,v2.y,v2.z), new Vector(v2.x,v1.y,v2.z), new Vector(v2.x,v2.y,v1.z));
	for (a=0; a < 8; a++)
		corners[a] = corners[a].transformed(this.transform);
	var nv1 = new Vector(), nv2 = new Vector();
	nv1.x = nv2.x = corners[0].x;
	nv1.y = nv2.y = corners[0].y;
	nv1.z = nv2.z = corners[0].z;
	for (a=1; a < 8; a++)
	{
		if (corners[a].x < nv1.x) nv1.x = corners[a].x;
		if (corners[a].y < nv1.y) nv1.y = corners[a].y;
		if (corners[a].z < nv1.z) nv1.z = corners[a].z;
		if (corners[a].x > nv2.x) nv2.x = corners[a].x;
		if (corners[a].y > nv2.y) nv2.y = corners[a].y;
		if (corners[a].z > nv2.z) nv2.z = corners[a].z;
	}
	this.boundedby.v1 = nv1;
	this.boundedby.v2 = nv2;
}
Obj.prototype.generalLowLevelObjectInitialization = function() { // called at the end of the initialization function of all lowest-level objects (spheres,boxes,cylinders,etc)
	this.volumeoutsideofbounds = !this.infinitebounds && this.inversed;
	if (!this.infinitebounds && !this.transform.identity)
		this.correctBounds();
}
Obj.findboundints = function(v1,v2, dim, odim1,odim2, s,d) // used by bounding test function
{
	if (d[dim] == 0) return false;
	int1 = (v1[dim]-s[dim])/d[dim];
	intpoint1 = Vector.add(s,Vector.scalar(d,int1));
	if (int1 > 0 && intpoint1[odim1] >= v1[odim1] && intpoint1[odim1] < v2[odim1] && intpoint1[odim2] >= v1[odim2] && intpoint1[odim2] < v2[odim2])
		return true;
	int2 = (v2[dim]-s[dim])/d[dim];
	intpoint2 = Vector.add(s,Vector.scalar(d,int2));
	if (int2 > 0 && intpoint2[odim1] >= v1[odim1] && intpoint2[odim1] < v2[odim1] && intpoint2[odim2] >= v1[odim2] && intpoint2[odim2] < v2[odim2])
		return true;
	return false;
}
Obj.prototype.boundtest = function(ray) {
	// create bounding box of the transformed object if it hasn't already been created:
	if (this.infinitebounds) return true;
	var v1 = this.boundedby.v1;
	var v2 = this.boundedby.v2;
	var s = ray.start;
	if (s.x >= v1.x && s.x <= v2.x && s.y >= v1.y && s.y <= v2.y && s.z >= v1.z && s.z <= v2.z) return true;
	var d = ray.dir;
	return (
		Obj.findboundints(v1,v2, 'x', 'y','z', s,d) ||
		Obj.findboundints(v1,v2, 'y', 'x','z', s,d) ||
		Obj.findboundints(v1,v2, 'z', 'x','y', s,d)
	);
}
Obj.prototype.boundtestpoint = function(pos) {
	if (this.infinitebounds) return true;
	var v1 = this.boundedby.v1;
	var v2 = this.boundedby.v2;
	return (pos.x >= v1.x && pos.y >= v1.y && pos.z >= v1.z && pos.x <= v2.x && pos.y <= v2.y && pos.z <= v2.z);
}
Obj.prototype.findIntersections = function(ray) {
	if ((ray.isshadowtest && !this.castshadows) || !this.boundtest(ray)) {
		return new Array();
	}

	if (this.transform.identity)
		return this.findIntersectionsUntransformed(ray);
	
	var usingray = ray.transformed(this.transform.inverse);
	var toreturn = this.findIntersectionsUntransformed(usingray);
	for (var a=0; a < toreturn.length; a++) {
		toreturn[a].depth *= usingray.distmult; // corrects depth since we're changing the intersection's ray
		toreturn[a].ray = ray;
	}
	return toreturn;
}
Obj.prototype.getNormalAt = function(pos,isect) {
	var toreturn;
	if (this.transform.identity) toreturn = Vector.normalize(this.getNormalAtUntransformed(pos,isect));
	else {
		// multiply by the transpose of the inverse of the transformation matrix.
		var transinv = this.transform.inverse;
		var basenorm = this.getNormalAtUntransformed(pos.transformed(transinv),isect);
		toreturn = Vector.normalize(new Vector(
			Vector.dot(new Vector(transinv.vx.x,transinv.vy.x,transinv.vz.x),basenorm),
			Vector.dot(new Vector(transinv.vx.y,transinv.vy.y,transinv.vz.y),basenorm),
			Vector.dot(new Vector(transinv.vx.z,transinv.vy.z,transinv.vz.z),basenorm)
		));
	}
	
	var inverseit = false;
	for (var a=0; a < isect.objectstack.length; a++) {
		if (isect.objectstack[a].inversed)
			inverseit = !inverseit;
	}
	if (inverseit)
		toreturn = toreturn.neg();
	return toreturn;
}
Obj.prototype.isPointInside = function(pos) {
	var toreturn;

	if (!this.boundtestpoint(pos)) toreturn = false;
	else if (this.transform.identity) toreturn = this.isPointInsideUntransformed(pos);
	else toreturn = this.isPointInsideUntransformed(pos.transformed(this.transform.inverse));
	
	if (this.inversed) toreturn = !toreturn;
	return toreturn;
}
Obj.prototype.boundstransformed = false;
Obj.prototype.castshadows = true;
Obj.prototype.setupdefaultmodifiers = function() {
	this.texture = Texture.defaulttex.copy();
	this.transform = Transformation.IdentityTrans.copy();
	this.castshadows = true;
	this.inversed = false;
	this.infinitebounds = false;
	this.volumeoutsideofbounds = false;
	this.textureoverridesparent = false;
}
Obj.prototype.copymodifiers = function(newobj) {
	newobj.texture = this.texture.copy();
	newobj.transform = this.transform.copy();
	newobj.castshadows = this.castshadows;
	newobj.inversed = this.inversed;
	newobj.infinitebounds = this.infinitebounds;
	newobj.volumeoutsideofbounds = this.volumeoutsideofbounds;
	newobj.textureoverridesparent = this.textureoverridesparent;
	newobj.initialize();
	return newobj;
}
