var TinyDim=sda.defineClass({
	__canvas:null,
	__context:null,
	__width:0,
	__height:0,
	__clearColor:"#000",
	__offsets:[0,0,-7],
	__angle:180,
	__zoom:100,
	__fov:3,
	__faces:[],
	__vertices:[],
	__globalLight:[3,3,3],
	$ctor:function(canvasElement){with(this){
		assignCanvas(canvasElement);
	}},
	assignCanvas:function(canvasElement){with(this){
		__canvas=canvasElement;
		__context=__canvas.getContext("2d");
		
		updateDimensions();
	}},
	updateDimensions:function(){with(this){
		__width=__canvas.width;
		__height=__canvas.height;
	}},
	setDimensions:function(width,height){with(this){
		__canvas.width=width;
		__canvas.height=height;
		
		updateDimensions();
	}},
	drawPolygon:function(pointList,pointLen,color){with(this){
		if(pointLen<1) return;

		__context.beginPath();
		__context.fillStyle=color;
		var func="moveTo";
		for(var i=0;i<pointLen;i++){
			__context[func](pointList[i][0],pointList[i][1]);
			func="lineTo";
		}
		__context.closePath();
		__context.fill();
	}},
	testPolygon:function(pointList,pointLen,x,y){with(this){
		if(pointLen<1) return false;
		
		var r;
		__context.beginPath();
		__context.moveTo(pointList[0][0],pointList[0][1]);
		for(var i=1;i<pointLen;i++)
			__context.lineTo(pointList[i][0],pointList[i][1]);
			
		r=__context.isPointInPath(x,y);
		__context.closePath();
		return(r);
	}},
	clearScreen:function(){with(this){
		__context.fillStyle=__clearColor;
		__context.fillRect(0,0,__width,__height);
	}},
	draw:function(testPoint){with(this){
		if(!testPoint) clearScreen();
		var f,x,yz,xr,yr,zr,xs,ys,zs;
		var ox2d=__width/2;
		var oy2d=__height/2;

		var aRad=(__angle+90)%360 *Math.PI/180;
		var sin=Math.sin(aRad);var cos=Math.cos(aRad);
		
		var ox3d=__offsets[0];
		var oy3d=__offsets[1]-.3;
		var oz3d=__offsets[2];

		var l;
		var v=0;
		var cc=0.4;
		var faces=[];

		for(var i=0;i<__faces.length;i++){
			f=__faces[i];
			
			var cd=((f[3][0]+ox3d)*cos + (f[3][2]+oz3d)*sin);
			if(cd>=50) continue;
			/*
			if(f[5]){
				var viewVec=[cos,0,sin];
				var a=Math.acos(
					(
						viewVec[0]*f[5][0]+
						viewVec[1]*f[5][1]+
						viewVec[2]*f[5][2]
					)/(
							Math.sqrt(
								Math.pow(viewVec[0],2)+
								Math.pow(viewVec[1],2)+
								Math.pow(viewVec[2],2)
							)
						*
							Math.sqrt(
								Math.pow(f[5][0],2)+
								Math.pow(f[5][1],2)+
								Math.pow(f[5][2],2)
							)
					)
				);
				
				if(a<Math.PI/2) continue;
			}
			//*/
			var s=[];
			for(var o=0;o<3;o++){
				xs=__vertices[f[o]][0]+ox3d;ys=__vertices[f[o]][1]+oy3d;zs=__vertices[f[o]][2]+oz3d;
				s.push([xs*sin-zs*cos,ys,xs*cos+zs*sin]);
			}
			
			xs=f[3][0]+ox3d;zs=f[3][2]+oz3d;
			var tc=[xs*sin-zs*cos,f[3][1]+oy3d,xs*cos+zs*sin];
			
			s=s.sort(function(a,b){ return a[2]<b[2]; });
			
			faces.push({
				pts:s,
				cd:cd,
				col:f[4],
				normal:f[5],
				distance:Math.sqrt(
					Math.pow(tc[0],2)+
					Math.pow(tc[1],2)+
					Math.pow(tc[2],2)
				),
				fid:i
			});
		}

		faces.sort(function(a,b){return a.distance<b.distance;});

		for(var i=0;i<faces.length;i++){
			var s=faces[i];

			v=0;
			if(s.pts[2][2]>cc){
				for(var o=0;o<3;o++){
					s.pts[o][0]=ox2d+s.pts[o][0]*__zoom/s.pts[o][2]*__fov;
					s.pts[o][1]=oy2d-s.pts[o][1]*__zoom/s.pts[o][2]*__fov;
				}
			}else{
				if(s.pts[0][2]>cc){
					if(s.pts[1][2]>cc){
						var o1rat=(s.pts[0][2]-cc)/(s.pts[0][2]-s.pts[2][2]);
						var o2rat=(s.pts[1][2]-cc)/(s.pts[1][2]-s.pts[2][2]);
						var o1xs=s.pts[0][0]-(s.pts[0][0]-s.pts[2][0])*o1rat;
						var o1ys=s.pts[0][1]-(s.pts[0][1]-s.pts[2][1])*o1rat;
						var o1zs=s.pts[0][2]-(s.pts[0][2]-s.pts[2][2])*o1rat;
						var o2xs=s.pts[1][0]-(s.pts[1][0]-s.pts[2][0])*o2rat;
						var o2ys=s.pts[1][1]-(s.pts[1][1]-s.pts[2][1])*o2rat;
						var o2zs=s.pts[1][2]-(s.pts[1][2]-s.pts[2][2])*o2rat;

						s.pts.push([
							ox2d+s.pts[1][0]*__zoom/s.pts[1][2]*__fov,
							oy2d-s.pts[1][1]*__zoom/s.pts[1][2]*__fov,
							s.pts[1][2]
						]);
						s.pts[0][0]=ox2d+s.pts[0][0]*__zoom/s.pts[0][2]*__fov;
						s.pts[0][1]=oy2d-s.pts[0][1]*__zoom/s.pts[0][2]*__fov;
						s.pts[1][0]=ox2d+o1xs*__zoom/o1zs*__fov;
						s.pts[1][1]=oy2d-o1ys*__zoom/o1zs*__fov;
						s.pts[1][2]=cc;
						s.pts[2][0]=ox2d+o2xs*__zoom/o2zs*__fov;
						s.pts[2][1]=oy2d-o2ys*__zoom/o2zs*__fov;
						s.pts[2][2]=cc;

					}else{
						var o1rat=(s.pts[0][2]-cc)/(s.pts[0][2]-s.pts[1][2]);
						var o2rat=(s.pts[0][2]-cc)/(s.pts[0][2]-s.pts[2][2]);
						var o1xs=s.pts[0][0]-(s.pts[0][0]-s.pts[1][0])*o1rat;
						var o1ys=s.pts[0][1]-(s.pts[0][1]-s.pts[1][1])*o1rat;
						var o1zs=s.pts[0][2]-(s.pts[0][2]-s.pts[1][2])*o1rat;
						var o2xs=s.pts[0][0]-(s.pts[0][0]-s.pts[2][0])*o2rat;
						var o2ys=s.pts[0][1]-(s.pts[0][1]-s.pts[2][1])*o2rat;
						var o2zs=s.pts[0][2]-(s.pts[0][2]-s.pts[2][2])*o2rat;
						
						s.pts[0][0]=ox2d+s.pts[0][0]*__zoom/s.pts[0][2]*__fov;
						s.pts[0][1]=oy2d-s.pts[0][1]*__zoom/s.pts[0][2]*__fov;
						s.pts[1][0]=ox2d+o1xs*__zoom/o1zs*__fov;
						s.pts[1][1]=oy2d-o1ys*__zoom/o1zs*__fov;
						s.pts[2][0]=ox2d+o2xs*__zoom/o2zs*__fov;
						s.pts[2][1]=oy2d-o2ys*__zoom/o2zs*__fov;
					}
				}
			}
		}

		var cl;
		var f=false;
		
		for(var o=0;o<faces.length;o++){
			var normal=faces[o].normal;
			if(normal){
				var a=Math.acos(
					(
						__globalLight[0]*normal[0]+
						__globalLight[1]*normal[1]+
						__globalLight[2]*normal[2]
					)/(
							Math.sqrt(
								Math.pow(__globalLight[0],2)+
								Math.pow(__globalLight[1],2)+
								Math.pow(__globalLight[2],2)
							)
						*
							Math.sqrt(
								Math.pow(normal[0],2)+
								Math.pow(normal[1],2)+
								Math.pow(normal[2],2)
							)
					)
				);
				cl=255-Math.floor(a*80+(faces[o].cd+3)*4);
			}else{
				cl=255-Math.floor((faces[o].cd+3)*8);
			}
			if(!testPoint) drawPolygon(faces[o].pts,faces[o].pts.length,"rgb("+Math.floor(faces[o].col[0]*cl/256)+","+Math.floor(faces[o].col[1]*cl/256)+","+Math.floor(faces[o].col[2]*cl/256)+")");
			else if(testPolygon(faces[o].pts,faces[o].pts.length,testPoint[0],testPoint[1])){
				f=faces[o].fid;
			}
		}
		if(testPoint){
			return f;
		}
	}},
	hit:function(x,y){with(this){
		return draw([x,y]);
	}},
	calculateCenter:function(i){with(this){
		function middle(){
			var min=arguments[0];
			var max=arguments[0];
			for(var i=1;i<arguments.length;i++){
				if(arguments[i]<min) min=arguments[i];
				if(arguments[i]>max) max=arguments[i];
			}
			return min+(max-min)/2;
		}

		var m1=__faces[i][0];
		var m2=__faces[i][1];
		var m3=__faces[i][2];
		var cx=middle(__vertices[m1][0],__vertices[m2][0],__vertices[m3][0]);
		var cy=middle(__vertices[m1][1],__vertices[m2][1],__vertices[m3][1]);
		var cz=middle(__vertices[m1][2],__vertices[m2][2],__vertices[m3][2]);
		__faces[i][3]=[cx,cy,cz];
	}},
	calculateCenters:function(){with(this){
		for(var i=0;i<__faces.length;i++)
			calculateCenter(i);
	}},
	loadOBJ:function(str){with(this){
		__faces=[];
		__vertices=[];

		var colors=[];
		colors.push([230,255,255]);
		
		var findColors = /c ([0-9]*) ([0-9]*) ([0-9]*)/gim;
		var findVertices = /v ([0-9\-.]*) ([0-9\-.]*) ([0-9\-.]*)/gim;
		var findNormals = /vn ([0-9\-.]*) ([0-9\-.]*) ([0-9\-.]*)/gim;
		var findFaces = /f ([0-9.]*).*?([0-9.]*) ([0-9.]*).*?([0-9.]*) ([0-9.]*)[^\s]*\/([0-9.]*)(.*)$/gim;
		var findFaceColor = /[0-9\/.]* ([0-9])$/;
		
		var matches=false;
		var update=[false];
		
		var normals=[];
		
		while(matches=findColors.exec(str))
			colors.push([parseInt(matches[1]),parseInt(matches[2]),parseInt(matches[3])]);
			
		while(matches=findVertices.exec(str))
			__vertices.push([parseFloat(matches[1]),parseFloat(matches[2]),parseFloat(matches[3])]);
			
		while(matches=findNormals.exec(str))
			normals.push([parseFloat(matches[1]),parseFloat(matches[2]),parseFloat(matches[3])]);
		
		while(matches=findFaces.exec(str)){
			
			var m4=0;
			var n=0;
			if(findFaceColor.test(matches[7])) m4=findFaceColor.exec(matches[7])[1];
			if(matches[2] && parseInt(matches[2]) && normals[parseInt(matches[2])-1]) n=normals[parseInt(matches[2])-1];
			__faces.push([
				parseInt(matches[1])-1,
				parseInt(matches[3])-1,
				parseInt(matches[5])-1,
				false,
				colors[m4],
				n
			]);
		}
		
		calculateCenters();
	}}
});

var Navigator3d=sda.defineClass({
	__canvas3d:null,
	__mouseDownListener:null,
	__mouseUpListener:null,
	__mouseMoveListener:null,
	__busy:false,
	__initPos:[0,0],
	__initOffsets:null,
	__initAngle:0,
	__mode:-1,
	__eventListeners:{},
	$ctor:function(canvas3DInstance){with(this){
		__canvas3d=canvas3DInstance;
		__mouseDownListener=new sda.EvtListener(__canvas3d.__canvas,"mousedown",startDrag);
	}},
	getEvtPos:function(evt){with(this){
		var cbbox=sda.getClientRect(__canvas3d.__canvas);
		return [evt.clientX-cbbox.left,evt.clientY-cbbox.top];
	}},
	cancelEvt:function(evt){with(this){
		evt.stopPropagation();
		evt.preventDefault();
		
		return true;
	}},
	addEventListener:function(type,callback,bubbledummy){with(this){
		if(!__eventListeners[type]) __eventListeners[type]=[];
		
		for(var i=0;i<__eventListeners[type].length;i++)
			if(__eventListeners[type][i][0]==callback && __eventListeners[type][i][1]==(!!bubbledummy)) return (__eventListeners[type][i][0]);
		
		__eventListeners[type].push([callback,(!!bubbledummy)]);
		return callback;
	}},
	removeEventListener:function(type,callback,bubbledummy){with(this){
		if(!__eventListeners[type]) return false;

		for(var i=0;i<__eventListeners[type].length;i++)
			if(__eventListeners[type][i][0]==callback && __eventListeners[type][i][1]==(!!bubbledummy)){
				__eventListeners[type][i].splice(i,1);
				return true;
			}
		
		return false;
	}},
	dispatchEvent:function(evt){with(this){
		if(!evt || !evt.type || !__eventListeners[evt.type]) return false;
		
		for(var i=0;i<__eventListeners[evt.type].length;i++)
			__eventListeners[evt.type][i][0](evt);
		
		return true;
	}},
	startDrag:function(evt){with(this){
		if(__busy) return;
		
		if(evt.button==0 || evt.button==2){
			__busy=true;
			
			__initPos=getEvtPos(evt);
			__initOffsets=sda.makeDeepCopy(__canvas3d.__offsets);
			__initAngle=__canvas3d.__angle;
			
			__mouseUpListener=new sda.EvtListener(document,"mouseup",endDrag);
			__mouseMoveListener=new sda.EvtListener(document,"mousemove",doDrag);
			
			if(evt.button==0)
				__mode=0;
			else if(evt.button==2)
				__mode=1;
			
			cancelEvt(evt);
			
			dispatchEvent({type:"startdrag",clientX:__initPos[0],clientY:__initPos[1]});
				
			return false;
		}
	}},
	doDrag:function(evt){with(this){
		if(!__busy) return;
		
		var evtPos=getEvtPos(evt);
		var evtOffset=[];
		evtOffset[0]=evtPos[0]-__initPos[0];
		evtOffset[1]=evtPos[1]-__initPos[1];
		if(__mode==0){
			__canvas3d.__offsets[0]=__initOffsets[0]
				+Math.cos((__canvas3d.__angle+90)%360 *Math.PI/180)/10*evtOffset[1]
				-Math.cos((__canvas3d.__angle)%360 *Math.PI/180)/10*evtOffset[0];
			__canvas3d.__offsets[2]=__initOffsets[2]
				+Math.sin((__canvas3d.__angle+90)%360 *Math.PI/180)/10*evtOffset[1]
				-Math.sin((__canvas3d.__angle)%360 *Math.PI/180)/10*evtOffset[0];
		}else if(__mode==1){
			__canvas3d.__angle=(1080+(__initAngle-evtOffset[0]/2))%360;
			__canvas3d.__offsets[1]=__initOffsets[1]-evtOffset[1]/50;
		}
		
		__canvas3d.draw();
		
		dispatchEvent({type:"dodrag",clientX:__initPos[0],clientY:__initPos[1]});
	}},
	endDrag:function(evt){with(this){
		if(!__busy) return;
		
		var evtPos=getEvtPos(evt);
		
		doDrag(evt);
		
		__mouseUpListener.cancel();
		__mouseMoveListener.cancel();
		__mouseUpListener=null;
		__mouseMoveListener=null;
		__mode=-1;
		__busy=false;
		
		cancelEvt(evt);
		dispatchEvent({type:"enddrag",clientX:__initPos[0],clientY:__initPos[1]});

		return false;
	}}
});
