// s:Shape maker object
function ra_Draw(render_to_id)
{
	
/*
Programmed By: Reginald J. Armond
Date: Some time ago in 2011
Purpose: Creates a drawing canvas including all functions needed to draw on the surface
this canvas is rasterized to a run length encoded stream of colored divs
methods:
setImageSize
createRectangle
createLine
createBox
createCircle
solidFill - two methods
setOpacity
composite - alpha composite another shape over this one
rasterize

*/
	
this.render_to_id=render_to_id; // document object, used when dom manipulation is needed

/* defaults,properties */
var rastermap=null; 
var rasterheight=0;
var rasterwidth=0;
var invisible="";
var opacity=100;
var translation_parameters={x:0,y:0};

this.inRange=fn_inRange; // x,y -> bool
this.fillLine=fn_fillLine; // x,y,color -> bool
this.fillModeFunction=fn_fmf_pointmatch; // x,y,sc,pc 
this.colorToRGB=fn_colortorgb; // S -> r g b 
this.rgbToColor=fn_rgbtocolor; // long r g b -> hexstring
/* Interface */
this.createRectangle=fn_createRectangle; // x,y,width,height,color -> bool
this.createLine=fn_createLine; // x,y,x1,y1,color,thickness -> bool
this.createBox=fn_createBox; // x,y,width,height,color,thickness -> bool
this.solidFill=fn_solidFill; // x,y,color -> bool
this.createCircle=fn_createCircle; // x,y,diameter,color,segments,thickness -> bool
this.setFillMode=fn_setFillMode; // S {default|border}
this.composite=fn_composite; // x,y,shapeobj
this.setOpacity=fn_setOpacity; // n
this.polarToCartesian=fn_polarToCartesian; // dia,deg -> X,Y
this.cartesianToPolar=fn_cartesianToPolar; // x,y -> DEG,DIA

this.setImageSize=fn_setImageSize; // width,height 
this.rasterize=fn_toString; // 
this.pset=fn_pset; // x,y,color
this.pget=fn_pget; // x,y

// need to support
/*
beginPath()
moveTo(x,y)
lineTo(x,y)
closePath()
save()
translate(x,y)
stroke()
restore()
context.lineWidth=4
context.lineJoin='round'
context.strokeStyle='#663300'
context.fillStyle='#339900'
*/
// e: need to support

this.render=function()
	{
	var obj=$('#'+this.render_to_id);	
	obj.html(this.rasterize());
	}

function fn_cartesianToPolar(x,y)
	{
	if(this.rastermap==null)
		return null;
	// px=cx+dia*sin(f)
	var px=-(this.rasterwidth>>1);
	var py=-(this.rasterheight>>1);
	px+=x;
	py+=y;
	var theta=Math.atan2(py,px);
	var d=Math.sqrt(px*px+py*py);
	return {DIA:d,DEG:theta};
	}

function fn_polarToCartesian(dia,deg)
	{
	if(this.rastermap==null)
		return null;
	var pi=6.2830;
	var f;
	var radius=this.rasterwidth*(dia/100);
	f=(deg/360)*pi;
	var px=(this.rasterwidth>>1)+Math.floor(radius*Math.sin(f));
	var py=(this.rasterheight>>1)+Math.floor(radius*Math.cos(f));
	return {X:px,Y:py};
	}

function fn_setOpacity(n)
	{
	if(n<0 || n>100)
		return false;
	this.opacity=n;
	return true;
	}

function fn_colortorgb(S)
	{
	var iPos=S.lastIndexOf("#");
	if(iPos>=0)
		S=S.substr(iPos+1);
	if(S.length!=6)
		return {R:0,G:0,B:0};
		
	var r=parseInt(S.substr(0,2),16),g=parseInt(S.substr(2,2),16),b=parseInt(S.substr(4,2),16);
	
	return {R:r,G:g,B:b};	
	}
	
function fn_rgbtocolor(V)
	{
	var r,g,b;
	if(V==null || V.R==null)
		return "000000";
	r="0"+V.R.toString(16); r=r.substring(r.length-2);
	g="0"+V.G.toString(16); g=g.substring(g.length-2);
	b="0"+V.B.toString(16); b=b.substring(b.length-2);
	
	return r+g+b;
	}

function fn_composite(x,y,shapeobj)
	{
	if(shapeobj==null || shapeobj.rastermap==null)
		return false;
	var xx=0,yy=0;
	var pc;
	for(yy=0;yy<shapeobj.rasterheight;yy++)
		for(xx=0;xx<shapeobj.rasterwidth;xx++)
			{
			pc=shapeobj.pget(xx,yy);
			if(pc!=shapeobj.invisible)
				this.pset(x+xx,y+yy,pc);			
			}
			
	return true;
	}

function fn_createCircle(x,y,diameter,color,segments,thickness)
	{
	if(this.rastermap==null)
		return false;
	var xx,yy;
	var px,py;
	var fx,fy;
	var pi=6.2830;
	var f;
	var hd=(diameter>>1);
	var step=(360/segments);
	xx=x; yy=y;
	for(i=0;i<360;i+=step)
		{
		f=(i/360)*pi;
		px=x+Math.floor(diameter*Math.sin(f));
		py=y+Math.floor(diameter*Math.cos(f));
		if(xx==x && yy==y)
			{
			fx=xx=px; fy=yy=py;
			}
		if(xx!=px || yy!=py)
			{
			this.createLine(xx,yy,px,py,color,thickness);
			xx=px; yy=py;
			}
		}
		
		this.createLine(px,py,fx,fy,color,thickness); // close the shape
		return true;
	}
	
function fn_fmf_pointmatch(x,y,sc,pc)
	{
	return (this.pget(x,y)==sc);
	}
	
function fn_fmf_pointborder(x,y,sc,pc)
	{
	return (this.pget(x,y)!=pc);
	}
	
function fn_setFillMode(S)
	{
	if(S=="border")
		{
		this.fillModeFunction=fn_fmf_pointborder;
		return true;
		}
	else
	if(S=="default")
		{
		this.fillModeFunction=fn_fmf_pointmatch;
		return true;
		}
		
	this.fillModeFunction=fn_fmf_pointmatch;
	return false;
	}

function fn_solidFill(x,y,color)
	{
	if(this.inRange(x,y)==false)
		return false;
	var fu=(y-1),fd=y;
	var au=true,ad=true;
	var sc=this.pget(x,y);
	while(au || ad)
		{
		if(this.fillModeFunction(x,fd,sc,color) && fd<this.rasterheight)
			{
			this.fillLine(x,fd,color);
			fd++;
			}
		else
			ad=false;
			
		if(this.fillModeFunction(x,fu,sc,color) && fu>0)
			{
			this.fillLine(x,fu,color);
			fu--;
			}
		else
			au=false;		
		}
	return true;
	}

function fn_inRange(x,y)
	{		
	if(this.rastermap==null)
	   return false;
	if(x<0 || x>(this.rasterwidth-1))
		return false;
	if(y<0 || y>(this.rasterheight-1))
		return false;
	return true;
	}

function fn_fillLine(x,y,color)
	{	
	if(this.inRange(x,y)==false)
		return false;
	var sc=this.pget(x,y);
	
	var ar=true,al=true;
	var fr=x,fl=(x-1);
	while(ar || al)
		{
		if(this.fillModeFunction(fr,y,sc,color) && fr<this.rasterwidth)
			{
			this.pset(fr,y,color);
			fr++;
			}
		else
			ar=false;
			
		if(this.fillModeFunction(fl,y,sc,color) && fl>0)
			{
			this.pset(fl,y,color);
			fl--;
			}
		else
			al=false;	
		}
	
	}
	

function fn_createRectangle(x,y,width,height,color)
	{
	var ix,iy;
	if(this.rastermap==null)
		return false;
	if(width==1 && height==1)
		return this.pset(x,y,color);
	for(ix=0;ix<width;ix++)
		for(iy=0;iy<height;iy++)
			this.pset(x+ix,y+iy,color);
	return true;
	}
	
function fn_createLine(x,y,x1,y1,color,thickness)
	{
	if(this.rastermap==null)
		return false;
	var dx=(x1-x),dy=(y1-y);
	var dist=(dx>0) ? dx:-dx;
	var d=(dy>0) ? dy:-dy;
	var ax,ay;
	var i,f;
	var adj=(thickness>>1);
	dist=(d>dist) ? d:dist;
	for(i=0;i<dist;i++)
		{
		f=(i/dist);
		ax=x+Math.floor(dx*f)-adj;
		ay=y+Math.floor(dy*f)-adj;
		this.createRectangle(ax,ay,thickness,thickness,color);
		}
	return true;
	}
	
function fn_createBox(x,y,width,height,color,thickness)
	{
	if(this.rastermap==null)
		return false;
	this.createLine(x,y,x+width,y,color,thickness);
	this.createLine(x,y+height,x+width,y+height,color,thickness);
	
	this.createLine(x,y,x,y+height,color,thickness);
	this.createLine(x+width,y,x+width,y+height,color,thickness);
	return true;
	}
	
function fn_setImageSize(width,height) // reset rastermap
	{
	var x,y,line;
	this.rastermap=[];
	for(y=0;y<height;y++)
		{
		line=[];
		for(x=0;x<width;x++)			
			line[x]=this.invisible;
		this.rastermap[y]=line;
		}
	this.rasterheight=y;
	this.rasterwidth=x;
	return true;
	}
	
function fn_toString()
	{
	/* Convert the rastermap to div rows */
	if(this.rastermap==null)
		return "";
	var S='<div style="display:block; width:'+this.rasterwidth+'px; height:'+this.rasterheight+'px;">';
	var E="";
	
	var vreset="x";
	var v=vreset;
	var r=0;
	var pv;
	var line;
	for(y=0;y<this.rasterheight;y++)
		{
		line=this.rastermap[y];
		v=vreset; r=0;
		for(x=0;x<this.rasterwidth;x++)
			{
			pv=line[x]; if(v==vreset) v=pv;
			if(pv==v)
				r++;
			else
				{
				var pc=(v!=this.invisible) ? "background-color:#"+v+";":"";
				E='<div style="display: block; width:'+r+'px; height:1px; '+pc+' float:left;"></div>';
				S+=E;
				E="";
				v=pv; r=1;
				}
			}
		if(r)
			{
			var pc=(v!=this.invisible) ? "background-color:#"+v+";":"";
			E='<div style="display: block; width:'+r+'px; height:1px; '+pc+' float:left;"></div>';
			S+=E;
			E="";
			v=vreset; r=0;
			}
		}
	S+="</div>";
	return S;
	}
	
function fn_pset(x,y,color)
	{
	var c1=0,c2=0,c3=0;
	var fv=1.0,fu=0.0;
	if(this.rastermap==null)
	   return false;
	if(this.inRange(x,y)==false)
	   return false;
	var line=this.rastermap[y];
	
	if(this.opacity<100)
		{
		fv=this.opacity/100.0;
		fu=1.0-fv;
		var cv=this.colorToRGB(color);
		var cu=this.colorToRGB(this.pget(x,y));
		cv.R=Math.floor(cu.R*fu+cv.R*fv);
		cv.G=Math.floor(cu.G*fu+cv.G*fv);
		cv.B=Math.floor(cu.B*fu+cv.B*fv);
		color=this.rgbToColor(cv);		
		}
	
	line[x]=color;
	return true;
	}
	
function fn_pget(x,y)
	{
	if(this.rastermap==null)
	   return this.invisible;
	if(this.inRange(x,y)==false)
	   return this.invisible;
	
	var line=this.rastermap[y];	
	return line[x];
	}
	

}
// e:Shape maker object1

// JLLPM - Javascript Logo like Language Processing Module
function JLLPM(instance_name)
	{
	// private
	var IA=[];
	var IP=0;
	var STACK={n:0,e:null}; // PUSH, POP
	var iname=instance_name;
	var turtle={x:0,y:0,xi:0,yi:0,heading:0,unit:1,pen_down:true,pen_down_callback:null,pen_down_array:null,done_callback:null,target_x:0,target_y:0};
	
	var STV=[]; // Script Translation Vector
	var PRV=[]; // Projectile Routing Vector
	
	function PUSH(element)
		{
		if(STACK.e==null)
			{
			STACK.e=[];
			STACK.n=0;
			}
		STACK.e[STACK.n++]=element;
		return STACK.n;
		}
		
	function POP()
		{		
		if(STACK.e==null)
			return null;
		var element=null;
		if(STACK.n>0)
			{
			element=STACK.e[(STACK.n-1)];
			STACK.n--;
			}
		if(STACK.n==0)
			STACK.e=null;
		return element;
		}
		
	function PUSH_TURTLE()
		{
		PUSH(turtle);
		}
		
	function POP_TURTLE()
		{
		turtle=POP();
		}
		
	function InstructionRecord(name)
		{
		var ir={fn:null,label:name,value:null,params:null};
		return ir;
		}
		
	function ScriptTranslationRecord(keyword,pfn)
		{
		var str={pfn_translator:pfn,keyword:keyword,me:this};
		return str;
		}
		
	function StreamInRecord(buffer)
		{
		var len=0;
		var offset=0;
		if(buffer!=null && typeof(buffer)=="string")
			len=buffer.length;
		var sir={buffer:buffer,offset:offset,length:len,ws_count:0};
		return sir;
		}
		
	
		
	function findItem(A,search)
		{
		var IDX=null;
		if(A.length<1)
			return null;
		IDX=A[0].idx;
		if(IDX==null)
			return null;
		var S="{"+search+"/";
		var L=null;
		var ipos=-1;
		ipos=IDX.indexOf(S);
		if(ipos<0)
			return null;
		L=parseInt(IDX.substring(ipos+S.length));
		return L;
		}
		
	function addItem(A,key,obj)
		{
		var IDX="";
		if(A.length>=1)
			IDX=A[0].idx;
		if(findItem(A,key)!=null)
			return false;
		var i=A.length;
		A[i]=obj;
		if(IDX!="")
			{
			IDX+=",";
			}
		IDX+= "{"+key+"/"+i;
		A[0].idx=IDX;
		return true;
		}
		
	/* Script Translation Functions */
	function isTokenWordChar(x)
		{
		if(x==null)
			return false;
		x=x.toLowerCase();
		if(x>="a" && x<="z")
			return true;
		if(x>="0" && x<="9")
			return true;
		if(x=="_")
			return true;
		return false;
		}
		
	function isTokenSymbol(x)
		{
		var symbols="(){},;:.\"=><!&|*/+-'";
		return (symbols.indexOf(x)>=0);
		}
		
	function isWhiteSpace(x)
		{
		var symbols=" "+String.fromCharCode(13)+String.fromCharCode(10)+String.fromCharCode(9);
		return (symbols.indexOf(x)>=0);
		}
		
	
	function nextToken(stream_in)
		{
		var token="";
		var c;
		if(stream_in.offset>=stream_in.length)
			return null;
		c=stream_in.buffer.charAt(stream_in.offset++);
		stream_in.ws_count=0;
		while((isWhiteSpace(c)) && stream_in.offset<stream_in.length)	{	
			stream_in.ws_count++;
			c=stream_in.buffer.charAt(stream_in.offset++);
		}
		while(isTokenWordChar(c) && stream_in.offset<stream_in.length)
			{
			token+=c;
			c=stream_in.buffer.charAt(stream_in.offset++);
			}
			
		if(token=="")
			{						
			if(isTokenSymbol(c))
				return c;
			}
		else
			stream_in.offset=(stream_in.offset>0) ? (stream_in.offset-1):stream_in.offset;
			
		if(token.length<1)
			return null;
		return token;
		}
	
	function CALL(key,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p)
		{
		var slot=findItem(IA,key);
		if(slot==null)
			return null;
		var ir=IA[slot];
		if(ir.fn==null)
			return null;
		
		var iii,ppp=[];
		ppp[0]=a; ppp[1]=b;	ppp[2]=c; ppp[3]=d;
		ppp[4]=e; ppp[5]=f;	ppp[6]=g; ppp[7]=h;
		ppp[8]=i; ppp[9]=j;	ppp[10]=k; ppp[11]=l;
		ppp[12]=m; ppp[13]=n; ppp[14]=o; ppp[15]=p;
		// ir={fn:null,label:name,value:null,params:null}
		if(ir.params!=null)
			{
			var plist=ir.params.split(",");			
			for(iii=0;(iii<plist.length && iii<ppp.length);iii++)				
				PUSH(ppp[iii]);				
			}
			
		if(turtle.pen_down_callback==null)
			turtle.pen_down_array=[];
			
		var result=ir.fn();
		DO_DONE();
		return result;
		}
	/* e:Script Translation Functions */
	
	function translate_internal_functions(t,stream_in)
		{
		var token;
		var name=nextToken(stream_in);
		var s=nextToken(stream_in);
		var params="";
		var out="";
		while(s!=null && s!=")")
			{
			if(s!="(" && s!=")")
				params+=s;
			s=nextToken(stream_in);
			}
			
			
		out=iname+"."+t+"("+params+")";	
		
			
		return out;		
		}
		
	function translate_external_functions(t,stream_in)
		{
		var token;
		var name=nextToken(stream_in);
		var s=nextToken(stream_in);
		var params="";
		var out="";
		while(s!=null && s!=")")
			{
			if(s!="(" && s!=")")
				params+=s;
			s=nextToken(stream_in);
			}
			
			
		out=iname+".call(\""+t+"\","+params+")";
			
		return out;
		}

	function PROCESS_FUNCTION_BODY(S)
		{
		var stream_in=StreamInRecord(S);
		var token=nextToken(stream_in);
		var I;
		var str,out;
		var tx="";
		while(token)
			{
			I=findItem(STV,token);
			if(I!=null)
				{
				str=STV[I];				
				out=str.pfn_translator(token,stream_in);
				}
			else
				out=token;			
			tx+=out;
			token=nextToken(stream_in);
			if(stream_in.ws_count>0)
				tx+=" ";
			}
		return tx;
		}

	
	function translate_function(stream_in)
		{
		var token;
		var name=nextToken(stream_in);
		var s=nextToken(stream_in);
		var params="";
		var body="",prolog="",lastToken="";
		var out="";
		var iii;
		while(s!=null && s!=")")
			{
			if(s!="(" && s!=")")
				params+=s;
			s=nextToken(stream_in);
			}
			
		var ldepth=0,ll=0;
		s=nextToken(stream_in);
		
		ldepth=(s=="{") ? 1:0;
		
		if(ldepth<1)
			return null;
			
		if(params!="")
			{
			var plist=params.split(",");
			for(iii=plist.length-1;iii>=0;iii--)
				{
				if(prolog=="")
					prolog="var ";
				else
					prolog+=",";
				prolog+=(plist[iii]+"="+iname+".pop()");
				}
			prolog+=";";
			}
		
		while(ldepth>0 && stream_in.offset<stream_in.length)
			{
			if(isTokenSymbol(s) && body.charAt(body.length-1)==" ")
			body=body.substring(0,body.length-1);			
				
			body+=s; 
			
			if(body=="{")
				body+=prolog;
					
			lastToken=s;
			
			s=nextToken(stream_in);
			if(stream_in.ws_count>0)
				body+=" ";
			ldepth=(s=="{") ? (ldepth+1):(s=="}") ? (ldepth-1):ldepth;
			}
			
		if(s=="}")
			body+=s;
			
		addTranslationRecord(STV,name,translate_external_functions);
		var ir=InstructionRecord(name);
		
		body=PROCESS_FUNCTION_BODY(body);
		
		try
		{
		ir.fn=new Function(body);
		ir.params=params;
		addItem(IA,name,ir);
		}
		catch(e)
		{
		ir.fn=null;
		}
		
		// ir={fn:null,label:name,value:null,params:null}
					
		// alert(name+" parameters: "+params+" body: "+body);
		out+=("function "+name+"("+params+") "+body);
		return out;
		}
		
	function addTranslationRecord(A,keyword,pfn)
	{
	var str;
	var b;	
	str=ScriptTranslationRecord(keyword,pfn);
	return addItem(A,keyword,str);
	}
	
	function SETUP()
	{	
	// rt,lt,fd,bd,pu,pd,sh,gh
	addTranslationRecord(STV,"function",translate_function);
	addTranslationRecord(STV,"rt",translate_internal_functions);
	addTranslationRecord(STV,"lt",translate_internal_functions);
	addTranslationRecord(STV,"fd",translate_internal_functions);
	addTranslationRecord(STV,"bd",translate_internal_functions);
	addTranslationRecord(STV,"pu",translate_internal_functions);
	addTranslationRecord(STV,"pd",translate_internal_functions);
	addTranslationRecord(STV,"sh",translate_internal_functions);
	addTranslationRecord(STV,"gh",translate_internal_functions);
	addTranslationRecord(STV,"sp",translate_internal_functions);	
	addTranslationRecord(STV,"sc",translate_internal_functions);
	addTranslationRecord(STV,"set_done",translate_internal_functions);
	addTranslationRecord(STV,"do_done",translate_internal_functions);
	addTranslationRecord(STV,"su",translate_internal_functions);
	addTranslationRecord(STV,"st",translate_internal_functions);
	addTranslationRecord(STV,"push_turtle",translate_internal_functions);
	addTranslationRecord(STV,"pop_turtle",translate_internal_functions);
	}
	
			
	function LOAD(s,pfn_complete)
		{		
		var stream_in=StreamInRecord(s);
		var token=nextToken(stream_in);
		var I;
		var str,out;
		var tx="";
		while(token)
			{
			I=findItem(STV,token);
			if(I!=null)
				{
				str=STV[I];			
				out=str.pfn_translator(stream_in);
				}
			else
				out=token;			
			tx+=out;
			token=nextToken(stream_in);
			if(stream_in.ws_count>0)
				tx+=" ";
			}
		if(pfn_complete!=null)
			pfn_complete();
		
		}
		
	function SET_PEN_DOWN_CALLBACK(pfn)
		{		
		if(pfn!=null)
			turtle.pen_down_callback=pfn;
		return (pfn!=null);
		}
		
	function GET_PEN_DOWN_ARRAY()
		{	
		return turtle.pen_down_array;
		}
		
	function RESET(pfn)
		{		
		turtle.x=turtle.y=turtle.heading=0;
		turtle.unit=1;				
		SET_PEN_DOWN_CALLBACK(pfn);		
		turtle.pen_down=false;
		turtle.pen_down_array=[];
		}
		
	function XIYI()
		{
		var rad=turtle.heading-135.0*(Math.PI/180);
			
		turtle.xi=0;
		turtle.yi=0;
		
		turtle.xi=Math.cos(rad)*turtle.unit - Math.sin(rad)*turtle.unit+turtle.xi;
		turtle.yi=Math.sin(rad)*turtle.unit + Math.cos(rad)*turtle.unit+turtle.yi;
		}
		
	
	function RT(angle)
		{
		// turtle={x:0,y:0,heading:0,unit:1,pen_down:true};
		var rad=angle*(Math.PI/180.0);
		turtle.heading+=rad;
		XIYI();
		return turtle.heading;
		}
		
	function LT(angle)
		{
		// turtle={x:0,y:0,heading:0,unit:1,pen_down:true};
		var rad=angle*(Math.PI/180.0);
		turtle.heading-=rad;
		XIYI();
		return turtle.heading;
		}
		
	function FD(units)
		{
		turtle.x+=turtle.xi;
		turtle.y+=turtle.yi;		
		
		var point={x:turtle.x,y:turtle.y,pen_down:turtle.pen_down};
		if(turtle.pen_down)
			{
			if(turtle.pen_down_callback)
				turtle.pen_down_callback(point);
			if(turtle.pen_down_array)
				{				
				var i=turtle.pen_down_array.length;								
				turtle.pen_down_array[i]=point;
				}
			}
		return point;
		}
		
	function BD(units)
		{			
		turtle.x-=turtle.xi;
		turtle.y-=turtle.yi;
		
		var point={x:turtle.x,y:turtle.y,pen_down:turtle.pen_down};
		if(turtle.pen_down)
			{
			if(turtle.pen_down_callback)
				turtle.pen_down_callback(point);
			if(turtle.pen_down_array)
				{
				var i=turtle.pen_down_array.length;
				turtle.pen_down_array[i]=point;
				}
			}
		return point;
		}
		
	function PU()
		{
		turtle.pen_down=false;
		var point={x:turtle.x,y:turtle.y,pen_down:turtle.pen_down};
		if(turtle.pen_down_callback)
				turtle.pen_down_callback(point);
		if(turtle.pen_down_array)
				{
				var i=turtle.pen_down_array.length;
				turtle.pen_down_array[i]=point;
				}
		
		}
		
	function PD()
		{
		turtle.pen_down=true;		
		}
		
	function SH(heading)
		{
		var rad=heading*(Math.PI/180.0);
		turtle.heading=rad;
		XIYI();
		return rad;
		}
		
	function GH(physics)
		{
		var r2d=57.2957795;
		var heading=turtle.heading*r2d;
		
		if(physics==null)
			return Math.round(heading);
		
		
		var th=null;
		if(typeof(physics)=="number")
			th=physics;
		else			
			th=physics.th;
			
			
		var right=0,left=0;
		right=Math.round((th<heading) ? (heading-th):360+(heading-th));
		left=Math.round((th>heading) ? (th-heading):360+(th-heading));
		
		left=(left==360) ? 0:left;
		right=(right==360) ? 0:right;
		
		var obj={right:right,left:left,distance:0,gforce:0};
		
		
		if(physics!=null && typeof(physics)=="object") // skew or bias by physics of the target
			{
			/*
			x,y - point
			radius - of gravitational field from centre
					if point is within this field then it is influenced by
					gravity, else heading is unchanged
					
			*/
			var xx=physics.x-turtle.x;
			var yy=physics.y-turtle.y;
			obj.distance=Math.sqrt(xx*xx+yy*yy);
			obj.gforce=(obj.distance<=physics.radius) ? (1.0-obj.distance/physics.radius):0;
			obj.left*=obj.gforce;
			obj.right*=obj.gforce;
			}
		
		
		return obj;
		}
	
		
	function SU(unit_size)
		{
		turtle.unit=unit_size;
		}
		
	function SP(x,y)
		{
		turtle.x=x; turtle.y=y;
		}
		
	function ST(x,y) // Set Target
		{
		var heading=0;
		// SOHCAHTOA
		var xx=x-turtle.x;
		var yy=y-turtle.y;
		var hh;
		
		var angle=0,start=0,flip=0;
		var q=0;
		
		if(xx>=0)			
			q=(yy>=0) ? 0:1;		
		else
			q=(yy>=0) ? 3:2;
			
		switch(q)
			{
			case 0:
				{
				start=0;
				break;
				}
			case 1:
				{
				yy*=-1.0;
				start=1.570796;
				break;
				}
			case 2:
				{
				yy*=-1.0;
				xx*=-1.0;
				start=3.14159;
				flip=7.85398;
				break;
				}
			case 3:
				{				
				xx*=-1.0;
				start=4.7123884;
				flip=0.0;
				break;
				}
			}
		
		
		if(xx>=0 && yy>=0)
			{			
			// no change
			if(xx>yy) // 0-45
				{
				angle=start+Math.asin(yy/Math.sqrt(xx*xx+yy*yy));
				}
			else      // 45-90
				angle=start+1.570796-Math.asin(xx/Math.sqrt(xx*xx+yy*yy));
			}
				
		if(flip>0.0)
			angle=flip-angle;		
		
		heading=angle*(180.0/Math.PI);		
				
		return SH(heading);		
		}
		
	function SET_DONE(pfn)
		{
		turtle.done_callback=pfn;
		}
		
	function DO_DONE()
		{
		if(turtle.done_callback!=null)			
			turtle.done_callback();			
		}
		
	function PHYSICS_RECORD()
		{			
		return {x:0,y:0,radius:0,th:0};
		}
	
	/*
	rt,lt,fd,bd,pu,pd,sh,gh
	*/
		
	this.push=PUSH;
	this.pop=POP;
	this.load=LOAD;
	this.setup=SETUP;
	this.call=CALL;
	this.set_pen_down_callback=SET_PEN_DOWN_CALLBACK;
	this.get_pen_down_array=GET_PEN_DOWN_ARRAY;
	
	this.reset=RESET;
	this.rt=RT;
	this.lt=LT;
	this.fd=FD;
	this.bd=BD;
	this.pu=PU;
	this.pd=PD;
	this.sh=SH;
	this.gh=GH;
	this.sp=SP;
	this.sc=SP;
	this.set_done=SET_DONE;
	this.do_done=DO_DONE;
	this.su=SU;
	this.st=ST;
	this.push_turtle=PUSH_TURTLE;
	this.pop_turtle=POP_TURTLE;	
	this.physics_record=PHYSICS_RECORD;
	
	this.loadfromfile=function(url,pfn_complete)
		{
		var me=this;

		$.ajax({url: url,dataType: "script",success: function(S)
															  {
																																
		me.load(S,pfn_complete);
																
															  },error:null});
		}
	
	
		
		
	function setupScriptTranslator()
		{
		var obj;
		obj=ScriptTranslationRecord("var");
		
		}

		
	function buildInstruction(address)
		{
		var I=0;
		/*
		convert string into a function if possible
		everything to run against object virtual machine		
		
		1. entries in IA are javascript functions
		2. input script is filtered by tokens
		3. list of native tokens that are allowed:
			var,for,in,switch,while,if,else
		4. list of instruction tokens that are allowed:
			function,sss,sh,lt,rt,fd,bd,call,<<defined functions>>
		
		must convert:
		var x,y,z=10; -> var x,y,z=10;
		sss(z); -> vm.sss(z);
		sss(10); -> vm.sss(10);
		x=15; -> x=15;
		x=y=z=15 -> x=y=z=15
		function_name(e0,14*x,e2) -> vm.exec("function_name",e1,14*x,e2)		
		function name(p1,p2,p3)
		{} -> vm.define("name",function_body);
		for(j=0;j<15;j++)
		{} -> for(vm.v("j",0);vm.v("j")<15;vm.v("j",vm.v("j")+1) code_block
		while(x)
		{} -> while(x) code_block
		if(x)
		{}
		else
		{} -> if(x) code_block else code_block
		
		x=(exp)? true:false -> x=(exp)? true:false
		
		
		name(p1,p2) -> vm.call("name",p1,p2)
		
		
		*/
		
		
		
		}
		
	function addInstruction(I)
		{
		if(typeof(I)!="object")
			return false;
		var i=IA.length;
		IA[i]=I;
		return true;
		}
		
	function buildIndex(A,property_name)
		{
		var IDX="";
		if(A.length<1)
			return false;
		
		var i;
		for(i=0;i<A.length;i++)
			{
			if(A[i][property_name]!=null)
				{
				if(IDX!="")				
					IDX+=",";				
				}
			IDX+= "{"+A[i][property_name]+"/"+i;				
			}
		A[0].idx=IDX;
		return true;
		}
		
		// public	
		/*
		Projectile Tool
		*/
	this.getProjectileInterface=function(options)
		{
		/*
		options:
		mass (0.0 - 1.0) ' 0.0 is massive, 1.0 is massless
		velocity - move units
		approach (intersect, orbit) ' 	intersect targets point,
										orbit targets gravitational radius 
										- need to look ahead to next point for
										sling shot trajectory
		
		interface:
		target_record() {x:0,y:0,radius:0,min_gravity:0}
		addTarget(target_record)
		reset()		
		fire() - navigates course, returns point array {x,y}
		*/
		var me=this;
		return {
				target_record:function()
								{
								return {x:0,y:0,radius:0.0,min_gravity:0.0};
								},
				addTarget:function(REC)
								{
								var i;
								i=me.PRV.length;
								me.PRV[i]=REC;
								return i+1;
								},
				reset:function()
								{
								me.PRV=[];
								},
				fire:function()
								{
								return me.get_pen_down_array();
								}
				}
		}
		
	}
// e:JLLPM
