فهرست منبع

mky.mojo2. Initial Import.

woollybah 9 سال پیش
والد
کامیت
999989c963
38فایلهای تغییر یافته به همراه5917 افزوده شده و 0 حذف شده
  1. 6 0
      .gitignore
  2. 43 0
      mojo2.mod/data/mojo2_bumpshader.glsl
  3. 17 0
      mojo2.mod/data/mojo2_fastshader.glsl
  4. BIN
      mojo2.mod/data/mojo2_font.png
  5. 9 0
      mojo2.mod/data/mojo2_lightmapshader.glsl
  6. 32 0
      mojo2.mod/data/mojo2_matteshader.glsl
  7. 261 0
      mojo2.mod/data/mojo2_program.glsl
  8. 7 0
      mojo2.mod/data/mojo2_shadowshader.glsl
  9. 76 0
      mojo2.mod/examples/bumptest/bumptest.bmx
  10. 42 0
      mojo2.mod/examples/directlight/directlight.bmx
  11. BIN
      mojo2.mod/examples/directlight/images/t3.png
  12. BIN
      mojo2.mod/examples/directlight/images/t3_NORMALS.png
  13. BIN
      mojo2.mod/examples/directlight/images/t3_SPECULAR.png
  14. 68 0
      mojo2.mod/examples/drawprimitives/drawprimitives.bmx
  15. 31 0
      mojo2.mod/examples/helloworld/helloworld.bmx
  16. 82 0
      mojo2.mod/examples/letterbox/letterbox.bmx
  17. BIN
      mojo2.mod/examples/mojotest/images/RedbrushAlpha.png
  18. 94 0
      mojo2.mod/examples/mojotest/mojotest.bmx
  19. 39 0
      mojo2.mod/examples/renderdrawlist/renderdrawlist.bmx
  20. BIN
      mojo2.mod/examples/rendererdemo/images/t3.png
  21. BIN
      mojo2.mod/examples/rendererdemo/images/t3_NORMALS.png
  22. BIN
      mojo2.mod/examples/rendererdemo/images/t3_SPECULAR.png
  23. 165 0
      mojo2.mod/examples/rendererdemo/rendererdemo.bmx
  24. 46 0
      mojo2.mod/examples/rendertoimage/rendertoimage.bmx
  25. 24 0
      mojo2.mod/examples/shadereffect/data/bwshader.glsl
  26. BIN
      mojo2.mod/examples/shadereffect/data/default_player.png
  27. 107 0
      mojo2.mod/examples/shadereffect/shadereffect.bmx
  28. 0 0
      mojo2.mod/examples/vectormonkey/data.txt
  29. 197 0
      mojo2.mod/examples/vectormonkey/vectormonkey.bmx
  30. 59 0
      mojo2.mod/examples/writepixels/writepixels.bmx
  31. 202 0
      mojo2.mod/glslparser.bmx
  32. 98 0
      mojo2.mod/glutil.bmx
  33. 3141 0
      mojo2.mod/graphics.bmx
  34. 247 0
      mojo2.mod/maps.bmx
  35. 150 0
      mojo2.mod/maps.c
  36. 167 0
      mojo2.mod/math3d.bmx
  37. 39 0
      mojo2.mod/mojo2.bmx
  38. 468 0
      mojo2.mod/renderer.bmx

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+.bmx
+*.bak
+*.x64.i
+*.x86.i
+*.x86.a
+*.x64.a

+ 43 - 0
mojo2.mod/data/mojo2_bumpshader.glsl

@@ -0,0 +1,43 @@
+
+//shader for full lighting.
+
+uniform sampler2D ColorTexture;
+uniform sampler2D SpecularTexture;
+uniform sampler2D NormalTexture;
+
+uniform vec4 AmbientColor;
+uniform float Roughness;
+
+void shader(){
+
+	vec4 color=texture2D( ColorTexture,b3d_Texcoord0 );
+	
+#if GAMMA_CORRECTION
+	color.rgb=pow( color.rgb,vec3( 2.2 ) );
+#endif
+
+	color*=b3d_Color;
+
+	b3d_Alpha=color.a;
+
+	b3d_Ambient=color * AmbientColor;
+
+	b3d_Diffuse=color * (1.0-AmbientColor);
+	
+	b3d_Specular=texture2D( SpecularTexture,b3d_Texcoord0 );
+
+#if GAMMA_CORRECTION
+	b3d_Specular.rgb=pow( b3d_Specular.rgb * b3d_Alpha,vec3( 2.2 ) );
+#else
+	b3d_Specular.rgb*=b3d_Alpha;
+#endif
+	
+#if OPT_FAST
+	b3d_Normal=texture2D( NormalTexture,b3d_Texcoord0 ).xyz*2.0-1.0;
+#else	
+	b3d_Normal=normalize( texture2D( NormalTexture,b3d_Texcoord0 ).xyz*2.0-1.0 );
+#endif
+	b3d_Normal.yz=-b3d_Normal.yz;
+
+	b3d_Roughness=Roughness;
+}

+ 17 - 0
mojo2.mod/data/mojo2_fastshader.glsl

@@ -0,0 +1,17 @@
+
+//shader for simple unlit sprites.
+
+uniform sampler2D ColorTexture;
+
+void shader(){
+
+	b3d_Ambient=texture2D( ColorTexture,b3d_Texcoord0 );
+	
+#if GAMMA_CORRECTION
+	b3d_Ambient.rgb=pow( b3d_Ambient.rgb,vec3( 2.2 ) );
+#endif
+
+	b3d_Ambient*=b3d_Color;	//apply vertex coloring
+
+	b3d_Alpha=b3d_Ambient.a;	//extract alpha
+}

BIN
mojo2.mod/data/mojo2_font.png


+ 9 - 0
mojo2.mod/data/mojo2_lightmapshader.glsl

@@ -0,0 +1,9 @@
+
+//shader for drawing lightmaps, ie: light images or layer light masks.
+
+uniform sampler2D ColorTexture;
+
+void shader(){
+
+	b3d_FragColor=vec4( texture2D( ColorTexture,b3d_Texcoord0 ).r );
+}

+ 32 - 0
mojo2.mod/data/mojo2_matteshader.glsl

@@ -0,0 +1,32 @@
+
+//shader for simple matte lighting.
+
+uniform sampler2D ColorTexture;
+uniform sampler2D SpecularTexture;
+uniform sampler2D NormalTexture;
+
+uniform vec4 AmbientColor;
+uniform float Roughness;
+
+void shader(){
+
+	vec4 color=texture2D( ColorTexture,b3d_Texcoord0 );
+	
+#if GAMMA_CORRECTION
+	color.rgb=pow( color.rgb,vec3( 2.2 ) );
+#endif
+
+	color*=b3d_Color;
+
+	b3d_Alpha=color.a;
+
+	b3d_Ambient=color * AmbientColor;
+
+	b3d_Diffuse=color * (1.0-AmbientColor);
+	
+	b3d_Specular=vec4( 0.0 );
+	
+	b3d_Normal=vec3( 0.0,0.0,-1.0 );
+
+	b3d_Roughness=Roughness;
+}

+ 261 - 0
mojo2.mod/data/mojo2_program.glsl

@@ -0,0 +1,261 @@
+
+//***** Mojo2 Mega Shader *****
+
+//***** USER OPTIONS ******
+
+//enable less accurate but faster rendering - make sure yer normals are right when enabling this!
+#define OPT_FAST 0
+
+//Apply cheesy_hdr to lighting - slower, but nice for colored lights as they saturate to white
+#define CHEESY_HDR 1
+
+//enable bump tangents, for correct bump mapping of rotating drawops - can only really handle uniform scaling/rotation, but will probably look OK otherwise...
+#define BUMP_TANGENTS 1
+
+//make sure shadows are working!
+#define DEBUG_SHADOWS 0
+
+//enable ortho lighting - faster + makes lights easier to position
+#define ORTHO_LIGHTING 1
+
+//enable fresnel effect - slower
+#define FRESNEL_EFFECT 1
+
+//enable gamma correction - slower. Don't use yet! Messes up with >4 lights...
+#define GAMMA_CORRECTION 0
+
+
+//***** ENABLED VARS *****
+
+#if NUM_LIGHTS && !defined(B3D_CLIPPOSITION)
+#define B3D_CLIPPOSITION 1
+#endif
+
+#if NUM_LIGHTS && !defined(B3D_VIEWPOSITION)
+#define B3D_VIEWPOSITION 1
+#endif
+
+#if NUM_LIGHTS && BUMP_TANGENTS && !defined(B3D_VIEWTANGENT)
+#define B3D_VIEWTANGENT 1
+#endif
+
+#ifdef B3D_TEXCOORD0
+varying vec2 b3d_Texcoord0;
+#endif
+
+#ifdef B3D_COLOR
+varying vec4 b3d_Color;
+#endif
+
+#ifdef B3D_VIEWPOSITION
+varying vec3 b3d_ViewPosition;
+#endif
+
+#ifdef B3D_VIEWNORMAL
+varying vec3 b3d_ViewNormal;
+#endif
+
+#ifdef B3D_VIEWTANGENT
+varying vec3 b3d_ViewTangent;
+#endif
+
+#ifdef B3D_CLIPPOSITION
+varying vec4 b3d_ClipPosition;
+#endif
+
+//@vertex
+
+uniform mat4 ModelViewProjectionMatrix;
+uniform mat4 ModelViewMatrix;
+uniform vec4 ClipPosScale;	//hack to handle inverted y when rendering to display
+uniform vec4 GlobalColor;
+
+attribute vec4 Position;
+attribute vec2 Texcoord0;
+attribute vec3 Tangent;
+attribute vec4 Color;
+
+void main(){
+
+	gl_Position=ModelViewProjectionMatrix * Position;
+	
+	gl_PointSize=1.0;
+	
+#ifdef B3D_CLIPPOSITION
+	b3d_ClipPosition=gl_Position * ClipPosScale;
+#endif
+
+#ifdef B3D_VIEWPOSITION
+	b3d_ViewPosition=(ModelViewMatrix * Position).xyz;
+#endif
+	
+#ifdef B3D_VIEWNORMAL
+	b3d_ViewNormal=(ModelViewMatrix * vec4( 0.0,0.0,-1.0 )).xyz;
+#endif
+
+#ifdef B3D_VIEWTANGENT
+#if OPT_FAST
+	b3d_ViewTangent=normalize( (ModelViewMatrix * vec4( Tangent,0.0 )).xyz );
+#else
+	b3d_ViewTangent=(ModelViewMatrix * vec4( Tangent,0.0 )).xyz;
+#endif
+#endif
+
+#ifdef B3D_TEXCOORD0
+	b3d_Texcoord0=Texcoord0;
+#endif
+
+#ifdef B3D_COLOR
+	b3d_Color=Color * GlobalColor;
+#endif
+		
+}
+
+//@fragment
+
+uniform vec4 FogColor;
+uniform vec4 AmbientLight;
+
+#if NUM_LIGHTS
+uniform sampler2D ShadowTexture;
+uniform vec4 LightColors[NUM_LIGHTS];
+uniform vec4 LightVectors[NUM_LIGHTS];
+#endif
+
+#define b3d_FragColor gl_FragColor
+float b3d_Alpha;
+float b3d_Roughness;
+vec4 b3d_Ambient;
+vec4 b3d_Diffuse;
+vec4 b3d_Specular;
+vec3 b3d_Normal;
+
+${SHADER}
+
+#if NUM_LIGHTS
+
+float gloss;
+float spow;
+float fnorm;
+float ndotv;
+#if ORTHO_LIGHTING
+const vec3 eyevec=vec3( 0.0,0.0,-1.0 );
+#else
+vec3 eyevec;
+#endif
+vec4 diffuse;
+vec4 specular;
+mat3 tanMatrix;
+
+void light( in vec4 lightVector,in vec4 lightColor,float shadow ){
+
+#if DEBUG_SHADOWS
+	diffuse+=1.0-shadow;
+	return;
+#endif
+
+	vec3 v=lightVector.xyz-b3d_ViewPosition;
+#if BUMP_TANGENTS
+	v=tanMatrix * v;
+#endif
+	
+	float falloff=max( 1.0-length( v )/lightVector.w,0.0 );
+	
+	vec3 lvec=normalize( v );
+	vec3 hvec=normalize( lvec + eyevec );
+	
+	float ndotl=max( dot( b3d_Normal,lvec ),0.0 );
+	float ndoth=max( dot( b3d_Normal,hvec ),0.0 );
+	
+	vec4 icolor=lightColor * ndotl * falloff * shadow;
+	
+	diffuse+=icolor;
+	specular+=icolor * pow( ndoth,spow ) * fnorm;
+}
+
+#if CHEESY_HDR
+vec4 cheesy_hdr( in vec4 color ){
+	vec4 ov=max( color-1.0,0.0 );
+	return color+( (ov.r+ov.g+ov.b)/3.0 );
+}
+#endif
+
+#endif
+
+void main(){
+
+	shader();
+	
+#ifndef B3D_FRAGCOLOR
+	
+#if NUM_LIGHTS
+
+#if !ORTHO_LIGHTING
+	eyevec=normalize( -b3d_ViewPosition );
+#endif
+
+	//specular power	
+	gloss=1.0-Roughness;
+	spow=pow( 2.0,gloss*12.0 );	//specular power
+	fnorm=spow*2.0/8.0;			//energy conservation - sharper highlights are brighter coz they're smaller.
+	
+#if FRESNEL_EFFECT				//fresnel effect - reflectivity approaches 1 as surface grazing angle approaches 0 for glossy surfaces.
+	ndotv=max( dot( b3d_Normal,eyevec ),0.0 );
+	b3d_Specular+=(1.0-b3d_Specular) * pow( 1.0-ndotv,5.0 ) * gloss;
+#endif
+
+	diffuse=AmbientLight;
+	specular=vec4( 0.0 );
+
+#if BUMP_TANGENTS
+#if OPT_FAST
+	vec3 vtan=b3d_ViewTangent;
+#else
+	vec3 vtan=normalize( b3d_ViewTangent );
+#endif
+	tanMatrix=mat3( vec3( vtan.x,-vtan.y,0.0 ),vec3( vtan.y,vtan.x,0.0 ),vec3( 0.0,0.0,1.0 ) );
+#endif
+	
+	vec4 clip=b3d_ClipPosition/b3d_ClipPosition.w;
+	vec4 shadow=texture2D( ShadowTexture,clip.xy*0.5+0.5 );
+	
+	light( LightVectors[0],LightColors[0],shadow.r );
+#if NUM_LIGHTS>1
+	light( LightVectors[1],LightColors[1],shadow.g );
+#if NUM_LIGHTS>2
+	light( LightVectors[2],LightColors[2],shadow.b );
+#if NUM_LIGHTS>3
+	light( LightVectors[3],LightColors[3],shadow.a );
+#endif
+#endif
+#endif
+
+#if CHEESY_HDR
+	diffuse=cheesy_hdr( diffuse );
+	specular=cheesy_hdr( specular );
+#endif
+
+	vec4 color=(b3d_Diffuse * diffuse) + (b3d_Specular * specular) + (b3d_Ambient * AmbientLight.a);
+	
+#else // NUM_LIGHTS
+
+#ifdef B3D_DIFFUSE
+	vec4 color=(b3d_Diffuse * AmbientLight) + (b3d_Ambient * AmbientLight.a);
+#else
+	vec4 color=b3d_Ambient * AmbientLight.a;
+	//vec4 color=vec4(.5, .5, .5, 1);
+#endif	
+	
+#endif // NUM_LIGHTS
+
+	color.rgb=mix( color.rgb,FogColor.rgb,FogColor.a * b3d_Alpha );
+
+#if GAMMA_CORRECTION
+	gl_FragColor=vec4( pow( color.rgb,vec3( 1.0/2.2 ) ),b3d_Alpha );
+#else
+	gl_FragColor=vec4( color.rgb,b3d_Alpha );
+#endif
+
+#endif	//B3D_FRAGCOLOR
+
+}

+ 7 - 0
mojo2.mod/data/mojo2_shadowshader.glsl

@@ -0,0 +1,7 @@
+
+//shader for drawing shadows
+
+void shader(){
+
+	b3d_FragColor=vec4( 0.0 );
+}

+ 76 - 0
mojo2.mod/examples/bumptest/bumptest.bmx

@@ -0,0 +1,76 @@
+SuperStrict
+
+Framework mky.mojo2
+?Not opengles
+Import brl.GLGraphics
+?opengles
+Import sdl.sdlgraphics
+?
+Import brl.pngloader
+
+Graphics 640, 480, 0
+
+'generate color texture
+Local colortex:TTexture=New TTexture.Create( 256,256,PF_RGBA8888,TTexture.ClampST|TTexture.RenderTarget )
+Local rcanvas:TCanvas=New TCanvas.CreateCanvas( colortex )
+rcanvas.Clear( 1,1,1 )
+rcanvas.Flush
+
+'generate normal texture		
+Local normtex:TTexture=New TTexture.Create( 256,256,PF_RGBA8888,TTexture.ClampST|TTexture.RenderTarget )
+rcanvas.SetRenderTarget( normtex )
+rcanvas.Clear( .5,.5,1.0,0.0 )
+For Local x:Int=0 Until 256 'Step 32
+	For Local y:Int=0 Until 256 'Step 32
+		
+		Local dx:Float=x-127.5
+		Local dy:Float=y-127.5
+		Local dz:Float=127.5*127.5-dx*dx-dy*dy
+		
+		If dz<=0 Continue
+		
+		dz=Sqr( dz )
+		
+		Local r:Float=(dx+127.5)/255.0
+		Local g:Float=(dy+127.5)/-255.0
+		Local b:Float=(dz+127.5)/255.0
+		
+		rcanvas.SetColor( r,g,b,1 )
+		rcanvas.DrawPoint( x,y )
+
+	Next
+Next
+rcanvas.Flush
+
+Local material:TMaterial=New TMaterial.Create( TShader.BumpShader() )
+material.SetTexture( "ColorTexture",colortex )
+material.SetTexture( "NormalTexture",normtex )
+material.SetVector( "AmbientColor",[0.0,0.0,0.0,1.0] )
+
+Local image:TImage=New TImage.CreateMaterial( material,.5,.5 )
+
+Local canvas:TCanvas=New TCanvas.CreateCanvas()
+canvas.SetAmbientLight .2,.2,.2
+
+Local rot:Float
+
+While Not KeyDown(key_escape)
+
+	canvas.Clear 0,0,0
+	
+	'Set light 0
+	canvas.SetLightType 0,1
+	canvas.SetLightColor 0,.3,.3,.3
+	canvas.SetLightPosition 0,MouseX(),MouseY(),-100
+	canvas.SetLightRange 0,400
+	
+	rot:+1
+	
+	canvas.DrawImageXYZS image,GraphicsWidth()/2,GraphicsHeight()/2,rot,.5,.5
+	
+	canvas.Flush
+
+	Flip
+
+Wend
+

+ 42 - 0
mojo2.mod/examples/directlight/directlight.bmx

@@ -0,0 +1,42 @@
+SuperStrict
+
+Framework mky.mojo2
+?Not opengles
+Import brl.GLGraphics
+?opengles
+Import sdl.sdlgraphics
+?
+Import brl.pngloader
+
+Graphics 800, 600, 0
+
+Local canvas:TCanvas = New TCanvas.CreateCanvas()
+
+canvas.SetAmbientLight .2,.2,.2
+		
+Local tile:TImage=TImage.Load( "images/t3.png",0,0 )
+
+
+While Not KeyDown(key_escape)
+
+	canvas.Clear 0,0,1
+
+	'Set light 0
+	canvas.SetLightType 0,1
+	canvas.SetLightColor 0,.3,.3,.3
+	canvas.SetLightPosition 0,MouseX(),MouseY(),-100
+	canvas.SetLightRange 0,200
+	
+	'Light will affect subsequent rendering...
+	For Local x:Int=0 Until GraphicsWidth() Step 128
+		For Local y:Int=0 Until GraphicsHeight() Step 128	
+			canvas.DrawImage tile,x,y
+		Next
+	Next
+	
+	canvas.Flush
+
+	Flip
+
+Wend
+

BIN
mojo2.mod/examples/directlight/images/t3.png


BIN
mojo2.mod/examples/directlight/images/t3_NORMALS.png


BIN
mojo2.mod/examples/directlight/images/t3_SPECULAR.png


+ 68 - 0
mojo2.mod/examples/drawprimitives/drawprimitives.bmx

@@ -0,0 +1,68 @@
+SuperStrict
+
+Framework mky.mojo2
+?Not opengles
+Import brl.GLGraphics
+?opengles
+Import sdl.sdlgraphics
+?
+Import brl.pngloader
+Import brl.random
+
+Graphics 800, 600, 0
+
+Local canvas:TCanvas = New TCanvas.CreateCanvas()
+
+Local vertices:Float[] = New Float[4*2*100]
+	
+Local indices:Int[]
+
+Local sz:Float=20.0
+Local p:Int=0
+
+For Local i:Int=0 Until 100
+
+	Local x:Float=Rnd(GraphicsWidth())-sz/2-GraphicsWidth()/2
+	Local y:Float=Rnd(GraphicsHeight())-sz/2-GraphicsHeight()/2
+	
+	vertices[p+0]=x
+	vertices[p+1]=y
+	
+	vertices[p+2]=x+sz
+	vertices[p+3]=y
+	
+	vertices[p+4]=x+sz
+	vertices[p+5]=y+sz
+	
+	vertices[p+6]=x
+	vertices[p+7]=y+sz
+	
+	p:+8
+Next
+
+'quick test of indices...
+indices = New Int[400]
+For Local i:Int=0 Until 400
+	indices[i]=i
+Next
+
+
+While Not KeyDown(key_escape)
+
+	canvas.Clear 0,0,1
+		
+	canvas.SetColor Sin( MilliSecs()*.01 )*.5+.5,Cos( MilliSecs()*.03 )*.5+.5,Sin( MilliSecs()*.05 )*.5+.5
+	
+	canvas.PushMatrix
+	canvas.Translate MouseX(),MouseY()
+	
+	canvas.DrawIndexedPrimitives 4,100,vertices,indices	'should draw same thing...
+	
+	canvas.PopMatrix
+
+	canvas.Flush
+
+	Flip
+
+Wend
+

+ 31 - 0
mojo2.mod/examples/helloworld/helloworld.bmx

@@ -0,0 +1,31 @@
+SuperStrict
+
+Framework mky.mojo2
+?Not opengles
+Import brl.GLGraphics
+?opengles
+Import sdl.sdlgraphics
+?
+Import brl.pngloader
+
+Graphics 800, 600, 0
+
+Local canvas:TCanvas = New TCanvas.CreateCanvas()
+
+While Not KeyDown(KEY_ESCAPE)
+
+	canvas.Clear 0,0,1
+	
+	canvas.SetBlendMode 3
+	canvas.SetColor 0,0,0,.5
+	canvas.DrawText "HELLO WORLD!",GraphicsWidth()/2+2,GraphicsHeight()/2+2,.5,.5
+	
+	canvas.SetBlendMode 1
+	canvas.SetColor 1,1,0,1
+	canvas.DrawText "HELLO WORLD!",GraphicsWidth()/2,GraphicsHeight()/2,.5,.5
+	
+	canvas.Flush
+	
+	Flip
+	
+Wend

+ 82 - 0
mojo2.mod/examples/letterbox/letterbox.bmx

@@ -0,0 +1,82 @@
+SuperStrict
+
+Framework mky.mojo2
+?Not opengles
+Import brl.GLGraphics
+?opengles
+Import sdl.sdlgraphics
+?
+Import brl.pngloader
+
+Graphics 800, 600, 0
+
+Const VWIDTH:int=320
+Const VHEIGHT:int=240
+
+Local canvas:TCanvas = New TCanvas.CreateCanvas()
+local splitScreen:int
+
+While Not KeyDown(key_escape)
+	
+	If KeyHit( KEY_SPACE ) then
+		splitScreen = Not splitScreen
+	End If
+
+	canvas.SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
+
+	canvas.Clear 0,0,0
+
+	If splitScreen
+	
+		Local h:Int=GraphicsHeight()/2
+
+		RenderScene( canvas, "PLAYER 1 READY",[0,0,GraphicsWidth(),h] )
+	
+		RenderScene( canvas, "PLAYER 2 READY",[0,h,GraphicsWidth(),h] )
+	
+	Else
+	
+		RenderScene( canvas, "SPACE TO TOGGLE SPLITSCREEN",[0,0,GraphicsWidth(),GraphicsHeight()] )
+
+	Endif
+
+	canvas.Flush
+
+	Flip
+
+Wend
+
+Function RenderScene( canvas:TCanvas, msg:String, devrect:Int[] )
+
+	Local vprect:Int[4]
+		
+	CalcLetterbox( VWIDTH,VHEIGHT,devrect,vprect )
+
+	canvas.SetViewport vprect[0],vprect[1],vprect[2],vprect[3]
+	
+	canvas.SetProjection2d 0,VWIDTH,0,VHEIGHT
+
+	canvas.Clear 0,0,1
+	
+	canvas.DrawText msg,VWIDTH/2,VHEIGHT/2,.5,.5
+	
+End Function
+
+Function CalcLetterbox( vwidth:Float,vheight:Float,devrect:Int[],vprect:Int[] )
+	
+	Local vaspect:Float=vwidth/vheight
+	Local daspect:Float=Float(devrect[2])/devrect[3]
+
+	If daspect > vaspect Then
+		vprect[2] = devrect[3]*vaspect
+		vprect[3] = devrect[3]
+		vprect[0] = (devrect[2]-vprect[2])/2+devrect[0]
+		vprect[1] = devrect[1]
+	Else
+		vprect[2] = devrect[2]
+		vprect[3] = devrect[2]/vaspect
+		vprect[0] = devrect[0]
+		vprect[1] = (devrect[3]-vprect[3])/2+devrect[1]
+	Endif
+
+End Function

BIN
mojo2.mod/examples/mojotest/images/RedbrushAlpha.png


+ 94 - 0
mojo2.mod/examples/mojotest/mojotest.bmx

@@ -0,0 +1,94 @@
+SuperStrict
+
+Framework mky.mojo2
+?Not opengles
+'Import brl.GLGraphics
+Import sdl.sdlgraphics
+?opengles
+Import sdl.sdlgraphics
+?
+Import brl.pngloader
+
+
+Graphics 640, 480, 0
+
+Local ms:Int
+Local me:Int
+
+Local canvas:TCanvas = New TCanvas.CreateCanvas()
+
+Local image:TImage=TImage.Load( "images/RedbrushAlpha.png" )
+Local tx:Float
+Local ty:Float
+
+Local c:Int = 7
+Local r:Int = 255
+Local g:Int = 255
+Local b:Int = 255
+
+Local ang:Float = 0
+
+While Not KeyDown(key_escape)
+
+ms = MilliSecs()
+	canvas.SetScissor 0,0,GraphicsWidth(),GraphicsHeight()
+	canvas.Clear 0,0,.5
+	
+	Local sz:Float=Sin(ang * 10)*32
+	Local sx:Int=32+sz
+	Local sy:Int=32
+	Local sw:Int=GraphicsWidth()-(64+sz*2)
+	Local sh:Int=GraphicsHeight()-(64+sz)
+	
+	canvas.SetScissor sx,sy,sw,sh
+	canvas.Clear 1,32.0/255.0,0
+
+	canvas.PushMatrix
+
+	canvas.Translate tx,ty
+	canvas.Scale GraphicsWidth()/640.0,GraphicsHeight()/480.0
+	canvas.Translate 320,240
+'	canvas.Rotate MilliSecs()/1000.0*12
+	canvas.Rotate ang
+	canvas.Translate -320,-240
+	
+	canvas.SetColor .5,1,0
+	canvas.DrawRect 32,32,640-64,480-64
+
+	canvas.SetColor 1,1,0
+	For Local y:Int=0 Until 480
+		For Local x:Int=16 Until 640 Step 32
+			canvas.SetAlpha Min( Abs( y-240.0 )/120.0,1.0 )
+			canvas.DrawPoint x,y
+		Next
+	Next
+	canvas.SetAlpha 1
+
+	canvas.SetColor 0,.5,1
+	canvas.DrawOval 64,64,640-128,480-128
+
+	canvas.SetColor 1,0,.5
+	canvas.DrawLine 32,32,640-32,480-32
+	canvas.DrawLine 640-32,32,32,480-32
+
+	canvas.SetColor r/255.0,g/255.0,b/255.0,Sin(ang * 5)*.5+.5
+	canvas.DrawImageXYZ image,320,240,0
+	canvas.SetAlpha 1
+
+	canvas.SetColor 1,0,.5
+	canvas.DrawPoly( [ 140.0,232.0, 320.0,224.0, 500.0,232.0, 500.0,248.0, 320.0,256.0, 140.0,248.0 ] )
+			
+	canvas.SetColor .5,.5,.5
+	canvas.DrawText "The Quick Brown Fox Jumps Over The Lazy Dog",320,240,.5,.5
+	
+
+	canvas.PopMatrix
+	
+	canvas.Flush()
+me = MilliSecs()
+
+	ang :+ 0.2
+
+'Print (me - ms)
+	Flip 1
+Wend

+ 39 - 0
mojo2.mod/examples/renderdrawlist/renderdrawlist.bmx

@@ -0,0 +1,39 @@
+SuperStrict
+
+Framework mky.mojo2
+?Not opengles
+Import brl.GLGraphics
+?opengles
+Import sdl.sdlgraphics
+?
+Import brl.pngloader
+Import brl.random
+
+Graphics 800, 600, 0
+
+Local canvas:TCanvas = New TCanvas.CreateCanvas()
+
+Local drawList:TDrawList = New TDrawList
+
+For Local i:Int=0 Until 100
+
+	drawList.SetColor Rnd(),Rnd(),Rnd()
+	
+	drawList.DrawCircle rnd(GraphicsWidth())-GraphicsWidth()/2,Rnd(GraphicsHeight())-GraphicsHeight()/2,Rnd(10,20)
+Next
+
+Local angle:float = 0
+
+While Not KeyDown(KEY_ESCAPE)
+
+	angle :+ 0.5
+
+	canvas.Clear 0,0,1
+
+	canvas.RenderDrawListXYZ drawList,MouseX(),MouseY(),angle
+	
+	canvas.Flush
+
+	Flip
+	
+Wend

BIN
mojo2.mod/examples/rendererdemo/images/t3.png


BIN
mojo2.mod/examples/rendererdemo/images/t3_NORMALS.png


BIN
mojo2.mod/examples/rendererdemo/images/t3_SPECULAR.png


+ 165 - 0
mojo2.mod/examples/rendererdemo/rendererdemo.bmx

@@ -0,0 +1,165 @@
+SuperStrict
+
+Framework mky.mojo2
+?Not opengles
+Import brl.GLGraphics
+?opengles
+Import sdl.sdlgraphics
+?
+Import brl.pngloader
+Import brl.random
+
+Graphics 800, 600, 0
+
+Const NUM_LIGHTS:Int = 5
+
+Local canvas:TCanvas = New TCanvas.CreateCanvas()
+'canvas.SetViewport( 0,0,GraphicsWidth(),GraphicsHeight())
+'canvas.SetProjectionMatrix( Mat4Ortho( 0,640,0,480,-1,1 ) )
+
+
+
+Local tile:TImage
+Local shadowCaster:TShadowCaster
+Local renderer:TRenderer
+Local layer0:TMyLayer
+Local rimage:TImage
+
+
+
+'create renderer
+renderer=New TRenderer
+renderer.SetAmbientLight( [0.1,0.1,0.1,1.0] )
+
+'load some gfx
+tile=TImage.Load( "images/t3.png",0,0 )
+
+'create layer 0
+layer0=New TMyLayer
+
+'add some lights to layer
+For Local i:Int=0 Until NUM_LIGHTS
+	Local light:TMyLight = New TMyLight
+	light.color[i Mod 3] = 0.5
+	layer0.lights.Push light
+Next
+
+For Local x:Int=0 Until 640 Step 128
+	For Local y:Int=0 Until 480 Step 128	
+		layer0.DrawImage tile,x,y
+	Next
+Next
+
+'create simple rect shadow caster
+shadowCaster=New TShadowCaster.Create()
+shadowCaster.SetVertices( [0.0,0.0, 32.0,0.0, 32.0,32.0, 0.0,32.0] )
+
+'draw some shadow casters
+For Local x:Int=100 Until 640 Step 220
+
+	For Local y:Int=60 Until 480 Step 180
+	
+		layer0.SetColor 1,1,0
+		layer0.DrawRect x-16,y-16,32,32
+		layer0.SetColor 1,1,1
+		
+		layer0.AddShadowCasterXY shadowCaster,x-16,y-16
+	Next
+Next
+
+'add layer to renderer		
+renderer.Layers.Push layer0
+
+Local angle:Float = 0
+
+Local ms:Int, me:Int
+While Not KeyDown(key_escape)
+
+	'move lights around a bit
+	For Local i:Int=0 Until NUM_LIGHTS
+		Local light:TMyLight=TMyLight(layer0.lights.Get(i))
+		Local radius:Float=120.0
+		'Local an:Float=(i*360.0/NUM_LIGHTS)+(MilliSecs()/50.0)
+		Local an:Float=(i*360.0/NUM_LIGHTS)+(angle)
+		light.matrix[12]=Cos( an )*radius+320
+		light.matrix[13]=Sin( an )*radius+240
+	Next
+	'render scene
+	renderer.Render(canvas)
+
+	Flip
+
+	angle :+ 0.5
+Wend
+
+
+'create an orthographics projection matrix
+Function Mat4Ortho:Float[]( Left:Float,Right:Float,bottom:Float,top:Float,znear:Float,zfar:Float )
+
+	Local w:Float=Right-Left,h:Float=top-bottom,d:Float=zfar-znear
+	
+	Return [ 2.0/w,0,0,0, 0,2.0/h,0,0, 0,0,2.0/d,0, -(Right+Left)/w,-(top+bottom)/h,-(zfar+znear)/d,1 ]
+End Function
+
+Type TMyLight Implements ILight
+
+	'note: x,y,z,w go in last 4 components of matrix...
+	Field matrix:Float[]=[1.0,0.0,0.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,1.0,0.0, 0.0,0.0,-100.0,1.0]
+	Field color:Float[]=[0.2,0.2,0.2,1.0]
+	Field Range:Float=400.0
+
+	'implement ILight interface...
+	'
+	Method LightMatrix:Float[]()
+		Return matrix
+	End Method
+	
+	Method LightType:Int()
+		Return 1
+	End Method
+	
+	Method LightColor:Float[]()
+		Return color
+	End Method
+	
+	Method LightRange:Float()
+		Return Range
+	End Method
+	
+	Method LightImage:TImage()
+		Return Null
+	End Method
+
+End Type
+
+Type TMyLayer Extends TDrawList Implements ILayer
+
+	Field lights:TILightStack=New TILightStack
+	Field _layerMatrix:Float[]=[1.0,0.0,0.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,1.0,0.0, 0.0,0.0,0.0,1.0]
+	Field _layerFogColor:Float[]=[0.0,0.0,0.0,0.0]
+
+	'implement ILayer interface...
+	'
+	Method LayerMatrix:Float[]()
+		Return _layerMatrix
+	End Method
+	
+	Method LayerFogColor:Float[]()
+		Return _layerFogColor
+	End Method
+	
+	Method LayerLightMaskImage:TImage()
+		Return Null
+	End Method
+	
+	Method EnumLayerLights( lights:TILightStack )
+		For Local i:Int = 0 Until Self.lights.length
+			lights.Push Self.lights.Get(i)
+		Next
+	End Method
+
+	Method OnRenderLayer( drawLists:TDrawListStack )
+		drawLists.Push Self
+	End Method
+
+End Type

+ 46 - 0
mojo2.mod/examples/rendertoimage/rendertoimage.bmx

@@ -0,0 +1,46 @@
+SuperStrict
+
+Framework mky.mojo2
+?Not opengles
+Import brl.GLGraphics
+?opengles
+Import sdl.sdlgraphics
+?
+Import brl.pngloader
+Import brl.linkedlist
+
+Graphics 800, 600, 0
+
+Local canvas:TCanvas = New TCanvas.CreateCanvas()
+
+Local image:TImage = New TImage.Create( 256,256 )
+Local 	icanvas:TCanvas=New TCanvas.CreateCanvas( image )
+
+
+While Not KeyDown(key_escape)
+
+	
+	'render to image...
+	For Local x:Int=0 Until 16
+		For Local y:Int=0 Until 16
+			If (x~y)&1
+				icanvas.SetColor Sin( MilliSecs*.1 )*.5+.5,Cos( MilliSecs*.1 )*.5+.5,.5
+			Else
+				icanvas.SetColor 1,1,0
+			EndIf
+			icanvas.DrawRect x*16,y*16,16,16
+		Next
+	Next
+	icanvas.Flush
+	
+	'render to main canvas...
+	canvas.Clear
+	canvas.DrawImage image,MouseX(),MouseY()
+
+
+	canvas.Flush
+
+	Flip
+
+Wend
+

+ 24 - 0
mojo2.mod/examples/shadereffect/data/bwshader.glsl

@@ -0,0 +1,24 @@
+
+//Simple black/white shader effect
+
+uniform sampler2D ColorTexture;
+
+uniform float EffectLevel;
+
+void shader(){
+
+	//convert clip position to valid tex coords
+	vec2 texcoords=(b3d_ClipPosition.st/b3d_ClipPosition.w)*0.5+0.5;
+	
+	//read source color
+	vec3 color=texture2D( ColorTexture,texcoords ).rgb;
+	
+	//calculate b/w color
+	vec3 result=vec3( (color.r+color.g+color.b)/3.0 );
+	
+	//mix based on effect level
+	color=mix( color,result,EffectLevel );
+	
+	//write output
+	b3d_FragColor=vec4( color,1.0 );
+}

BIN
mojo2.mod/examples/shadereffect/data/default_player.png


+ 107 - 0
mojo2.mod/examples/shadereffect/shadereffect.bmx

@@ -0,0 +1,107 @@
+SuperStrict
+
+Framework mky.mojo2
+?Not opengles
+Import brl.GLGraphics
+?opengles
+Import sdl.sdlgraphics
+?
+Import brl.pngloader
+
+Graphics 800, 600, 0
+
+Local canvas:TCanvas = New TCanvas.CreateCanvas()
+
+Local sourceImage:TImage = TImage.Load( "data/default_player.png" )
+Local targetImage:TImage = New TImage.Create( sourceImage.Width(),sourceImage.Height() )
+
+Local effect:TShaderEffect = New TShaderEffect
+Local level:Float = 1
+
+While Not KeyDown(key_escape)
+
+	If KeyDown( KEY_A )
+		level=Min( level+.01,1.0 )
+	Else If KeyDown( KEY_Z )
+		level=Max( level-.01,0.0 )
+	EndIf
+
+	effect.SetLevel level
+	
+	effect.Render( sourceImage,targetImage )
+	
+	canvas.Clear
+	
+	canvas.DrawImage targetImage,MouseX(),MouseY()
+	
+	canvas.DrawText "Effect level="+level+" (A/Z to change)",0,0
+	
+	canvas.Flush
+
+
+	Flip
+
+Wend
+
+
+
+
+
+'Our custom shader
+Type TBWShader Extends TShader
+
+	Method Create:TBWShader(source:String)
+		Build( LoadString( source ) )
+		Return Self
+	End Method
+	
+	'must implement this - sets valid/default material params
+	Method OnInitMaterial( material:TMaterial )
+		material.SetTexture "ColorTexture",TTexture.White()
+		material.SetScalar "EffectLevel",1
+	End Method
+	
+	Function Instance:TBWShader()
+		If Not _instance _instance=New TBWShader.Create("data/bwshader.glsl")
+		Return _instance
+	End Function
+	
+	Private
+	
+	Global _instance:TBWShader
+	
+End Type
+
+Type TShaderEffect
+
+	Method New()
+		If Not _canvas _canvas=New TCanvas.CreateCanvas()
+
+		_material=New TMaterial.Create( TBWShader.Instance() )
+	End Method
+	
+	Method SetLevel( level:Float )
+	
+		_material.SetScalar "EffectLevel",level
+	End Method
+	
+	Method Render( source:TImage,target:TImage )
+	
+		_material.SetTexture "ColorTexture",source.Material.ColorTexture()
+		
+		_canvas.SetRenderTarget target
+		_canvas.SetViewport 0,0,target.Width(),target.Height()
+		_canvas.SetProjection2d 0,target.Width(),0,target.Height()
+		
+		_canvas.DrawRect 0,0,target.Width(),target.Height(),_material
+		
+		_canvas.Flush
+	End Method
+	
+	'Private
+	
+	Global _canvas:TCanvas	'shared between ALL effects
+	
+	Field _material:TMaterial
+	
+End Type

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
mojo2.mod/examples/vectormonkey/data.txt


+ 197 - 0
mojo2.mod/examples/vectormonkey/vectormonkey.bmx

@@ -0,0 +1,197 @@
+SuperStrict
+
+Framework mky.mojo2
+?Not opengles
+Import brl.GLGraphics
+?opengles
+Import sdl.sdlgraphics
+?
+Import brl.pngloader
+Import brl.linkedlist
+
+Graphics 800, 600, 0
+
+Local canvas:TCanvas = New TCanvas.CreateCanvas()
+
+Local drawing:TList = MakeVectorDrawing()
+Local rotstep:Float
+Local wiggle:Float
+Local rot:Float
+
+
+While Not KeyDown(key_escape)
+
+	 If KeyHit(KEY_W) 
+	 	wiggle = 0.5
+
+	 EndIf
+	 
+	 If MouseDown(1)
+	 	For Local pt:TTriPrims = EachIn drawing 		 
+ 			pt.Pull(MouseX(),MouseY(),0.0001) 		
+ 		Next
+	 EndIf
+	 
+	 If MouseDown(2)
+	 	For Local pt:TTriPrims = EachIn drawing 		 
+ 			pt.Pull(MouseX(),MouseY(),-0.0001) 		
+ 		Next
+	 EndIf
+
+	If KeyHit(KEY_SPACE)
+		wiggle = 0
+		rot = 0
+		For Local pt:TTriPrims = EachIn drawing 		 
+ 			pt.ResetMorph(1)	
+ 		Next
+		 
+	 
+		rotstep = 0
+	EndIf 
+
+
+	canvas.ResetMatrix()
+	
+	canvas.Clear .5,.7,1
+	 	
+	canvas.SetColor 0.5,0.5,0.5
+	canvas.DrawText "Mouse Click with left/right mouse button",10,10
+	canvas.DrawText "Press [W] to wiggle",10,30
+	canvas.DrawText "Press [Space] to reset",10,50
+	 
+	rot = 0
+	 	
+	For Local pt:TTriPrims = EachIn drawing
+		canvas.Rotate rot
+		pt.Draw(canvas)
+		canvas.Rotate -rot
+		rot :+ rotstep
+	Next
+	 	
+	canvas.Flush
+	rotstep :+ Cos(Float(MilliSecs() Mod 360))*wiggle
+	 
+	wiggle :* 0.95
+	
+	rotstep :* 0.99
+	
+	For Local pt:TTriPrims = EachIn drawing 		 
+		pt.ResetMorph(0.01)	
+	Next
+	
+	Flip
+
+Wend
+
+
+
+Function MakeVectorDrawing:TList()
+
+	Local data:String=LoadString( "data.txt" )
+
+	Local primslist:TList= New TList
+	
+	
+	Local LINES:String[] = data.Split("*")
+	
+	Local tp:TTriPrims
+	
+	For Local line:String = EachIn LINES
+		Local parts:String[] = line.Split(";")
+	
+		 
+		 Select parts[0]
+		 
+		 	Case "color"
+				tp = New TTriPrims
+				
+				Local vals:String[] = parts[1].Split(",")
+				tp.r = Float(vals[0])
+				tp.g = Float(vals[1])
+				tp.b = Float(vals[2])
+			 	primslist.AddLast(tp)
+			 	
+			 Case "vertices"	
+			 	Local vals:String[] = parts[1].Split(",")
+			 	
+			 	tp.vertices = New Float[vals.Length]
+			 	For Local i:Int = 0  Until vals.Length
+			 		tp.vertices[i] = Float(vals[i])*0.01
+			 	Next
+			 			
+				 
+			 Case "indexes"				
+				Local vals:String[] = parts[1].Split(",")
+			 	tp.indexes = New Int[vals.Length]
+			 	For Local i:Int = 0  Until vals.Length
+			 		tp.indexes[i] = Int(vals[i])
+			 	Next			
+		 
+		 End Select
+		 
+		 Next
+		 
+		 
+		 Return primslist
+		 
+End Function
+
+
+
+Type TTriPrims
+	Field r:Float,g:Float,b:Float,a:Float = 1
+	Field vertices:Float[]
+	Field morphedvertices:Float[]
+	Field indexes:Int[]
+	
+	Method ResetMorph(factor:Float = 1.0)
+	
+		If factor >= 1
+			Local invfactor:Float = 1.0 - factor
+			For Local i:Int = 0 Until vertices.Length
+				morphedvertices[i] =  vertices[i]  
+			Next
+			Return 
+		EndIf
+	
+	
+		Local invfactor:Float = 1.0 - factor
+		For Local i:Int = 0 Until vertices.Length
+			Local newval:Float =  morphedvertices[i] *invfactor +  vertices[i] * factor
+		Next
+	End Method
+	
+	
+	
+	Method Draw(canvas:TCanvas)
+		If morphedvertices.Length<>vertices.Length
+			morphedvertices= New Float[vertices.Length]
+			ResetMorph()
+	 	EndIf
+	
+		canvas.SetColor r,g,b,a
+		canvas.DrawIndexedPrimitives(3,indexes.Length/3,morphedvertices,indexes)
+	End Method
+	
+	Method Pull(x:Float,y:Float,factor:Float)
+		
+		For Local i:Int = 0 Until vertices.Length Step 2
+		
+			Local vx:Float = (morphedvertices[i]-x)
+			Local vy:Float = (morphedvertices[i+1]-y) 
+		
+			Local dist:Float = Sqr(vx*vx+vy*vy)
+		
+	 		dist = 200-dist
+
+			Local newx:Float = morphedvertices[i]  + vx  * dist  * factor 
+			Local newy:Float = morphedvertices[i+1]  + vy *  dist  * factor 
+ 
+			morphedvertices[i] = newx
+			morphedvertices[i+1] = newy
+ 
+		Next
+	End Method
+	
+End Type
+

+ 59 - 0
mojo2.mod/examples/writepixels/writepixels.bmx

@@ -0,0 +1,59 @@
+SuperStrict
+
+Framework mky.mojo2
+?Not opengles
+Import brl.GLGraphics
+?opengles
+Import sdl.sdlgraphics
+?
+Import brl.pngloader
+Import brl.linkedlist
+
+Graphics 800, 600, 0
+
+Local canvas:TCanvas = New TCanvas.CreateCanvas()
+
+Local image:TImage = New TImage.Create( 256,256 )
+Local data:TPixmap = New TPixmap.Create(16,16,PF_RGBA8888)
+
+While Not KeyDown(key_escape)
+
+	Local pitch:Int=256*4
+		
+	For Local x:Int=0 Until 16
+	
+		For Local y:Int=0 Until 16
+
+			Local r:Float=1.0
+			Local g:Float=1.0
+			Local b:Float=1.0
+			Local a:Float=1.0
+			
+			If (x~y)&1 Then
+				r=Sin( MilliSecs()*.1 )*.5+.5
+				g=Cos( MilliSecs()*.1 )*.5+.5
+				b=.5
+			EndIf
+			
+			Local rgba:Int=a*255 Shl 24 | b*255 Shl 16 | g*255 Shl 8 | r*255
+			
+			Local pix:Int Ptr = data.pixels
+			For Local i:Int=0 Until 16*16
+				pix[i] = rgba
+			Next
+			
+			image.WritePixels x*16,y*16,16,16,data
+
+		Next
+	Next
+	
+	'render image to main canvas...
+	'
+	canvas.Clear
+	canvas.DrawImage image,MouseX(),MouseY()
+	canvas.Flush
+
+
+	Flip
+
+Wend

+ 202 - 0
mojo2.mod/glslparser.bmx

@@ -0,0 +1,202 @@
+'
+' BlitzMax port, 2015 Bruce A Henderson
+' 
+' Copyright (c) 2015 Mark Sibly
+' 
+' This software is provided 'as-is', without any express or implied
+' warranty. In no event will the authors be held liable for any damages
+' arising from the use of this software.
+' 
+' Permission is granted to anyone to use this software for any purpose,
+' including commercial applications, and to alter it and redistribute it
+' freely, subject to the following restrictions:
+' 
+' 1. The origin of this software must not be misrepresented; you must not
+'    claim that you wrote the original software. If you use this software
+'    in a product, an acknowledgement in the product documentation would be
+'    appreciated but is not required.
+' 2. Altered source versions must be plainly marked as such, and must not be
+'    misrepresented as being the original software.
+' 3. This notice may not be removed or altered from any source distribution.
+' 
+SuperStrict
+
+
+Const TOKE_EOF:Int=0
+Const TOKE_IDENT:Int=1
+Const TOKE_INTLIT:Int=2
+Const TOKE_FLOATLIT:Int=3
+Const TOKE_STRINGLIT:Int=4
+Const TOKE_SYMBOL:Int=5
+	
+Const CHAR_QUOTE:Int=34
+Const CHAR_PLUS:Int=43
+Const CHAR_MINUS:Int=45
+Const CHAR_PERIOD:Int=46
+Const CHAR_UNDERSCORE:Int=95
+Const CHAR_APOSTROPHE:Int=39
+
+Function IsDigit:Int( ch:Int )
+	Return (ch>=48 And ch<58)
+End Function
+
+Function IsAlpha:Int( ch:Int )
+	Return (ch>=65 And ch<65+26) Or (ch>=97 And ch<97+26)
+End Function
+
+Function IsIdent:Int( ch:Int )
+	Return (ch>=65 And ch<65+26) Or (ch>=97 And ch<97+26) Or (ch>=48 And ch<58) Or ch=CHAR_UNDERSCORE
+End Function
+
+Type TParser
+
+	Method Create:TParser( Text:String )
+		SetText Text
+		Return Self
+	End Method
+
+	Method SetText( Text:String )
+		_text=Text
+		_pos=0
+		_len=_text.Length
+		Bump
+	End Method
+	
+	Method Bump:String()
+
+		While _pos<_len
+			Local ch:Int=_text[_pos]
+			If ch<=32
+				_pos:+1
+				Continue
+			EndIf
+			If ch<>CHAR_APOSTROPHE Exit
+			_pos:+1
+			While _pos<_len And _text[_pos]<>10
+				_pos:+1
+			Wend
+		Wend
+		
+		If _pos=_len
+			_toke=""
+			_tokeType=TOKE_EOF
+			Return _toke
+		EndIf
+		
+		Local pos:Int=_pos
+		Local ch:Int=_text[_pos]
+		_pos:+1
+		
+		If IsAlpha( ch ) Or ch=CHAR_UNDERSCORE
+		
+			While _pos<_len
+				Local ch:Int=_text[_pos]
+				If Not IsIdent( ch ) Exit
+				_pos:+1
+			Wend
+			_tokeType=TOKE_IDENT
+			
+		Else If IsDigit( ch ) 
+		
+			While _pos<_len
+				If Not IsDigit( _text[_pos] ) Exit
+				_pos:+1
+			Wend
+			_tokeType=TOKE_INTLIT
+			
+		Else If ch=CHAR_QUOTE
+		
+			While _pos<_len
+				Local ch:Int=_text[_pos]
+				If ch=CHAR_QUOTE Exit
+				_pos:+1
+			Wend
+			If _pos=_len Throw "String literal missing closing quote"
+			_tokeType=TOKE_STRINGLIT
+			_pos:+1
+			
+		Else
+			Local digraphs:String[]=[":="]
+			If _pos<_len
+				Local ch:Int=_text[_pos]
+				For Local t:String=EachIn digraphs
+					If ch=t[1]
+						_pos:+1
+						Exit
+					EndIf
+				Next
+			EndIf
+			_tokeType=TOKE_SYMBOL
+		EndIf
+		
+		_toke=_text[pos.._pos]
+		
+		Return _toke
+	End Method
+	
+	Method Toke:String()
+		Return _toke
+	End Method
+	
+	Method TokeType:Int()
+		Return _tokeType
+	End Method
+	
+	Method CParse:Int( toke:String )
+		If _toke<>toke Return False
+		Bump
+		Return True
+	End Method
+	
+	Method CParseIdent:String()
+		If _tokeType<>TOKE_IDENT Return ""
+		Local id:String=_toke
+		Bump
+		Return id
+	End Method
+	
+	Method CParseLiteral:String()
+		If _tokeType<>TOKE_INTLIT And _tokeType<>TOKE_FLOATLIT And _tokeType<>TOKE_STRINGLIT Return ""
+		Local id:String=_toke
+		Bump
+		Return id
+	End Method
+	
+	Method Parse:String()
+		Local toke:String=_toke
+		Bump
+		Return toke
+	End Method
+	
+	Method ParseToke( toke:String )
+		If Not CParse( toke ) Throw "Expecting '"+toke+"'"
+	End Method
+	
+	Method ParseIdent:String()
+		Local id:String=CParseIdent()
+		If Not id Throw "Expecting identifier"
+		Return id
+	End Method
+	
+	Method ParseLiteral:String()
+		Local id:String=CParseLiteral()
+		If Not id Throw "Expecting literal"
+		Return id
+	End Method
+
+	Field _text:String
+	Field _pos:Int
+	Field _len:Int
+	Field _toke:String
+	Field _tokeType:Int
+	
+End Type
+
+Type TGlslParser Extends TParser
+
+	Method ParseType:String()
+		Local id:String=ParseIdent()
+		Return id
+	End Method
+	
+End Type

+ 98 - 0
mojo2.mod/glutil.bmx

@@ -0,0 +1,98 @@
+'
+' BlitzMax port, 2015 Bruce A Henderson
+' 
+' Copyright (c) 2015 Mark Sibly
+' 
+' This software is provided 'as-is', without any express or implied
+' warranty. In no event will the authors be held liable for any damages
+' arising from the use of this software.
+' 
+' Permission is granted to anyone to use this software for any purpose,
+' including commercial applications, and to alter it and redistribute it
+' freely, subject to the following restrictions:
+' 
+' 1. The origin of this software must not be misrepresented; you must not
+'    claim that you wrote the original software. If you use this software
+'    in a product, an acknowledgement in the product documentation would be
+'    appreciated but is not required.
+' 2. Altered source versions must be plainly marked as such, and must not be
+'    misrepresented as being the original software.
+' 3. This notice may not be removed or altered from any source distribution.
+' 
+SuperStrict
+
+Import brl.standardio
+?opengles
+Import Pub.OpenGLES
+?Not opengles
+Import Pub.Glew
+Import Pub.OpenGL
+?
+
+Private
+
+Global tmpi:Int
+
+Public
+
+Function glCheck()
+	Local err:Int=glGetError()
+	If err=GL_NO_ERROR Return
+	Throw "GL ERROR! err=" + err
+End Function
+
+Function glPushTexture2d( tex:Int )
+	glGetIntegerv GL_TEXTURE_BINDING_2D, Varptr tmpi
+	glBindTexture GL_TEXTURE_2D,tex
+End Function
+
+Function glPopTexture2d()
+	glBindTexture GL_TEXTURE_2D, tmpi
+End Function
+
+Function glPushFramebuffer( framebuf:Int )
+	glGetIntegerv GL_FRAMEBUFFER_BINDING, Varptr tmpi
+	glBindFramebuffer GL_FRAMEBUFFER, framebuf
+End Function
+
+Function glPopFramebuffer()
+	glBindFramebuffer GL_FRAMEBUFFER, tmpi
+End Function
+
+Function glCompile:Int( kind:Int,source:String )
+	
+?opengles
+		source="precision mediump float;~n"+source
+?
+	
+	Local shader:Int = glCreateShader( kind )
+	Local s:Byte Ptr = source.ToCString()
+	glShaderSource shader,1, Varptr s, Null
+	MemFree(s)
+	glCompileShader shader
+	glGetShaderiv shader,GL_COMPILE_STATUS, Varptr tmpi
+	If Not tmpi
+		Local buf:Byte[1024]
+		Local l:Int
+		glGetShaderInfoLog( shader, 1024, Varptr l, buf)
+		Print "Failed to compile fragment shader:"+ String.FromBytes(buf, l)
+		Local LINES:String[]=source.Split( "~n" )
+		For Local i:Int=0 Until LINES.Length
+			Print (i+1)+":~t"+LINES[i]
+		Next
+		Throw "Compile fragment shader failed"
+	EndIf
+	Return shader
+
+End Function
+
+Function glLink( program:Int )
+	glLinkProgram program
+	glGetProgramiv program,GL_LINK_STATUS, Varptr tmpi
+	If Not tmpi
+		Local buf:Byte[1024]
+		Local l:Int
+		glGetProgramInfoLog( program, 1024, Varptr l, buf)
+		Throw "Failed to link program:"+ String.FromBytes(buf, l)
+	End If
+End Function

+ 3141 - 0
mojo2.mod/graphics.bmx

@@ -0,0 +1,3141 @@
+'
+' BlitzMax port, 2015 Bruce A Henderson
+' 
+' Copyright (c) 2015 Mark Sibly
+' 
+' This software is provided 'as-is', without any express or implied
+' warranty. In no event will the authors be held liable for any damages
+' arising from the use of this software.
+' 
+' Permission is granted to anyone to use this software for any purpose,
+' including commercial applications, and to alter it and redistribute it
+' freely, subject to the following restrictions:
+' 
+' 1. The origin of this software must not be misrepresented; you must not
+'    claim that you wrote the original software. If you use this software
+'    in a product, an acknowledgement in the product documentation would be
+'    appreciated but is not required.
+' 2. Altered source versions must be plainly marked as such, and must not be
+'    misrepresented as being the original software.
+' 3. This notice may not be removed or altered from any source distribution.
+' 
+SuperStrict
+
+
+'Framework brl.standardio
+
+Import brl.bank
+Import brl.map
+Import brl.ramstream
+Import brl.pixmap
+Import brl.filesystem
+Import brl.system
+Import brl.Graphics
+Import "math3d.bmx"
+Import "glutil.bmx"
+Import "glslparser.bmx"
+Import "maps.bmx"
+
+
+Incbin "data/mojo2_font.png"
+Incbin "data/mojo2_program.glsl"
+Incbin "data/mojo2_fastshader.glsl"
+Incbin "data/mojo2_bumpshader.glsl"
+Incbin "data/mojo2_matteshader.glsl"
+Incbin "data/mojo2_shadowshader.glsl"
+Incbin "data/mojo2_lightmapshader.glsl"
+
+Private
+
+Global _inited:Int
+
+Global mainShader:String
+
+Global _fastShader:TShader
+Global _bumpShader:TShader
+Global _matteShader:TShader
+Global _shadowShader:TShader
+Global _lightMapShader:TShader
+
+Global defaultFont:TFont
+Global _defaultShader:TShader
+
+Global freeOps:TDrawOpStack=New TDrawOpStack
+Global nullOp:TDrawOp=New TDrawOp
+
+Global tmpi:Int[16]
+Global tmpf:Float[16]
+Global defaultFbo:Int
+
+Global tmpMat2d:Float[6]
+Global tmpMat3d:Float[16]
+Global tmpMat3d2:Float[16]
+
+Global flipYMatrix:Float[]=Mat4New()
+Global graphicsSeq:Int=1
+Global vbosSeq:Int
+
+'shader params
+Global rs_projMatrix:Float[]=Mat4New()
+Global rs_modelViewMatrix:Float[]=Mat4New()
+Global rs_modelViewProjMatrix:Float[]=Mat4New()
+Global rs_clipPosScale:Float[]=[1.0,1.0,1.0,1.0]
+Global rs_globalColor:Float[]=[1.0,1.0,1.0,1.0]
+Global rs_numLights:Int
+Global rs_fogColor:Float[]=[0.0,0.0,0.0,0.0]
+Global rs_ambientLight:Float[]=[0.0,0.0,0.0,1.0]
+Global rs_lightColors:Float[MAX_LIGHTS*4]
+Global rs_lightVectors:Float[MAX_LIGHTS*4]
+Global rs_shadowTexture:TTexture
+Global rs_program:TGLProgram
+Global rs_material:TMaterial
+Global rs_blend:Int=-1
+Global rs_vbo:Int
+Global rs_ibo:Int
+
+Const VBO_USAGE:Int=GL_STREAM_DRAW
+Const VBO_ORPHANING_ENABLED:Int=False
+
+Const MAX_LIGHTS:Int=4
+Const BYTES_PER_VERTEX:Int=28
+
+'can really be anything <64K (due to 16bit indices) but this keeps total VBO size<64K, and making it bigger doesn't seem to improve performance much.
+Const MAX_VERTICES:Int=65536/BYTES_PER_VERTEX	
+
+Const MAX_QUADS:Int=MAX_VERTICES/4
+Const MAX_QUAD_INDICES:Int=MAX_QUADS*6
+Const PRIM_VBO_SIZE:Int=MAX_VERTICES*BYTES_PER_VERTEX
+
+Function IsPow2:Int( sz:Int )
+	Return (sz & (sz-1))=0
+End Function
+
+Public
+
+Type TLightData
+	Field kind:Int=0
+	Field color:Float[]=[1.0,1.0,1.0,1.0]
+	Field position:Float[]=[0.0,0.0,-10.0]
+	Field Range:Float=10
+	'
+	Field vector:Float[]=[0.0,0.0,-10.0,1.0]
+	Field tvector:Float[4]
+End Type
+
+Private
+
+Function InitVbos()
+	If vbosSeq=graphicsSeq Return
+	vbosSeq=graphicsSeq
+
+	glGenBuffers 1, Varptr rs_vbo
+	glBindBuffer GL_ARRAY_BUFFER,rs_vbo
+	glBufferData GL_ARRAY_BUFFER,PRIM_VBO_SIZE,Null,VBO_USAGE
+	
+	glEnableVertexAttribArray 0
+	glVertexAttribPointer 0,2,GL_FLOAT,False,BYTES_PER_VERTEX,0
+	
+	glEnableVertexAttribArray 1
+	glVertexAttribPointer 1,2,GL_FLOAT,False,BYTES_PER_VERTEX,8
+	
+	glEnableVertexAttribArray 2
+	glVertexAttribPointer 2,2,GL_FLOAT,False,BYTES_PER_VERTEX,16
+	
+	glEnableVertexAttribArray 3
+	glVertexAttribPointer 3,4,GL_UNSIGNED_BYTE,True,BYTES_PER_VERTEX,24
+	
+	glGenBuffers(1, Varptr rs_ibo)
+	glBindBuffer GL_ELEMENT_ARRAY_BUFFER,rs_ibo
+	Local idxs:TBank=New TBank.Create( MAX_QUAD_INDICES*4*2 )
+	For Local j:Int = 0 Until 4
+		Local k:Int = j*MAX_QUAD_INDICES*2
+		For Local i:Int = 0 Until MAX_QUADS
+			idxs.PokeShort i*12+k+0,i*4+j+0
+			idxs.PokeShort i*12+k+2,i*4+j+1
+			idxs.PokeShort i*12+k+4,i*4+j+2
+			idxs.PokeShort i*12+k+6,i*4+j+0
+			idxs.PokeShort i*12+k+8,i*4+j+2
+			idxs.PokeShort i*12+k+10,i*4+j+3
+		Next
+	Next
+	glBufferData GL_ELEMENT_ARRAY_BUFFER,idxs.Size(),idxs._buf,GL_STATIC_DRAW
+	'idxs.Discard
+End Function
+
+Global inited:Int
+
+Function InitMojo2()
+	If inited Return
+	inited=True
+
+?Not opengles
+	glewInit()
+?
+	
+	InitVbos
+	
+	glGetIntegerv GL_FRAMEBUFFER_BINDING,tmpi
+	defaultFbo=tmpi[0]
+	
+	mainShader=LoadString( "incbin::data/mojo2_program.glsl" )
+	
+	_fastShader=New TShader.Create( LoadString( "incbin::data/mojo2_fastshader.glsl" ) )
+	_bumpShader=New TBumpShader.Create( LoadString( "incbin::data/mojo2_bumpshader.glsl" ) )
+	_matteShader=New TMatteShader.Create( LoadString( "incbin::data/mojo2_matteshader.glsl" ) )
+	_shadowShader=New TShader.Create( LoadString( "incbin::data/mojo2_shadowshader.glsl" ) )
+	_lightMapShader=New TShader.Create( LoadString( "incbin::data/mojo2_lightmapshader.glsl" ) )
+	_defaultShader=_bumpShader
+
+	defaultFont=TFont.Load( "incbin::data/mojo2_font.png",32,96,True )'9,13,1,0,7,13,32,96 )
+	If Not defaultFont Throw "Can't load default font"
+	
+	flipYMatrix[5]=-1
+End Function
+
+Public
+
+Type TRefCounted
+
+	Method Retain()
+		If _refs<=0 Throw "Internal error"
+		_refs:+1
+	End Method
+	
+	Method Free()
+		If _refs<=0 Throw "Internal error"
+		_refs:-1
+		If _refs Return
+		_refs=-1
+		Destroy
+	End Method
+	
+	Method Destroy() Abstract
+
+	Field _refs:Int=1
+End Type
+
+'***** Texture *****
+
+rem
+bbdoc: Textures contains image data for use by shaders when rendering.
+about: For more information, please see the #TShader type.
+end rem
+Type TTexture Extends TRefCounted
+
+	'flags
+	Const Filter:Int=1
+	Const Mipmap:Int=2
+	Const ClampS:Int=4
+	Const ClampT:Int=8
+	Const ClampST:Int=12
+	Const RenderTarget:Int=16
+	Const Managed:Int=256
+
+	Rem
+	bbdoc: Creates a new texture.
+	about: The @width and @height are parameters are the size of the new texture.
+	The @format parameter must be 4.
+	The @flags parameter can be a bitwise combination of:
+	| @ Flags				| @Description
+	| Texture.Filter			| The texture is filtered when magnified
+	| Texture.Mipmap			| The texture is mipmapped when minified
+	| Texture.ClampS			| Texture S coordinate is clamped
+	| Texture.ClampT			| Texture T coordinate is clamped
+	| Texture.ClampST		| Texture S and T coordinates are clamped.
+	| Texture.RenderTarget	| The texture can rendered to using a #Canvas.
+	| Texture.Managed		| Texture contents are preserved when graphics are lost
+	End Rem
+	Method Create:TTexture( width:Int,height:Int,format:Int,flags:Int, data:TPixmap = Null )
+
+		If format<>PF_RGBA8888 Then
+			Throw "Invalid texture format: "+format
+		End If
+
+		'can't mipmap NPOT textures on gles20
+		If Not IsPow2( width ) Or Not IsPow2( height ) flags:&~Mipmap
+		
+		_width=width
+		_height=height
+		_format=format
+		_flags=flags
+		_data=data
+
+		If _flags & Managed
+			_managed=New TPixmap.Create( width,height,PF_RGBA8888 )
+			If _data
+				_managed.Paste( _data,0,0 )
+				_data=Null
+			Else
+				_managed.ClearPixels( $ffff00ff )
+			EndIf
+		EndIf
+		
+		Validate()
+
+		Return Self
+	End Method
+	
+	Method Destroy()
+		If _seq=graphicsSeq glDeleteTextures 1, Varptr _glTexture
+		_glTexture=0
+		_glFramebuffer=0
+	End Method
+	
+	rem
+	bbdoc: Gets texture width.
+	end rem
+	Method Width:Int()
+		Return _width
+	End Method
+	
+	rem
+	bbdoc: Gets texture height.
+	end rem
+	Method Height:Int()
+		Return _height
+	End Method
+	
+	rem
+	bbdoc: Gets texture format.
+	end rem
+	Method Format:Int()
+		Return _format
+	End Method
+	
+	rem
+	bbdoc: Gets texture flags.
+	end rem
+	Method Flags:Int()
+		Return _flags
+	End Method
+
+	rem
+	bbdoc: Writes pixel data to texture.
+	about: Pixels should be in premultiplied alpha format.
+	end rem
+	Method WritePixels( x:Int,y:Int,width:Int,height:Int,data:TPixmap,dataOffset:Int=0,dataPitch:Int=0 )
+
+		glPushTexture2d GLTexture()
+	
+		If Not dataPitch Or dataPitch=width*4
+		
+			glTexSubImage2D GL_TEXTURE_2D,0,x,y,width,height,GL_RGBA,GL_UNSIGNED_BYTE,data.pixels + dataOffset
+			
+		Else
+			For Local iy:Int=0 Until height
+				glTexSubImage2D GL_TEXTURE_2D,0,x,y+iy,width,1,GL_RGBA,GL_UNSIGNED_BYTE,data.pixels + dataOffset+iy*dataPitch
+			Next
+		EndIf
+		
+		glPopTexture2d
+		
+		If _flags & Managed
+		
+			Local texPitch:Int=_width*4
+			If Not dataPitch dataPitch=width*4
+			
+			For Local iy:Int=0 Until height
+				MemCopy _data.pixels + (y+iy)*texPitch+x*4, data.pixels + dataOffset+iy*dataPitch, width*4
+			Next
+			
+		EndIf
+
+	End Method
+
+	Method SetData( x:Int,y:Int,pixmap:TPixmap )
+		
+		If _managed
+			If pixmap<>_managed _managed.Paste( pixmap,x,y )
+		Else If _data
+			If pixmap<>_data Throw "Texture is read only" 
+		EndIf
+		
+		glPushTexture2d( GLTexture() )
+		
+		Local width:Int=pixmap.Width
+		Local height:Int=pixmap.Height
+		
+		If pixmap.Pitch=_width*4
+		
+			glTexSubImage2D( GL_TEXTURE_2D,0,x,y,width,height,GL_RGBA,GL_UNSIGNED_BYTE,pixmap.pixels )
+			
+		Else
+		
+			For Local iy:Int=0 Until height
+				glTexSubImage2D( GL_TEXTURE_2D,0,x,y+iy,width,1,GL_RGBA,GL_UNSIGNED_BYTE,pixmap.PixelPtr( 0,iy ) )
+			Next
+			
+		EndIf
+		
+		glPopTexture2d
+		
+	End Method
+		
+	Method UpdateMipmaps()
+		If Not (_flags & Mipmap) Return
+			
+		If _seq<>graphicsSeq
+			Validate()
+			Return
+		EndIf
+
+		glPushTexture2d GLTexture()
+
+		glGenerateMipmap GL_TEXTURE_2D
+		
+		glPopTexture2d
+	End Method
+	
+	Method Loading:Int()
+			Return False
+	End Method
+	
+	Method GLTexture:Int()
+		Validate
+		Return _glTexture
+	End Method
+	
+	Method GLFramebuffer:Int()
+		Validate
+		Return _glFramebuffer
+	End Method
+	
+	Function TexturesLoading:Int()
+			Return 0
+	End Function
+	
+	rem
+	bbdoc: Loads a texture from a url.
+	end rem
+	Function Load:TTexture( url:Object,format:Int=PF_RGBA8888,flags:Int=Filter|Mipmap|ClampST )
+	
+		Local info:Int[2]
+		
+		Local data:TPixmap=LoadPixmap(url)
+		If Not data Return Null
+		
+		' convert to RGBA
+		If data.format <> format Then
+			data = data.Convert(format)
+		End If
+		
+		PremultiplyAlpha(data)
+			
+		Local tex:TTexture=New TTexture.Create( data.width,data.height,format,flags,data )
+		
+		Return tex
+	End Function
+
+	Function PremultiplyAlpha(pix:TPixmap)
+		For Local y:Int=0 Until pix.height
+			For Local x:Int=0 Until pix.width
+				Local pixel:Int=pix.ReadPixel( x,y )
+				Local a:Int=pixel Shr 24 & 255
+				Local b:Int=(pixel Shr 16 & 255)*a/255
+				Local g:Int=(pixel Shr 8 & 255)*a/255
+				Local r:Int=(pixel & 255)*a/255
+				pixel=a Shl 24 | b Shl 16 | g Shl 8 | r
+				pix.WritePixel( x,y,pixel )
+			Next
+		Next
+	End Function
+	
+	Function Color:TTexture( color:Int )
+		Local tex:TTexture=TTexture(_colors.ValueForKey( color ))
+		If tex Return tex
+
+		Local pixmap:TPixmap=New TPixmap.Create( 1,1,PF_RGBA8888 )
+		pixmap.ClearPixels( color )
+
+		tex=New TTexture.Create( 1,1,PF_RGBA8888,ClampST,pixmap )
+		_colors.Insert color,tex
+		Return tex
+	End Function
+	
+	rem
+	bbdoc: Returns a stock single texel black texture.
+	end rem
+	Function Black:TTexture()
+		If Not _black _black=Color( $ff000000 )
+		Return _black
+	End Function
+	
+	rem
+	bbdoc: Returns a stock single texel white texture.
+	end rem
+	Function White:TTexture()
+		If Not _white _white=Color( $ffffffff )
+		Return _white
+	End Function
+	
+	rem
+	bbdoc: Returnss a stock single texel magenta texture.
+	end rem
+	Function Magenta:TTexture()
+		If Not _magenta _magenta=Color( $ffff00ff )
+		Return _magenta
+	End Function
+	
+	rem
+	bbdoc: Returns a stock single texel 'flat' texture for normal mapping.
+	end rem
+	Function Flat:TTexture()
+		If Not _flat _flat=Color( $ff888888 )
+		Return _flat
+	End Function
+	
+	Private
+	
+	Field _seq:Int
+	Field _width:Int
+	Field _height:Int
+	Field _format:Int
+	Field _flags:Int
+	Field _data:TPixmap
+	Field _managed:TPixmap
+	
+	Field _glTexture:Int
+	Field _glFramebuffer:Int
+
+	Global _colors:TIntMap=New TIntMap'<TTexture>
+	Global _black:TTexture
+	Global _white:TTexture
+	Global _magenta:TTexture
+	Global _flat:TTexture
+
+	Method Validate()
+		
+		If _seq=graphicsSeq Return
+		
+		InitMojo2()
+	
+		_seq=graphicsSeq
+	
+		glGenTextures(1, Varptr _glTexture)
+		
+		glPushTexture2d _glTexture
+		
+		If _flags & Filter
+			glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR
+		Else
+			glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST
+		EndIf
+		If (_flags & Mipmap) And (_flags & Filter)
+			glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR
+		Else If _flags & Mipmap
+			glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST_MIPMAP_NEAREST
+		Else If _flags & Filter
+			glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR
+		Else
+			glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST
+		EndIf
+
+		If _flags & ClampS glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE
+		If _flags & ClampT glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE
+
+		glTexImage2D GL_TEXTURE_2D,0,GL_RGBA,_width,_height,0,GL_RGBA,GL_UNSIGNED_BYTE,Null
+
+		glPopTexture2d
+		
+		If _flags & RenderTarget
+		
+			glGenFramebuffers(1, Varptr _glFramebuffer)
+			
+			glPushFramebuffer _glFramebuffer
+			
+			glBindFramebuffer GL_FRAMEBUFFER,_glFramebuffer
+			glFramebufferTexture2D GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,_glTexture,0
+			
+			If glCheckFramebufferStatus( GL_FRAMEBUFFER )<>GL_FRAMEBUFFER_COMPLETE Throw "Incomplete framebuffer"
+			
+			glPopFramebuffer
+			
+		EndIf
+		
+		If _managed Then
+
+			SetData( 0,0,_managed )
+			UpdateMipmaps()
+			
+		Else If _data
+		
+			SetData( 0,0,_data )
+			UpdateMipmaps()
+		
+		EndIf
+		
+	End Method
+	
+	Method LoadData( data:TPixmap )
+		glPushTexture2d GLTexture()
+		
+		glTexImage2D GL_TEXTURE_2D,0,GL_RGBA,_width,_height,0,GL_RGBA,GL_UNSIGNED_BYTE,data.pixels
+
+		glPopTexture2d
+		
+		UpdateMipmaps
+	End Method
+	
+End Type
+
+'***** Shader ****
+
+Public
+
+Type TGLUniform
+	Field name:String
+	Field location:Int
+	Field size:Int
+	Field kind:Int
+	
+	Method Create:TGLUniform( name:String,location:Int,size:Int,kind:Int )
+		Self.name=name
+		Self.location=location
+		Self.size=size
+		Self.kind=kind
+		Return Self
+	End Method
+	
+End Type
+
+Type TGLProgram
+	Field program:Int
+	'material uniforms
+	Field matuniforms:TGLUniform[]
+	'hard coded uniform locations
+	Field mvpMatrix:Int
+	Field mvMatrix:Int
+	Field clipPosScale:Int
+	Field globalColor:Int
+	Field AmbientLight:Int
+	Field fogColor:Int
+	Field lightColors:Int
+	Field lightVectors:Int
+	Field shadowTexture:Int
+	
+	Method Create:TGLProgram( program:Int,matuniforms:TGLUniform[] )
+		Self.program=program
+		Self.matuniforms=matuniforms
+		mvpMatrix=glGetUniformLocation( program,"ModelViewProjectionMatrix" )
+		mvMatrix=glGetUniformLocation( program,"ModelViewMatrix" )
+		clipPosScale=glGetUniformLocation( program,"ClipPosScale" )
+		globalColor=glGetUniformLocation( program,"GlobalColor" )
+		fogColor=glGetUniformLocation( program,"FogColor" )
+		AmbientLight=glGetUniformLocation( program,"AmbientLight" )
+		lightColors=glGetUniformLocation( program,"LightColors" )
+		lightVectors=glGetUniformLocation( program,"LightVectors" )
+		shadowTexture=glGetUniformLocation( program,"ShadowTexture" )
+		Return Self
+	End Method
+	
+	Method Bind()
+	
+		glUseProgram program
+		
+		If mvpMatrix<>-1 glUniformMatrix4fv mvpMatrix,1,False,rs_modelViewProjMatrix
+		If mvMatrix<>-1 glUniformMatrix4fv mvMatrix,1,False,rs_modelViewMatrix
+		If clipPosScale<>-1 glUniform4fv clipPosScale,1,rs_clipPosScale
+		If globalColor<>-1 glUniform4fv globalColor,1,rs_globalColor
+		If fogColor<>-1 glUniform4fv fogColor,1,rs_fogColor
+		If AmbientLight<>-1 glUniform4fv AmbientLight,1,rs_ambientLight
+		If lightColors<>-1 glUniform4fv lightColors,rs_numLights,rs_lightColors
+		If lightVectors<>-1 glUniform4fv lightVectors,rs_numLights,rs_lightVectors
+		glActiveTexture GL_TEXTURE0+7
+		If shadowTexture<>-1 And rs_shadowTexture
+			glBindTexture GL_TEXTURE_2D,rs_shadowTexture.GLTexture()
+			glUniform1i shadowTexture,7
+		Else
+			glBindTexture GL_TEXTURE_2D,TTexture.White().GLTexture()
+		End If
+		glActiveTexture GL_TEXTURE0
+	End Method
+	
+End Type
+
+Public
+
+Type TShader
+
+	Method Create:TShader( source:String )
+		Build source
+		Return Self
+	End Method
+	
+	Method DefaultMaterial:TMaterial()
+		If Not _defaultMaterial _defaultMaterial=New TMaterial.Create( Self )
+		Return _defaultMaterial
+	End Method
+	
+	Function FastShader:TShader()
+		Return _fastShader
+	End Function
+	
+	Rem
+	bbdoc: Returns a stock bump shader for drawing lit sprites with specular and normal maps.
+	about: 
+The following material properties are supported:
+
+| @Property			| @Type		| @Default
+| ColorTexture		| Texture	| White
+| SpecularTexture	| Texture	| Black
+| NormalTexture		| Texture	| Flat
+| AmbientColor		| Float[4]	| [0.0,0.0,0.0,1.0]
+| Roughness			| Float		| 0.5
+
+The shader b3d_Ambient value is computed by multiplying ColorTexture by AmbientColor.
+
+The shader b3d_Diffuse value is computed by multiplying ColorTexture by 1-AmbientColor.
+
+When loading materials that use the bump shader, diffuse, specular and normal maps can be given the following files names:
+
+| @Texture map		| @Valid paths
+| Diffuse			| (FILE).(EXT) ; (FILE)_d.(EXT) ; (FILE)_diff.(EXT) ; (FILE)_diffuse.(EXT)
+| Specular			| (FILE)_s.(EXT) ; (FILE)_spec.(EXT) ; (FILE)_specular.(EXT) ;(FILE)_SPECUALR.(EXT)
+| Normal			| (FILE)_n.(EXT) ; (FILE)_norm.(EXT) ; (FILE)_normal.(EXT) ; (FILE)_NORMALS.(EXT)
+
+Where (FILE) is the filename component of the path provided to Material.Load or Image.Load, and (EXT) is the file extension, eg: png, jpg.
+	end rem
+	Function BumpShader:TShader()
+		Return _bumpShader
+	End Function
+	
+	Rem
+	bbdoc: Returns a stock matte shader for drawing lit sprites with no specular or normal maps.
+	about: 
+The following material properties are supported:
+
+| @Property			| @Type		| @Default
+| ColorTexture		| Texture	| White
+| AmbientColor		| Float[4]	| [0.0,0.0,0.0,1.0]
+| Roughness			| Float		| 0.5
+	end rem
+	Function MatteShader:TShader()
+		Return _matteShader
+	End Function
+	
+	Rem
+	bbdoc: Returns a stock shadow shader for drawing shadows.
+	about: This shader simply writes 'black' to b3d_FragColor.
+	end rem
+	Function ShadowShader:TShader()
+		Return _shadowShader
+	End Function
+	
+	Rem
+	bbdoc: Returns a stock shader for drawing light textures and light mask effects.
+	about: 
+This shader performs a texture lookup, and writes the red component to b3d_FragColor.
+	
+The following material properties are supported:
+
+| @Property			| @Type		| @Default
+| ColorTexture		| Texture	| White
+	end rem	
+	Function LightMapShader:TShader()
+		Return _lightMapShader
+	End Function
+	
+	Rem
+	bbdoc: Returns the default shader used when a material is created with a 'Null' shader.
+	about: This is initially the #BumpShader, but can be modified using #SetDefaultShader.
+	end rem
+	Function DefaultShader:TShader()
+		Return _defaultShader
+	End Function
+	
+	Rem
+	bbdoc: Sets the default shader used when a material is created with a 'Null' shader.
+	end rem
+	Function SetDefaultShader( shader:TShader )
+		If Not shader shader=_bumpShader
+		_defaultShader=shader
+	End Function
+	
+	'Protected
+	
+	Rem
+	bbdoc: Compiles and links the shader.
+	about: Types that extend Shader must call this method at some point. This is usually done in the subclasses constructor.
+	end rem
+	Method Build( source:String )
+		_source=source
+		BuildInit
+	End Method
+	
+	Rem
+	bbdoc: Types that extend Shader must set defalut values for all valid shader parameters in this method.
+	end rem
+	Method OnInitMaterial( material:TMaterial )
+		material.SetTexture "ColorTexture",TTexture.White()
+	End Method
+	
+	Rem
+	bbdoc: Classes that extend Shader should load textures and other valid shader parameters from @path into @material in this method.
+	about: The interpretation of @path is completely up to the shader. The @texFlags parameter contains texture flag values that should be used for any textures loaded.
+	The @material parameter is an already initialized material.
+	This method should return @material if successful, or null on failure.
+	end rem
+	Method OnLoadMaterial:TMaterial( material:TMaterial,path:String,texFlags:Int )
+		Local texture:TTexture=TTexture.Load( path,4,texFlags )
+		If Not texture Return Null
+		material.SetTexture "ColorTexture",texture
+		If texture texture.Free
+		Return material
+	End Method
+	
+	'Private
+	
+	Const MAX_FLAGS:Int=8
+	
+	Field _seq:Int
+	Field _source:String
+	
+	Field _vsource:String
+	Field _fsource:String
+	Field _uniforms:TStringMap=New TStringMap
+	
+	Field _glPrograms:TGLProgram[MAX_LIGHTS+1]
+	
+	Field _defaultMaterial:TMaterial
+	
+	Method Bind()	
+		Local program:TGLProgram=GLProgram()
+		
+		If program=rs_program Return
+
+		rs_program=program
+		rs_material=Null
+		
+		program.Bind
+	End Method
+	
+	Method GLProgram:TGLProgram()
+	
+		If _seq<>graphicsSeq 
+			_seq=graphicsSeq
+			rs_program=Null
+			BuildInit
+		EndIf
+		
+		Return _glPrograms[rs_numLights]
+	End Method
+	
+	Method BuildProgram:TGLProgram( numLights:Int )
+
+		Local defs:String=""
+		defs:+"#define NUM_LIGHTS "+numLights+"~n"
+
+		Local vshader:Int=glCompile( GL_VERTEX_SHADER,defs+_vsource )
+		Local fshader:Int=glCompile( GL_FRAGMENT_SHADER,defs+_fsource )
+		
+		Local program:Int=glCreateProgram()
+		glAttachShader program,vshader
+		glAttachShader program,fshader
+		glDeleteShader vshader
+		glDeleteShader fshader
+		
+		glBindAttribLocation program,0,"Position"
+		glBindAttribLocation program,1,"Texcoord0"
+		glBindAttribLocation program,2,"Tangent"
+		glBindAttribLocation program,3,"Color"
+		
+		glLink program
+		
+		'enumerate program uniforms	
+		Local matuniforms:TGLUniform[] = New TGLUniform[0]
+		Local size:Int
+		Local kind:Int
+		Local buf:Byte[1024]
+		Local l:Int
+		glGetProgramiv program,GL_ACTIVE_UNIFORMS,tmpi
+		For Local i:Int=0 Until tmpi[0]
+			glGetActiveUniform program,i,1024,Varptr l,Varptr size, Varptr kind, buf
+			Local name:String = String.FromBytes(buf, l)
+			If _uniforms.Contains( name )
+				Local location:Int=glGetUniformLocation( program,name )
+				If location=-1 Continue  'IE fix...
+				matuniforms :+ [New TGLUniform.Create( name,location,size,kind )]
+'				Print name[0]+"->"+location
+			EndIf
+		Next
+		
+		Return New TGLProgram.Create( program,matuniforms )
+	
+	End Method
+	
+	Method BuildInit()
+		InitMojo2
+
+		Local p:TGlslParser=TGlslParser(New TGlslParser.Create( _source ))
+
+		Local vars:TMap=New TMap
+		
+		While p.Toke
+		
+			If p.CParse( "uniform" )
+				'uniform decl
+				Local ty:String=p.ParseType()
+				Local id:String=p.ParseIdent()
+				p.ParseToke ";"
+				_uniforms.Insert id, id
+'				Print "uniform "+ty+" "+id+";"
+				Continue
+			EndIf
+			
+			Local id:String=p.CParseIdent()
+			If id
+				If id.StartsWith( "gl_" )
+					vars.Insert "B3D_"+id.ToUpper(), ""
+				Else If id.StartsWith( "b3d_" ) 
+					vars.Insert id.ToUpper(), ""
+				EndIf
+				Continue
+			EndIf
+			
+			p.Bump
+		Wend
+		
+		Local vardefs:String=""
+		For Local v:String=EachIn vars.Keys()
+			vardefs:+"#define "+v+" 1~n"
+		Next
+		
+'		Print "Vardefs:";Print vardefs
+		
+		Local source:String=mainShader
+		Local i0:Int=source.Find( "//@vertex" )
+		If i0=-1 Throw "Can't find //@vertex chunk"
+		Local i1:Int=source.Find( "//@fragment" )
+		If i1=-1 Throw "Can't find //@fragment chunk"
+		
+		Local header:String=vardefs+source[..i0]
+		_vsource=header+source[i0..i1]
+		_fsource=header+source[i1..].Replace( "${SHADER}",_source )
+		
+		For Local numLights:Int=0 To MAX_LIGHTS
+		
+			_glPrograms[numLights]=BuildProgram( numLights )
+
+			If numLights Or vars.Contains( "B3D_DIFFUSE" ) Or vars.Contains( "B3D_SPECULAR" ) Continue
+			
+			For Local i:Int=1 To MAX_LIGHTS
+				_glPrograms[i]=_glPrograms[0]
+			Next
+			
+			Exit
+			
+		Next
+		
+		
+	End Method
+	
+End Type
+
+Type TBumpShader Extends TShader
+
+'	Method New( source:String )
+'		Super.New( source )
+'	End
+
+	'Protected
+	
+	Method OnInitMaterial( material:TMaterial )
+		material.SetTexture "ColorTexture",TTexture.White()
+		material.SetTexture "SpecularTexture",TTexture.Black()
+		material.SetTexture "NormalTexture",TTexture.Flat()
+		material.SetVector "AmbientColor",[1.0,1.0,1.0,1.0]
+		material.SetScalar "Roughness",1.0
+	End Method
+	
+	Method OnLoadMaterial:TMaterial( material:TMaterial,path:String,texFlags:Int )
+
+		Local format:Int = PF_RGBA8888
+	
+		Local ext:String = ExtractExt( path )
+		If ext path=StripExt( path ) Else ext="png"
+		
+		Local colorTex:TTexture=TTexture.Load( path+"."+ext,format,texFlags )
+		If Not colorTex colorTex=TTexture.Load( path+"_d."+ext,format,texFlags )
+		If Not colorTex colorTex=TTexture.Load( path+"_diff."+ext,format,texFlags )
+		If Not colorTex colorTex=TTexture.Load( path+"_diffuse."+ext,format,texFlags )
+		
+		Local specularTex:TTexture=TTexture.Load( path+"_s."+ext,format,texFlags )
+		If Not specularTex specularTex=TTexture.Load( path+"_spec."+ext,format,texFlags )
+		If Not specularTex specularTex=TTexture.Load( path+"_specular."+ext,format,texFlags )
+		If Not specularTex specularTex=TTexture.Load( path+"_SPECULAR."+ext,format,texFlags )
+		
+		Local normalTex:TTexture=TTexture.Load( path+"_n."+ext,format,texFlags )
+		If Not normalTex normalTex=TTexture.Load( path+"_norm."+ext,format,texFlags )
+		If Not normalTex normalTex=TTexture.Load( path+"_normal."+ext,format,texFlags )
+		If Not normalTex normalTex=TTexture.Load( path+"_NORMALS."+ext,format,texFlags )
+		
+		If Not colorTex And Not specularTex And Not normalTex Return Null
+
+		material.SetTexture "ColorTexture",colorTex
+		material.SetTexture "SpecularTexture",specularTex
+		material.SetTexture "NormalTexture",normalTex
+		
+		If specularTex Or normalTex
+			material.SetVector "AmbientColor",[0.0,0.0,0.0,1.0]
+			material.SetScalar "Roughness",.5
+		EndIf
+		
+		If colorTex colorTex.Free
+		If specularTex specularTex.Free
+		If normalTex normalTex.Free
+		
+		Return material
+	End Method
+	
+End	Type
+
+Type TMatteShader Extends TShader
+
+'	Method Create( source:String )
+'		Super.New( source )
+'	End
+	
+'	Protected
+	
+	Method OnInitMaterial( material:TMaterial )
+		material.SetTexture "ColorTexture",TTexture.White()
+		material.SetVector "AmbientColor",[0.0,0.0,0.0,1.0]
+		material.SetScalar "Roughness",1.0
+	End Method
+	
+End Type
+
+'***** Material *****
+
+Rem
+bbdoc: Materials contain shader parameters that map to shader uniforms variables when rendering. 
+End Rem
+Type TMaterial Extends TRefCounted
+
+	Rem
+	bbdoc: Creates a new material.
+	End Rem
+	Method Create:TMaterial( shader:TShader=Null )
+		InitMojo2
+		
+		If Not shader shader=_defaultShader
+		_shader=shader
+		_shader.OnInitMaterial( Self )
+		_inited=True
+		
+		Return Self
+	End Method
+	
+	Method Destroy()
+		For Local tex:TTexture=EachIn _textures
+			tex.Free()
+		Next
+	End Method
+	
+	Rem
+	bbdoc: Gets material shader.
+	End Rem
+	Method Shader:TShader() 
+		Return _shader
+	End Method
+	
+	Method ColorTexture:TTexture()
+		Return _colorTexture
+	End Method
+	
+	Method Width:Int()
+		If _colorTexture Return _colorTexture._width
+		Return 0
+	End Method
+	
+	Method Height:Int()
+		If _colorTexture Return _colorTexture._height
+		Return 0
+	End Method
+	
+	Rem
+	bbdoc: Sets float shader parameter.
+	End Rem
+	Method SetScalar( param:String,scalar:Float )
+		If _inited And Not _scalars.Contains( param ) Return
+		_scalars.Insert param,scalar
+	End Method
+	
+	Rem
+	bbdoc: Gets float shader parameter.
+	End Rem
+	Method GetScalar:Float( param:String,defValue:Float=1.0 )
+		If Not _scalars.Contains( param ) Return defValue
+		Return _scalars.ValueForKey( param )
+	End Method
+	
+	Rem
+	bbdoc: Sets vector shader parameter.
+	End Rem
+	Method SetVector( param:String,vector:Float[] )
+		If _inited And Not _vectors.Contains( param ) Return
+		_vectors.Insert param,vector
+	End Method
+	
+	Rem
+	bbdoc: Gets vector shader parameter.
+	End Rem
+	Method GetVector:Float[]( param:String,defValue:Float[]=[1.0,1.0,1.0,1.0] )
+		If Not _vectors.Contains( param ) Return defValue
+		Return _vectors.ValueForKey( param )
+	End Method
+	
+	Rem
+	bbdoc: Sets texture shader parameter.
+	End Rem
+	Method SetTexture( param:String,texture:TTexture )
+		If Not texture Return
+		If _inited And Not _textures.Contains( param ) Return
+		
+		Local old:TTexture=TTexture(_textures.ValueForKey( param ))
+		texture.Retain
+		_textures.Insert param,texture
+		If old old.Free
+		
+		If param="ColorTexture" _colorTexture=texture
+		
+	End Method
+	
+	Rem
+	bbdoc: Gets texture shader parameter.
+	End Rem
+	Method GetTexture:TTexture( param:String,defValue:TTexture=Null )
+		If Not _textures.Contains( param ) Return defValue
+		Return TTexture(_textures.ValueForKey( param ))
+	End Method
+	
+	Method Loading:Int()
+		Return False
+	End Method
+	
+	Rem
+	bbdoc: Loads a material.
+	about: If @shader is null, the TShader.DefaultShader is used.
+	End Rem
+	Function Load:TMaterial( path:String,texFlags:Int,shader:TShader )
+	
+		Local material:TMaterial=New TMaterial.Create( shader )
+
+		material=material.Shader.OnLoadMaterial( material,path,texFlags )
+		
+		Return material
+	End Function
+	
+	'Private
+	
+	Field _shader:TShader
+	Field _colorTexture:TTexture
+	Field _scalars:TStringFloatMap=New TStringFloatMap
+	Field _vectors:TStringMap=New TStringMap
+	Field _textures:TStringMap=New TStringMap
+	Field _inited:Int
+	
+	Method Bind:Int()
+	
+		_shader.Bind
+		
+		If rs_material=Self Return True
+		
+		rs_material=Self
+	
+		Local texid:Int=0
+		
+		For Local u:TGLUniform=EachIn rs_program.matuniforms
+			Select u.kind
+			Case GL_FLOAT
+				glUniform1f u.location,GetScalar( u.name )
+			Case GL_FLOAT_VEC4
+				glUniform4fv u.location,1,GetVector( u.name )
+			Case GL_SAMPLER_2D
+				Local tex:TTexture=GetTexture( u.name )
+'				If tex.Loading
+'					rs_material=Null 
+'					Exit
+'				Endif
+				glActiveTexture GL_TEXTURE0+texid
+				glBindTexture GL_TEXTURE_2D,tex.GLTexture()
+				glUniform1i u.location,texid
+				texid:+1
+			Default
+				Throw "Unsupported uniform type:"+u.kind 
+			End Select
+		Next
+
+		If texid glActiveTexture GL_TEXTURE0
+		
+		Return rs_material=Self
+	End Method
+	
+End Type
+
+'***** ShaderCaster *****
+
+Rem
+bbdoc: The ShadowCaster class provides support for simple 2d shadow rendering.
+about: Shadow casters are used by #Renderer objects when rendering layers. To render shadows, you will need to add
+shadow casters to the drawlists returned by ILayer.OnRenderLayer.
+A shadow caster can either be added to a drawlist using [[DrawList.AddShadowCaster]], or attached to images using [[Image.SetShadowCaster]]. Shadow casters attached to images are automatically added to drawlists when an image is drawn.
+A shadow caster contains a set of 2d vertices which describe the geometric shape of the object that casts a shadow. The vertices should describe a convex polygon.
+End Rem
+Type TShadowCaster
+
+	Method Create:TShadowCaster( verts:Float[] = Null,kind:Int = -1 )
+		If verts Then
+			_verts=verts
+		End If
+		If kind >= 0 Then
+			_kind=kind
+		End If
+		Return Self
+	End Method
+	
+	Rem
+	bbdoc: Set shadow caster vertices.
+	end rem
+	Method SetVertices( vertices:Float[] )
+		_verts=vertices
+	End Method
+	
+	Rem
+	bbdoc: Get shadow caster vertices.
+	end rem
+	Method Vertices:Float[]()
+		Return _verts
+	End Method
+
+	Method SetKind( kind:Int )
+		_kind=kind
+	End Method
+	
+	Method Kind:Int()
+		Return _kind
+	End Method
+	
+	'Private
+	
+	Field _verts:Float[]
+	Field _kind:Int
+	
+End Type
+
+'***** Image *****
+
+Rem
+bbdoc: An image is a rectangular area of pixels within a material, that can be drawn using one of the [[DrawList.DrawImage]] methods.
+about: You can create a new image using one of the [[Image.Create]] methods, or load an image from file using [[Image.Load|Image.Load]].
+An image also has a handle, an offset within the image that represents it's origin whan it is drawn. Image handles are specified in fractional values, where 0,0 is the top-left of an image, 1,1 is the bottom-right and .5,.5 is the center.
+end rem
+Type TImage
+
+	Const Filter:Int=TTexture.Filter
+	Const Mipmap:Int=TTexture.Mipmap
+	Const Managed:Int=TTexture.Managed
+	
+	Method Create:TImage( width:Int,height:Int,xhandle:Float=.5,yhandle:Float=.5,flags:Int=TImage.Filter )
+		flags:&_flagsMask
+		Local texture:TTexture=New TTexture.Create( width,height,PF_RGBA8888,flags|TTexture.ClampST|TTexture.RenderTarget )
+		_material=New TMaterial.Create( _fastShader )
+		_material.SetTexture "ColorTexture",texture
+		_width=width
+		_height=height
+		SetHandle xhandle,yhandle
+		Return Self
+	End Method
+	
+	Method CreateImage:TImage( image:TImage,x:Int,y:Int,width:Int,height:Int,xhandle:Float=.5,yhandle:Float=.5 )
+		_material=image._material
+		_material.Retain
+		_x=image._x+x
+		_y=image._y+y
+		_width=width
+		_height=height
+		SetHandle xhandle,yhandle
+		Return Self
+	End Method
+	
+	Method CreateMaterial:TImage( material:TMaterial,xhandle:Float=.5,yhandle:Float=.5 )
+		Local texture:TTexture=material.ColorTexture()
+		If Not texture Throw "Material has no ColorTexture"
+		_material=material
+		_material.Retain
+		_width=_material.Width
+		_height=_material.Height
+		SetHandle xhandle,yhandle
+		Return Self
+	End Method
+
+	Method CreateMaterialSize:TImage( material:TMaterial,x:Int,y:Int,width:Int,height:Int,xhandle:Float=.5,yhandle:Float=.5 )
+		Local texture:TTexture=material.ColorTexture()
+		If Not texture Throw "Material has no ColorTexture"
+		_material=material
+		_material.Retain
+		_x=x
+		_y=y
+		_width=width
+		_height=height
+		SetHandle xhandle,yhandle
+		Return Self
+	End Method
+
+	Method Discard()
+		If _material _material.Free
+		_material=Null
+	End Method
+	
+	Method Material:TMaterial()
+		Return _material
+	End Method
+	
+	Method X0:Float()
+		Return _x0
+	End Method
+	
+	Method Y0:Float()
+		Return _y0
+	End Method
+	
+	Method X1:Float()
+		Return _x1
+	End Method
+	
+	Method Y1:Float()
+		Return _y1
+	End Method
+	
+	Method Width:Int()
+		Return _width
+	End Method
+	
+	Method Height:Int()
+		Return _height
+	End Method
+	
+	Method HandleX:Float()
+		Return -_x0/(_x1-_x0)
+	End Method
+	
+	Method HandleY:Float()
+		Return -_y0/(_y1-_y0)
+	End Method
+	
+	Method WritePixels( x:Int,y:Int,width:Int,height:Int,data:TPixmap,dataOffset:Int=0,dataPitch:Int=0 )
+		_material.ColorTexture().WritePixels( x+_x,y+_y,width,height,data,dataOffset,dataPitch )
+	End Method
+	
+	Method SetHandle( xhandle:Float,yhandle:Float )
+		_x0=Float(_width)*-xhandle
+		_x1=Float(_width)*(1-xhandle)
+		_y0=Float(_height)*-yhandle
+		_y1=Float(_height)*(1-yhandle)
+		_s0=Float(_x)/Float(_material.Width)
+		_t0=Float(_y)/Float(_material.Height)
+		_s1=Float(_x+_width)/Float(_material.Width)
+		_t1=Float(_y+_height)/Float(_material.Height)
+	End Method
+	
+	Method SetShadowCaster( shadowCaster:TShadowCaster )
+		_caster=shadowCaster
+	End Method
+	
+	Method ShadowCaster:TShadowCaster()
+		Return _caster
+	End Method
+	
+	Method Loading:Int()
+		Return _material.Loading()
+	End Method
+	
+	Function ImagesLoading:Int()
+		Return TTexture.TexturesLoading>0
+	End Function
+	
+	Function SetFlagsMask( mask:Int )
+		_flagsMask=mask
+	End Function
+	
+	Function FlagsMask:Int()
+		Return _flagsMask
+	End Function
+	
+	Function Load:TImage( path:String,xhandle:Float=.5,yhandle:Float=.5,flags:Int=TImage.Filter|TImage.Mipmap,shader:TShader=Null )
+		flags:&_flagsMask
+	
+		Local material:TMaterial=TMaterial.Load( path,flags|TTexture.ClampST,shader )
+		If Not material Return Null
+
+		Return New TImage.CreateMaterial( material,xhandle,yhandle )
+	End Function
+	
+	Function LoadFrames:TImage[]( path:String,numFrames:Int,padded:Int=False,xhandle:Float=.5,yhandle:Float=.5,flags:Int=TImage.Filter|TImage.Mipmap,shader:TShader=Null )
+		flags:&_flagsMask
+	
+		Local material:TMaterial=TMaterial.Load( path,flags|TTexture.ClampST,shader )
+		If Not material Return Null
+		
+		Local cellWidth:Int=material.Width/numFrames
+		Local cellHeight:Int=material.Height
+		
+		Local x:Int=0
+		Local width:Int=cellWidth
+		If padded Then
+			x:+1
+			width:-2
+		End If
+		
+		Local frames:TImage[]=New TImage[numFrames]
+		
+		For Local i:Int=0 Until numFrames
+			frames[i]=New TImage.CreateMaterialSize( material,i*cellWidth+x,0,width,cellHeight,xhandle,yhandle )
+		Next
+		
+		Return frames
+	End Function
+	
+	'Private
+	
+	Global _flagsMask:Int=Filter|Mipmap|Managed
+	
+	Field _material:TMaterial
+	Field _x:Int,_y:Int,_width:Int,_height:Int
+	Field _x0:Float=-1,_y0:Float=-1,_x1:Float=1,_y1:Float=1
+	Field _s0:Float=0 ,_t0:Float=0 ,_s1:Float=1,_t1:Float=1
+
+	Field _caster:TShadowCaster
+	
+'	Method SetFrame( x0:Float,y0:Float,x1:Float,y1:Float,s0:Float,t0:Float,s1:Float,t1:Float )
+'		_x0=x0;_y0=y0;_x1=x1;_y1=y1
+'		_s0=s0;_t0=t0;_s1=s1;_t1=t1
+'	End
+	
+End Type
+
+'***** Font *****
+
+Type TGlyph
+	Field image:TImage
+	Field char:Int
+	Field x:Int
+	Field y:Int
+	Field width:Int
+	Field height:Int
+	Field advance:Float
+	
+	Method Create:TGlyph( image:TImage,char:Int,x:Int,y:Int,width:Int,height:Int,advance:Float )
+		Self.image=image
+		Self.char=char
+		Self.x=x
+		Self.y=y
+		Self.width=width
+		Self.height=height
+		Self.advance=advance
+		Return Self
+	End Method
+End Type
+
+Rem
+bbdoc: Provides support for simple fixed width bitmap fonts.
+End Rem
+Type TFont
+
+	Method Create:TFont( glyphs:TGlyph[],firstChar:Int,height:Float )
+		_glyphs=glyphs
+		_firstChar=firstChar
+		_height=height
+		Return Self
+	End Method
+
+	Method GetGlyph:TGlyph( char:Int )
+		Local i:Int=char-_firstChar
+		If i>=0 And i<_glyphs.Length Return _glyphs[i]
+		Return Null
+	End Method
+	
+	Rem
+	bbdoc: Gets width of @text drawn in this font.
+	End Rem
+	Method TextWidth:Float( Text:String )
+		Local w:Float=0.0
+		For Local char:Int=EachIn Text
+			Local glyph:TGlyph=GetGlyph( char )
+			If Not glyph Continue
+			w:+glyph.advance
+		Next
+		Return w
+	End Method
+
+	Rem
+	bbdoc: Gets height of @text drawn in this font.
+	End Rem
+	Method TextHeight:Float( Text:String )
+		Return _height
+	End Method
+	
+	Rem
+	bbdoc: Loads a fixed width font from @path.
+	about: Glyphs should be laid out horizontally within the source image.
+	If @padded is true, then each glyph is assumed to have a transparent one pixel padding border around it.
+	End Rem
+	Function Load:TFont( path:String,firstChar:Int,numChars:Int,padded:Int )
+
+		Local image:TImage=TImage.Load( path )
+		If Not image Return Null
+		
+		Local cellWidth:Int=image.Width/numChars
+		Local cellHeight:Int=image.Height
+		Local glyphX:Int=0,glyphY:Int=0,glyphWidth:Int=cellWidth,glyphHeight:Int=cellHeight
+		If padded glyphX:+1;glyphY:+1;glyphWidth:-2;glyphHeight:-2
+
+		Local w:Int=image.Width/cellWidth
+		Local h:Int=image.Height/cellHeight
+
+		Local glyphs:TGlyph[]=New TGlyph[numChars]
+		
+		For Local i:Int=0 Until numChars
+			Local y:Int=i / w
+			Local x:Int=i Mod w
+			Local glyph:TGlyph=New TGlyph.Create( image,firstChar+i,x*cellWidth+glyphX,y*cellHeight+glyphY,glyphWidth,glyphHeight,glyphWidth )
+			glyphs[i]=glyph
+		Next
+		
+		Return New TFont.Create( glyphs,firstChar,glyphHeight )
+	
+	End Function
+	
+	Function LoadSize:TFont( path:String,cellWidth:Int,cellHeight:Int,glyphX:Int,glyphY:Int,glyphWidth:Int,glyphHeight:Int,firstChar:Int,numChars:Int )
+
+		Local image:TImage=TImage.Load( path )
+		If Not image Return Null
+
+		Local w:Int=image.Width/cellWidth
+		Local h:Int=image.Height/cellHeight
+
+		Local glyphs:TGlyph[]=New TGlyph[numChars]
+		
+		For Local i:Int=0 Until numChars
+			Local y:Int=i / w
+			Local x:Int=i Mod w
+			Local glyph:TGlyph=New TGlyph.Create( image,firstChar+i,x*cellWidth+glyphX,y*cellHeight+glyphY,glyphWidth,glyphHeight,glyphWidth )
+			glyphs[i]=glyph
+		Next
+		
+		Return New TFont.Create( glyphs,firstChar,glyphHeight )
+	End Function
+	
+	'Private
+	
+	Field _glyphs:TGlyph[]
+	Field _firstChar:Int
+	Field _height:Float
+	
+End Type
+
+'***** DrawList *****
+
+Type TDrawOp
+'	Field shader:Shader
+	Field material:TMaterial
+	Field blend:Int
+	Field order:Int
+	Field count:Int
+End Type
+
+Type TBlendMode
+	Const Opaque:Int=0
+	Const Alpha:Int=1
+	Const Additive:Int=2
+	Const Multiply:Int=3
+	Const Multiply2:Int=4
+End Type
+
+Rem
+bbdoc: A drawlist contains drawing state and a sequence of 2d drawing operations.
+about:
+You add drawing operations to a drawlist using any of the Draw methods. When a drawing operation is added, the current drawing state is captured by the drawing operation. Further changes to the drawing state will not affect drawing operations already in the drawlist.
+A [[Canvas]] extends [[DrawList]], and can be used to draw directly to the app window or an image. A drawlist can also be rendered to a canvas using [[Canvas.RenderDrawList]].
+A drawlist's drawing state consists of:
+| @Drawing state			| @Description
+| Current color			| [[SetColor]]
+| Current 2d matrix		| [[Translate]], [[Rotate]], [[Scale]], [[PushMatrix]], [[PopMatrix]]
+| Current blend mode		| [[SetBlendMode]]
+| Current font			| [[DrawText]]
+End Rem
+Type TDrawList
+
+	Method New()
+		InitMojo2
+		
+		_color = __colorArray
+		
+		SetFont Null
+		SetDefaultMaterial _fastShader.DefaultMaterial
+	End Method
+	
+	Method SetBlendMode( blend:Int )
+		_blend=blend
+	End Method
+	
+	Method BlendMode:Int()
+		Return _blend
+	End Method
+	
+	Method SetColor( r:Float,g:Float,b:Float,a:Float = -1 )
+		Local c:Float Ptr = _color
+		c[0]=r
+		c[1]=g
+		c[2]=b
+		If a >= 0
+			c[3]=a
+			_alpha=a*255
+		End If
+		_pmcolor=Int(_alpha) Shl 24 | Int(c[2]*_alpha) Shl 16 | Int(c[1]*_alpha) Shl 8 | Int(c[0]*_alpha)
+	End Method
+	
+	Method SetAlpha( a:Float )
+		_color[3]=a
+		_alpha=a*255
+		_pmcolor=Int(_alpha) Shl 24 | Int(_color[2]*_alpha) Shl 16 | Int(_color[1]*_alpha) Shl 8 | Int(_color[0]*_alpha)
+	End Method
+	
+	Method Color:Float[]()
+		Return [_color[0],_color[1],_color[2],_color[3]]
+	End Method
+	
+	Method GetColor( color:Float[] )
+		color[0]=_color[0]
+		color[1]=_color[1]
+		color[2]=_color[2]
+		If color.Length>3 color[3]=_color[3]
+	End Method
+	
+	Method Alpha:Float()
+		Return _color[3]
+	End Method
+	
+	rem
+	bbdoc: Sets the current 2d matrix to the identity matrix.
+	about: Same as SetMatrix( 1,0,0,1,0,0 ).
+	end rem
+	Method ResetMatrix()
+		_ix=1;_iy=0
+		_jx=0;_jy=1
+		_tx=0;_ty=0
+	End Method
+	
+	rem
+	bbdoc: Sets the current 2d matrix to the given matrix.
+	end rem
+	Method SetMatrix( ix:Float,iy:Float,jx:Float,jy:Float,tx:Float,ty:Float )
+		_ix=ix;_iy=iy
+		_jx=jx;_jy=jy
+		_tx=tx;_ty=ty
+	End Method
+	
+	rem
+	bbdoc: Gets the current 2d matrix.
+	end rem
+	Method GetMatrix( matrix:Float[] )
+		matrix[0]=_ix
+		matrix[1]=_iy
+		matrix[2]=_jx
+		matrix[3]=_jy
+		matrix[4]=_tx
+		matrix[5]=_ty
+	End Method
+	
+	rem
+	bbdoc: Multiplies the current 2d matrix by the given matrix.
+	end rem
+	Method Transform( ix:Float,iy:Float,jx:Float,jy:Float,tx:Float,ty:Float )
+		Local ix2:Float=ix*_ix+iy*_jx
+		Local iy2:Float=ix*_iy+iy*_jy
+		Local jx2:Float=jx*_ix+jy*_jx
+		Local jy2:Float=jx*_iy+jy*_jy
+		Local tx2:Float=tx*_ix+ty*_jx+_tx
+		Local ty2:Float=tx*_iy+ty*_jy+_ty
+		SetMatrix ix2,iy2,jx2,jy2,tx2,ty2
+	End Method
+
+	rem
+	bbdoc: Translates the current 2d matrix.
+	end rem
+	Method Translate( tx:Float,ty:Float )
+		Transform 1,0,0,1,tx,ty
+	End Method
+	
+	rem
+	bbdoc: Rotates the current 2d matrix.
+	end rem
+	Method Rotate( rz:Float )
+		Transform Cos( rz ),-Sin( rz ),Sin( rz ),Cos( rz ),0,0
+	End Method
+	
+	rem
+	bbdoc: Scales the current 2d matrix.
+	end rem
+	Method Scale( sx:Float,sy:Float )
+		Transform sx,0,0,sy,0,0
+	End Method
+	
+	rem
+	bbdoc: Translates and rotates (in that order) the current 2d matrix.
+	end rem
+	Method TranslateRotate( tx:Float,ty:Float,rz:Float )
+		Translate tx,ty
+		Rotate rz
+	End Method
+	
+	rem
+	bbdoc: Rotates and scales (in that order) the current 2d matrix.
+	end rem
+	Method RotateScale( rz:Float,sx:Float,sy:Float )
+		Rotate rz
+		Scale sx,sy
+	End Method
+	
+	Method TranslateScale( tx:Float,ty:Float,sx:Float,sy:Float )
+		Translate tx,ty
+		Scale sx,sy
+	End Method
+	
+	rem
+	bbdoc: Translates, rotates and scales (in that order) the current 2d matrix.
+	end rem
+	Method TranslateRotateScale( tx:Float,ty:Float,rz:Float,sx:Float,sy:Float )
+		Translate tx,ty
+		Rotate rz
+		Scale sx,sy
+	End Method
+	
+	rem
+	bbdoc: Sets the maximum number of 2d matrices that can be pushed onto the matrix stack using @PushMatrix.
+	end rem
+	Method SetMatrixStackCapacity( capacity:Int )
+		'_matStack=_matStack.Resize( capacity*6 )
+		_matStack = _matStack[..capacity*6]
+		_matSp=0
+	End Method
+	
+	rem
+	bbdoc: Gets the maximum number of 2d matrices that can be pushed onto the matrix stack using @PushMatrix.
+	end rem
+	Method MatrixStackCapacity:Int()
+		Return _matStack.Length/6
+	End Method
+	
+	rem
+	bbdoc: Pushes the current 2d matrix on the 2d matrix stack.
+	end rem
+	Method PushMatrix()
+		_matStack[_matSp+0]=_ix;_matStack[_matSp+1]=_iy
+		_matStack[_matSp+2]=_jx;_matStack[_matSp+3]=_jy
+		_matStack[_matSp+4]=_tx;_matStack[_matSp+5]=_ty
+		_matSp:+6
+		If _matSp>=_matStack.Length _matSp:-_matStack.Length
+	End Method
+	
+	rem
+	bbdoc: Pops the current 2d matrix from the 2d matrix stack.
+	end rem
+	Method PopMatrix()
+		_matSp:-6
+		If _matSp<0 _matSp:+_matStack.Length
+		_ix=_matStack[_matSp+0]
+		_iy=_matStack[_matSp+1]
+		_jx=_matStack[_matSp+2]
+		_jy=_matStack[_matSp+3]
+		_tx=_matStack[_matSp+4]
+		_ty=_matStack[_matSp+5]
+	End Method
+	
+	rem
+	bbdoc: Sets current font for use with #DrawText.
+	about: 	If @font is null, a default font is used.
+	end rem
+	Method SetFont( font:TFont )
+		If Not font font=defaultFont
+		_font=font
+	End Method
+	
+	rem
+	bbdoc: Gets the current font.
+	end rem
+	Method Font:TFont()
+		Return _font
+	End Method
+	
+	rem
+	bbdoc: Sets the default material used for drawing operations that use a null material.
+	end rem
+	Method SetDefaultMaterial( material:TMaterial )
+		_defaultMaterial=material
+	End Method
+	
+	rem
+	bbdoc: Returns the current default material.
+	end rem
+	Method DefaultMaterial:TMaterial()
+		Return _defaultMaterial
+	End Method
+	
+	rem
+	bbdoc: Draws a point at @x0,@y0.
+	about: If @material is null, the current default material is used.
+	end rem
+	Method DrawPoint( x0:Float,y0:Float,material:TMaterial=Null,s0:Float=0,t0:Float=0 )
+		BeginPrim material,1
+		PrimVert x0+.5,y0+.5,s0,t0
+	End Method
+	
+	rem
+	bbdoc: Draws a line from @x0,@y0 to @x1,@y1.
+	about: If @material is null, the current default material is used.
+	end rem
+	Method DrawLine( x0:Float,y0:Float,x1:Float,y1:Float,material:TMaterial=Null,s0:Float=0,t0:Float=0,s1:Float=1,t1:Float=0 )
+		BeginPrim material,2
+		PrimVert x0+.5,y0+.5,s0,t0
+		PrimVert x1+.5,y1+.5,s1,t1
+	End Method
+	
+	Rem
+	bbdoc: Draw a triangle.
+	about: If @material is null, the current default material is used.
+	End Rem
+	Method DrawTriangle( x0:Float,y0:Float,x1:Float,y1:Float,x2:Float,y2:Float,material:TMaterial=Null,s0:Float=.5,t0:Float=0,s1:Float=1,t1:Float=1,s2:Float=0,t2:Float=1 )
+		BeginPrim material,3
+		PrimVert x0,y0,s0,t0
+		PrimVert x1,y1,s1,t1
+		PrimVert x2,y2,s2,t2
+	End Method
+	
+	Rem
+	bbdoc: Draw a quad.
+	about: If @material is null, the current default material is used.
+	end rem
+	Method DrawQuad( x0:Float,y0:Float,x1:Float,y1:Float,x2:Float,y2:Float,x3:Float,y3:Float,material:TMaterial=Null,s0:Float=.5,t0:Float=0,s1:Float=1,t1:Float=1,s2:Float=0,t2:Float=1 )
+		BeginPrim material,4
+		PrimVert x0,y0,s0,t0
+		PrimVert x1,y1,s1,t1
+		PrimVert x2,y2,s2,t2
+		PrimVert x3,y3,s2,t2
+	End Method
+	
+	Rem
+	bbdoc: Draw an oval in the given rectangle.
+	about: If @material is null, the current default material is used.
+	end rem
+	Method DrawOval( x:Float,y:Float,width:Float,height:Float,material:TMaterial=Null )
+		Local xr:Float=width/2.0
+		Local yr:Float=height/2.0
+		
+		Local dx_x:Float=xr*_ix
+		Local dx_y:Float=xr*_iy
+		Local dy_x:Float=yr*_jx
+		Local dy_y:Float=yr*_jy
+		Local dx:Float=Sqr( dx_x*dx_x+dx_y*dx_y )
+		Local dy:Float=Sqr( dy_x*dy_x+dy_y*dy_y )
+
+		Local n:Int=Int( dx+dy )
+		If n<12 
+			n=12 
+		Else If n>MAX_VERTICES
+			n=MAX_VERTICES
+		Else
+			n:&~3
+		EndIf
+		
+		Local x0:Float=x+xr
+		Local y0:Float=y+yr
+		
+		BeginPrim material,n
+		
+		For Local i:Int=0 Until n
+			Local th:Float=i*360.0/n
+			Local px:Float=x0+Cos( th ) * xr
+			Local py:Float=y0+Sin( th ) * yr
+			PrimVert px,py,0,0
+		Next
+	End Method
+	
+	Rem
+	bbdoc: Draw an ellipse at @x, @y with radii @xRadius, @yRadius.
+	about: If @material is null, the current default material is used.
+	end rem
+	Method DrawEllipse( x:Float,y:Float,xr:Float,yr:Float,material:TMaterial=Null )
+		DrawOval x-xr,y-yr,xr*2,yr*2,material
+	End Method
+	
+	Rem
+	bbdoc: Draw a circle at @x, @y with radius @radius.
+	about: If @material is null, the current default material is used.
+	end rem
+	Method DrawCircle( x:Float,y:Float,r:Float,material:TMaterial=Null )
+		DrawOval x-r,y-r,r*2,r*2,material
+	End Method
+	
+	Method DrawPoly( vertices:Float[],material:TMaterial=Null )
+	
+		Local n:Int=vertices.Length/2
+		If n<3 Or n>MAX_VERTICES Return
+	
+		BeginPrim material,n
+
+		For Local i:Int=0 Until n
+			PrimVert vertices[i*2],vertices[i*2+1],0,0
+		Next
+	End Method
+	
+	Rem
+	bbdoc: Draw a batch of primtives.
+	about:
+	@order is the number of vertices for each primitive, eg: 1 for points, 2 for lines, 3 for triangles etc.
+	@count is the number of primitives to draw.
+	The @vertices array contains x,y vertex data, and must be at least @count \* @order \* 2 long.
+	If @material is null, the current default material is used.
+	end rem
+	Method DrawPrimitives( order:Int,count:Int,vertices:Float[],material:TMaterial=Null )
+	
+		BeginPrims material,order,count
+		Local p:Int=0
+		For Local i:Int=0 Until count
+			For Local j:Int=0 Until order
+				PrimVert vertices[p],vertices[p+1],0,0
+				p:+2
+			Next
+		Next
+	End Method
+	
+	Rem
+	bbdoc: Draw a batch of primtives.
+	about: 
+	@order is the number of vertices for each primitive, eg: 1 for points, 2 for lines, 3 for triangles etc.
+	@count is the number of primitives to draw.
+	The @vertices array contains x,y vertex data, and must be at least @count \* @order \* 2 long.
+	The @texcoords array contains s,t texture coordinate data, and must be at least @count \* @order \* 2 long.
+	If @material is null, the current default material is used.
+	end rem
+	Method DrawPrimitivesCoords( order:Int,count:Int,vertices:Float[],texcoords:Float[],material:TMaterial=Null )
+	
+		BeginPrims material,order,count
+		Local p:Int=0
+		For Local i:Int=0 Until count
+			For Local j:Int=0 Until order
+				PrimVert vertices[p],vertices[p+1],texcoords[p],texcoords[p+1]
+				p:+2
+			Next
+		Next
+	End Method
+	
+	Rem
+	bbdoc: Draw a batch of indexed primtives.
+	about:
+	@order is the number of vertices for each primitive, eg: 1 for points, 2 for lines, 3 for triangles etc.
+	@count is the number of primitives to draw.
+	The @vertices array contains x,y vertex data.
+	The @indices array contains vertex indices, and must be at least @count \* @order long.
+	If @material is null, the current default material is used.
+	end rem
+	Method DrawIndexedPrimitives( order:Int,count:Int,vertices:Float[],indices:Int[],material:TMaterial=Null )
+	
+		BeginPrims material,order,count
+		Local p:Int=0
+		For Local i:Int=0 Until count
+			For Local j:Int=0 Until order
+				Local k:Int=indices[p+j]*2
+				PrimVert vertices[k],vertices[k+1],0,0
+			Next
+			p:+order
+		Next
+	
+	End Method
+	
+	Method DrawIndexedPrimitivesCoords( order:Int,count:Int,vertices:Float[],texcoords:Float[],indices:Int[],material:TMaterial=Null )
+	
+		BeginPrims material,order,count
+		Local p:Int=0
+		For Local i:Int=0 Until count
+			For Local j:Int=0 Until order
+				Local k:Int=indices[p+j]*2
+				PrimVert vertices[k],vertices[k+1],texcoords[k],texcoords[k+1]
+			Next
+			p:+order
+		Next
+	
+	End Method
+	
+	rem
+	bbdoc: Draws a rect from @x,@y to @x+@width,@y+@height.
+	about: If @material is null, the current default material is used.
+	end rem
+	Method DrawRect( x0:Float,y0:Float,width:Float,height:Float,material:TMaterial=Null,s0:Float=0,t0:Float=0,s1:Float=1,t1:Float=1 )
+		Local x1:Float=x0+width
+		Local y1:Float=y0+height
+		BeginPrim material,4
+		PrimVert x0,y0,s0,t0
+		PrimVert x1,y0,s1,t0
+		PrimVert x1,y1,s1,t1
+		PrimVert x0,y1,s0,t1
+	End Method
+	
+	rem
+	bbdoc: Draws a rect from @x,@y to @x+@width,@y+@height filled with @image.
+	about: The image's handle is ignored.
+	end rem
+	Method DrawRectImage( x0:Float,y0:Float,width:Float,height:Float,image:TImage )
+		DrawRect x0,y0,width,height,image._material,image._s0,image._t0,image._s1,image._t1
+	End Method
+	
+	rem
+	bbdoc: Draws a rect at @x,@y filled with the given subrect of @image.
+	about: The image's handle is ignored.
+	end rem
+	Method DrawRectImageSource( x:Float,y:Float,image:TImage,sourceX:Int,sourceY:Int,sourceWidth:Int,sourceHeight:Int )
+		DrawRectImageSourceSize( x,y,sourceWidth,sourceHeight,image,sourceX,sourceY,sourceWidth,sourceHeight )
+	End Method
+
+	rem
+	bbdoc: Draws a rect from @x,@y to @x+@width,@y+@height filled with the given subrect of @image.
+	about: The image's handle is ignored.
+	end rem
+	Method DrawRectImageSourceSize( x0:Float,y0:Float,width:Float,height:Float,image:TImage,sourceX:Int,sourceY:Int,sourceWidth:Int,sourceHeight:Int )
+		Local material:TMaterial=image._material
+		Local s0:Float=Float(image._x+sourceX)/Float(material.Width)
+		Local t0:Float=Float(image._y+sourceY)/Float(material.Height)
+		Local s1:Float=Float(image._x+sourceX+sourceWidth)/Float(material.Width)
+		Local t1:Float=Float(image._y+sourceY+sourceHeight)/Float(material.Height)
+		DrawRect x0,y0,width,height,material,s0,t0,s1,t1
+	End Method
+	
+	'gradient rect - kinda hacky, but doesn't slow anything else down
+	Method DrawGradientRect( x0:Float,y0:Float,width:Float,height:Float,r0:Float,g0:Float,b0:Float,a0:Float,r1:Float,g1:Float,b1:Float,a1:Float,axis:Int )
+	
+		r0:*_color[0];g0:*_color[1];b0:*_color[2];a0:*_alpha
+		r1:*_color[0];g1:*_color[1];b1:*_color[2];a1:*_alpha
+		
+		Local pm0:Int=Int( a0 ) Shl 24 | Int( b0*a0 ) Shl 16 | Int( g0*a0 ) Shl 8 | Int( r0*a0 )
+		Local pm1:Int=Int( a1 ) Shl 24 | Int( b1*a0 ) Shl 16 | Int( g1*a0 ) Shl 8 | Int( r1*a0 )
+		
+		Local x1:Float=x0+width
+		Local y1:Float=y0+height
+		Local s0:Float=0.0
+		Local t0:Float=0.0
+		Local s1:Float=1.0
+		Local t1:Float=1.0
+		
+		BeginPrim Null,4
+
+		Local pmcolor:Int=_pmcolor
+		
+		BeginPrim Null,4
+		
+		Select axis
+		Case 0	'left->right
+			_pmcolor=pm0
+			PrimVert x0,y0,s0,t0
+			_pmcolor=pm1
+			PrimVert x1,y0,s1,t0
+			PrimVert x1,y1,s1,t1
+			_pmcolor=pm0
+			PrimVert x0,y1,s0,t1
+		Default	'top->bottom
+			_pmcolor=pm0
+			PrimVert x0,y0,s0,t0
+			PrimVert x1,y0,s1,t0
+			_pmcolor=pm1
+			PrimVert x1,y1,s1,t1
+			PrimVert x0,y1,s0,t1
+		End Select
+		
+		_pmcolor=pmcolor
+	End Method
+	
+	Method DrawImageImage( image:TImage )
+		BeginPrim image._material,4
+		PrimVert image._x0,image._y0,image._s0,image._t0
+		PrimVert image._x1,image._y0,image._s1,image._t0
+		PrimVert image._x1,image._y1,image._s1,image._t1
+		PrimVert image._x0,image._y1,image._s0,image._t1
+		If image._caster AddShadowCaster image._caster
+	End Method
+	
+	Method DrawImage( image:TImage,tx:Float,ty:Float )
+		PushMatrix
+		Translate tx,ty
+		DrawImageImage image
+		PopMatrix
+	End Method
+
+	Method DrawImageXYZ( image:TImage,tx:Float,ty:Float,rz:Float )
+		PushMatrix
+		TranslateRotate tx,ty,rz
+		DrawImageImage image
+		PopMatrix
+	End Method
+	
+	Method DrawImageXYZS( image:TImage,tx:Float,ty:Float,rz:Float,sx:Float,sy:Float )
+		PushMatrix
+		TranslateRotateScale tx,ty,rz,sx,sy
+		DrawImageImage image
+		PopMatrix
+	End Method
+	
+	rem
+	bbdoc: Draws @text at @x,@y in the current font.
+	end rem
+	Method DrawText( Text:String,x:Float,y:Float,xhandle:Float=0,yhandle:Float=0 )
+		x:-_font.TextWidth( Text )*xhandle
+		y:-_font.TextHeight( Text )*yhandle
+		For Local char:Int=EachIn Text
+			Local glyph:TGlyph=_font.GetGlyph( char )
+			If Not glyph Continue
+			DrawRectImageSource x,y,glyph.image,glyph.x,glyph.y,glyph.width,glyph.height
+			x:+glyph.advance
+		Next
+	End Method
+	
+	rem
+	bbdoc: Draws a shadow volume.
+	end rem
+	Method DrawShadow:Int( lx:Float,ly:Float,x0:Float,y0:Float,x1:Float,y1:Float )
+	
+		Local ext:Int=1024
+	
+		Local dx:Float=x1-x0
+		Local dy:Float=y1-y0
+		Local d0:Float=Sqr( dx*dx+dy*dy )
+		Local nx:Float=-dy/d0
+		Local ny:Float=dx/d0
+		Local pd:Float=-(x0*nx+y0*ny)
+		
+		Local d:Float=lx*nx+ly*ny+pd
+		If d<0 Return False
+
+		Local x2:Float=x1-lx
+		Local y2:Float=y1-ly
+		'Local d2:Float=ext/Sqr( x2*x2+y2*y2 )
+		x2=lx+x2*ext;y2=ly+y2*ext
+		
+		Local x3:Float=x0-lx
+		Local y3:Float=y0-ly
+		'Local d3:Float=ext/Sqr( x3*x3+y3*y3 )
+		x3=lx+x3*ext;y3=ly+y3*ext
+		
+		Local x4:Float=(x2+x3)/2-lx
+		Local y4:Float=(y2+y3)/2-ly
+		'Local d4:Float=ext/Sqr( x4*x4+y4*y4 )
+		x4=lx+x4*ext;y4=ly+y4*ext
+		
+		DrawTriangle x0,y0,x4,y4,x3,y3
+		DrawTriangle x0,y0,x1,y1,x4,y4
+		DrawTriangle x1,y1,x2,y2,x4,y4
+		
+		Return True
+	End Method
+	
+	rem
+	bbdoc: Draws multiple shadow volumes.
+	end rem
+	Method DrawShadows( x0:Float,y0:Float,drawList:TDrawList )
+	
+		Local lx:Float= x0 * _ix + y0 * _jx + _tx
+		Local ly:Float= x0 * _iy + y0 * _jy + _ty
+
+		Local verts:Float[]=drawList._casterVerts.Data
+		Local v0:Int=0
+		
+		For Local i:Int=0 Until drawList._casters.Length
+		
+			Local caster:TShadowCaster=drawList._casters.Get( i )
+			Local n:Int=caster._verts.Length
+			
+			Select caster._kind
+			Case 0	'closed loop
+				Local x0:Float=verts[v0+n-2]
+				Local y0:Float=verts[v0+n-1]
+				For Local i:Int=0 Until n-1 Step 2
+					Local x1:Float=verts[v0+i]
+					Local y1:Float=verts[v0+i+1]
+					DrawShadow( lx,ly,x0,y0,x1,y1 )
+					x0=x1
+					y0=y1
+				Next
+			Case 1	'open loop
+			Case 2	'edge soup
+			End Select
+			
+			v0:+n
+		Next
+		
+	End Method
+	
+	rem
+	bbdoc: Adds a shadow caster to the drawlist.
+	end rem
+	Method AddShadowCaster( caster:TShadowCaster )
+		_casters.Push caster
+		Local verts:Float[]=caster._verts
+		For Local i:Int=0 Until verts.Length-1 Step 2
+			Local x0:Float=verts[i]
+			Local y0:Float=verts[i+1]
+			_casterVerts.Push x0*_ix+y0*_jx+_tx
+			_casterVerts.Push x0*_iy+y0*_jy+_ty
+		Next
+	End Method
+	
+	rem
+	bbdoc: Adds a shadow caster to the drawlist at @tx,@ty.
+	end rem
+	Method AddShadowCasterXY( caster:TShadowCaster,tx:Float,ty:Float )
+		PushMatrix
+		Translate tx,ty
+		AddShadowCaster caster
+		PopMatrix
+	End Method
+	
+	Method AddShadowCasterXYZ( caster:TShadowCaster,tx:Float,ty:Float,rz:Float )
+		PushMatrix
+		TranslateRotate tx,ty,rz
+		AddShadowCaster caster
+		PopMatrix
+	End Method
+	
+	Method AddShadowCasterXYZS( caster:TShadowCaster,tx:Float,ty:Float,rz:Float,sx:Float,sy:Float )
+		PushMatrix
+		TranslateRotateScale tx,ty,rz,sx,sy
+		AddShadowCaster caster
+		PopMatrix
+	End Method
+	
+	Method IsEmpty:Int()
+		Return _next=0
+	End Method
+	
+	Method Compact()
+		If _data.Size()=_next Return
+		Local data:TBank=New TBank.Create( _next )
+		'_data.CopyBytes 0,data,0,_next
+		'_data.Discard
+		MemCopy(data._buf,_data._buf,_next)
+		_data=data
+	End Method
+	
+	Method RenderOp( op:TDrawOp,index:Int,count:Int )
+	
+		If Not op.material.Bind() Return
+		
+		If op.blend<>rs_blend
+			rs_blend=op.blend
+			Select rs_blend
+			Case TBlendMode.Opaque
+				glDisable GL_BLEND
+			Case TBlendMode.Alpha
+				glEnable GL_BLEND
+				glBlendFunc GL_ONE,GL_ONE_MINUS_SRC_ALPHA
+			Case TBlendMode.Additive
+				glEnable GL_BLEND
+				glBlendFunc GL_ONE,GL_ONE
+			Case TBlendMode.Multiply
+				glEnable GL_BLEND
+				glBlendFunc GL_DST_COLOR,GL_ONE_MINUS_SRC_ALPHA
+			Case TBlendMode.Multiply2
+				glEnable GL_BLEND
+				glBlendFunc GL_DST_COLOR,GL_ZERO
+			End Select
+		End If
+		
+		Select op.order
+		Case 1
+			glDrawArrays GL_POINTS,index,count
+		Case 2
+			glDrawArrays GL_LINES,index,count
+		Case 3
+			glDrawArrays GL_TRIANGLES,index,count
+		Case 4
+			glDrawElements GL_TRIANGLES,count/4*6,GL_UNSIGNED_SHORT,(index/4*6 + (index&3)*MAX_QUAD_INDICES)*2
+		Default
+			Local j:Int=0
+			While j<count
+				glDrawArrays GL_TRIANGLE_FAN,index+j,op.order
+				j:+op.order
+			Wend
+		End Select
+
+	End Method
+	
+	Method Render()
+		If Not _next Return
+		
+		Local offset:Int=0
+		Local opid:Int=0
+		Local ops:Object[]=_ops.Data
+		Local length:Int=_ops.length
+				
+		While offset<_next
+		
+			Local size:Int=_next-offset
+			Local lastop:Int=length
+			
+			If size>PRIM_VBO_SIZE
+			
+				size=0
+				lastop=opid
+				While lastop<length
+					Local op:TDrawOp=TDrawOp(ops[lastop])
+					Local n:Int=op.count*BYTES_PER_VERTEX
+					If size+n>PRIM_VBO_SIZE Exit
+					size:+n
+					lastop:+1
+				Wend
+				
+				If Not size
+					Local op:TDrawOp=TDrawOp(ops[opid])
+					Local count:Int=op.count
+					While count
+						Local n:Int=count
+						If n>MAX_VERTICES n=MAX_VERTICES/op.order*op.order
+						Local size:Int=n*BYTES_PER_VERTEX
+						
+						If VBO_ORPHANING_ENABLED glBufferData GL_ARRAY_BUFFER,PRIM_VBO_SIZE,Null,VBO_USAGE
+						glBufferSubData GL_ARRAY_BUFFER,0,size,_data._buf + offset
+						
+						RenderOp op,0,n
+						
+						offset:+size
+						count:-n
+					Wend
+					opid:+1
+					Continue
+				EndIf
+				
+			EndIf
+			
+			If VBO_ORPHANING_ENABLED glBufferData GL_ARRAY_BUFFER,PRIM_VBO_SIZE,Null,VBO_USAGE
+			glBufferSubData GL_ARRAY_BUFFER,0,size,_data._buf + offset
+			
+			Local index:Int=0
+			While opid<lastop
+				Local op:TDrawOp=TDrawOp(ops[opid])
+				RenderOp op,index,op.count
+				index:+op.count
+				opid:+1
+			Wend
+			offset:+size
+			
+		Wend
+		
+		glGetError
+		
+	End Method
+	
+	Method Reset()
+		_next=0
+		
+		Local data:TDrawOp[]=_ops.Data
+		For Local i:Int=0 Until _ops.Length
+			data[i].material=Null
+			freeOps.Push data[i]
+		Next
+		_ops.Clear()
+		_op=nullOp
+		
+		_casters.Clear
+		_casterVerts.Clear
+	End Method
+	
+	Method Flush()
+		Render
+		Reset
+	End Method
+	
+'	Protected
+
+	Field _blend:Float=1
+	Field _alpha:Float=255.0
+	Field _color:Float Ptr
+	Field __colorArray:Float[]=[1.0,1.0,1.0,1.0]
+	Field _pmcolor:Int=$ffffffff
+	Field _ix:Float=1,_iy:Float
+	Field _jx:Float,_jy:Float=1
+	Field _tx:Float,_ty:Float
+	Field _matStack:Float[64*6]
+	Field _matSp:Int
+	Field _font:TFont
+	Field _defaultMaterial:TMaterial
+	
+'	Private
+	
+	Field _data:TBank=New TBank.Create( 4096 )
+	Field _next:Int=0
+	
+	Field _op:TDrawOp=nullOp
+	Field _ops:TDrawOpStack=New TDrawOpStack'<DrawOp>
+	Field _casters:TShadowCasterStack=New TShadowCasterStack'<ShadowCaster>
+	Field _casterVerts:TFloatStack=New TFloatStack
+
+	Method BeginPrim( material:TMaterial,order:Int ) Final
+	
+		If Not material material=_defaultMaterial
+		
+		If _next+order*BYTES_PER_VERTEX>_data.Size()
+			Local newsize:Int=Max( _data.Size()+_data.Size()/2,_next+order*BYTES_PER_VERTEX )
+			Local data:TBank=New TBank.Create( newsize )
+			MemCopy(data._buf, _data._buf, _next)
+			'_data.CopyBytes 0,data,0,_next
+			'_data.Discard
+			_data=data
+		EndIf
+	
+		If material=_op.material And _blend=_op.blend And order=_op.order
+			_op.count:+order
+			Return
+		EndIf
+		
+		If freeOps.Length _op=freeOps.Pop() Else _op=New TDrawOp
+		
+		_ops.Push _op
+		_op.material=material
+		_op.blend=_blend
+		_op.order=order
+		_op.count=order
+	End Method
+	
+	Method BeginPrims( material:TMaterial,order:Int,count:Int ) Final
+	
+		If Not material material=_defaultMaterial
+		
+		count:*order
+		
+		If _next+count*BYTES_PER_VERTEX>_data.Size()
+			Local newsize:Int=Max( _data.Size()+_data.Size()/2,_next+count*BYTES_PER_VERTEX )
+			Local data:TBank=New TBank.Create( newsize )
+			MemCopy data._buf, _data._buf, _next
+			_data=data
+		EndIf
+	
+		If material=_op.material And _blend=_op.blend And order=_op.order
+			_op.count:+count
+			Return
+		EndIf
+		
+		If freeOps.Length _op=freeOps.Pop() Else _op=New TDrawOp
+		
+		_ops.Push _op
+		_op.material=material
+		_op.blend=_blend
+		_op.order=order
+		_op.count=count
+	End Method
+	
+	Method PrimVert( x0:Float,y0:Float,s0:Float,t0:Float ) Final
+		Local df:Float Ptr = (_data._buf + _next)
+		Local di:Int Ptr = (_data._buf + _next)
+		df[0] = x0 * _ix + y0 * _jx + _tx
+		df[1] = x0 * _iy + y0 * _jy + _ty
+		df[2] = s0
+		df[3] = t0
+		df[4] = _ix
+		df[5] = _iy
+		di[6] = _pmcolor
+		_next:+BYTES_PER_VERTEX
+	End Method
+	
+End Type
+
+
+'***** Canvas *****
+
+Type TCanvas Extends TDrawList
+
+	Const MaxLights:Int=MAX_LIGHTS
+
+	Method CreateCanvas:TCanvas( target:Object=Null )
+'		Super.Create()
+		Init
+		SetRenderTarget target
+		SetViewport 0,0,_width,_height
+		SetProjection2d 0,_width,0,_height
+		Return Self
+	End Method
+	
+	Method Discard()
+	End Method
+
+	Method SetRenderTarget( target:Object )
+
+		FlushPrims
+		
+		If Not target
+		
+			_image=Null
+			_texture=Null
+			_width=GraphicsWidth()
+			_height=GraphicsHeight()
+			_twidth=_width
+			_theight=_height
+		
+		Else If TImage( target )
+		
+			_image=TImage( target )
+			_texture=_image.Material.ColorTexture
+			If Not (_texture.Flags & TTexture.RenderTarget) Throw "Texture is not a render target texture"
+			_width=_image.Width
+			_height=_image.Height
+			_twidth=_texture.Width
+			_theight=_texture.Height
+			
+		Else If TTexture( target )
+		
+			_image=Null
+			_texture=TTexture( target )
+
+			If Not (_texture.Flags & TTexture.RenderTarget) Throw "Texture is not a render target texture"
+			_width=_texture.Width
+			_height=_texture.Height
+			_twidth=_texture.Width
+			_theight=_texture.Height
+			
+		Else
+		
+			Throw "RenderTarget object must a TImage, a TTexture or Null"
+			
+		EndIf
+		
+		_dirty=-1
+		
+	End Method
+
+	Method RenderTarget:Object()
+		If _image Return _image Else Return _texture
+	End Method
+	
+	Method Width:Int()
+		Return _width
+	End Method
+	
+	Method Height:Int()
+		Return _height
+	End Method
+	
+	Method SetColorMask( r:Int,g:Int,b:Int,a:Int )
+		FlushPrims
+		_colorMask[0]=r
+		_colorMask[1]=g
+		_colorMask[2]=b
+		_colorMask[3]=a
+		_dirty:|DIRTY_COLORMASK
+	End Method
+	
+	Method ColorMask:Int[]()
+		Return _colorMask
+	End Method
+	
+	Method SetViewport( x:Int,y:Int,w:Int,h:Int )
+		FlushPrims
+		_viewport[0]=x
+		_viewport[1]=y
+		_viewport[2]=w
+		_viewport[3]=h
+		_dirty:|DIRTY_VIEWPORT
+	End Method
+	
+	Method Viewport:Int[]()
+		Return _viewport
+	End Method
+	
+	Method SetScissor( x:Int,y:Int,w:Int,h:Int )
+		FlushPrims
+		_scissor[0]=x
+		_scissor[1]=y
+		_scissor[2]=w
+		_scissor[3]=h
+		_dirty:|DIRTY_VIEWPORT
+	End Method
+	
+	Method Scissor:Int[]()
+		Return _scissor
+	End Method
+	
+	Method SetProjectionMatrix( projMatrix:Float[] )
+		FlushPrims
+		If projMatrix
+			Mat4Copy projMatrix,_projMatrix
+		Else
+			Mat4InitArray _projMatrix
+		EndIf
+		_dirty:|DIRTY_SHADER
+	End Method
+	
+	Method SetProjection2d( Left:Float,Right:Float,top:Float,bottom:Float,znear:Float=-1,zfar:Float=1 )
+		FlushPrims
+		Mat4Ortho Left,Right,top,bottom,znear,zfar,_projMatrix
+		_dirty:|DIRTY_SHADER
+	End Method
+	
+	Method ProjectionMatrix:Float[]()
+		Return _projMatrix
+	End Method
+	
+	Method SetViewMatrix( viewMatrix:Float[] )
+		FlushPrims
+		If viewMatrix
+			Mat4Copy viewMatrix,_viewMatrix
+		Else
+			Mat4InitArray _viewMatrix
+		End If
+		_dirty:|DIRTY_SHADER
+	End Method
+	
+	Method ViewMatrix:Float[]()
+		Return _viewMatrix
+	End Method
+	
+	Method SetModelMatrix( modelMatrix:Float[] )
+		FlushPrims
+		If modelMatrix
+			Mat4Copy modelMatrix,_modelMatrix
+		Else
+			Mat4InitArray _modelMatrix
+		EndIf
+		_dirty:|DIRTY_SHADER
+	End Method
+	
+	Method ModelMatrix:Float[]()
+		Return _modelMatrix
+	End Method
+
+	Method SetAmbientLight( r:Float,g:Float,b:Float,a:Float=1 )
+		FlushPrims
+		_ambientLight[0]=r
+		_ambientLight[1]=g
+		_ambientLight[2]=b
+		_ambientLight[3]=a
+		_dirty:|DIRTY_SHADER
+	End Method
+	
+	Method AmbientLight:Float[]()
+		Return _ambientLight
+	End Method
+	
+	Method SetFogColor( r:Float,g:Float,b:Float,a:Float )
+		FlushPrims
+		_fogColor[0]=r
+		_fogColor[1]=g
+		_fogColor[2]=b
+		_fogColor[3]=a
+		_dirty:|DIRTY_SHADER
+	End Method
+	
+	Method FogColor:Float[]()
+		Return _fogColor
+	End Method
+	
+	Method SetLightType( index:Int,kind:Int )
+		FlushPrims
+		Local light:TLightData=_lights[index]
+		light.kind=kind
+		_dirty:|DIRTY_SHADER
+	End Method
+	
+	Method GetLightType:Int( index:Int )
+		Return _lights[index].kind
+	End Method
+	
+	Method SetLightColor( index:Int,r:Float,g:Float,b:Float,a:Float=1 )
+		FlushPrims
+		Local light:TLightData=_lights[index]
+		light.color[0]=r
+		light.color[1]=g
+		light.color[2]=b
+		light.color[3]=a
+		_dirty:|DIRTY_SHADER
+	End Method
+	
+	Method GetLightColor:Float[]( index:Int )
+		Return _lights[index].color
+	End Method
+	
+	Method SetLightPosition( index:Int,x:Float,y:Float,z:Float )
+		FlushPrims
+		Local light:TLightData=_lights[index]
+		light.position[0]=x
+		light.position[1]=y
+		light.position[2]=z
+		light.vector[0]=x
+		light.vector[1]=y
+		light.vector[2]=z
+		_dirty:|DIRTY_SHADER
+	End Method
+	
+	Method GetLightPosition:Float[]( index:Int )
+		Return _lights[index].position
+	End Method
+	
+	Method SetLightRange( index:Int,Range:Float )
+		FlushPrims
+		Local light:TLightData=_lights[index]
+		light.Range=Range
+		_dirty:|DIRTY_SHADER
+	End Method
+	
+	Method GetLightRange:Float( index:Int )
+		Return _lights[index].Range
+	End Method
+	
+	Method SetShadowMap( image:TImage )
+		FlushPrims
+		_shadowMap=image
+		_dirty:|DIRTY_SHADER
+	End Method
+	
+	Method ShadowMap:TImage()
+		Return _shadowMap
+	End Method
+	
+	Method SetLineWidth( lineWidth:Float )
+		FlushPrims
+		_lineWidth=lineWidth
+		_dirty:|DIRTY_LINEWIDTH
+	End Method
+	
+	Method LineWidth:Float()
+		Return _lineWidth
+	End Method
+	
+	Method Clear( r:Float=0,g:Float=0,b:Float=0,a:Float=1 )
+		FlushPrims
+		Validate
+		If _clsScissor
+			glEnable GL_SCISSOR_TEST
+			glScissor _vpx,_vpy,_vpw,_vph
+		EndIf
+		glClearColor r,g,b,a
+		glClear GL_COLOR_BUFFER_BIT
+		If _clsScissor glDisable GL_SCISSOR_TEST
+	End Method
+	
+	Method ReadPixels( x:Int,y:Int,width:Int,height:Int,data:TBank,dataOffset:Int=0,dataPitch:Int=0 )
+	
+		FlushPrims
+		
+		If Not dataPitch Or dataPitch=width*4
+			glReadPixels x,y,width,height,GL_RGBA,GL_UNSIGNED_BYTE,data._buf + dataOffset
+		Else
+			For Local iy:Int=0 Until height
+				glReadPixels x,y+iy,width,1,GL_RGBA,GL_UNSIGNED_BYTE,data._buf + dataOffset+dataPitch*iy
+			Next
+		EndIf
+
+	End Method
+
+	Method RenderDrawList( drawbuf:TDrawList )
+
+		Local fast:Int=_ix=1 And _iy=0 And _jx=0 And _jy=1 And _tx=0 And _ty=0 And _color[0]=1 And _color[1]=1 And _color[2]=1 And _color[3]=1
+		
+		If fast
+			FlushPrims
+			Validate
+			drawbuf.Render
+			Return
+		EndIf
+		
+		tmpMat3d[0]=_ix
+		tmpMat3d[1]=_iy
+		tmpMat3d[4]=_jx
+		tmpMat3d[5]=_jy
+		tmpMat3d[12]=_tx
+		tmpMat3d[13]=_ty
+		tmpMat3d[10]=1
+		tmpMat3d[15]=1
+		
+		Mat4Multiply _modelMatrix,tmpMat3d,tmpMat3d2
+		
+		FlushPrims
+		
+		Local tmp:Float[]=_modelMatrix
+		_modelMatrix=tmpMat3d2
+		rs_globalColor[0]=_color[0]*_color[3]
+		rs_globalColor[1]=_color[1]*_color[3]
+		rs_globalColor[2]=_color[2]*_color[3]
+		rs_globalColor[3]=_color[3]
+		_dirty:|DIRTY_SHADER
+		
+		Validate
+		drawbuf.Render
+		
+		_modelMatrix=tmp
+		rs_globalColor[0]=1
+		rs_globalColor[1]=1
+		rs_globalColor[2]=1
+		rs_globalColor[3]=1
+		_dirty:|DIRTY_SHADER
+	End Method
+	
+	Method RenderDrawListXYZ( drawList:TDrawList,tx:Float,ty:Float,rz:Float=0,sx:Float=1,sy:Float=1 )
+		Super.PushMatrix
+		Super.TranslateRotateScale tx,ty,rz,sx,sy
+		RenderDrawList( drawList )
+		Super.PopMatrix
+	End Method
+
+	Method Flush()
+		FlushPrims
+		
+		If Not _texture Return
+		
+		If _texture._flags & TTexture.Managed
+			Validate
+
+			glDisable GL_SCISSOR_TEST
+			glViewport 0,0,_twidth,_theight
+			
+			If _width=_twidth And _height=_theight
+				glReadPixels 0,0,_twidth,_theight,GL_RGBA,GL_UNSIGNED_BYTE, _texture._data.pixels
+			Else
+				For Local y:Int=0 Until _height
+					glReadPixels _image._x,_image._y+y,_width,1,GL_RGBA,GL_UNSIGNED_BYTE,_texture._data.pixels + (_image._y+y) * (_twidth*4) + (_image._x*4)
+				Next
+			EndIf
+
+			_dirty:|DIRTY_VIEWPORT
+		EndIf
+
+		_texture.UpdateMipmaps
+	End Method
+	
+	Global _tformInvProj:Float[16]
+	Global _tformT:Float[]=[0.0,0.0,-1.0,1.0]
+	Global _tformP:Float[4]
+	
+	Method TransformCoords( coords_in:Float[],coords_out:Float[],Mode:Int=0 )
+	
+		Mat4Inverse _projMatrix,_tformInvProj
+
+		Select Mode
+		Case 0
+			_tformT[0]=(coords_in[0]-_viewport[0])/_viewport[2]*2-1
+			_tformT[1]=(coords_in[1]-_viewport[1])/_viewport[3]*2-1
+			Mat4Transform _tformInvProj,_tformT,_tformP
+			_tformP[0]:/_tformP[3]
+			_tformP[1]:/_tformP[3]
+			_tformP[2]:/_tformP[3]
+			_tformP[3]=1
+			coords_out[0]=_tformP[0]
+			coords_out[1]=_tformP[1]
+			If coords_out.Length>2 coords_out[2]=_tformP[2]
+		Default
+			Throw "Invalid TransformCoords mode"
+		End Select
+	End Method
+	
+	'Private
+
+	Const DIRTY_RENDERTARGET:Int=1
+	Const DIRTY_VIEWPORT:Int=2
+	Const DIRTY_SHADER:Int=4
+	Const DIRTY_LINEWIDTH:Int=8
+	Const DIRTY_COLORMASK:Int=16
+		
+	Field _seq:Int
+	Field _dirty:Int=-1
+	Field _image:TImage
+	Field _texture:TTexture	
+	Field _width:Int
+	Field _height:Int
+	Field _twidth:Int
+	Field _theight:Int
+	Field _shadowMap:TImage
+	Field _colorMask:Int[]=[True,True,True,True]
+	Field _viewport:Int[]=[0,0,640,480]
+	Field _scissor:Int[]=[0,0,100000,100000]
+	Field _vpx:Int,_vpy:Int,_vpw:Int,_vph:Int
+	Field _scx:Int,_scy:Int,_scw:Int,_sch:Int
+	Field _clsScissor:Int
+	Field _projMatrix:Float[]=Mat4New()
+	Field _invProjMatrix:Float[]=Mat4New()
+	Field _viewMatrix:Float[]=Mat4New()
+	Field _modelMatrix:Float[]=Mat4New()
+	Field _ambientLight:Float[]=[0.0,0.0,0.0,1.0]
+	Field _fogColor:Float[]=[0.0,0.0,0.0,0.0]
+	Field _lights:TLightData[4]
+	Field _lineWidth:Float=1
+
+	Global _active:TCanvas
+	
+	Method Init()
+		For Local i:Int=0 Until 4
+			_lights[i]=New TLightData
+		Next
+		_dirty=-1
+	End Method
+
+	Method FlushPrims()
+		If Super.IsEmpty Return
+		Validate
+		Super.Flush
+	End Method
+	
+	Method Validate()
+		If _seq<>graphicsSeq	
+			_seq=graphicsSeq
+			InitVbos
+			_dirty=-1
+		EndIf
+	
+		If _active=Self
+			If Not _dirty Return
+		Else
+			If _active _active.Flush
+			_active=Self
+			_dirty=-1
+		EndIf
+
+'		_dirty=-1
+		
+		If _dirty & DIRTY_RENDERTARGET
+
+			If _texture
+				glBindFramebuffer GL_FRAMEBUFFER,_texture.GLFramebuffer()
+			Else
+				glBindFramebuffer GL_FRAMEBUFFER,defaultFbo
+			EndIf
+		End If
+		
+		If _dirty & DIRTY_VIEWPORT
+		
+			_vpx=_viewport[0];_vpy=_viewport[1];_vpw=_viewport[2];_vph=_viewport[3]
+			If _image
+				_vpx:+_image._x
+				_vpy:+_image._y
+			EndIf
+			
+			_scx=_scissor[0];_scy=_scissor[1];_scw=_scissor[2];_sch=_scissor[3]
+			
+			If _scx<0 _scx=0 Else If _scx>_vpw _scx=_vpw
+			If _scw<0 _scw=0 Else If _scx+_scw>_vpw _scw=_vpw-_scx
+			
+			If _scy<0 _scy=0 Else If _scy>_vph _scy=_vph
+			If _sch<0 _sch=0 Else If _scy+_sch>_vph _sch=_vph-_scy
+			
+			_scx:+_vpx;_scy:+_vpy
+		
+			If Not _texture
+				_vpy=_theight-_vpy-_vph
+				_scy=_theight-_scy-_sch
+			EndIf
+			
+			glViewport _vpx,_vpy,_vpw,_vph
+			
+			If _scx<>_vpx Or _scy<>_vpy Or _scw<>_vpw Or _sch<>_vph
+				glEnable GL_SCISSOR_TEST
+				glScissor _scx,_scy,_scw,_sch
+				_clsScissor=False
+			Else
+				glDisable GL_SCISSOR_TEST
+				_clsScissor=(_scx<>0 Or _scy<>0 Or _vpw<>_twidth Or _vph<>_theight)
+			EndIf
+			
+		EndIf
+		
+		If _dirty & DIRTY_SHADER
+		
+			rs_program=Null
+			
+			If _texture
+				rs_clipPosScale[1]=1
+				Mat4Copy _projMatrix,rs_projMatrix
+			Else
+				rs_clipPosScale[1]=-1
+				Mat4Multiply flipYMatrix,_projMatrix,rs_projMatrix
+			EndIf
+			
+			Mat4Multiply _viewMatrix,_modelMatrix,rs_modelViewMatrix
+			Mat4Multiply rs_projMatrix,rs_modelViewMatrix,rs_modelViewProjMatrix
+			Vec4Copy _ambientLight,rs_ambientLight
+			Vec4Copy _fogColor,rs_fogColor
+			
+			rs_numLights=0
+			For Local i:Int=0 Until MAX_LIGHTS
+
+				Local light:TLightData=_lights[i]
+				If Not light.kind Continue
+				
+				Mat4Transform _viewMatrix,light.vector,light.tvector
+				
+				rs_lightColors[rs_numLights*4+0]=light.color[0]
+				rs_lightColors[rs_numLights*4+1]=light.color[1]
+				rs_lightColors[rs_numLights*4+2]=light.color[2]
+				rs_lightColors[rs_numLights*4+3]=light.color[3]
+				
+				rs_lightVectors[rs_numLights*4+0]=light.tvector[0]
+				rs_lightVectors[rs_numLights*4+1]=light.tvector[1]
+				rs_lightVectors[rs_numLights*4+2]=light.tvector[2]
+				rs_lightVectors[rs_numLights*4+3]=light.Range
+
+				rs_numLights:+1
+			Next
+			
+			If _shadowMap
+				rs_shadowTexture=_shadowMap._material._colorTexture
+			Else 
+				rs_shadowTexture=Null
+			EndIf
+			
+			rs_blend=-1
+
+		End If
+		
+		If _dirty & DIRTY_LINEWIDTH
+			glLineWidth _lineWidth
+		EndIf
+		
+		If _dirty & DIRTY_COLORMASK
+			glColorMask _colorMask[0],_colorMask[1],_colorMask[2],_colorMask[3]
+		End If
+		
+		_dirty=0
+	End Method
+	
+End Type
+
+' stacks
+
+Type TFloatStack
+	Field data:Float[]
+	Field length:Int
+
+	Method Push( value:Float )
+		If length=data.Length
+			data=data[..length*2+10]
+		EndIf
+		data[length]=value
+		length:+1
+	End Method
+
+	Method Pop:Float()
+		length:-1
+		Local v:Float=data[length]
+		data[length]=Null
+		Return v
+	End Method
+
+	Method Top:Float()
+		Return data[length-1]
+	End Method
+
+	Method Clear()
+		For Local i:Int=0 Until length
+			data[i]=Null
+		Next
+		length=0
+	End Method
+
+End Type
+
+Type TDrawOpStack
+	Field data:TDrawOp[]
+	Field length:Int
+
+	Method Push( value:TDrawOp )
+		If length=data.Length
+			data=data[..length*2+10]
+		EndIf
+		data[length]=value
+		length:+1
+	End Method
+
+	Method Pop:TDrawOp()
+		length:-1
+		Local v:TDrawOp=data[length]
+		data[length]=Null
+		Return v
+	End Method
+
+	Method Top:TDrawOp()
+		Return data[length-1]
+	End Method
+
+	Method Clear()
+		For Local i:Int=0 Until length
+			data[i]=Null
+		Next
+		length=0
+	End Method
+
+End Type
+
+
+Type TShadowCasterStack
+	Field data:TShadowCaster[]
+	Field length:Int
+
+	Method Push( value:TShadowCaster )
+		If length=data.Length
+			data=data[..length*2+10]
+		EndIf
+		data[length]=value
+		length:+1
+	End Method
+
+	Method Pop:TShadowCaster()
+		length:-1
+		Local v:TShadowCaster=data[length]
+		data[length]=Null
+		Return v
+	End Method
+
+	Method Top:TShadowCaster()
+		Return data[length-1]
+	End Method
+
+	Method Get:TShadowCaster(index:Int)
+		Return data[index]
+	End Method
+	
+	Method Clear()
+		For Local i:Int=0 Until length
+			data[i]=Null
+		Next
+		length=0
+	End Method
+
+End Type
+
+Type TDrawListStack
+	Field data:TDrawList[]
+	Field length:Int
+
+	Method Push( value:TDrawList )
+		If length=data.Length
+			data=data[..length*2+10]
+		EndIf
+		data[length]=value
+		length:+1
+	End Method
+
+	Method Pop:TDrawList()
+		length:-1
+		Local v:TDrawList=data[length]
+		data[length]=Null
+		Return v
+	End Method
+
+	Method Top:TDrawList()
+		Return data[length-1]
+	End Method
+	
+	Method Get:TDrawList(index:Int)
+		Return data[index]
+	End Method
+
+	Method Clear()
+		For Local i:Int=0 Until length
+			data[i]=Null
+		Next
+		length=0
+	End Method
+
+End Type

+ 247 - 0
mojo2.mod/maps.bmx

@@ -0,0 +1,247 @@
+' 
+' Copyright (c) 2015 Bruce Henderson
+' 
+' This software is provided 'as-is', without any express or implied
+' warranty. In no event will the authors be held liable for any damages
+' arising from the use of this software.
+' 
+' Permission is granted to anyone to use this software for any purpose,
+' including commercial applications, and to alter it and redistribute it
+' freely, subject to the following restrictions:
+' 
+' 1. The origin of this software must not be misrepresented; you must not
+'    claim that you wrote the original software. If you use this software
+'    in a product, an acknowledgement in the product documentation would be
+'    appreciated but is not required.
+' 2. Altered source versions must be plainly marked as such, and must not be
+'    misrepresented as being the original software.
+' 3. This notice may not be removed or altered from any source distribution.
+' 
+Strict
+
+Import "maps.c"
+
+Extern
+	Function bmx_map_stringfloatmap_clear(root:Byte Ptr Ptr)
+	Function bmx_map_stringfloatmap_isempty:Int(root:Byte Ptr Ptr)
+	Function bmx_map_stringfloatmap_insert(key:String, value:Float, root:Byte Ptr Ptr)
+	Function bmx_map_stringfloatmap_contains:Int(key:String, root:Byte Ptr Ptr)
+	Function bmx_map_stringfloatmap_valueforkey:Float(key:String, root:Byte Ptr Ptr)
+	Function bmx_map_stringfloatmap_remove:Int(key:String, root:Byte Ptr Ptr)
+	Function bmx_map_stringfloatmap_firstnode:Byte Ptr(root:Byte Ptr)
+	Function bmx_map_stringfloatmap_nextnode:Byte Ptr(node:Byte Ptr)
+	Function bmx_map_stringfloatmap_key:String(node:Byte Ptr)
+	Function bmx_map_stringfloatmap_value:Float(node:Byte Ptr)
+	Function bmx_map_stringfloatmap_hasnext:Int(node:Byte Ptr, root:Byte Ptr)
+	Function bmx_map_stringfloatmap_copy(dst:Byte Ptr Ptr, _root:Byte Ptr)
+End Extern
+
+Type TStringFloatMap
+
+	Method Delete()
+		Clear
+	End Method
+
+	Method Clear()
+?ngcmod
+		If Not IsEmpty() Then
+			_modCount :+ 1
+		End If
+?
+		bmx_map_stringfloatmap_clear(Varptr _root)
+	End Method
+	
+	Method IsEmpty()
+		Return bmx_map_stringfloatmap_isempty(Varptr _root)
+	End Method
+	
+	Method Insert( key:String,value:Float )
+		bmx_map_stringfloatmap_insert(key, value, Varptr _root)
+?ngcmod
+		_modCount :+ 1
+?
+	End Method
+
+	Method Contains:Int( key:String )
+		Return bmx_map_stringfloatmap_contains(key, Varptr _root)
+	End Method
+	
+	Method ValueForKey:Float( key:String )
+		Return bmx_map_stringfloatmap_valueforkey(key, Varptr _root)
+	End Method
+	
+	Method Remove( key:String )
+?ngcmod
+		_modCount :+ 1
+?
+		Return bmx_map_stringfloatmap_remove(key, Varptr _root)
+	End Method
+
+	Method _FirstNode:TStringNode()
+		If Not IsEmpty() Then
+			Local node:TStringNode= New TStringNode
+			node._root = _root
+			Return node
+		Else
+			Return Null
+		End If
+	End Method
+	
+	Method Keys:TStringFloatMapEnumerator()
+		Local nodeenum:TStringNodeEnumerator
+		If Not isEmpty() Then
+			nodeenum=New TStringKeyEnumerator
+			nodeenum._node=_FirstNode()
+		Else
+			nodeenum=New TStringEmptyEnumerator
+		End If
+		Local mapenum:TStringFloatMapEnumerator=New TStringFloatMapEnumerator
+		mapenum._enumerator=nodeenum
+		nodeenum._map = Self
+?ngcmod
+		nodeenum._expectedModCount = _modCount
+?
+		Return mapenum
+	End Method
+	
+	Method Values:TStringFloatMapEnumerator()
+		Local nodeenum:TStringNodeEnumerator
+		If Not isEmpty() Then
+			nodeenum=New TStringValueEnumerator
+			nodeenum._node=_FirstNode()
+		Else
+			nodeenum=New TStringEmptyEnumerator
+		End If
+		Local mapenum:TStringFloatMapEnumerator=New TStringFloatMapEnumerator
+		mapenum._enumerator=nodeenum
+		nodeenum._map = Self
+?ngcmod
+		nodeenum._expectedModCount = _modCount
+?
+		Return mapenum
+	End Method
+	
+	Method Copy:TStringFloatMap()
+		Local map:TStringFloatMap=New TStringFloatMap
+		bmx_map_stringfloatmap_copy(Varptr map._root, _root)
+		Return map
+	End Method
+	
+	Method ObjectEnumerator:TStringNodeEnumerator()
+		Local nodeenum:TStringNodeEnumerator=New TStringNodeEnumerator
+		nodeenum._node=_FirstNode()
+		nodeenum._map = Self
+?ngcmod
+		nodeenum._expectedModCount = _modCount
+?
+		Return nodeenum
+	End Method
+
+	Field _root:Byte Ptr
+
+?ngcmod
+	Field _modCount:Int
+?
+
+End Type
+
+Type TStringNode
+	Field _root:Byte Ptr
+	Field _nodePtr:Byte Ptr
+	
+	Method Key:String()
+		Return bmx_map_stringfloatmap_key(_nodePtr)
+	End Method
+	
+	Method Value:Float()
+		Return bmx_map_stringfloatmap_value(_nodePtr)
+	End Method
+
+	Method HasNext()
+		Return bmx_map_stringfloatmap_hasnext(_nodePtr, _root)
+	End Method
+	
+	Method NextNode:TStringNode()
+		If Not _nodePtr Then
+			_nodePtr = bmx_map_stringfloatmap_firstnode(_root)
+		Else
+			_nodePtr = bmx_map_stringfloatmap_nextnode(_nodePtr)
+		End If
+
+		Return Self
+	End Method
+	
+End Type
+
+Type TStringNodeEnumerator
+	Method HasNext()
+		Local has:Int = _node.HasNext()
+		If Not has Then
+			_map = Null
+		End If
+		Return has
+	End Method
+	
+	Method NextObject:Object()
+?ngcmod
+		Assert _expectedModCount = _map._modCount, "TStringFloatMap Concurrent Modification"
+?
+		Local node:TStringNode=_node
+		_node=_node.NextNode()
+		Return node
+	End Method
+
+	'***** PRIVATE *****
+		
+	Field _node:TStringNode	
+
+	Field _map:TStringFloatMap
+?ngcmod
+	Field _expectedModCount:Int
+?
+End Type
+
+Type TStringKeyEnumerator Extends TStringNodeEnumerator
+	Method NextObject:Object()
+?ngcmod
+		Assert _expectedModCount = _map._modCount, "TStringFloatMap Concurrent Modification"
+?
+		Local node:TStringNode=_node
+		_node=_node.NextNode()
+		Return node.Key()
+	End Method
+End Type
+
+Type TStringValueEnumerator Extends TStringNodeEnumerator
+	Method NextObject:Object()
+?ngcmod
+		Assert _expectedModCount = _map._modCount, "TStringFloatMap Concurrent Modification"
+?
+		Local node:TStringNode=_node
+		_node=_node.NextNode()
+		_floatObj.value = node.Value()
+		Return _floatObj
+	End Method
+	
+	Field _floatObj:TFloat = New TFLoat
+End Type
+
+Type TFloat
+	Field value:Float
+End Type
+
+
+Type TStringFloatMapEnumerator
+	Method ObjectEnumerator:TStringNodeEnumerator()
+		Return _enumerator
+	End Method
+	Field _enumerator:TStringNodeEnumerator
+End Type
+
+Type TStringEmptyEnumerator Extends TStringNodeEnumerator
+	Method HasNext()
+		_map = Null
+		Return False
+	End Method
+End Type
+

+ 150 - 0
mojo2.mod/maps.c

@@ -0,0 +1,150 @@
+/*
+
+Copyright (c) 2015 Bruce Henderson
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+   claim that you wrote the original software. If you use this software
+   in a product, an acknowledgement in the product documentation would be
+   appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+   misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+
+*/
+#include "brl.mod/blitz.mod/blitz.h"
+#include "brl.mod/blitz.mod/tree/tree.h"
+
+#define generic_compare(x, y) (((x) > (y)) - ((x) < (y)))
+
+/* +++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+struct stringfloatmap_node {
+	struct avl_root link;
+	BBString * key;
+	float value;
+};
+
+static int compare_stringfloatmap_nodes(const void *x, const void *y) {
+        struct stringfloatmap_node * node_x = (struct stringfloatmap_node *)x;
+        struct stringfloatmap_node * node_y = (struct stringfloatmap_node *)y;
+
+        return bbStringCompare(node_x->key, node_y->key);
+}
+
+void bmx_map_stringfloatmap_clear(struct avl_root ** root) {
+	struct stringfloatmap_node *node;
+	struct stringfloatmap_node *tmp;
+	avl_for_each_entry_safe(node, tmp, *root, link) {
+		BBRELEASE(node->key);
+		avl_del(&node->link, root);
+		free(node);
+	}
+}
+
+int bmx_map_stringfloatmap_isempty(struct avl_root ** root) {
+	return *root == 0;
+}
+
+void bmx_map_stringfloatmap_insert( BBString * key, float value, struct avl_root ** root) {
+	struct stringfloatmap_node * node = (struct stringfloatmap_node *)malloc(sizeof(struct stringfloatmap_node));
+	node->key = key;
+	BBRETAIN(key);
+	node->value = value;
+	
+	struct stringfloatmap_node * old_node = (struct stringfloatmap_node *)avl_map(&node->link, compare_stringfloatmap_nodes, root);
+
+	if (&node->link != &old_node->link) {
+		BBRELEASE(old_node->key);
+		// key already exists. Store the value in this node.
+		old_node->value = value;
+		// delete the new node, since we don't need it
+		free(node);
+	}
+}
+
+int bmx_map_stringfloatmap_contains(BBString * key, struct avl_root ** root) {
+	struct stringfloatmap_node node;
+	node.key = key;
+	
+	struct stringfloatmap_node * found = (struct stringfloatmap_node *)tree_search(&node, compare_stringfloatmap_nodes, *root);
+	if (found) {
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+float bmx_map_stringfloatmap_valueforkey(BBString * key, struct avl_root ** root) {
+	struct stringfloatmap_node node;
+	node.key = key;
+	
+	struct stringfloatmap_node * found = (struct stringfloatmap_node *)tree_search(&node, compare_stringfloatmap_nodes, *root);
+	
+	if (found) {
+		return found->value;
+	}
+	
+	return 0.0f;
+}
+
+int bmx_map_stringfloatmap_remove(BBString * key, struct avl_root ** root) {
+	struct stringfloatmap_node node;
+	node.key = key;
+	
+	struct stringfloatmap_node * found = (struct stringfloatmap_node *)tree_search(&node, compare_stringfloatmap_nodes, *root);
+	
+	if (found) {
+		BBRELEASE(found->key);
+		avl_del(&found->link, root);
+		free(found);
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+struct stringfloatmap_node * bmx_map_stringfloatmap_nextnode(struct stringfloatmap_node * node) {
+	return tree_successor(node);
+}
+
+struct stringfloatmap_node * bmx_map_stringfloatmap_firstnode(struct avl_root * root) {
+	return tree_min(root);
+}
+
+BBString * bmx_map_stringfloatmap_key(struct stringfloatmap_node * node) {
+	return node->key;
+}
+
+float bmx_map_stringfloatmap_value(struct stringfloatmap_node * node) {
+	return node->value;
+}
+
+int bmx_map_stringfloatmap_hasnext(struct stringfloatmap_node * node, struct avl_root * root) {
+	if (!root) {
+		return 0;
+	}
+	
+	if (!node) {
+		return 1;
+	}
+	
+	return (tree_successor(node) != 0) ? 1 : 0;
+}
+
+void bmx_map_stringfloatmap_copy(struct avl_root ** dst_root, struct avl_root * src_root) {
+	struct stringfloatmap_node *src_node;
+	struct stringfloatmap_node *tmp;
+	avl_for_each_entry_safe(src_node, tmp, src_root, link) {
+		bmx_map_stringfloatmap_insert(src_node->key, src_node->value, dst_root);
+	}
+}
+
+/* +++++++++++++++++++++++++++++++++++++++++++++++++++++ */

+ 167 - 0
mojo2.mod/math3d.bmx

@@ -0,0 +1,167 @@
+'
+' BlitzMax port, 2015 Bruce A Henderson
+' 
+' Copyright (c) 2015 Mark Sibly
+' 
+' This software is provided 'as-is', without any express or implied
+' warranty. In no event will the authors be held liable for any damages
+' arising from the use of this software.
+' 
+' Permission is granted to anyone to use this software for any purpose,
+' including commercial applications, and to alter it and redistribute it
+' freely, subject to the following restrictions:
+' 
+' 1. The origin of this software must not be misrepresented; you must not
+'    claim that you wrote the original software. If you use this software
+'    in a product, an acknowledgement in the product documentation would be
+'    appreciated but is not required.
+' 2. Altered source versions must be plainly marked as such, and must not be
+'    misrepresented as being the original software.
+' 3. This notice may not be removed or altered from any source distribution.
+' 
+SuperStrict
+
+
+Global Mat4Identity:Float[] = [1.0,0.0,0.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,1.0,0.0, 0.0,0.0,0.0,1.0]
+
+Function Vec4Init( x:Float,y:Float,z:Float,w:Float,r:Float[] )
+	r[0]=x
+	r[1]=y
+	r[2]=z
+	r[3]=w
+End Function
+
+Function Vec4Copy( v:Float[],r:Float[] )
+	r[0]=v[0]
+	r[1]=v[1]
+	r[2]=v[2]
+	r[3]=v[3]
+End Function
+
+Function Vec4CopySrcDst( v:Float[],r:Float[],src:Int,dst:Int )
+	r[0+dst]=v[0+src]
+	r[1+dst]=v[1+src]
+	r[2+dst]=v[2+src]
+	r[3+dst]=v[3+src]
+End Function
+
+Function Mat4New:Float[]()
+	Return [1.0,0.0,0.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,1.0,0.0, 0.0,0.0,0.0,1.0]
+End Function
+
+Function Mat4Init( ix:Float,jy:Float,kz:Float,tw:Float,r:Float[] )
+	r[0]= ix; r[1]=  0; r[2]=  0; r[3]=  0
+	r[4]=  0; r[5]= jy; r[6]=  0; r[7]=  0
+	r[8]=  0; r[9]=  0; r[10]=kz; r[11]= 0
+	r[12]= 0; r[13]= 0; r[14]= 0; r[15]=tw
+End Function
+
+Function Mat4InitFull( ix:Float,iy:Float,iz:Float,iw:Float,jx:Float,jy:Float,jz:Float,jw:Float,kx:Float,ky:Float,kz:Float,kw:Float,tx:Float,ty:Float,tz:Float,tw:Float,r:Float[] )
+	r[0]= ix;r[1]= iy;r[2]= iz;r[3]= iw
+	r[4]= jx;r[5]= jy;r[6]= jz;r[7]= jw
+	r[8]= kx;r[9]= ky;r[10]=kz;r[11]=kw
+	r[12]=tx;r[13]=ty;r[14]=tz;r[15]=tw
+End Function
+
+Function Mat4InitArray( r:Float[] )
+	Mat4Init 1,1,1,1,r
+End Function
+
+Function Mat4Copy( m:Float[],r:Float[] )
+	r[0]=m[0]
+	r[1]=m[1]
+	r[2]=m[2]
+	r[3]=m[3]
+	r[4]=m[4]
+	r[5]=m[5]
+	r[6]=m[6]
+	r[7]=m[7]
+	r[8]=m[8]
+	r[9]=m[9]
+	r[10]=m[10]
+	r[11]=m[11]
+	r[12]=m[12]
+	r[13]=m[13]
+	r[14]=m[14]
+	r[15]=m[15]
+End Function
+
+Function Mat4Ortho( Left:Float,Right:Float,bottom:Float,top:Float,znear:Float,zfar:Float,r:Float[] )
+	Local w:Float=Right-Left
+	Local h:Float=top-bottom
+	Local d:Float=zfar-znear
+	Mat4InitFull 2.0/w,0,0,0, 0,2.0/h,0,0, 0,0,2.0/d,0, -(Right+Left)/w,-(top+bottom)/h,-(zfar+znear)/d,1,r
+End Function
+
+Function Mat4Frustum( Left:Float,Right:Float,bottom:Float,top:Float,znear:Float,zfar:Float,r:Float[] )	
+	Local w:Float=Right-Left
+	Local h:Float=top-bottom
+	Local d:Float=zfar-znear
+	Local znear2:Float=znear*2
+	Mat4InitFull znear2/w,0,0,0, 0,znear2/h,0,0, (Right+Left)/w,(top+bottom)/h,(zfar+znear)/d,1, 0,0,-(zfar*znear2)/d,0, r
+End Function
+
+Function Mat4Transpose( m:Float[],r:Float[] )
+	Mat4InitFull m[0],m[4],m[8],m[12], m[1],m[5],m[9],m[13], m[2],m[6],m[10],m[14], m[3],m[7],m[11],m[15],r
+End Function
+
+Function Mat4Inverse( m:Float[],r:Float[] )
+	r[0] = m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] + m[9] * m[7] * m[14] + m[13] * m[6] * m[11] - m[13] * m[7] * m[10]
+	r[4] =-m[4] * m[10] * m[15] + m[4] * m[11] * m[14] + m[8] * m[6] * m[15] - m[8] * m[7] * m[14] - m[12] * m[6] * m[11] + m[12] * m[7] * m[10]
+	r[8] = m[4] * m[9]  * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] + m[8] * m[7] * m[13] + m[12] * m[5] * m[11] - m[12] * m[7] * m[9]
+	r[12]=-m[4] * m[9]  * m[14] + m[4] * m[10] * m[13] + m[8] * m[5] * m[14] - m[8] * m[6] * m[13] - m[12] * m[5] * m[10] + m[12] * m[6] * m[9]
+	r[1] =-m[1] * m[10] * m[15] + m[1] * m[11] * m[14] + m[9] * m[2] * m[15] - m[9] * m[3] * m[14] - m[13] * m[2] * m[11] + m[13] * m[3] * m[10]
+	r[5] = m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] + m[8] * m[3] * m[14] + m[12] * m[2] * m[11] - m[12] * m[3] * m[10]
+	r[9] =-m[0] * m[9]  * m[15] + m[0] * m[11] * m[13] + m[8] * m[1] * m[15] - m[8] * m[3] * m[13] - m[12] * m[1] * m[11] + m[12] * m[3] * m[9]
+	r[13]= m[0] * m[9]  * m[14] - m[0] * m[10] * m[13] - m[8] * m[1] * m[14] + m[8] * m[2] * m[13] + m[12] * m[1] * m[10] - m[12] * m[2] * m[9]
+	r[2] = m[1] * m[6]  * m[15] - m[1] * m[7]  * m[14] - m[5] * m[2] * m[15] + m[5] * m[3] * m[14] + m[13] * m[2] * m[7]  - m[13] * m[3] * m[6]
+	r[6] =-m[0] * m[6]  * m[15] + m[0] * m[7]  * m[14] + m[4] * m[2] * m[15] - m[4] * m[3] * m[14] - m[12] * m[2] * m[7]  + m[12] * m[3] * m[6]
+	r[10]= m[0] * m[5]  * m[15] - m[0] * m[7]  * m[13] - m[4] * m[1] * m[15] + m[4] * m[3] * m[13] + m[12] * m[1] * m[7]  - m[12] * m[3] * m[5]
+	r[14]=-m[0] * m[5]  * m[14] + m[0] * m[6]  * m[13] + m[4] * m[1] * m[14] - m[4] * m[2] * m[13] - m[12] * m[1] * m[6]  + m[12] * m[2] * m[5]
+	r[3] =-m[1] * m[6]  * m[11] + m[1] * m[7]  * m[10] + m[5] * m[2] * m[11] - m[5] * m[3] * m[10] - m[9]  * m[2] * m[7]  + m[9]  * m[3] * m[6]
+	r[7] = m[0] * m[6]  * m[11] - m[0] * m[7]  * m[10] - m[4] * m[2] * m[11] + m[4] * m[3] * m[10] + m[8]  * m[2] * m[7]  - m[8]  * m[3] * m[6]
+	r[11]=-m[0] * m[5]  * m[11] + m[0] * m[7]  * m[9]  + m[4] * m[1] * m[11] - m[4] * m[3] * m[9]  - m[8]  * m[1] * m[7]  + m[8]  * m[3] * m[5]
+	r[15]= m[0] * m[5]  * m[10] - m[0] * m[6]  * m[9]  - m[4] * m[1] * m[10] + m[4] * m[2] * m[9]  + m[8]  * m[1] * m[6]  - m[8]  * m[2] * m[5]
+	Local c:Float=1.0 / (m[0] * r[0] + m[1] * r[4] + m[2] * r[8] + m[3] * r[12])
+	For Local i:Int = 0 Until 16
+		r[i]:*c
+	Next
+End Function
+
+Function Mat4Multiply( m:Float[],n:Float[],r:Float[] )
+	Mat4InitFull( ..
+	m[0]*n[0]  + m[4]*n[1]  + m[8]*n[2]  + m[12]*n[3],  m[1]*n[0]  + m[5]*n[1]  + m[9]*n[2]  + m[13]*n[3],  m[2]*n[0]  + m[6]*n[1]  + m[10]*n[2]  + m[14]*n[3],  m[3]*n[0]  + m[7]*n[1]  + m[11]*n[2]  + m[15]*n[3], ..
+	m[0]*n[4]  + m[4]*n[5]  + m[8]*n[6]  + m[12]*n[7],  m[1]*n[4]  + m[5]*n[5]  + m[9]*n[6]  + m[13]*n[7],  m[2]*n[4]  + m[6]*n[5]  + m[10]*n[6]  + m[14]*n[7],  m[3]*n[4]  + m[7]*n[5]  + m[11]*n[6]  + m[15]*n[7], ..
+	m[0]*n[8]  + m[4]*n[9]  + m[8]*n[10] + m[12]*n[11], m[1]*n[8]  + m[5]*n[9]  + m[9]*n[10] + m[13]*n[11], m[2]*n[8]  + m[6]*n[9]  + m[10]*n[10] + m[14]*n[11], m[3]*n[8]  + m[7]*n[9]  + m[11]*n[10] + m[15]*n[11], ..
+	m[0]*n[12] + m[4]*n[13] + m[8]*n[14] + m[12]*n[15], m[1]*n[12] + m[5]*n[13] + m[9]*n[14] + m[13]*n[15], m[2]*n[12] + m[6]*n[13] + m[10]*n[14] + m[14]*n[15], m[3]*n[12] + m[7]*n[13] + m[11]*n[14] + m[15]*n[15],r )
+End Function
+
+Function Mat4Transform( m:Float[],v:Float[],r:Float[] )
+	Vec4Init( ..
+	m[0]*v[0] + m[4]*v[1] + m[8] *v[2] + m[12]*v[3],..
+	m[1]*v[0] + m[5]*v[1] + m[9] *v[2] + m[13]*v[3], ..
+	m[2]*v[0] + m[6]*v[1] + m[10]*v[2] + m[14]*v[3], ..
+	m[3]*v[0] + m[7]*v[1] + m[11]*v[2] + m[15]*v[3],r )
+End Function
+
+Function Mat4Project( m:Float[],v:Float[],r:Float[] )
+	Vec4Init( ..
+	m[0]*v[0] + m[4]*v[1] + m[8] *v[2] + m[12]*v[3], ..
+	m[1]*v[0] + m[5]*v[1] + m[9] *v[2] + m[13]*v[3], ..
+	m[2]*v[0] + m[6]*v[1] + m[10]*v[2] + m[14]*v[3], ..
+	m[3]*v[0] + m[7]*v[1] + m[11]*v[2] + m[15]*v[3],r )
+	r[0] :/ r[3]
+	r[1] :/ r[3]
+	r[2] :/ r[3]
+	r[3]=1
+End Function
+
+Function Mat4Roll( rz:Float,m:Float[] )
+End Function
+
+Function Mat4Scale( x:Float,y:Float,z:Float,m:Float[] )
+	m[0]=x;m[1]=0;m[2]=0;m[3]=0
+	m[4]=0;m[5]=y;m[6]=0;m[7]=0
+	m[8]=0;m[9]=0;m[10]=1;m[11]=0
+	m[12]=0;m[13]=0;m[14]=0;m[15]=1
+End Function

+ 39 - 0
mojo2.mod/mojo2.bmx

@@ -0,0 +1,39 @@
+'
+' BlitzMax port, 2015 Bruce A Henderson
+' 
+' Copyright (c) 2015 Mark Sibly
+' 
+' This software is provided 'as-is', without any express or implied
+' warranty. In no event will the authors be held liable for any damages
+' arising from the use of this software.
+' 
+' Permission is granted to anyone to use this software for any purpose,
+' including commercial applications, and to alter it and redistribute it
+' freely, subject to the following restrictions:
+' 
+' 1. The origin of this software must not be misrepresented; you must not
+'    claim that you wrote the original software. If you use this software
+'    in a product, an acknowledgement in the product documentation would be
+'    appreciated but is not required.
+' 2. Altered source versions must be plainly marked as such, and must not be
+'    misrepresented as being the original software.
+' 3. This notice may not be removed or altered from any source distribution.
+' 
+SuperStrict
+
+Rem
+bbdoc: Shader-based 2D Renderer
+End Rem
+Module mky.mojo2
+
+ModuleInfo "Version: 1.00"
+ModuleInfo "Author: Mark Sibly, Bruce A Henderson"
+ModuleInfo "License: zlib/libpng"
+ModuleInfo "Copyright: Monkey2 Mojo2 - 2015 Mark Sibly"
+ModuleInfo "Copyright: BlitzMax port - 2015 Bruce A Henderson"
+
+ModuleInfo "History: 1.00"
+ModuleInfo "History: Initial Release."
+
+Import "renderer.bmx"
+

+ 468 - 0
mojo2.mod/renderer.bmx

@@ -0,0 +1,468 @@
+'
+' BlitzMax port, 2015 Bruce A Henderson
+' 
+' Copyright (c) 2015 Mark Sibly
+' 
+' This software is provided 'as-is', without any express or implied
+' warranty. In no event will the authors be held liable for any damages
+' arising from the use of this software.
+' 
+' Permission is granted to anyone to use this software for any purpose,
+' including commercial applications, and to alter it and redistribute it
+' freely, subject to the following restrictions:
+' 
+' 1. The origin of this software must not be misrepresented; you must not
+'    claim that you wrote the original software. If you use this software
+'    in a product, an acknowledgement in the product documentation would be
+'    appreciated but is not required.
+' 2. Altered source versions must be plainly marked as such, and must not be
+'    misrepresented as being the original software.
+' 3. This notice may not be removed or altered from any source distribution.
+' 
+SuperStrict
+
+Import "graphics.bmx"
+
+
+Type TLayerData
+	Field matrix:Float[]=Mat4New()
+	Field invMatrix:Float[]=Mat4New()
+	Field drawList:TDrawList
+End Type
+
+Global lvector:Float[4]
+Global tvector:Float[4]
+
+Public
+
+Interface ILight
+
+	Method LightMatrix:Float[]()
+	Method LightType:Int()
+	Method LightColor:Float[]()
+	Method LightRange:Float()
+	Method LightImage:TImage()
+	
+End Interface
+
+Interface ILayer
+
+	Method LayerMatrix:Float[]()
+	Method LayerFogColor:Float[]()
+	Method LayerLightMaskImage:TImage()
+	Method EnumLayerLights( lights:TILightStack )
+	Method OnRenderLayer( drawLists:TDrawListStack )
+	
+End Interface
+
+Type TRenderer
+
+	Method New()
+		_canvas=New TCanvas.CreateCanvas()
+		Mat4Ortho( 0,_canvas.Width,0,_canvas.Height,-1,1,_projectionMatrix )
+	End Method
+	
+	Method SetClearMode( clearMode:Int )
+		_clearMode=clearMode
+	End Method
+	
+	Method SetClearColor( clearColor:Float[] )
+		_clearColor=clearColor
+	End Method
+	
+	Method SetAmbientLight( AmbientLight:Float[] )
+		_ambientLight=AmbientLight
+	End Method
+	
+	Method SetCameraMatrix( cameraMatrix:Float[] )
+		_cameraMatrix=cameraMatrix
+	End Method
+	
+	Method SetProjectionMatrix( projectionMatrix:Float[] )
+		_projectionMatrix=projectionMatrix
+	End Method
+	
+	Method Layers:TILayerStack()
+		Return _layers
+	End Method
+	
+	Method Render(dcanvas:TCanvas)
+
+		Local canvas:TCanvas=dcanvas
+		_canvas=canvas
+		_viewport=canvas.Viewport()
+		_projectionMatrix=canvas.ProjectionMatrix()
+
+		Local vwidth:Int=_viewport[2]
+		Local vheight:Int=_viewport[3]
+
+		If vwidth<=0 Or vheight<=0 Return
+		
+		Mat4Inverse( _projectionMatrix,_invProjMatrix )
+
+		lvector[0]=-1;lvector[1]=-1;lvector[2]=-1;lvector[3]=1
+		Mat4Project( _invProjMatrix,lvector,tvector )
+		Local px0:Float=tvector[0]
+		Local py0:Float=tvector[1]
+
+		lvector[0]=1;lvector[1]=1;lvector[2]=-1;lvector[3]=1
+		Mat4Project( _invProjMatrix,lvector,tvector )
+		Local px1:Float=tvector[0]
+		Local py1:Float=tvector[1]
+
+		Local twidth:Float=px1-px0,theight:Float=py1-py0
+
+'		Local twidth:Int=vwidth/1
+'		Local theight:Int=vheight/1
+		
+		If Not _timage Or _timage.Width()<>twidth Or _timage.Height()<>theight
+			_timage=New TImage.Create( twidth,theight,0,0 )
+		End If
+		
+		If Not _timage2 Or _timage2.Width()<>twidth Or _timage2.Height()<>theight
+			_timage2=New TImage.Create( twidth,theight,0,0 )
+		End If
+
+		If Not _tcanvas
+			_tcanvas=New TCanvas.CreateCanvas( _timage )
+		EndIf
+		
+		_tcanvas.SetProjectionMatrix( _projectionMatrix )
+		_tcanvas.SetViewport( 0,0,twidth,theight )
+		_tcanvas.SetScissor( 0,0,twidth,theight )
+
+		Mat4Inverse _cameraMatrix,_viewMatrix
+		
+		Local invProj:Int=False
+
+		'Clear!		
+		'_canvas.SetRenderTarget _image	
+		'_canvas.SetViewport _viewport[0],_viewport[1],_viewport[2],_viewport[3]
+		Select _clearMode
+		Case 1
+			_canvas.Clear _clearColor[0],_clearColor[1],_clearColor[2],_clearColor[3]
+		End Select
+		
+		For Local layerId:Int=0 Until _layers.Length
+		
+			Local layer:ILayer=ILayer(_layers.Get( layerId ))
+			Local fog:Float[]=layer.LayerFogColor()
+			
+			Local layerMatrix:Float[]=layer.LayerMatrix()
+			Mat4Inverse layerMatrix,_invLayerMatrix
+			
+			_drawLists.Clear
+			layer.OnRenderLayer( _drawLists )
+			
+			Local lights:TILightStack=New TILightStack
+			layer.EnumLayerLights( lights )
+			
+			If Not lights.Length
+			
+				For Local i:Int=0 Until 4
+					canvas.SetLightType i,0
+				Next
+			
+				'canvas.SetRenderTarget _image
+				canvas.SetShadowMap Null'_timage
+				'canvas.SetViewport _viewport[0],_viewport[1],_viewport[2],_viewport[3]
+				'canvas.SetProjectionMatrix _projectionMatrix
+				canvas.SetViewMatrix _viewMatrix
+				canvas.SetModelMatrix layerMatrix
+				canvas.SetAmbientLight _ambientLight[0],_ambientLight[1],_ambientLight[2],1
+				canvas.SetFogColor fog[0],fog[1],fog[2],fog[3]
+				
+				canvas.SetColor 1,1,1,1
+				For Local i:Int=0 Until _drawLists.Length
+					canvas.RenderDrawList _drawLists.Get( i )
+				Next
+				canvas.Flush
+				
+				Continue
+				
+			EndIf
+			
+			Local light0:Int=0
+			
+			Repeat
+			
+				Local numLights:Int=Min(lights.Length-light0,4)
+				
+				'Shadows
+				'
+				canvas=_tcanvas
+				canvas.SetRenderTarget _timage
+				canvas.SetShadowMap Null
+				'canvas.SetViewport 0,0,twidth,theight
+				'canvas.SetProjectionMatrix _projectionMatrix
+				canvas.SetViewMatrix _viewMatrix
+				canvas.SetModelMatrix layerMatrix
+				canvas.SetAmbientLight 0,0,0,0
+				canvas.SetFogColor 0,0,0,0
+				
+				canvas.Clear 1,1,1,1
+				canvas.SetBlendMode 0
+				canvas.SetColor 0,0,0,0
+
+				canvas.SetDefaultMaterial TShader.ShadowShader().DefaultMaterial
+				
+				For Local i:Int=0 Until numLights
+				
+					Local light:ILight=lights.Get(light0+i)
+					
+					Local matrix:Float[]=light.LightMatrix()
+					
+					Vec4CopySrcDst matrix,lvector,12,0
+					Mat4Transform _invLayerMatrix,lvector,tvector
+					Local lightx:Float=tvector[0]
+					Local lighty:Float=tvector[1]
+					
+					canvas.SetColorMask i=0,i=1,i=2,i=3
+					
+					Local image:TImage=light.LightImage()
+					If image
+						canvas.Clear 0,0,0,0
+						canvas.PushMatrix
+						canvas.SetMatrix matrix[0],matrix[1],matrix[4],matrix[5],lightx,lighty
+						canvas.DrawImageImage image
+						canvas.PopMatrix
+					EndIf
+		
+					For Local j:Int=0 Until _drawLists.Length
+						canvas.DrawShadows lightx,lighty,_drawLists.Get( j )
+					Next
+				
+				Next
+				
+				canvas.SetDefaultMaterial TShader.FastShader().DefaultMaterial
+				canvas.SetColorMask True,True,True,True
+				canvas.Flush
+				
+				'LightMask
+				'
+				Rem
+				Local lightMask:TImage=layer.LayerLightMaskImage()
+				If lightMask
+				
+					If Not invProj
+						Mat4Inverse _projectionMatrix,_invProjMatrix
+						Mat4Project( _invProjMatrix,[-1.0,-1.0,-1.0,1.0],_ptl )
+						Mat4Project( _invProjMatrix,[ 1.0, 1.0,-1.0,1.0],_pbr )
+					EndIf
+					
+					Local fwidth:Float=(_pbr[0]-_ptl[0])
+					Local fheight:Float=(_pbr[1]-_ptl[1])
+					
+					If _projectionMatrix[15]=0
+						Local scz:Float=(layerMatrix[14]-_cameraMatrix[14])/_ptl[2]
+						fwidth:*scz
+						fheight:*scz
+					EndIf
+				
+					_canvas.SetProjection2d 0,fwidth,0,fheight					
+					_canvas.SetViewMatrix Mat4Identity
+					_canvas.SetModelMatrix Mat4Identity
+					
+					'test...
+					'_canvas.SetBlendMode 0
+					'_canvas.SetColor 1,1,1,1
+					'_canvas.DrawRect 0,0,fwidth,fheight
+					
+					_canvas.SetBlendMode 4
+					
+					Local w:Float=lightMask.Width()
+					Local h:Float=lightMask.Height()
+					Local x:Int:-w
+					While x<fwidth+w
+						Local y:Int:-h
+						While y<fheight+h
+							_canvas.DrawImage lightMask,x,y
+							y:+h
+						Wend
+						x:+w
+					Wend
+					
+					_canvas.Flush
+
+				EndIf
+				end rem
+				
+				'Enable lights
+				'
+				canvas=_canvas
+				If light0 canvas=_tcanvas
+				
+				For Local i:Int=0 Until numLights
+				
+					Local light:ILight=lights.Get(light0+i)
+					
+					Local c:Float[]=light.LightColor()
+					Local m:Float[]=light.LightMatrix()
+					
+					canvas.SetLightType i,1
+					canvas.SetLightColor i,c[0],c[1],c[2],c[3]
+					canvas.SetLightPosition i,m[12],m[13],m[14]
+					canvas.SetLightRange i,light.LightRange()
+				Next
+				For Local i:Int=numLights Until 4
+					canvas.SetLightType i,0
+				Next
+				
+				If light0=0	'first pass?
+				
+					'render lights+ambient to output
+					'
+					canvas=_canvas
+					canvas.SetShadowMap _timage
+					canvas.SetViewMatrix _viewMatrix
+					canvas.SetModelMatrix layerMatrix
+					canvas.SetAmbientLight _ambientLight[0],_ambientLight[1],_ambientLight[2],1
+					canvas.SetFogColor fog[0],fog[1],fog[2],fog[3]
+					
+					canvas.SetColor 1,1,1,1
+					For Local i:Int=0 Until _drawLists.Length
+						canvas.RenderDrawList _drawLists.Get( i )
+					Next
+					canvas.Flush
+					
+				Else
+				
+					'render lights only
+					'
+					canvas=_tcanvas
+					canvas.SetRenderTarget _timage2
+					canvas.SetShadowMap _timage
+					canvas.SetViewMatrix _viewMatrix
+					canvas.SetModelMatrix layerMatrix
+					canvas.SetAmbientLight 0,0,0,0
+					canvas.SetFogColor 0,0,0,fog[3]
+					
+					canvas.Clear 0,0,0,1
+					canvas.SetColor 1,1,1,1
+					For Local i:Int=0 Until _drawLists.Length
+						canvas.RenderDrawList _drawLists.Get( i )
+					Next
+					canvas.Flush
+					
+					'add light to output
+					'
+					'_canvas.SetRenderTarget _image
+					canvas=_canvas
+					canvas.SetShadowMap Null
+					canvas.SetViewMatrix Mat4Identity
+					canvas.SetModelMatrix Mat4Identity
+					canvas.SetAmbientLight 0,0,0,1
+					canvas.SetFogColor 0,0,0,0
+					
+					canvas.SetBlendMode 2
+					canvas.SetColor 1,1,1,1
+					canvas.DrawImageImage _timage2
+					canvas.Flush
+					
+				EndIf
+				
+				light0:+4
+			
+			Until light0>=lights.Length
+			
+		Next
+	End Method
+	
+	'Protected
+	
+	Field _canvas:TCanvas
+	Field _tcanvas:TCanvas
+	
+	Field _timage:TImage		'tmp lighting texture
+	Field _timage2:TImage		'another tmp lighting image for >4 lights
+
+	Field _viewport:Int[]=[0,0,640,480]
+	Field _clearMode:Int=1
+	Field _clearColor:Float[]=[0.0,0.0,0.0,1.0]
+	Field _ambientLight:Float[]=[1.0,1.0,1.0,1.0]
+	Field _projectionMatrix:Float[]=Mat4New()
+	Field _cameraMatrix:Float[]=Mat4New()
+	Field _viewMatrix:Float[]=Mat4New()
+	
+	Field _layers:TILayerStack=New TILayerStack
+	
+	Field _invLayerMatrix:Float[16]
+	Field _drawLists:TDrawListStack=New TDrawListStack
+	
+	Field _invProjMatrix:Float[16]
+	Field _ptl:Float[4]
+	Field _pbr:Float[4]
+		
+End Type
+
+Type TILightStack
+	Field data:ILight[]
+	Field length:Int
+
+	Method Push( value:ILight )
+		If length=data.Length
+			data=data[..length*2+10]
+		EndIf
+		data[length]=value
+		length:+1
+	End Method
+
+	Method Pop:ILight()
+		length:-1
+		Local v:ILight=data[length]
+		data[length]=Null
+		Return v
+	End Method
+
+	Method Top:ILight()
+		Return data[length-1]
+	End Method
+	
+	Method Get:ILight(index:Int)
+		Return data[index]
+	End Method
+
+	Method Clear()
+		For Local i:Int=0 Until length
+			data[i]=Null
+		Next
+		length=0
+	End Method
+
+End Type
+
+Type TILayerStack
+	Field data:ILayer[]
+	Field length:Int
+
+	Method Push( value:ILayer )
+		If length=data.Length
+			data=data[..length*2+10]
+		EndIf
+		data[length]=value
+		length:+1
+	End Method
+
+	Method Pop:ILayer()
+		length:-1
+		Local v:ILayer=data[length]
+		data[length]=Null
+		Return v
+	End Method
+
+	Method Top:ILayer()
+		Return data[length-1]
+	End Method
+	
+	Method Get:ILayer(index:Int)
+		Return data[index]
+	End Method
+
+	Method Clear()
+		For Local i:Int=0 Until length
+			data[i]=Null
+		Next
+		length=0
+	End Method
+
+End Type

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است