Browse Source

Added mojo3d modules!

Mark Sibly 8 years ago
parent
commit
fceee9422b
95 changed files with 9272 additions and 3 deletions
  1. 0 3
      .gitignore
  2. BIN
      bin/mx2cc_windows.exe
  3. 349 0
      modules/mojo3d-loaders/loaders/assimp.monkey2
  4. 8 0
      modules/mojo3d-loaders/module.json
  5. 13 0
      modules/mojo3d-loaders/mojo3d-loaders.monkey2
  6. BIN
      modules/mojo3d-loaders/tests/assets/miramar-skybox.jpg
  7. BIN
      modules/mojo3d-loaders/tests/assets/turtle1.b3d
  8. BIN
      modules/mojo3d-loaders/tests/assets/turtle1.png
  9. 93 0
      modules/mojo3d-loaders/tests/turtle.monkey2
  10. 40 0
      modules/mojo3d-loaders/tests/util.monkey2
  11. 8 0
      modules/mojo3d-physics/module.json
  12. 16 0
      modules/mojo3d-physics/mojo3d-physics.monkey2
  13. 74 0
      modules/mojo3d-physics/physics/bttypeconvs.monkey2
  14. 171 0
      modules/mojo3d-physics/physics/collider.monkey2
  15. 35 0
      modules/mojo3d-physics/physics/native/internaledges.cpp
  16. 12 0
      modules/mojo3d-physics/physics/native/internaledges.h
  17. 16 0
      modules/mojo3d-physics/physics/native/kinematicmotionstate.h
  18. 184 0
      modules/mojo3d-physics/physics/rigidbody.monkey2
  19. 78 0
      modules/mojo3d-physics/physics/world.monkey2
  20. 164 0
      modules/mojo3d-physics/tests/shapes.monkey2
  21. 40 0
      modules/mojo3d-physics/tests/util.monkey2
  22. 8 0
      modules/mojo3d/docs/manual.md
  23. 174 0
      modules/mojo3d/graphics/animation.monkey2
  24. 55 0
      modules/mojo3d/graphics/animator.monkey2
  25. 110 0
      modules/mojo3d/graphics/bloomeffect.monkey2
  26. 166 0
      modules/mojo3d/graphics/camera.monkey2
  27. 165 0
      modules/mojo3d/graphics/deferredrenderer.monkey2
  28. 651 0
      modules/mojo3d/graphics/entity.monkey2
  29. 78 0
      modules/mojo3d/graphics/fogeffect.monkey2
  30. 34 0
      modules/mojo3d/graphics/forwardrenderer.monkey2
  31. 526 0
      modules/mojo3d/graphics/gltf2.monkey2
  32. 275 0
      modules/mojo3d/graphics/gltf2loader.monkey2
  33. 128 0
      modules/mojo3d/graphics/light.monkey2
  34. 22 0
      modules/mojo3d/graphics/loader.monkey2
  35. 125 0
      modules/mojo3d/graphics/material.monkey2
  36. 429 0
      modules/mojo3d/graphics/mesh.monkey2
  37. 541 0
      modules/mojo3d/graphics/meshprims.monkey2
  38. 195 0
      modules/mojo3d/graphics/model.monkey2
  39. 70 0
      modules/mojo3d/graphics/monochromeeffect.monkey2
  40. 205 0
      modules/mojo3d/graphics/pbrmaterial.monkey2
  41. 79 0
      modules/mojo3d/graphics/posteffect.monkey2
  42. 18 0
      modules/mojo3d/graphics/renderable.monkey2
  43. 461 0
      modules/mojo3d/graphics/renderer.monkey2
  44. 179 0
      modules/mojo3d/graphics/scene.monkey2
  45. 72 0
      modules/mojo3d/graphics/shaders/bloom.glsl
  46. 184 0
      modules/mojo3d/graphics/shaders/boned-material.glsl
  47. 26 0
      modules/mojo3d/graphics/shaders/copy.glsl
  48. 159 0
      modules/mojo3d/graphics/shaders/directional-light.glsl
  49. 41 0
      modules/mojo3d/graphics/shaders/fog.glsl
  50. 153 0
      modules/mojo3d/graphics/shaders/material.glsl
  51. 31 0
      modules/mojo3d/graphics/shaders/monochrome.glsl
  52. 118 0
      modules/mojo3d/graphics/shaders/point-light.glsl
  53. 32 0
      modules/mojo3d/graphics/shaders/skybox.glsl
  54. 41 0
      modules/mojo3d/graphics/shaders/sprite3d.glsl
  55. 154 0
      modules/mojo3d/graphics/shaders/terrain.glsl
  56. 153 0
      modules/mojo3d/graphics/shaders/water.glsl
  57. 122 0
      modules/mojo3d/graphics/sprite.monkey2
  58. 93 0
      modules/mojo3d/graphics/spritebuffer.monkey2
  59. 68 0
      modules/mojo3d/graphics/spritematerial.monkey2
  60. 198 0
      modules/mojo3d/graphics/terrain.monkey2
  61. 71 0
      modules/mojo3d/graphics/terrainmaterial.monkey2
  62. BIN
      modules/mojo3d/graphics/textures/env_default.jpg
  63. 59 0
      modules/mojo3d/graphics/util3d.monkey2
  64. 99 0
      modules/mojo3d/graphics/watermaterial.monkey2
  65. 8 0
      modules/mojo3d/module.json
  66. 44 0
      modules/mojo3d/mojo3d.monkey2
  67. BIN
      modules/mojo3d/tests/assets/Acadia-Tree-Sprite.png
  68. BIN
      modules/mojo3d/tests/assets/Monkey2logo_64.png
  69. 238 0
      modules/mojo3d/tests/assets/duck.gltf/Duck.gltf
  70. BIN
      modules/mojo3d/tests/assets/duck.gltf/Duck0.bin
  71. BIN
      modules/mojo3d/tests/assets/duck.gltf/DuckCM.png
  72. BIN
      modules/mojo3d/tests/assets/heightmap_256.BMP
  73. BIN
      modules/mojo3d/tests/assets/miramar-skybox.jpg
  74. 1 0
      modules/mojo3d/tests/assets/mossy-ground1.pbr/About these PBR files.txt
  75. BIN
      modules/mojo3d/tests/assets/mossy-ground1.pbr/color.png
  76. BIN
      modules/mojo3d/tests/assets/mossy-ground1.pbr/height.png
  77. BIN
      modules/mojo3d/tests/assets/mossy-ground1.pbr/metalness.png
  78. BIN
      modules/mojo3d/tests/assets/mossy-ground1.pbr/normal.png
  79. BIN
      modules/mojo3d/tests/assets/mossy-ground1.pbr/occlusion.png
  80. BIN
      modules/mojo3d/tests/assets/mossy-ground1.pbr/roughness.png
  81. 507 0
      modules/mojo3d/tests/assets/spheres.gltf/MetalRoughSpheres.gltf
  82. BIN
      modules/mojo3d/tests/assets/spheres.gltf/MetalRoughSpheres0.bin
  83. BIN
      modules/mojo3d/tests/assets/spheres.gltf/Spheres_BaseColor.png
  84. BIN
      modules/mojo3d/tests/assets/spheres.gltf/Spheres_MetalRough.png
  85. BIN
      modules/mojo3d/tests/assets/terrain_256.png
  86. BIN
      modules/mojo3d/tests/assets/water1.jpg
  87. BIN
      modules/mojo3d/tests/assets/water_normal0.png
  88. BIN
      modules/mojo3d/tests/assets/water_normal1.png
  89. 79 0
      modules/mojo3d/tests/donut.monkey2
  90. 144 0
      modules/mojo3d/tests/ducks.monkey2
  91. 84 0
      modules/mojo3d/tests/pbrspheres.monkey2
  92. 93 0
      modules/mojo3d/tests/sprites.monkey2
  93. 78 0
      modules/mojo3d/tests/terraintest.monkey2
  94. 40 0
      modules/mojo3d/tests/util.monkey2
  95. 87 0
      modules/mojo3d/tests/water.monkey2

+ 0 - 3
.gitignore

@@ -43,9 +43,6 @@ __MANPAGES__/
 /modules/gles30
 /modules/timelinefx
 /modules/admob
-/modules/mojo3d
-/modules/mojo3d-loaders
-/modules/mojo3d-physics
 
 /modules/module-manager/backups
 

BIN
bin/mx2cc_windows.exe


+ 349 - 0
modules/mojo3d-loaders/loaders/assimp.monkey2

@@ -0,0 +1,349 @@
+
+Namespace mojo3d.loaders
+
+Struct aiVector3D extension
+	
+	Operator To:Vec3f()
+		Return New Vec3f( x,y,z )
+	End
+
+End
+
+Struct aiQuaternion Extension
+	
+	Operator To:Quatf()
+		Return New Quatf( x,y,z,-w )
+	End
+
+End
+
+Struct aiMatrix4x4 Extension
+	
+	Operator To:Mat4f()
+		Return New Mat4f(
+			New Vec4f( a1,b1,c1,d1 ),
+			New Vec4f( a2,b2,c2,d2 ),
+			New Vec4f( a3,b3,c3,d3 ),
+			New Vec4f( a4,b4,c4,d4 ) )
+	End
+	
+	Operator To:AffineMat4f()
+		If d1<>0 Or d2<>0 Or d3<>0 Or d4<>1 Print "WARNING! Assimp node matrix is not affine!"
+		Return New AffineMat4f(
+			New Vec3f( a1,b1,c1 ),
+			New Vec3f( a2,b2,c2 ),
+			New Vec3f( a3,b3,c3 ),
+			New Vec3f( a4,b4,c4 ) )
+	End
+	
+End
+
+Class AssimpLoader
+	
+	Method New( scene:aiScene,dir:String )
+		_scene=scene
+		_dir=dir
+	End
+	
+	Method LoadMesh:Mesh()
+		
+		Local model:=New Model
+		
+		For Local i:=0 Until _scene.mNumMeshes
+			
+			AddMesh( _scene.mMeshes[i],model )
+		Next
+		
+		model.Mesh.UpdateTangents()
+		
+		Return model.Mesh
+	End
+	
+	Method LoadModel:Model()
+		
+		Local model:=LoadNode( _scene.mRootNode,Null )
+		
+		model.Animator=LoadAnimator()
+		
+		Return model
+	End
+	
+	Private
+	
+	Field _scene:aiScene
+	Field _dir:String
+	
+	Field _materials:=New Stack<Material>
+	Field _nodes:=New StringMap<Entity>
+	Field _entityIds:=New StringMap<Int>
+	Field _entities:=New Stack<Entity>
+	
+	Method LoadMaterial:Material( aimaterial:aiMaterial,boned:Bool=False )
+		
+		Local material:=New PbrMaterial( boned )
+		
+		Local aipath:aiString,path:String
+		Local aicolor:aiColor4D,color:Color
+			
+		aiGetMaterialTexture( aimaterial,aiTextureType_DIFFUSE,0,Varptr aipath )
+		path=aipath.data
+		If path
+			path=_dir+StripDir( path )
+			Local texture:=Texture.Load( path,TextureFlags.FilterMipmap )
+			If texture material.ColorTexture=texture
+		Endif
+			
+		aiGetMaterialColor( aimaterial,AI_MATKEY_COLOR_DIFFUSE,0,0,Varptr aicolor )
+		material.ColorFactor=New Color( aicolor.r,aicolor.g,aicolor.b,aicolor.a )
+		
+		Return material
+	End
+	
+	Method AddMesh( aimesh:aiMesh,model:Model )
+
+		Local vertices:=New Vertex3f[ aimesh.mNumVertices ]
+		
+		Local vp:=aimesh.mVertices
+		Local np:=aimesh.mNormals
+		Local tp:=aimesh.mTextureCoords[0]
+		
+		For Local i:=0 Until vertices.Length
+			vertices[i].position=New Vec3f( vp[i].x,vp[i].y,vp[i].z )
+			vertices[i].normal=New Vec3f( np[i].x,np[i].y,np[i].z )
+			If tp vertices[i].texCoord0=New Vec2f( tp[i].x,tp[i].y )
+		Next
+		
+		If aimesh.mNumBones
+
+			Local n:=aimesh.mNumBones
+			
+			Local bones:=model.Bones,i0:=bones.Length
+		
+			bones=bones.Resize( i0+n )
+			
+			Print "numBones="+n
+			
+			For Local i:=0 Until n
+		
+				Local aibone:=aimesh.mBones[i]
+				
+				bones[i0+i].entity=_entities[ _entityIds[ aibone.mName.data ] ]
+				
+				bones[i0+i].offset=Cast<AffineMat4f>( aibone.mOffsetMatrix )
+				
+				For Local j:=0 Until aibone.mNumWeights
+					
+					Local aiweight:=aibone.mWeights[j]
+					
+					Local wp:=Cast<Float Ptr>( Varptr vertices[aiweight.mVertexId].weights )
+					Local bp:=Cast<UByte Ptr>( Varptr vertices[aiweight.mVertexId].bones )
+					
+					Local k:=0
+					For k=0 Until 4
+						If wp[k] Continue
+						wp[k]=aiweight.mWeight
+						bp[k]=i0+i
+						Exit
+					Next
+					If k=4 print "Too many vertex weights"
+						
+				Next
+			Next
+		
+			model.Bones=bones
+		
+		Endif
+
+		Local indices:=New UInt[ aimesh.mNumFaces*3 ]
+		
+		Local fp:=aimesh.mFaces,v0:=model.Mesh.NumVertices
+		
+		For Local i:=0 Until aimesh.mNumFaces
+			indices[i*3+0]=fp[i].mIndices[0]+v0
+			indices[i*3+1]=fp[i].mIndices[1]+v0
+			indices[i*3+2]=fp[i].mIndices[2]+v0
+		Next
+		
+		model.Mesh.AddVertices( vertices )
+		
+		model.Mesh.AddTriangles( indices )
+
+	End
+	
+	Method LoadNode:Model( node:aiNode,parent:Model )
+		
+		Local model:=New Model( parent )
+		
+		model.Name=node.mName.data
+		
+		Local matrix:=Cast<AffineMat4f>( node.mTransformation )
+
+		Local scl:=matrix.m.GetScaling()
+		Local rot:=matrix.m.Scale( 1/scl.x,1/scl.y,1/scl.z )
+		Local pos:=matrix.t
+
+		model.Position=pos
+		model.Basis=rot
+		model.Scale=scl
+		
+		_nodes[ node.mName.data ]=model
+		_entityIds[ node.mName.data ]=_entities.Length
+		_entities.Push( model )
+		
+		For Local i:=0 Until node.mNumChildren
+			
+			LoadNode( node.mChildren[i],model )
+		Next
+
+		If node.mNumMeshes
+		
+			model.Mesh=New Mesh
+			
+			Local materials:=New Stack<Material>
+		
+			For Local i:=0 Until node.mNumMeshes
+				
+				Local aimesh:=_scene.mMeshes[ node.mMeshes[i] ]
+					
+				AddMesh( aimesh,model )
+				
+				materials.Push( LoadMaterial( _scene.mMaterials[aimesh.mMaterialIndex],aimesh.mNumBones>0 ) )
+			Next
+						
+			model.Mesh.UpdateTangents()
+			
+			model.Materials=materials.ToArray()
+			
+		Endif
+		
+		Return model
+	End
+	
+	Method LoadAnimationChannel:AnimationChannel( aichan:aiNodeAnim )
+		
+		Local posKeys:=New PositionKey[ aichan.mNumPositionKeys ]
+		
+		For Local i:=0 Until posKeys.Length
+			
+			Local aikey:=aichan.mPositionKeys[i]
+			
+			posKeys[i]=New PositionKey( aikey.mTime,aikey.mValue )
+		Next
+		
+		Local rotKeys:=New RotationKey[ aichan.mNumRotationKeys ]
+		
+		For Local i:=0 Until rotKeys.Length
+			
+			Local aikey:=aichan.mRotationKeys[i]
+			
+			rotKeys[i]=New RotationKey( aikey.mTime,aikey.mValue )
+		Next
+		
+		Local sclKeys:=New ScaleKey[ aichan.mNumScalingKeys ]
+		
+		For Local i:=0 Until sclKeys.Length
+			
+			Local aikey:=aichan.mScalingKeys[i]
+			
+			sclKeys[i]=New ScaleKey( aikey.mTime,aikey.mValue )
+		Next
+		
+		Return New AnimationChannel( posKeys,rotKeys,sclKeys )
+		
+	End
+	
+	Method LoadAnimation:Animation( aianim:aiAnimation )
+		
+		Local channels:=New AnimationChannel[ _entities.Length ]
+		
+		For Local i:=0 Until aianim.mNumChannels
+			
+			Local aichan:=aianim.mChannels[i]
+			
+			Local id:=_entityIds[ aichan.mNodeName.data ]
+			
+			Print "i="+i+", id="+id
+			
+			channels[id]=LoadAnimationChannel( aichan )
+		
+		Next
+		
+		Return New Animation( channels,aianim.mDuration,aianim.mTicksPerSecond )
+		
+	End
+	
+	Method LoadAnimator:Animator()
+		
+		If Not _scene.mNumAnimations Return Null
+		
+		Local animations:=New Animation[_scene.mNumAnimations]
+		
+		For Local i:=0 Until _scene.mNumAnimations
+			
+			animations[i]=LoadAnimation( _scene.mAnimations[i] )
+
+		Next
+		
+		Return New Animator( animations,_entities.ToArray() )
+
+	End
+
+End
+
+Public
+
+#rem monkeydoc @hidden
+#End
+Class AssimpMojo3dLoader Extends Mojo3dLoader
+
+	Const Instance:=New AssimpMojo3dLoader
+	
+	Method LoadMesh:Mesh( path:String ) Override
+
+		Local flags:UInt=0
+		
+		flags|=aiProcess_MakeLeftHanded | aiProcess_FlipWindingOrder | aiProcess_FlipUVs
+		flags|=aiProcess_JoinIdenticalVertices | aiProcess_SortByPType
+		flags|=aiProcess_Triangulate | aiProcess_GenSmoothNormals 
+		flags|=aiProcess_PreTransformVertices
+		
+		Local scene:=LoadScene( path,flags )
+		If Not scene Return Null
+		
+		Local loader:=New AssimpLoader( scene,ExtractDir( path ) )
+		Local mesh:=loader.LoadMesh()
+		
+		Return mesh
+	End
+	
+	Method LoadModel:Model( path:String ) Override
+	
+		Local flags:UInt=0
+		
+		flags|=aiProcess_MakeLeftHanded | aiProcess_FlipWindingOrder | aiProcess_FlipUVs
+		flags|=aiProcess_JoinIdenticalVertices | aiProcess_SortByPType
+		flags|=aiProcess_Triangulate | aiProcess_GenSmoothNormals 
+		
+		Local scene:=LoadScene( path,flags )
+		If Not scene Return Null
+		
+		Local loader:=New AssimpLoader( scene,ExtractDir( path ) )
+		Local model:=loader.LoadModel()
+		
+		Return model
+	End
+	
+	Private
+
+	Function LoadScene:aiScene( path:String,flags:UInt )
+		
+		Local data:=DataBuffer.Load( path )
+
+		Local scene:=aiImportFileFromMemory( Cast<libc.char_t Ptr>( data.Data ),data.Length,flags,ExtractExt( path ).Slice( 1 ) )
+		
+		data.Discard()
+		
+		Return scene
+	End
+	
+End

+ 8 - 0
modules/mojo3d-loaders/module.json

@@ -0,0 +1,8 @@
+{
+	"module":"mojo3d-loaders",
+	"about":"Mojo3d assimp support",
+	"author":"Mark Sibly",
+	"version":"1.0.0",
+	"support":"http://monkeycoder.co.nz",
+	"depends":["std","assimp","mojo3d"]
+}

+ 13 - 0
modules/mojo3d-loaders/mojo3d-loaders.monkey2

@@ -0,0 +1,13 @@
+
+Namespace mojo3d
+
+#Import "<std>"
+#Import "<assimp>"
+#Import "<mojo3d>"
+
+Using std..
+Using assimp..
+Using mojo3d..
+Using mojo..
+
+#Import "loaders/assimp"

BIN
modules/mojo3d-loaders/tests/assets/miramar-skybox.jpg


BIN
modules/mojo3d-loaders/tests/assets/turtle1.b3d


BIN
modules/mojo3d-loaders/tests/assets/turtle1.png


+ 93 - 0
modules/mojo3d-loaders/tests/turtle.monkey2

@@ -0,0 +1,93 @@
+
+Namespace myapp
+
+#Import "<std>"
+#Import "<mojo>"
+#Import "<mojo3d>"
+#Import "<mojo3d-assimp>"
+
+#Import "../mojo3d"
+
+#Import "assets/"
+
+#Import "util"
+
+Using std..
+Using mojo..
+Using mojo3d..
+
+Class MyWindow Extends Window
+	
+	Field _scene:Scene
+	
+	Field _camera:Camera
+	
+	Field _light:Light
+	
+	Field _ground:Model
+	
+	Field _turtle:Model
+	
+	Method New( title:String="Simple mojo app",width:Int=640,height:Int=480,flags:WindowFlags=WindowFlags.Resizable )
+
+		Super.New( title,width,height,flags )
+		
+		'create scene
+		'		
+		_scene=Scene.GetCurrent()
+		_scene.SkyTexture=Texture.Load( "asset::miramar-skybox.jpg",TextureFlags.FilterMipmap|TextureFlags.Cubemap )
+		
+		'create camera
+		'
+		_camera=New Camera
+		_camera.Near=.1
+		_camera.Far=100
+		_camera.Move( 0,10,-20 )
+		
+		'create light
+		'
+		_light=New Light
+		_light.Rotate( Pi/2,0,0 )	'aim directional light 'down' - Pi/2=90 degrees.
+		
+		'create ground
+		'
+'		_ground=Model.CreateBox( New Boxf( -50,-1,-50,50,0,50 ),1,1,1,New PbrMaterial( Color.Green,0,.5 ) )
+		
+		'create turtle
+		'		
+		_turtle=Model.Load( "asset::turtle1.b3d" )
+		
+'		_turtle.Mesh.FitVertices( New Boxf( -1,1 ) )
+'		_turtle.Move( 0,10,0 )
+		
+	End
+		
+	Method OnRender( canvas:Canvas ) Override
+		
+		Global time:=0.0
+		
+		RequestRender()
+		
+		util.Fly( _camera,Self )
+		
+		If Keyboard.KeyDown( Key.Space ) time+=12.0/60.0
+			
+		_turtle.Animator.Animate( 0,time )
+		
+		_scene.Render( canvas,_camera )
+
+		canvas.Scale( Width/640.0,Height/480.0 )
+		
+		canvas.DrawText( "Width="+Width+", Height="+Height+", FPS="+App.FPS,0,0 )
+	End
+	
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MyWindow
+	
+	App.Run()
+End

+ 40 - 0
modules/mojo3d-loaders/tests/util.monkey2

@@ -0,0 +1,40 @@
+
+Namespace util
+
+Function Fly( entity:Entity,view:View )
+
+	If Keyboard.KeyDown( Key.Up )
+		entity.RotateX( .1 )
+	Else If Keyboard.KeyDown( Key.Down )
+		entity.RotateX( -.1 )
+	Endif
+	
+	If Keyboard.KeyDown( Key.Q )
+		entity.RotateZ( .1 )
+	Else If Keyboard.KeyDown( Key.W )
+		entity.RotateZ( -.1 )
+	Endif
+	
+	If Keyboard.KeyDown( Key.Left )
+		entity.RotateY( .1,True )
+	Else If Keyboard.KeyDown( Key.Right )
+		entity.RotateY( -.1,True )
+	Endif
+
+	If Mouse.ButtonDown( MouseButton.Left )
+		If Mouse.X<view.Width/3
+			entity.RotateY( .1,True )
+		Else If Mouse.X>view.Width/3*2
+			entity.RotateY( -.1,True )
+		Else
+			entity.Move( New Vec3f( 0,0,.1 ) )
+		Endif
+	Endif
+	
+	If Keyboard.KeyDown( Key.A )
+		entity.MoveZ( .1 )	'( New Vec3f( 0,0,.1 ) )
+	Else If Keyboard.KeyDown( Key.Z )
+		entity.MoveZ( -.1 )	'( New Vec3f( 0,0,-.1 ) )
+	Endif
+		
+End

+ 8 - 0
modules/mojo3d-physics/module.json

@@ -0,0 +1,8 @@
+{
+	"module":"mojo3d-physics",
+	"about":"Mojo3d bullet support",
+	"author":"Mark Sibly",
+	"version":"1.0.0",
+	"support":"http://monkeycoder.co.nz",
+	"depends":["std","assimp","mojo3d"]
+}

+ 16 - 0
modules/mojo3d-physics/mojo3d-physics.monkey2

@@ -0,0 +1,16 @@
+
+Namespace mojo3d
+
+#Import "<std>"
+#Import "<mojo3d>"
+#Import "<bullet>"
+
+Using std..
+Using mojo3d..
+Using bullet..
+
+#Import "physics/bttypeconvs"
+#Import "physics/collider"
+#Import "physics/rigidbody"
+#Import "physics/world"
+

+ 74 - 0
modules/mojo3d-physics/physics/bttypeconvs.monkey2

@@ -0,0 +1,74 @@
+
+Namespace mojo3d.physics
+
+Public
+
+Struct Vec3<T> Extension
+
+	Operator To:btVector3()
+
+		Return New btVector3( x,y,z )
+	End
+End
+
+Struct btVector3 Extension
+
+	Operator To:Vec3f()
+
+		Return New Vec3f( x,y,z )
+	End
+End
+
+Struct Vec4<T> Extension
+	Operator To:btVector4()
+
+		Return New btVector4( x,y,z,w )
+	End
+End
+
+Struct btVector4 Extension
+
+	Operator To:Vec4f()
+	
+		Return New Vec4f( x,y,z,w )
+	End
+End
+
+Struct Mat3f Extension
+
+	Operator To:btMatrix3x3()
+
+		Return New btMatrix3x3( i.x,j.x,k.x, i.y,j.y,k.y, i.z,j.z,k.z )
+	End
+End
+
+Struct btMatrix3x3 Extension
+
+	Operator To:Mat3f()
+	
+		Return New Mat3f( Cast<Vec3f>( getColumn(0) ),Cast<Vec3f>( getColumn(1) ),Cast<Vec3f>( getColumn(2) ) )
+	End
+End
+
+
+Struct AffineMat4f<T> Extension
+	
+	Operator To:btTransform()
+	
+		Return New btTransform( Cast<btMatrix3x3>( m ),Cast<btVector3>( t ) )
+	End
+
+End
+
+Struct btTransform Extension
+	
+	Operator To:AffineMat4f()
+	
+		Return New AffineMat4f( Cast<Mat3f>( getBasis() ),Cast<Vec3f>( getOrigin() ) )
+	End
+
+End
+
+
+
+

+ 171 - 0
modules/mojo3d-physics/physics/collider.monkey2

@@ -0,0 +1,171 @@
+
+Namespace mojo3d.physics
+
+#Import "native/internaledges.cpp"
+#Import "native/internaledges.h"
+
+Extern
+
+Function CreateInternalEdgeInfo( mesh:btBvhTriangleMeshShape )="bbBullet::createInternalEdgeInfo"
+	
+Public
+
+Class Collider
+
+	Property btShape:btCollisionShape()
+	
+		Return _btshape
+	End
+
+Protected
+
+	Field _btshape:btCollisionShape
+	
+	Method SetOrigin( origin:Vec3f )
+		
+		If origin=Null Return
+		
+		Local shape:=New btCompoundShape( False,1 )
+		
+		shape.addChildShape( AffineMat4f.Translation( origin ),_btshape )
+		
+		_btshape=shape
+	End
+	
+End
+
+Class BoxCollider Extends Collider
+	
+	Method New( box:Boxf )
+	
+		_btshape=New btBoxShape( box.Size/2 )
+		
+		SetOrigin( box.Center )
+	End
+	
+End
+
+Class SphereCollider Extends Collider
+	
+	Method New( radius:Float,origin:Vec3f=Null )
+		
+		_btshape=New btSphereShape( radius )
+		
+		SetOrigin( origin )
+	End
+	
+End
+
+Class CylinderCollider Extends Collider
+	
+	Method New( radius:Float,length:Float,axis:Axis,origin:Vec3f=Null )
+		
+		Select axis
+		case Axis.X
+			_btshape=New btCylinderShapeX( New btVector3( length/2,radius,radius ) )
+		Case Axis.Y
+			_btshape=New btCylinderShape ( New btVector3( radius,length/2,radius ) )
+		case Axis.Z
+			_btshape=New btCylinderShapeZ( New btVector3( radius,radius,length/2 ) )
+		Default
+			RuntimeError( "Invalid Cylinder Axis" )
+		End
+		
+		SetOrigin( origin )
+	End
+
+End
+
+Class CapsuleCollider Extends Collider
+	
+	Method New( radius:Float,length:Float,axis:Axis,origin:Vec3f=Null )
+		
+		Select axis
+		Case Axis.X
+			_btshape=New btCapsuleShapeX( radius,length )
+		Case Axis.Y
+			_btshape=New btCapsuleShape ( radius,length )
+		Case Axis.Z
+			_btshape=New btCapsuleShapeZ( radius,length )
+		Default
+			RuntimeError( "Invalid Capsule Axis" )
+		End
+		
+		SetOrigin( origin )
+	End
+	
+End
+
+Class ConeCollider Extends Collider
+	
+	Method New( radius:Float,length:Float,axis:Axis,origin:Vec3f=Null )
+		
+		Select axis
+		Case Axis.X
+			_btshape=New btConeShapeX( radius,length )
+		Case Axis.Y
+			_btshape=New btConeShape ( radius,length )
+		Case Axis.Z
+			_btshape=New btConeShapeZ( radius,length )
+		Default
+			RuntimeError( "Invalid Cone Axis" )
+		End
+		
+		SetOrigin( origin )
+	End
+
+End
+
+Class MeshCollider Extends Collider
+
+	Method New( mesh:Mesh )
+	
+		Local vertices:=mesh.GetVertices()
+		_vertices=New btVector3[vertices.Length]
+		
+		For Local i:=0 Until vertices.Length
+			_vertices[i].x=vertices[i].position.x
+			_vertices[i].y=vertices[i].position.y
+			_vertices[i].z=vertices[i].position.z
+		Next
+		
+		Local indices:=mesh.GetAllIndices()
+		_indices=New Int[indices.Length]
+		For Local i:=0 Until indices.Length
+			_indices[i]=indices[i]
+		Next
+		
+		_btmesh=New btTriangleIndexVertexArray( _indices.Length/3,_indices.Data,12,_vertices.Length,Cast<btScalar Ptr>( _vertices.Data ),16 )
+		
+		Local shape:=New btBvhTriangleMeshShape( _btmesh,True,True )
+		
+		CreateInternalEdgeInfo( shape )
+		
+		_btshape=shape
+	End
+	
+	Private
+	
+	Field _vertices:btVector3[]
+	Field _indices:Int[]
+	
+	Field _btmesh:btTriangleIndexVertexArray
+	
+End
+
+Class TerrainCollider Extends Collider
+
+	Method New( box:Boxf,data:Pixmap )
+	
+		Local shape:=New btHeightfieldTerrainShape( data.Width,data.Height,data.Data,1.0/255.0,0.0,1.0,1,PHY_UCHAR,False )
+		
+		shape.setUseDiamondSubdivision( True )
+		
+		_btshape=shape
+		
+		_btshape.setLocalScaling( New Vec3f( box.Width/data.Width,box.Height,box.Depth/data.Height ) )
+		
+		SetOrigin( box.Center )
+	End
+
+End

+ 35 - 0
modules/mojo3d-physics/physics/native/internaledges.cpp

@@ -0,0 +1,35 @@
+
+#include "internaledges.h"
+
+#include "BulletCollision/CollisionDispatch/btInternalEdgeUtility.h"
+
+namespace{
+
+	bool CustomMaterialCombinerCallback( btManifoldPoint& cp,const btCollisionObjectWrapper* colObj0Wrap,int partId0,int index0,const btCollisionObjectWrapper *colObj1Wrap,int partId1,int index1 ){
+	
+		btAdjustInternalEdgeContacts( cp,colObj1Wrap,colObj0Wrap,partId1,index1 );
+		
+//		cp.m_combinedRestitution=colObj0Wrap->getCollisionObject()->getRestitution() * colObj1Wrap->getCollisionObject()->getRestitution();
+		
+//		cp.m_combinedFriction=0;
+		
+		return false;
+	}
+}
+
+namespace bbBullet{
+
+	void createInternalEdgeInfo( btBvhTriangleMeshShape *mesh ){
+	
+		// enable callback
+		gContactAddedCallback=CustomMaterialCombinerCallback;
+
+		// create edge info
+		btTriangleInfoMap *info=new btTriangleInfoMap();
+		
+		btGenerateInternalEdgeInfo( mesh,info );
+
+		mesh->setTriangleInfoMap( info );
+	}
+
+}

+ 12 - 0
modules/mojo3d-physics/physics/native/internaledges.h

@@ -0,0 +1,12 @@
+
+#ifndef BB_INTERNAL_EDGES_H
+#define BB_INTERNAL_EDGES_H
+
+#include "btBulletDynamicsCommon.h"
+
+namespace bbBullet{
+
+	void createInternalEdgeInfo( btBvhTriangleMeshShape *mesh );
+}
+
+#endif

+ 16 - 0
modules/mojo3d-physics/physics/native/kinematicmotionstate.h

@@ -0,0 +1,16 @@
+
+#ifndef BB_KINEMATICMOTIONSTATE_H
+#define BB_KINEMATICMOTIONSTATE_H
+
+#include "btBulletDynamicsCommon.h"
+
+class bbKinematicMotionState : public btMotionState{
+	
+	virtual btTransform getWorldTransform()=0;
+	
+	virtual void setWorldTransform( const btTransform &tform ){}
+	
+	virtual void getWorldTransform( btTransform &tform )const{ tform=const_cast<bbKinematicMotionState*>( this )->getWorldTransform(); }
+};
+
+#endif

+ 184 - 0
modules/mojo3d-physics/physics/rigidbody.monkey2

@@ -0,0 +1,184 @@
+
+Namespace mojo3d.physics
+
+#Import "native/kinematicmotionstate.h"
+
+Extern
+
+Class bbKinematicMotionState Extends btMotionState
+	
+	Method GetWorldTransform:btTransform() Abstract="getWorldTransform"
+	
+End
+
+Public
+
+Class KinematicMotionState Extends bbKinematicMotionState
+	
+	Method New( entity:Entity )
+		
+		_entity=entity
+	End
+	
+	Method GetWorldTransform:btTransform() Override
+		
+		Return _entity.WorldMatrix
+	End
+	
+	Private
+	
+	Field _entity:Entity
+End
+
+#rem monkeydoc The RigidBody class.
+#end
+Class RigidBody
+	
+	#rem monkeydoc Creates a new rigid body.
+	#end
+	Method New( mass:Float,collider:Collider,entity:Entity,kinematic:Bool=False )
+		
+		_mass=mass
+		_collider=collider
+		_entity=entity
+		_kinematic=kinematic
+		_world=World.GetDefault()
+		
+		If _kinematic
+			_btmotion=New KinematicMotionState( _entity )
+		Else
+			_btmotion=New btDefaultMotionState( _entity.WorldMatrix )
+		Endif
+
+		Local inertia:btVector3=_collider ? _collider.btShape.calculateLocalInertia( _mass ) Else New btVector3( 0,0,0 )
+		
+		_btbody=New btRigidBody( _mass,_btmotion,_collider.btShape,inertia )
+			
+		If _kinematic 
+			_btbody.setCollisionFlags( _btbody.getCollisionFlags() | btCollisionObject.CF_KINEMATIC_OBJECT )
+			_btbody.setActivationState( DISABLE_DEACTIVATION )
+		Endif
+		
+		If Cast<MeshCollider>( _collider ) _btbody.setCollisionFlags( _btbody.getCollisionFlags() | btCollisionObject.CF_CUSTOM_MATERIAL_CALLBACK )
+
+		_entity.Shown+=Lambda()
+		
+			_world.Add( Self )
+		End
+		
+		_entity.Hidden+=Lambda()
+		
+			_world.Remove( Self )
+		End
+		
+		_entity.Copied+=Lambda( copy:Entity )
+		
+			Local body:=New RigidBody( Mass,Collider,copy )
+			
+			body.LinearVelocity=LinearVelocity
+			body.Restitution=Restitution
+			body.Friction=Friction
+			body.RollingFriction=RollingFriction
+		End
+		
+		If _entity.Visible _world.Add( Self )
+			
+		Restitution=0
+		Friction=1
+		RollingFriction=0
+	End
+
+	Property Mass:Float()
+		
+		Return _mass
+	End
+	
+	Property Collider:Collider()
+		
+		Return _collider
+	End
+	
+	Property Entity:Entity()
+	
+		Return _entity
+	End
+	
+	Property LinearVelocity:Vec3f()
+	
+		Return _btbody.getLinearVelocity()
+	
+	Setter( velocity:Vec3f )
+	
+		_btbody.setLinearVelocity( velocity )
+	End
+	
+	Property Restitution:Float()
+	
+		Return _btbody.getRestitution()
+		
+	Setter( restitution:Float )
+	
+		_btbody.setRestitution( restitution )
+	End
+	
+	Property Friction:Float()
+	
+		Return _btbody.getFriction()
+	
+	Setter( friction:Float )
+	
+		_btbody.setFriction( friction )
+	End
+	
+	Property RollingFriction:Float()
+	
+		Return _btbody.getRollingFriction()
+	
+	Setter( friction:Float )
+	
+		_btbody.setRollingFriction( friction )
+	End
+	
+	'***** INTERNAL *****
+	
+	Property btBody:btRigidBody()
+	
+		Return _btbody
+	End
+	
+	Method Validate()
+		
+		If _kinematic Or _seq=_entity.Seq Return
+		
+		_btbody.setWorldTransform( _entity.WorldMatrix )
+		
+		_btmotion.setWorldTransform( _entity.WorldMatrix )
+	End
+	
+	Method Update()
+		
+		If _kinematic Return
+	
+		Local tform:=_btmotion.getWorldTransform()
+		
+		_entity.WorldPosition=tform.getOrigin()
+		
+		_entity.WorldBasis=tform.getBasis()
+		
+		_seq=_entity.Seq
+	End
+
+Private
+
+	Field _mass:Float	
+	Field _collider:Collider
+	Field _entity:Entity
+	Field _kinematic:Bool
+	Field _world:World
+	Field _seq:Int
+	
+	Field _btmotion:btMotionState
+	Field _btbody:btRigidBody
+	
+End
+

+ 78 - 0
modules/mojo3d-physics/physics/world.monkey2

@@ -0,0 +1,78 @@
+
+Namespace mojo3d.physics
+
+Class World
+	
+	Method New()
+		
+		Local broadphase:=New btDbvtBroadphase()
+		
+		Local config:=New btDefaultCollisionConfiguration()
+
+		Local dispatcher:=New btCollisionDispatcher( config )
+		
+		Local solver:=New btSequentialImpulseConstraintSolver()
+		
+		_btworld=New btDiscreteDynamicsWorld( dispatcher,broadphase,solver,config )
+		
+		Gravity=New Vec3f( 0,-9.81,0 )
+		
+	End
+
+	Property Gravity:Vec3f()
+	
+		Return _btworld.getGravity()
+		
+	Setter( gravity:Vec3f )
+	
+		_btworld.setGravity( gravity )
+	End
+	
+	Method Update()
+
+		For Local body:=Eachin _bodies
+		
+			body.Validate()
+		Next
+		
+		_btworld.stepSimulation( 1.0/60.0 )
+		
+		For Local body:=Eachin _bodies
+		
+			body.Update()
+		Next
+		
+	End
+	
+	Function GetDefault:World()
+	
+		Global _default:=New World
+		
+		Return _default
+	End
+
+	'***** INTERNAL *****
+		
+	Method Add( body:RigidBody )
+		
+		_bodies.Add( body )
+		
+		_btworld.addRigidBody( body.btBody )
+	End
+	
+	Method Remove( body:RigidBody )
+		
+		_bodies.Remove( body )
+
+		_btworld.removeRigidBody( body.btBody )
+	End
+		
+	Private
+	
+	Field _btworld:btDynamicsWorld
+	
+	Field _newBodies:=New Stack<RigidBody>
+	
+	Field _bodies:=New Stack<RigidBody>
+	
+End

+ 164 - 0
modules/mojo3d-physics/tests/shapes.monkey2

@@ -0,0 +1,164 @@
+Namespace myapp
+
+#Import "<std>"
+#Import "<mojo>"
+#Import "<mojo3d>"
+#Import "<mojo3d-physics>"
+
+#Import "assets/"
+
+#Import "util"
+
+Using std..
+Using mojo..
+Using mojo3d..
+
+Function Randomize( model:Model )
+	
+	Local vertices:=model.Mesh.GetVertices()
+	
+	Local indices:=model.Mesh.GetAllIndices()
+	
+	Local mesh:=New Mesh
+	
+	mesh.AddVertices( vertices )
+	
+	mesh.AddMaterials( 9 )
+	
+	Local materials:=New Material[10]
+	
+	For Local i:=0 Until 10
+
+		materials[i]=New PbrMaterial( New Color( Rnd(),Rnd(),Rnd() ) )
+	
+	Next
+	
+	For Local i:=0 Until indices.Length Step 3
+		
+		mesh.AddTriangles( New UInt[]( indices[i],indices[i+1],indices[i+2] ),Rnd(10) )
+	
+	Next
+	
+	model.Mesh=mesh
+	
+	model.Materials=materials
+End
+
+Class MyWindow Extends Window
+	
+	Field _scene:Scene
+	
+	Field _camera:Camera
+	
+	Field _light:Light
+	
+	Field _ground:Model
+	
+	Field _sphere:Model
+	
+	Method New( title:String="Simple mojo app",width:Int=640,height:Int=480,flags:WindowFlags=WindowFlags.Resizable )
+
+		Super.New( title,width,height,flags )
+		
+		_scene=Scene.GetCurrent()
+		
+		_scene.ClearColor=Color.Sky
+		
+		'create camera
+		'
+		_camera=New Camera
+		_camera.Near=.1
+		_camera.Far=60
+		_camera.Move( 0,10,-10 )
+		
+		New RigidBody( 0,New SphereCollider( 1 ),_camera,True )
+
+		'create fog
+		'		
+		Local fog:=New FogEffect
+		fog.Color=Color.Sky
+		fog.Near=50
+		fog.Far=60
+		
+		'create light
+		'
+		_light=New Light
+		_light.RotateX( Pi/2 )	'aim directional light 'down' - Pi/2=90 degrees.
+		
+		'create ground
+		'
+		Local groundBox:=New Boxf( -60,-1,-60,60,0,60 )
+		
+		_ground=Model.CreateBox( groundBox,16,16,16,New PbrMaterial( Color.Green ) )
+		
+'		Randomize( _ground )
+		
+'		Local collider:Collider=New MeshCollider( _ground.Mesh )	'UGH - FIXME!
+		Local collider:Collider=New BoxCollider( groundBox )
+		
+		Local body:=New RigidBody( 0,collider,_ground )
+		
+		'create some meshes/colliders
+		
+		Local meshes:=New Mesh[5]
+		Local colliders:=New Collider[5]
+		
+		meshes[0]=Mesh.CreateBox( New Boxf( -1,-5,-1,1,5,1 ),1,1,1 )
+		colliders[0]=New BoxCollider(  New Boxf( -1,-5,-1,1,5,1 ) )
+		
+		meshes[1]=Mesh.CreateSphere( 1,32,16 )
+		colliders[1]=New SphereCollider( 1 )
+
+		meshes[2]=Mesh.CreateCylinder( 1,8,Axis.Y,32 )
+		colliders[2]=New CylinderCollider( 1,8,Axis.Y )
+
+		meshes[3]=Mesh.CreateCapsule( 1,10,Axis.Y,32 )
+		colliders[3]=New CapsuleCollider( 1,10,Axis.Y )
+
+		meshes[4]=Mesh.CreateCone( 2.5,5,Axis.Y,32 )
+		colliders[4]=New ConeCollider( 2.5,5,Axis.Y )
+		
+		For Local x:=-40 To 40 Step 8
+			
+			For Local z:=-40 To 40 Step 8
+				
+				Local i:=Int( Rnd(5) )
+				
+				Local mesh:=meshes[i]
+				Local material:=New PbrMaterial( New Color( Rnd(),Rnd(),Rnd() ) )
+				
+				Local model:=New Model( mesh,material )
+				model.Move( x,10,z )
+'				model.RotateZ( Rnd(-.1,.1) )
+				
+				Local body:=New RigidBody( 1,colliders[i],model )
+				
+			Next
+		
+		Next
+		
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		RequestRender()
+		
+		util.Fly( _camera,Self )
+		
+		World.GetDefault().Update()
+		
+		_scene.Render( canvas,_camera )
+		
+		canvas.DrawText( "Width="+Width+", Height="+Height+", FPS="+App.FPS,0,0 )
+	End
+	
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MyWindow
+	
+	App.Run()
+End

+ 40 - 0
modules/mojo3d-physics/tests/util.monkey2

@@ -0,0 +1,40 @@
+
+Namespace util
+
+Function Fly( entity:Entity,view:View )
+
+	If Keyboard.KeyDown( Key.Up )
+		entity.RotateX( .1 )
+	Else If Keyboard.KeyDown( Key.Down )
+		entity.RotateX( -.1 )
+	Endif
+	
+	If Keyboard.KeyDown( Key.Q )
+		entity.RotateZ( .1 )
+	Else If Keyboard.KeyDown( Key.W )
+		entity.RotateZ( -.1 )
+	Endif
+	
+	If Keyboard.KeyDown( Key.Left )
+		entity.RotateY( .1,True )
+	Else If Keyboard.KeyDown( Key.Right )
+		entity.RotateY( -.1,True )
+	Endif
+
+	If Mouse.ButtonDown( MouseButton.Left )
+		If Mouse.X<view.Width/3
+			entity.RotateY( .1,True )
+		Else If Mouse.X>view.Width/3*2
+			entity.RotateY( -.1,True )
+		Else
+			entity.Move( New Vec3f( 0,0,.1 ) )
+		Endif
+	Endif
+	
+	If Keyboard.KeyDown( Key.A )
+		entity.MoveZ( .1 )	'( New Vec3f( 0,0,.1 ) )
+	Else If Keyboard.KeyDown( Key.Z )
+		entity.MoveZ( -.1 )	'( New Vec3f( 0,0,-.1 ) )
+	Endif
+		
+End

+ 8 - 0
modules/mojo3d/docs/manual.md

@@ -0,0 +1,8 @@
+
+@manpage Mojo3d
+
+# Welcome to Mojo3d!
+
+The mojo3d module is a lightweight, easy to use 3d module.
+
+Please see [[mojo3d.graphics]] for API details.

+ 174 - 0
modules/mojo3d/graphics/animation.monkey2

@@ -0,0 +1,174 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc @hidden
+#end
+Alias PositionKey:AnimationKey<Vec3f>
+
+#rem monkeydoc @hidden
+#end
+Alias RotationKey:AnimationKey<Quatf>
+
+#rem monkeydoc @hidden
+#end
+Alias ScaleKey:AnimationKey<Vec3f>
+
+#rem monkeydoc @hidden
+#end
+Class Animation
+	
+	Method New( channels:AnimationChannel[],duration:Float,hertz:Float )
+		
+		_channels=channels
+		
+		_duration=duration
+		
+		_hertz=hertz
+	End
+	
+	Property Channels:AnimationChannel[]()
+		
+		Return _channels
+	End
+	
+	Property Duration:Float()
+		
+		Return _duration
+	End
+	
+	Property Hertz:Float()
+		
+		Return _hertz
+	End
+	
+	Private
+	
+	Field _channels:AnimationChannel[]
+	Field _duration:Float
+	Field _hertz:Float
+	
+End
+
+#rem monkeydoc @hidden
+#end
+Class AnimationChannel
+	
+	Method New( posKeys:PositionKey[],rotKeys:RotationKey[],sclKeys:ScaleKey[] )
+		
+		_posKeys=posKeys
+		_rotKeys=rotKeys
+		_sclKeys=sclKeys
+	End
+	
+	Property PositionKeys:PositionKey[]()
+		
+		Return _posKeys
+	End
+	
+	Property RotationKeys:RotationKey[]()
+		
+		Return _rotKeys
+	End
+	
+	Property ScaleKeys:ScaleKey[]()
+	
+		Return _sclKeys
+	End
+	
+	Method GetPosition:Vec3f( time:Float )
+		
+		If Not _posKeys Return New Vec3f( 0 )
+		
+		Return GetKey( _posKeys,time )
+	End
+	
+	Method GetRotation:Quatf( time:Float )
+		
+		If Not _rotKeys Return New Quatf( 0,0,0,1 )
+		
+		Return GetKey( _rotKeys,time )
+	End
+	
+	Method GetScale:Vec3f( time:Float )
+		
+		If Not _sclKeys Return New Vec3f( 1 )
+		
+		Return GetKey( _sclKeys,time )
+	End
+	
+	Method GetMatrix:AffineMat4f( time:Float )
+		
+		Local pos:=GetPosition( time )
+		Local rot:=GetRotation( time )
+		Local scl:=GetScale( time )
+		
+		Return New AffineMat4f( Mat3f.Rotation( rot ).Scale( scl ),pos )
+	End
+	
+	Private
+	
+	Field _posKeys:PositionKey[]
+	Field _rotKeys:RotationKey[]
+	Field _sclKeys:ScaleKey[]
+	
+	Method Blend:Vec3f( a:Vec3f,b:Vec3f,alpha:Float )
+		
+		Return a.Blend( b,alpha )
+	End
+	
+	Method Blend:Quatf( a:Quatf,b:Quatf,alpha:Float )
+		
+		Return a.Slerp( b,alpha )
+	End
+	
+	Method GetKey<T>:T( keys:AnimationKey<T>[],time:Float )
+		
+		DebugAssert( keys )
+		
+		Local pkey:AnimationKey<T>
+		
+		For Local key:=Eachin keys
+			
+			If time<=key.Time
+				
+				If pkey Return Blend( pkey.Value,key.Value,(time-pkey.Time)/(key.Time-pkey.Time) )
+				
+				Return key.Value
+				
+			Endif
+			
+			pkey=key
+		End
+		
+		Return pkey.Value
+	End
+
+End
+
+#rem monkeydoc @hidden
+#end
+Class AnimationKey<T>
+	
+	Method New( time:Float,value:T )
+		
+		_time=time
+		_value=value
+	End
+	
+	Property Time:Float()
+		
+		Return _time
+	End
+	
+	Property Value:T()
+		
+		Return _value
+	End
+
+	Private
+		
+	Field _time:float
+	Field _value:T
+End
+
+

+ 55 - 0
modules/mojo3d/graphics/animator.monkey2

@@ -0,0 +1,55 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc The Animator class.
+#end
+Class Animator
+	
+	Method New( animations:Animation[],entities:Entity[] )
+		
+		For Local i:=0 Until animations.Length
+			Print "anim["+i+"].Channels="+animations[i].Channels.Length
+			Print "anim["+i+"].Duration="+animations[i].Duration
+			Print "anim["+i+"].Hertz="+animations[i].Hertz
+		Next			
+		
+		Print "entities="+entities.Length
+		
+		_animations=animations
+		
+		_entities=entities
+	End
+	
+	Property Animations:Animation[]()
+		
+		Return _animations
+	End
+	
+	Property Entities:Entity[]()
+		
+		Return _entities
+	End
+	
+	Method Animate( animationId:Int,time:Float )
+		
+		Local animation:=_animations[animationId]
+
+		For Local i:=0 Until animation.Channels.Length
+			
+			Local channel:=animation.Channels[i]
+			If Not channel continue
+			
+			_entities[i].Position=channel.GetPosition( time )
+			_entities[i].Basis=New Mat3f( channel.GetRotation( time ) )
+			_entities[i].Scale=channel.GetScale( time )
+		End
+		
+	End
+	
+	Private
+
+	Field _animations:Animation[]
+
+	Field _entities:Entity[]
+	
+End

+ 110 - 0
modules/mojo3d/graphics/bloomeffect.monkey2

@@ -0,0 +1,110 @@
+
+Namespace mojo3d.graphics
+
+#rem The BloomEffect class.
+
+This class implements a 'bloom' post processing effect.
+
+#end
+Class BloomEffect Extends PostEffect
+	
+	#rem monkeydoc Creates a new bloom effect.
+	#end
+	Method New( passes:Int=2 )
+		
+		_shader=Shader.Open( "bloom" )
+		
+		_uniforms=New UniformBlock( 2 )
+		
+		Passes=passes
+	End
+	
+	#rem monkeydoc The number of passes.
+	
+	Must be an even number greater than 0.
+	
+	#end
+	Property Passes:Int()
+		
+		Return _passes
+	
+	Setter( passes:Int )
+		Assert( passes>0 And (passes&1)=0,"BloomEffect passes must be even and >0" )
+		
+		_passes=passes
+	End
+	
+	Protected
+	
+	#rem monkeydoc @hidden
+	#end
+	Method OnRender() Override
+		
+		Local rsize:=Device.Viewport.Size
+		Local rtarget:=Device.RenderTarget
+		Local rtexture:=rtarget.GetColorTexture( 0 )
+		
+		If Not _target0 Or rsize.x>_target0.Size.x Or rsize.y>_target0.Size.y
+			
+			SafeDiscard( _target0 ) ; SafeDiscard( _texture0 )
+			SafeDiscard( _target1 ) ; SafeDiscard( _texture1 )
+			
+			_texture0=New Texture( rsize.x,rsize.y,rtexture.Format,Null )
+			_texture1=New Texture( rsize.x,rsize.y,rtexture.Format,Null )
+
+			_target0=New RenderTarget( New Texture[]( _texture0 ),Null )
+			_target1=New RenderTarget( New Texture[]( _texture1 ),Null )
+		Endif
+
+		Device.Shader=_shader
+		Device.BindUniformBlock( _uniforms )
+
+		Local target:=_target0
+		Local source:=rtexture
+		
+		For Local i:=0 Until _passes
+
+			_uniforms.SetTexture( "SourceTexture",source )
+			_uniforms.SetVec2f( "SourceTextureSize",source.Size )
+			_uniforms.SetVec2f( "SourceTextureScale",Cast<Vec2f>( rsize )/Cast<Vec2f>( source.Size ) )
+
+			Device.RenderTarget=target
+			Device.RenderPass=i ? 2-(i&1) Else 0	'0,1,2,1,2,1,2...
+			
+			RenderQuad()
+			
+			If target=_target0
+				source=_texture0
+				target=_target1
+			Else
+				source=_texture1
+				target=_target0
+			Endif
+			
+		Next
+		
+		_uniforms.SetTexture( "SourceTexture",source )
+		_uniforms.SetVec2f( "SourceTextureSize",source.Size )
+		_uniforms.SetVec2f( "SourceTextureScale",Cast<Vec2f>( rsize )/Cast<Vec2f>( source.Size ) )
+		
+		Device.RenderTarget=rtarget
+		Device.BlendMode=BlendMode.Additive
+		Device.RenderPass=3
+		
+		RenderQuad()
+	End
+	
+	Private
+	
+	Field _shader:Shader
+	Field _uniforms:UniformBlock
+	
+	Field _passes:Int=4
+	
+	Field _texture0:Texture
+	Field _texture1:Texture
+	
+	Field _target0:RenderTarget
+	Field _target1:RenderTarget
+	
+End

+ 166 - 0
modules/mojo3d/graphics/camera.monkey2

@@ -0,0 +1,166 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc The Camera class.
+#end
+Class Camera Extends Entity
+
+	#rem monkeydoc Creates a new camera.
+	#end	
+	Method New( parent:Entity=Null )
+		Super.New( parent )
+		
+		Viewport=New Recti( 0,0,640,480 )
+		Fov=90
+		Near=1
+		Far=1000
+		
+		Show()
+	End
+	
+	#rem monkeydoc Copies the camera.
+	#end
+	Method Copy:Camera( parent:Entity=Null ) Override
+		
+		Local copy:=New Camera( Self,parent )
+		
+		CopyComplete( copy )
+		
+		Return copy
+	End
+
+	#rem monkeydoc @hidden
+	#end	
+	Property Viewport:Recti()
+		
+		Return _viewport
+		
+	Setter( viewport:Recti )
+		
+		_viewport=viewport
+
+		_aspect=Float( _viewport.Width )/Float( _viewport.Height )
+		
+		_dirty|=Dirty.ProjMatrix
+	End
+	
+	#rem monkeydoc Aspect ratio.
+	
+	Defaults to 1.0.
+	
+	#end
+	Property Aspect:Float()
+		
+		Return _aspect
+	End
+	
+	#rem monkeydoc Vertical field of view in degrees.
+	
+	Defaults to 90.0
+	
+	#end
+	Property Fov:Float()
+	
+		Return _fovy
+		
+	Setter( fovy:Float )
+	
+		_fovy=fovy
+		
+		_dirty|=Dirty.ProjMatrix
+	End
+	
+	#rem monkeydoc Near clip plane distance.
+	
+	Defaults to 1.0.
+	
+	#end
+	Property Near:Float()
+	
+		Return _near
+	
+	Setter( nearz:Float )
+	
+		_near=nearz
+		
+		_dirty|=Dirty.ProjMatrix
+	End
+	
+	#rem monkeydoc Far clip plane distance.
+	
+	Defaults to 1000.0.
+	
+	#end
+	Property Far:Float()
+	
+		Return _farz
+	
+	Setter( farz:Float )
+	
+		_farz=farz
+		
+		_dirty|=Dirty.ProjMatrix
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property ProjectionMatrix:Mat4f()
+	
+		If _dirty & Dirty.ProjMatrix
+			
+			_projMatrix=Mat4f.Perspective( _fovy,_aspect,_near,_farz )
+		
+			_dirty&=~Dirty.ProjMatrix
+		Endif
+		
+		Return _projMatrix
+	End
+	
+	Protected
+
+	#rem monkeydoc @hidden
+	#end	
+	Method New( camera:Camera,parent:Entity )
+		Super.New( camera,parent )
+		
+		Viewport=camera.Viewport
+		Fov=camera.Fov
+		Near=camera.Near
+		Far=camera.Far
+		
+		Show()
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Method OnShow() Override
+		Scene.Cameras.Add( Self )
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Method OnHide() Override
+		Scene.Cameras.Remove( Self )
+	End
+	
+	Private
+	
+	Enum Dirty
+		ProjMatrix=1
+	End
+	
+	Field _viewport:=New Recti( 0,0,640,480 )
+	
+	Field _aspect:Float
+	
+	Field _fovy:Float
+	
+	Field _near:Float
+	
+	Field _farz:Float
+	
+	Field _dirty:Dirty=Dirty.ProjMatrix
+	
+	Field _projMatrix:Mat4f
+	
+End

+ 165 - 0
modules/mojo3d/graphics/deferredrenderer.monkey2

@@ -0,0 +1,165 @@
+
+Namespace mojo3d.graphics
+
+Private
+
+Const WIDTH:=1920
+Const HEIGHT:=1080
+Const MRT_COLOR_FORMAT:=PixelFormat.RGBA8'RGBA32F
+
+Public
+
+#rem monkeydoc @hidden The DeferredRenderer class.
+#end
+Class DeferredRenderer Extends Renderer
+	
+	Method New()
+	
+		Local size:=New Vec2i( WIDTH,HEIGHT )
+	
+		_copyShader=Shader.Open( "copy" )
+		_plightShader=Shader.Open( "point-light" )
+		_dlightShader=Shader.Open( "directional-light" )
+		
+		_hdrTexture=New Texture( size.x,size.y,MRT_COLOR_FORMAT,TextureFlags.Filter|TextureFlags.Dynamic )			'output hdr image
+		_colorTexture=New Texture( size.x,size.y,MRT_COLOR_FORMAT,TextureFlags.Filter|TextureFlags.Dynamic )		'metalness in 'a'
+		_normalTexture=New Texture( size.x,size.y,MRT_COLOR_FORMAT,TextureFlags.Filter|TextureFlags.Dynamic )		'roughness in 'a'
+		_depthTexture=New Texture( size.x,size.y,PixelFormat.Depth32F,TextureFlags.Dynamic )
+		
+		_rpass0Target=New RenderTarget( New Texture[]( _hdrTexture,_colorTexture,_normalTexture ),_depthTexture )
+		_rpass2Target=New RenderTarget( New Texture[]( _hdrTexture ),Null )
+		
+		'_uniforms.SetTexture( "HdrTexture",_hdrTexture )
+		
+		_uniforms.SetTexture( "ColorBuffer",_colorTexture )
+		_uniforms.SetTexture( "NormalBuffer",_normalTexture )
+		_uniforms.SetTexture( "DepthBuffer",_depthTexture )
+	End
+
+	Protected
+	
+	Method OnRender() Override
+		
+		_device.RenderTarget=_rpass0Target
+		_device.Viewport=New Recti( 0,0,_renderViewport.Size )
+		_device.Scissor=_device.Viewport
+
+		RenderBackground()
+		
+		RenderAmbient()
+
+		_device.RenderTarget=_rpass2Target
+		
+		_uniforms.SetVec2f( "BufferCoordScale",Cast<Vec2f>( _renderViewport.Size )/Cast<Vec2f>( _hdrTexture.Size ) )
+		
+		For Local light:=Eachin _scene.Lights
+			
+			If light.Type=LightType.Point Continue
+			
+			RenderCSMShadows( light )
+			
+			RenderLight( light )
+		Next
+		
+		RenderSprites()
+
+		RenderEffects()
+		
+		RenderCopy()
+	End
+	
+	Method RenderLight( light:Light )
+	
+		_uniforms.SetVec4f( "LightColor",light.Color )
+		_uniforms.SetFloat( "LightRange",light.Range )
+		_uniforms.SetMat4f( "LightViewMatrix",_camera.InverseWorldMatrix * light.WorldMatrix )
+		
+		_uniforms.SetMat4f( "InverseProjectionMatrix",-_camera.ProjectionMatrix )
+		
+		_device.ColorMask=ColorMask.All
+		_device.DepthMask=False
+		_device.DepthFunc=DepthFunc.Always
+		_device.BlendMode=BlendMode.Additive
+		_device.CullMode=CullMode.None
+		_device.RenderPass=3
+		
+		Select light.Type
+		Case LightType.Directional
+		
+			_device.Shader=_dlightShader
+			_device.VertexBuffer=_quadVertices
+			_device.Render( 4,1,0 )
+		
+		Case LightType.Point
+
+			_device.Shader=_plightShader
+			_device.VertexBuffer=_quadVertices
+			_device.Render( 4,1,0 )
+		End
+
+	End
+	
+	Method RenderEffects()
+		
+		_device.ColorMask=ColorMask.All
+		_device.DepthMask=False
+		_device.DepthFunc=DepthFunc.Always
+		_device.CullMode=CullMode.None
+
+		_device.VertexBuffer=_quadVertices
+		
+		For Local effect:=Eachin _scene.PostEffects
+			
+			If Not effect.Enabled Continue
+			
+			_device.BlendMode=BlendMode.Opaque
+			_device.RenderPass=0
+			
+			effect.Render( _device )
+		Next
+		
+	End
+	
+	Method RenderCopy()
+		
+		Local source:=_device.RenderTarget.GetColorTexture( 0 )
+		
+		_uniforms.SetTexture( "SourceTexture",source )
+		_uniforms.SetVec2f( "SourceCoordScale",Cast<Vec2f>( _renderViewport.Size )/Cast<Vec2f>( source.Size ) )
+
+		_device.RenderTarget=_renderTarget
+		_device.Resize( _renderTargetSize )
+		_device.Viewport=_renderViewport
+		_device.Scissor=_device.Viewport
+		_device.ColorMask=ColorMask.All
+		_device.DepthMask=False
+		_device.DepthFunc=DepthFunc.Always
+		_device.BlendMode=BlendMode.Opaque
+		_device.CullMode=CullMode.None
+		_device.RenderPass=0
+		
+		_device.VertexBuffer=_quadVertices
+		_device.Shader=_copyShader
+		
+		_device.Render( 4,1 )
+		
+		_device.RenderTarget=Null
+		_device.Resize( Null )
+	End
+	
+	Private
+	
+	Field _copyShader:Shader
+	Field _plightShader:Shader
+	Field _dlightShader:Shader
+	
+	Field _hdrTexture:Texture		'contains output linear HDR color
+	Field _colorTexture:Texture		'contains surface color/M
+	Field _normalTexture:Texture	'contains surface normal/R
+	Field _depthTexture:Texture		'contains surface depth
+	Field _shadowTexture:Texture
+	
+	Field _rpass0Target:RenderTarget
+	Field _rpass2Target:RenderTarget
+	
+End

+ 651 - 0
modules/mojo3d/graphics/entity.monkey2

@@ -0,0 +1,651 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc The Entity class.
+#end
+Class Entity
+	
+	#rem monkeydoc Copied signal.
+	
+	Invoked after an entity is copied.
+	
+	#end
+	Field Copied:Void( copy:Entity )
+
+	#rem monkeydoc Destroyed signal.
+	
+	Invoked after an entity is destroyed.
+	
+	#end
+	Field Destroyed:Void()
+	
+	#rem monkeydoc Hidden signal.
+	
+	Invoked after an entity is hidden.
+	
+	#end
+	Field Hidden:Void()
+	
+	#rem monkeydoc Shown signal.
+	
+	Invoked after an entity is shown.
+	
+	#end
+	Field Shown:Void()
+	
+	#rem monkeydoc Creates a new entity.
+	#end
+	Method New( parent:Entity=Null )
+		
+		_parent=parent
+		
+		If _parent 
+			_scene=_parent._scene
+			
+			_parent._children.Add( Self )
+		Else
+			_scene=Scene.GetCurrent()
+			
+			_scene.RootEntities.Add( Self )
+		Endif
+			
+		Invalidate()
+	End
+	
+	#rem monkeydoc Creates a copy of the entity.
+	#end
+	Method Copy:Entity( parent:Entity=Null ) Virtual
+		
+		Local copy:=New Entity( Self,parent )
+
+		CopyComplete( copy )
+		
+		Return copy
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property Seq:Int()
+		
+		Return _seq
+	End
+	
+	#rem monkeydoc Entity name.
+	#end
+	Property Name:String()
+		
+		Return _name
+	
+	Setter( name:String )
+		
+		_name=name
+	End
+
+	#rem monkeydoc @hidden
+	#end
+	Property Scene:Scene()
+	
+		Return _scene
+	End
+	
+	#rem monkeydoc Parent entity.
+	#end
+	Property Parent:Entity()
+		
+		Return _parent
+	
+	Setter( parent:Entity )
+		
+		Assert( Not parent Or parent._scene=_scene )
+		
+		If _parent
+			_parent._children.Remove( Self )
+		Else
+			_scene.RootEntities.Remove( Self )
+		Endif
+		
+		_parent=parent
+		
+		If _parent 
+			_parent._children.Add( Self )
+		Else
+			_scene.RootEntities.Add( Self )
+		Endif
+			
+		Invalidate()
+	End
+	
+	#rem monkeydoc entity visibility flag.
+	#end
+	Property Visible:Bool()
+		
+		Return _visible
+	
+	Setter( visible:Bool )
+		
+		If _visible Show() Else Hide()
+	End
+
+	#rem monkeydoc entity animator.
+	#end	
+	Property Animator:Animator()
+		
+		Return _animator
+	
+	Setter( animator:Animator )
+		
+		_animator=animator
+	End
+	
+	'***** Local space properties *****
+
+	#rem monkeydoc Local transformation matrix.
+	
+	The local matrix combines the local position, rotation and scale of the entity into a single affine 4x4 matrix.
+	
+	#end
+	Property Matrix:AffineMat4f()
+		
+		If _dirty & Dirty.M
+			_M=New AffineMat4f( _r.Scale( _s ),_t )
+			_dirty&=~Dirty.M
+		Endif
+		
+		Return _M
+	End
+
+	#rem monkeydoc Local position.
+	#end
+	Property Position:Vec3f()
+
+		Return _t
+		
+	Setter( position:Vec3f )
+		
+		_t=position
+		
+		Invalidate()
+	End
+	
+	#rem monkeydoc Local rotation basis matrix.
+	
+	A basis matrix is a 3x3 matrix representation of a rotation.
+	
+	A basis matrix is orthogonal (ie: the i,j,k members are perpendicular to each other) and normalized (ie: the i,j,k members all have unit length).
+	
+	#end
+	Property Basis:Mat3f()
+		
+		Return _r
+	
+	Setter( basis:Mat3f )
+		
+		_r=basis
+		
+		Invalidate()
+	End
+
+	#rem monkeydoc Local rotation in euler angles.
+	#end
+	Property Rotation:Vec3f()
+		
+		Return _r.GetRotation()
+	
+	Setter( rotation:Vec3f )
+		
+		_r=Mat3f.Rotation( rotation )
+		
+		Invalidate()
+	End
+	
+	#rem monkeydoc Local scale.
+	#end	
+	Property Scale:Vec3f()
+		
+		Return _s
+	
+	Setter( scale:Vec3f )
+		
+		_s=scale
+		
+		Invalidate()
+	End
+	
+	#rem monkeydoc X coordinate of local position.
+	#end
+	Property X:Float()
+		
+		Return _t.x
+		
+	Setter( x:Float )
+		
+		_t.x=x
+		
+		Invalidate()
+	End
+	
+	#rem monkeydoc Y coordinate of local position.
+	#end
+	Property Y:Float()
+	
+		Return _t.y
+	
+	Setter( y:Float )
+		
+		_t.y=y
+		
+		Invalidate()
+	End
+
+	#rem monkeydoc Z coordinate of local position.
+	#end
+	Property Z:Float()
+	
+		Return _t.z
+	
+	Setter( z:Float )
+		
+		_t.z=z
+		
+		Invalidate()
+	End
+	
+	'***** World space properties *****
+	
+	#rem monkeydoc World transformation matrix.
+	
+	The world matrix combines the world position, rotation and scale of the entity into a single affine 4x4 matrix.
+	
+	#end
+	Property WorldMatrix:AffineMat4f()
+		
+		If _dirty & Dirty.W
+			_W=_parent ? _parent.WorldMatrix * Matrix Else Matrix
+			_dirty&=~Dirty.W
+		Endif
+		
+		Return _W
+	End
+	
+	#rem monkeydoc Inverse world matrix.
+	#end
+	Property InverseWorldMatrix:AffineMat4f()
+		
+		If _dirty & Dirty.IW
+			_IW=-WorldMatrix
+			_dirty&=~Dirty.IW
+		Endif
+		
+		Return _IW
+	End
+	
+	#rem monkeydoc World position.
+	#end
+	Property WorldPosition:Vec3f()
+		
+		Return WorldMatrix.t
+		
+	Setter( position:Vec3f )
+		
+		_t=_parent ? _parent.InverseWorldMatrix * position Else position
+		
+		Invalidate()
+	End
+	
+	#rem monkeydoc World basis rotation matrix.
+
+	A basis matrix is a 3x3 matrix representation of a rotation.
+	
+	A basis matrix is orthogonal (ie: the i,j,k members are perpendicular to each other) and normalized (ie: the i,j,k members all have unit length).
+	
+	#end
+	Property WorldBasis:Mat3f()
+		
+		Return _parent ? _parent.WorldBasis * _r Else _r
+	
+	Setter( basis:Mat3f )
+		
+		_r=_parent ? ~_parent.WorldBasis * basis Else basis
+		
+		Invalidate()
+	End
+	
+	#rem monkeydoc World rotation in euler angles.
+	#end
+	Property WorldRotation:Vec3f()
+		
+		Return WorldBasis.GetRotation()
+	
+	Setter( rotation:Vec3f )
+		
+		WorldBasis=Mat3f.Rotation( rotation )
+	End
+	
+	#rem monkeydoc World scale.
+	#end	
+	Property WorldScale:Vec3f()
+		
+		Return _parent ? Scale * _parent.WorldScale Else _s
+	
+	Setter( scale:Vec3f )
+		
+		_s=_parent ? scale / _parent.WorldScale Else scale
+		
+		Invalidate()
+	End
+	
+	#rem monkeydoc X coordinate of world position.
+	#end
+	Property WorldX:Float()
+		
+		Return WorldPosition.x
+		
+	Setter( x:Float )
+
+		Local v:=WorldPosition		
+		WorldPosition=New Vec3f( x,v.y,v.z )
+	End
+	
+	#rem monkeydoc Y coordinate of world position.
+	#end
+	Property WorldY:Float()
+	
+		Return WorldPosition.y
+	
+	Setter( y:Float )
+		
+		Local v:=WorldPosition		
+		WorldPosition=New Vec3f( v.x,y,v.z )
+	End
+
+	#rem monkeydoc Z coordinate of world position.
+	#end
+	Property WorldZ:Float()
+	
+		Return _t.z
+	
+	Setter( z:Float )
+		
+		Local v:=WorldPosition		
+		WorldPosition=New Vec3f( v.x,v.y,z )
+	End
+	
+	'***** Methods ******
+
+	#rem monkeydoc Hides the entity and all of its children
+	#end
+	Method Hide()
+		
+		If _visible
+			_visible=False
+			OnHide()
+		Endif
+		
+		For Local child:=Eachin _children
+			child.Hide()
+		Next
+	End
+	
+	#rem monkeydoc Hides the entity and all of its children
+	#end
+	Method Show()
+		
+		If Not _visible
+			_visible=True
+			OnShow()
+		Endif
+
+		For Local child:=Eachin _children
+			child.Show()
+		Next
+	End
+	
+	#rem monkeydoc Destroys the entity and all of its children.
+	#end
+	Method Destroy()
+		
+		While Not _children.Empty
+			_children.Top.Destroy()
+		Wend
+		
+		If _visible 
+			OnHide()
+			_visible=False
+		Endif
+
+		If _parent
+			_parent._children.Remove( Self )
+		Else
+			_scene.RootEntities.Remove( Self )
+		Endif
+		
+		_parent=Null
+		
+		_scene=Null
+	End
+	
+	#rem monkeydoc Sets entity position in local or world space.
+	#end
+	Method SetPosition( position:Vec3f,worldSpace:Bool=False )
+		
+		If worldSpace WorldPosition=position Else Position=position
+	End
+	
+	Method SetPosition( x:Float,y:Float,z:Float,worldSpace:Bool=False )
+		
+		SetPosition( x,y,z,worldSpace )
+	End
+	
+	#rem monkeydoc Gets entity position in local or world space.
+	#end
+	Method GetPostition:Vec3f( worldSpace:Bool=False )
+		
+		Return worldSpace ? WorldPosition Else Position
+	End
+	
+	#rem monkeydoc Sets entity rotation in euler angles in local or world space.
+	#end
+	Method SetRotation( rotation:Vec3f,worldSpace:Bool=False )
+		
+		If worldSpace WorldRotation=rotation Else Rotation=rotation
+	End
+	
+	Method SetRotation( rx:Float,ry:Float,rz:Float,worldSpace:Bool=False )
+		
+		SetRotation( New Vec3f( rx,ry,rz ),worldSpace )
+	End
+	
+	#rem monkeydoc Gets entity rotation in euler angles in local or world space.
+	#end
+	Method GetRotation:Vec3f( worldSpace:Bool=False )
+		
+		Return worldSpace ? WorldRotation Else Rotation
+	End
+	
+	#rem monkeydoc Sets entity scale in local or world space.
+	#end
+	Method SetScale( scale:Vec3f,worldSpace:Bool=False )
+		
+		If worldSpace WorldScale=scale Else Scale=scale
+	End
+	
+	Method SetScale( sx:Float,sy:Float,sz:Float,worldSpace:Bool=False )
+		
+		SetScale( New Vec3f( sx,sy,sz ),worldSpace )
+	End
+
+	#rem monkeydoc Gets entity scale in local or world space.
+	#end
+	Method GetScale:Vec3f( worldSpace:Bool=False )
+		
+		Return worldSpace ? WorldScale Else Scale
+	End
+	
+	#rem monkeydoc Moves the entity.
+	
+	Moves the entity relative to its current orientation.
+	
+	#end	
+	Method Move( tv:Vec3f )
+		
+		Position+=Basis * tv
+	End
+	
+	Method Move( tx:Float,ty:Float,tz:Float )
+		
+		Move( New Vec3f( tx,ty,tz ) )
+	End
+	
+	#rem monkeydoc Moves the entity on the X axis.
+	
+	Moves the entity relative to its current orientation.
+	
+	#end	
+	Method MoveX( tx:Float )
+		
+		Position+=Basis.i * tx
+	End
+	
+	#rem monkeydoc Moves the entity on the Y axis.
+	
+	Moves the entity relative to its current orientation.
+	
+	#end	
+	Method MoveY( ty:Float )
+
+		Position+=Basis.j * ty
+	End
+	
+	#rem monkeydoc Moves the entity on the Z axis.
+	
+	Moves the entity relative to its current orientation.
+	
+	#end	
+	Method MoveZ( tz:Float )
+
+		Position+=Basis.k * tz
+	End
+
+	#rem monkeydoc Rotates the entity.
+	
+	Rotates the entity.
+	
+	If `postRotate` is true, the rotation is applied after the entity's world rotation.
+		
+	If `postRotate` is false, the rotation is applied before the entity's local rotation.
+		
+	#end
+	Method Rotate( rv:Vec3f,postRotate:Bool=False )
+		
+		If postRotate WorldBasis=Mat3f.Rotation( rv )*WorldBasis Else Basis*=Mat3f.Rotation( rv )
+	End
+	
+	Method Rotate( rx:Float,ry:Float,rz:Float,postRotate:Bool=False )
+		
+		Rotate( New Vec3f( rx,ry,rz ),postRotate )
+	End
+	
+	#rem monkeydoc Rotates the entity around the X axis.
+	#end
+	Method RotateX( rx:Float,postRotate:Bool=False )
+		
+		If postRotate WorldBasis=Mat3f.Pitch( rx )*WorldBasis Else Basis*=Mat3f.Pitch( rx )
+	End
+
+	#rem monkeydoc Rotates the entity around the Y axis.
+	#end
+	Method RotateY( ry:Float,postRotate:Bool=False )
+		
+		If postRotate WorldBasis=Mat3f.Yaw( ry )*WorldBasis Else Basis*=Mat3f.Yaw( ry )
+	End
+
+	#rem monkeydoc Rotates the entity around the Z axis.
+	#end
+	Method RotateZ( rz:Float,postRotate:Bool=False )
+		
+		If postRotate WorldBasis=Mat3f.Roll( rz )*WorldBasis Else Basis*=Mat3f.Roll( rz )
+	End
+	
+Protected
+
+	#rem monkeydoc @hidden
+	#end
+	Method New( entity:Entity,parent:Entity )
+		Self.New( parent )
+		
+		_t=entity._t
+		_r=entity._r
+		_s=entity._s
+		
+		Invalidate()
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method OnShow() Virtual
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method OnHide() Virtual
+	End
+		
+	#rem monkeydoc @hidden
+	#end
+	Method CopyComplete( copy:Entity )
+	
+		For Local child:=Eachin _children
+			child.Copy( copy )
+		Next
+
+		Copied( copy )
+	End
+
+Private
+	
+	Enum Dirty
+		M=1
+		W=2
+		IW=4
+		All=7
+	End
+	
+	Field _name:String
+	Field _scene:Scene
+	Field _parent:Entity
+	Field _children:=New Stack<Entity>
+	Field _visible:Bool
+	Field _animator:Animator
+	
+	Field _t:Vec3f=New Vec3f
+	Field _r:Mat3f=New Mat3f
+	Field _s:Vec3f=New Vec3f(1)
+	Field _seq:Int=1
+	
+	Field _dirty:Dirty=Dirty.All
+	Field _M:AffineMat4f
+	Field _W:AffineMat4f
+	Field _IW:AffineMat4f
+	
+	Method InvalidateWorld()
+		
+		If _dirty & _dirty.W Return
+		
+		_dirty|=Dirty.W|Dirty.IW
+		
+		For Local child:=Eachin _children
+			
+			child.InvalidateWorld()
+		Next
+		
+		_seq+=1
+	End
+		
+	Method Invalidate()
+		
+		_dirty|=Dirty.M
+		
+		InvalidateWorld()
+	End
+
+End

+ 78 - 0
modules/mojo3d/graphics/fogeffect.monkey2

@@ -0,0 +1,78 @@
+
+Namespace mojo3d.graphics
+
+#rem The FogEffect class.
+
+This class implements a 'fog' post processing effect.
+
+#end
+Class FogEffect Extends PostEffect
+
+	#rem monkeydoc Creates a new fog effect.
+	#end	
+	Method New( color:Color=std.graphics.Color.Sky,near:Float=0,far:Float=1000 )
+
+		_shader=Shader.Open( "fog" )
+		
+		_uniforms=New UniformBlock( 2 )
+		
+		Color=color
+		Near=near
+		Far=far
+	End
+
+	#rem monkeydoc Color of the fog.
+	#end
+	Property Color:Color()
+		
+		Return _uniforms.GetColor( "Color" )
+	
+	Setter( color:Color )
+		
+		_uniforms.SetColor( "Color",color )
+	End
+
+	#rem monkeydoc Near distance of the fog.
+	#end	
+	Property Near:Float()
+		
+		Return _uniforms.GetFloat( "Near" )
+	
+	Setter( near:Float )
+		
+		_uniforms.SetFloat( "Near",near )
+	End
+	
+	#rem monkeydoc Far distance of the fog.
+	#end	
+	Property Far:Float()
+		
+		Return _uniforms.GetFloat( "Far" )
+	
+	Setter( far:Float )
+		
+		_uniforms.SetFloat( "Far",far )
+	End
+	
+	Protected
+	
+	#rem monkeydoc @hidden
+	#end
+	Method OnRender() Override
+		
+		Device.Shader=_shader
+		
+		Device.BindUniformBlock( _uniforms )
+
+		Device.BlendMode=BlendMode.Alpha
+		
+		RenderQuad()
+	End
+	
+	Private
+	
+	Field _shader:Shader
+	
+	Field _uniforms:UniformBlock
+	
+End

+ 34 - 0
modules/mojo3d/graphics/forwardrenderer.monkey2

@@ -0,0 +1,34 @@
+
+Namespace mojo3d.graphics
+
+Class ForwardRenderer Extends Renderer
+
+	Method OnRender() Override
+	
+		_device.RenderTarget=_renderTarget
+		_device.Resize( _renderTargetSize )
+		
+		For Local camera:=Eachin _scene.Cameras
+		
+			SetCamera( camera )
+			
+			Local viewport:=_camera.Viewport+_renderViewport.Origin
+			Local scissor:=viewport & _renderViewport
+			
+			_device.Viewport=viewport
+			_device.Scissor=scissor
+			_device.ColorMask=ColorMask.All
+			_device.DepthMask=True
+			
+			_device.DepthFunc=DepthFunc.LessEqual
+			_device.BlendMode=BlendMode.Opaque
+			_device.CullMode=CullMode.Back
+			_device.RenderPass=0
+			
+			RenderModels( -_camera.WorldMatrix,_camera.ProjectionMatrix )
+			
+		Next
+			
+	End
+
+End

+ 526 - 0
modules/mojo3d/graphics/gltf2.monkey2

@@ -0,0 +1,526 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc @hidden
+#end
+Class Gltf2Buffer
+	Field uri:String
+	Field byteLength:Int
+End
+
+#rem monkeydoc @hidden
+#end
+Class Gltf2BufferView
+	Field buffer:Gltf2Buffer
+	Field byteOffset:Int
+	Field byteLength:Int
+	Field byteStride:Int
+	Field target:Int
+End
+
+#rem monkeydoc @hidden
+#end
+Class Gltf2Accessor
+	Field bufferView:Gltf2BufferView
+	Field byteOffset:Int
+	Field componentType:Int
+	Field count:Int
+	Field type:String
+	Field sizeInBytes:Int
+	Field numberOfComponents:Int
+End
+
+#rem monkeydoc @hidden
+#end
+Class Gltf2Image
+	Field uri:String
+End
+
+#rem monkeydoc @hidden
+#end
+Class Gltf2Sampler
+	Field magFilter:Int
+	Field minFilter:Int
+	Field wrapS:Int
+	Field wrapT:Int
+End
+
+#rem monkeydoc @hidden
+#end
+Class Gltf2Texture
+	Field sampler:Gltf2Sampler
+	Field source:Gltf2Image
+End
+
+#rem monkeydoc @hidden
+#end
+Class Gltf2Material
+	Field name:String
+	Field baseColorTexture:Gltf2Texture
+	Field baseColorFactor:Vec4f=New Vec4f(1)
+	Field metallicRoughnessTexture:Gltf2Texture
+	Field metallicFactor:Float=1
+	Field roughnessFactor:Float=1
+	Field occlusionTexture:Gltf2Texture
+	Field occlusionFactor:Vec3f=New Vec3f(1)
+	Field emissiveTexture:Gltf2Texture
+	Field emissiveFactor:Vec3f=New Vec3f(0)
+	Field normalTexture:Gltf2Texture
+End
+
+#rem monkeydoc @hidden
+#end
+Class Gltf2Primitive
+	Field POSITION:Gltf2Accessor
+	Field NORMAL:Gltf2Accessor
+	Field TANGENT:Gltf2Accessor
+	Field TEXCOORD_0:Gltf2Accessor
+	Field indices:Gltf2Accessor
+	Field material:Gltf2Material
+	Field mode:Int
+End
+
+#rem monkeydoc @hidden
+#end
+Class Gltf2Mesh
+	Field name:String
+	Field primitives:Gltf2Primitive[]
+End
+
+#rem monkeydoc @hidden
+#end
+Class Gltf2Node
+	Field name:String
+	Field parent:Gltf2Node
+	Field children:Gltf2Node[]
+	Field translation:Vec3f=New Vec3f(0)
+	Field rotation:Quatf=New Quatf
+	Field scale:Vec3f=New Vec3f(1)
+	Field matrix:Mat4f=New Mat4f
+	Field mesh:Gltf2Mesh
+	Field hasMatrix:Bool
+End
+
+#rem monkeydoc @hidden
+#end
+Class Gltf2Scene
+	Field name:String
+	Field nodes:Gltf2Node[]
+End
+
+#rem monkeydoc @hidden
+#end
+Class Gltf2Asset
+	
+	Field buffers:Gltf2Buffer[]
+	Field bufferViews:Gltf2BufferView[]
+	Field accessors:Gltf2Accessor[]
+	Field images:Gltf2Image[]
+	Field samplers:Gltf2Sampler[]
+	Field textures:Gltf2Texture[]
+	Field materials:Gltf2Material[]
+	Field meshes:Gltf2Mesh[]
+	Field nodes:Gltf2Node[]
+	Field scenes:Gltf2Scene[]
+	
+	Function Load:Gltf2Asset( path:String )
+		
+		Local root:=JsonObject.Load( path )
+		If Not root Return Null
+
+		Local asset:=New Gltf2Asset( root )
+		If Not asset.LoadAsset() Return Null
+		
+		Return asset
+	End
+	
+	Private
+	
+	Field root:JsonObject
+	
+	Method New( root:JsonObject )
+		Self.root=root
+	End
+	
+	Method GetQuatf:Quatf( jval:JsonArray )
+		Return New Quatf( jval.GetNumber(0),jval.GetNumber(1),jval.GetNumber(2),jval.GetNumber(3) )
+	End
+	
+	Method GetVec4f:Vec4f( jval:JsonArray )
+		Return New Vec4f( jval.GetNumber(0),jval.GetNumber(1),jval.GetNumber(2),jval.GetNumber(3) )
+	End
+	
+	Method GetVec3f:Vec3f( jval:JsonArray )
+		Return New Vec3f( jval.GetNumber(0),jval.GetNumber(1),jval.GetNumber(2) )
+	End
+	
+	Method GetMat4f:Mat4f( jval:JsonArray )
+		Return New Mat4f(
+			New Vec4f( jval.GetNumber(0),jval.GetNumber(1),jval.GetNumber(2),jval.GetNumber(3) ),
+			New Vec4f( jval.GetNumber(4),jval.GetNumber(5),jval.GetNumber(6),jval.GetNumber(7) ),
+			New Vec4f( jval.GetNumber(8),jval.GetNumber(9),jval.GetNumber(10),jval.GetNumber(11) ),
+			New Vec4f( jval.GetNumber(12),jval.GetNumber(13),jval.GetNumber(14),jval.GetNumber(15) ) )
+	End
+	
+	Method LoadBuffers:Bool()
+		
+		Local jbuffers:=root.GetArray( "buffers" )
+		If Not jbuffers Return True
+		
+		buffers=New Gltf2Buffer[jbuffers.Length]
+		
+		For Local i:=0 Until buffers.Length
+			
+			Local jbuffer:=jbuffers.GetObject( i )
+			
+			Local buffer:=New Gltf2Buffer
+			buffers[i]=buffer
+			
+			buffer.byteLength=jbuffer.GetNumber( "byteLength" )
+			buffer.uri=jbuffer.GetString( "uri" )
+		Next
+		
+		Return True
+	End
+	
+	Method LoadBufferViews:Bool()
+
+		Local jbufferViews:=root.GetArray( "bufferViews" )
+		If Not jbufferViews Return True
+		
+		bufferViews=New Gltf2BufferView[jbufferViews.Length]
+		
+		For Local i:=0 Until bufferViews.Length
+			
+			Local jbufferView:=jbufferViews.GetObject( i )
+			
+			Local bufferView:=New Gltf2BufferView
+			bufferViews[i]=bufferView
+			
+			bufferView.buffer=buffers[jbufferView.GetNumber( "buffer" )]
+			bufferView.byteLength=jbufferView.GetNumber( "byteLength" )
+			bufferView.byteOffset=jbufferView.GetNumber( "byteOffset" )
+			bufferView.byteStride=jbufferView.GetNumber( "byteStride" )
+			bufferView.target=jbufferView.GetNumber( "target" )
+		Next
+		
+		Return True
+	End
+	
+	Method LoadAccessors:Bool()
+		
+		Local jaccessors:=root.GetArray( "accessors" )
+		If Not jaccessors Return True
+		
+		accessors=New Gltf2Accessor[ jaccessors.Length ]
+		
+		For Local i:=0 Until accessors.Length
+			
+			Local jaccessor:=jaccessors.GetObject( i )
+			
+			Local accessor:=New Gltf2Accessor
+			accessors[i]=accessor
+			
+			accessor.bufferView=bufferViews[jaccessor.GetNumber( "bufferView" )]
+			accessor.byteOffset=jaccessor.GetNumber( "byteOffset" )
+			accessor.componentType=jaccessor.GetNumber( "componentType" )
+			accessor.count=jaccessor.GetNumber( "count" )
+			accessor.type=jaccessor.GetString( "type" )
+		Next
+		
+		Return True
+	End
+	
+	Method LoadImages:Bool()
+		
+		Local jimages:=root.GetArray( "images" )
+		If Not jimages Return True
+		
+		images=New Gltf2Image[jimages.Length]
+		
+		For Local i:=0 Until images.Length
+			
+			Local jimage:=jimages.GetObject( i )
+			
+			Local image:=New Gltf2Image
+			images[i]=image
+			
+			image.uri=jimage.GetString( "uri" )
+		Next
+		
+		Return True
+	End
+	
+	Method LoadSamplers:Bool()
+		
+		Local jsamplers:=root.GetArray( "samplers" )
+		If Not jsamplers Return True
+		
+		samplers=New Gltf2Sampler[jsamplers.Length]
+		
+		For Local i:=0 Until samplers.Length
+			
+			Local jsampler:=jsamplers.GetObject( i )
+			
+			Local sampler:=New Gltf2Sampler
+			samplers[i]=sampler
+			
+			sampler.magFilter=jsampler.GetNumber( "magFilter" )
+			sampler.minFilter=jsampler.GetNumber( "minFilter" )
+			sampler.wrapS=jsampler.GetNumber( "wrapS" )
+			sampler.wrapT=jsampler.GetNumber( "wrapT" )
+		Next
+		
+		Return True
+	End
+	
+	Method LoadTextures:Bool()
+		
+		Local jtextures:=root.GetArray( "textures" )
+		If Not jtextures Return True
+		
+		textures=New Gltf2Texture[jtextures.Length]
+		
+		For Local i:=0 Until textures.Length
+			
+			Local jtexture:=jtextures.GetObject( i )
+			
+			Local texture:=New Gltf2Texture
+			textures[i]=texture
+			
+			If Not jtexture.Contains( "source" ) Return False
+			texture.source=images[jtexture.GetNumber( "source" )]
+			
+			If jtexture.Contains( "sampler" )
+				texture.sampler=samplers[jtexture.GetNumber( "sampler" ) ]
+			Endif
+		Next
+		
+		Return True
+	End
+	
+	Method LoadMaterials:Bool()
+		
+		Local jmaterials:=root.GetArray( "materials" )
+		If Not jmaterials Return True
+		
+		materials=New Gltf2Material[jmaterials.Length]
+		
+		For Local i:=0 Until materials.Length
+			
+			Local jmaterial:=jmaterials.GetObject( i )
+			
+			Local material:=New Gltf2Material
+			materials[i]=material
+			
+			material.name=jmaterial.GetString( "name" )
+
+			Local jpbr:=jmaterial.GetObject( "pbrMetallicRoughness" )
+			If jpbr
+				Local jobj:=jpbr.GetObject( "baseColorTexture" )
+				If jobj
+					material.baseColorTexture=textures[jobj.GetNumber( "index" )]
+				Endif
+				Local jarr:=jpbr.GetArray( "baseColorFactor" )
+				If jarr
+					material.baseColorFactor=GetVec4f( jarr )
+				Endif
+				jobj=jpbr.GetObject( "metallicRoughnessTexture" )
+				If jobj
+					material.metallicRoughnessTexture=textures[jobj.GetNumber( "index" )]
+				Endif
+				If jpbr.Contains( "metallicFactor" )
+					material.metallicFactor=jpbr.GetNumber( "metallicFactor" )
+				Endif
+				If jpbr.Contains( "roughnessFactor" )
+					material.metallicFactor=jpbr.GetNumber( "roughnessFactor" )
+				Endif
+			End
+			
+			Local jobj:=jmaterial.GetObject( "emissiveTexture" )
+			If jobj
+				material.emissiveTexture=textures[jobj.GetNumber( "index" )]
+			Endif
+			Local jarr:=jmaterial.GetArray( "emissiveFactor" )
+			If jarr
+				material.emissiveFactor=GetVec3f( jarr )
+			Endif
+			jobj=jmaterial.GetObject( "normalTexture" )
+			If jobj
+				material.normalTexture=textures[jobj.GetNumber( "index" )]
+			Endif
+
+		Next
+		
+		Return True
+	End
+	
+	Method LoadMeshes:Bool()
+		
+		Local jmeshes:=root.GetArray( "meshes" )
+		If Not jmeshes Return True
+		
+		meshes=New Gltf2Mesh[jmeshes.Length]
+		
+		For Local i:=0 Until meshes.Length
+			
+			Local mesh:=New Gltf2Mesh
+			meshes[i]=mesh
+			
+			Local jmesh:=jmeshes.GetObject( i )
+			mesh.name=jmesh.GetString( "name" )
+			
+			Local jprims:=jmesh.GetArray( "primitives" )
+			
+			mesh.primitives=New Gltf2Primitive[jprims.Length]
+			
+			For Local j:=0 Until jprims.Length
+				
+				Local prim:=New Gltf2Primitive
+				mesh.primitives[j]=prim
+				
+				Local jprim:=jprims.GetObject( j )
+				
+				Local jattribs:=jprim.GetObject( "attributes" )
+				
+				If jattribs.Contains( "POSITION" )
+					prim.POSITION=accessors[jattribs.GetNumber( "POSITION" )]
+				Endif
+				If jattribs.Contains( "NORMAL" )
+					prim.NORMAL=accessors[jattribs.GetNumber( "NORMAL" )]
+				Endif
+				If jattribs.Contains( "TANGENT" )
+					prim.TANGENT=accessors[jattribs.GetNumber( "TANGENT" )]
+				Endif
+				If jattribs.Contains( "TEXCOORD_0" )
+					prim.TEXCOORD_0=accessors[jattribs.GetNumber( "TEXCOORD_0" )]
+				Endif
+				If jprim.Contains( "indices" )
+					prim.indices=accessors[jprim.GetNumber( "indices" )]
+				Endif
+				If jprim.Contains( "material" )
+					prim.material=materials[jprim.GetNumber( "material" )]
+				Endif
+				If jprim.Contains( "mode" )
+					prim.mode=jprim.GetNumber( "mode" )
+				Endif
+			
+			Next
+		
+		Next
+		
+		Return True
+	End
+
+	Method LoadNodes:Bool()
+
+		Local jnodes:=root.GetArray( "nodes" )
+		If Not jnodes Return True
+		
+		nodes=New Gltf2Node[ jnodes.Length ]
+		
+		For Local i:=0 Until nodes.Length
+			nodes[i]=New Gltf2Node
+		Next
+		
+		For Local i:=0 Until jnodes.Length
+
+			Local jnode:=jnodes.GetObject( i )
+			
+			Local node:=nodes[i]
+			node.name=jnode.GetString( "name" )
+			
+			Local jchildren:=jnode.GetArray( "children" )
+			If jchildren
+
+				node.children=New Gltf2Node[jchildren.Length]
+				
+				For Local j:=0 Until jchildren.Length
+					
+					Local child:=nodes[jchildren.GetNumber( j )]
+					node.children[j]=child
+					
+					child.parent=node
+				Next
+			Endif
+			
+			If jnode.Contains( "translation" )
+				node.translation=GetVec3f( jnode.GetArray( "translation" ) )
+				node.translation.z=-node.translation.z
+			Endif
+
+			If jnode.Contains( "rotation" )
+				node.rotation=GetQuatf( jnode.GetArray( "rotation" ) )
+			Endif
+			
+			If jnode.Contains( "scale" )
+				node.scale=GetVec3f( jnode.GetArray( "scale" ) )
+			Endif
+				
+			If jnode.Contains( "matrix" )
+				node.matrix=GetMat4f( jnode.GetArray( "matrix" ) )
+				node.matrix.t.z=-node.matrix.t.z
+			Else
+				node.matrix=Mat4f.Translation( node.translation ) * Mat4f.Rotation( node.rotation ) * Mat4f.Scaling( node.scale )
+			Endif
+			
+			If jnode.Contains( "mesh" )
+				node.mesh=meshes[jnode.GetNumber( "mesh" )]
+			Endif
+			
+		Next
+		
+		Return True
+	End
+	
+	Method LoadScenes:Bool()
+		
+		Local jscenes:=root.GetArray( "scenes" )
+		If Not jscenes Return True
+		
+		scenes=New Gltf2Scene[jscenes.Length]
+		
+		For Local i:=0 Until jscenes.Length
+			
+			Local jscene:=jscenes.GetObject( i )
+			
+			Local scene:=New Gltf2Scene
+			scenes[i]=scene
+
+			scene.name=jscene.GetString( "name" )
+
+			Local jnodes:=jscene.GetArray( "nodes" )
+			scene.nodes=New Gltf2Node[jnodes.Length]
+			
+			For Local j:=0 Until jnodes.Length
+				scene.nodes[j]=nodes[jnodes.GetNumber( j )]
+			Next
+		
+		Next
+		
+		Return True
+	End
+	
+	Method LoadAsset:Bool()
+		
+		Local asset:=root.GetObject( "asset" )
+		If Not asset Return False
+		
+		Local version:=asset.GetString( "version" )
+		Print "Gtf2 version="+version
+		
+		If Not LoadBuffers() Return False
+		If Not LoadBufferViews() Return False
+		If Not LoadAccessors() Return False
+		If Not LoadImages() Return False
+		If Not LoadSamplers() Return False
+		If Not LoadTextures() Return False
+		If Not LoadMaterials() Return False
+		If Not LoadMeshes() Return False
+		If Not LoadNodes() Return False
+		If Not LoadScenes() Return False
+		
+		Return True
+	End
+	
+End

+ 275 - 0
modules/mojo3d/graphics/gltf2loader.monkey2

@@ -0,0 +1,275 @@
+
+Namespace mojo3d.graphics
+
+Private
+
+Class Gltf2Loader
+
+	Method New( asset:Gltf2Asset,dir:String )
+		_asset=asset
+		_dir=dir
+	End
+	
+	Method LoadMesh:Mesh()
+		
+		_loadedMesh=New Mesh
+		_loadedMaterials=New Stack<Material>
+		
+		For Local node:=Eachin _asset.scenes[0].nodes
+			AddMeshes( node )
+		Next
+		
+		_loadedMesh.UpdateTangents()
+		
+		Local mesh:=_loadedMesh
+		
+		Return mesh
+	End
+
+	Method LoadModel:Model()
+		
+		_loadedMesh=New Mesh
+		_loadedMaterials=New Stack<Material>
+		
+		For Local node:=Eachin _asset.scenes[0].nodes
+			AddMeshes( node )
+		Next
+		
+		_loadedMesh.UpdateTangents()
+		
+		Local model:=New Model
+		model.Mesh=_loadedMesh
+		model.Materials=_loadedMaterials.ToArray()
+		
+		Return model
+	End
+	
+	Private
+	
+	Alias IndexType:UInt
+	
+	Field _asset:Gltf2Asset
+	Field _dir:String
+	
+	Field _data:=New StringMap<DataBuffer>
+	Field _textureCache:=New Map<Gltf2Texture,Texture>
+	Field _materialCache:=New Map<Gltf2Material,Material>
+	
+	Field _loadedMesh:Mesh
+	Field _loadedMaterials:Stack<Material>
+
+	Method GetData:UByte Ptr( uri:String )
+		Local data:=_data[uri]
+		If Not data
+			data=DataBuffer.Load( _dir+uri )
+			_data[uri]=data
+		Endif
+		Return data.Data
+	End
+	
+	Method GetData:UByte Ptr( buffer:Gltf2Buffer )
+		Return GetData( buffer.uri )
+	End
+	
+	Method GetData:UByte Ptr( bufferView:Gltf2BufferView )
+		Return GetData( bufferView.buffer )+bufferView.byteOffset
+	End
+	
+	Method GetData:UByte Ptr( accessor:Gltf2Accessor )
+		Return GetData( accessor.bufferView )+accessor.byteOffset
+	End
+	
+	Method GetTexture:Texture( texture:Gltf2Texture )
+		
+		If Not texture Return Null
+		
+		If _textureCache.Contains( texture ) Return _textureCache[texture]
+		
+		Local flags:=TextureFlags.Filter|TextureFlags.Mipmap|TextureFlags.WrapS|TextureFlags.WrapT
+		
+		Local tex:=Texture.Load( _dir+texture.source.uri,flags )
+		
+		Print "Opened texture:"+_dir+texture.source.uri
+		
+		_textureCache[texture]=tex
+		Return tex
+	End
+	
+	Method GetMaterial:Material( material:Gltf2Material )
+		
+		If Not material Return New PbrMaterial( Color.Magenta )
+		
+		If _materialCache.Contains( material ) Return _materialCache[material]
+		
+		Local mat:=New PbrMaterial
+		
+		Local texture:=GetTexture( material.baseColorTexture )
+		If texture
+			mat.ColorTexture=texture
+		Endif
+		mat.ColorFactor=New Color( material.baseColorFactor.x,material.baseColorFactor.y,material.baseColorFactor.z )
+		
+		texture=GetTexture( material.emissiveTexture )
+		If texture
+			mat.EmissiveTexture=texture
+			mat.EmissiveFactor=New Color( material.emissiveFactor.x,material.emissiveFactor.y,material.emissiveFactor.z )
+		Endif
+		
+		texture=GetTexture( material.metallicRoughnessTexture )
+		If texture
+			mat.MetalnessTexture=texture
+			mat.RoughnessTexture=texture
+		Endif
+		mat.MetalnessFactor=material.metallicFactor
+		mat.RoughnessFactor=material.roughnessFactor
+		
+		texture=GetTexture( material.occlusionTexture )
+		If texture
+			mat.OcclusionTexture=texture
+		Endif
+		
+		texture=GetTexture( material.normalTexture )
+		If texture
+			mat.NormalTexture=texture
+		Endif
+			
+		_materialCache[material]=mat
+		Return mat
+	End
+	
+	Method GetMatrix:Mat4f( node:Gltf2Node )
+		
+		If node.parent Return GetMatrix( node.parent ) * node.matrix
+		
+		Return node.matrix
+	End
+	
+	Method AddMeshes( node:Gltf2Node )
+		
+		If node.mesh
+			
+			Print "mesh="+node.mesh.name
+			
+			For Local prim:=Eachin node.mesh.primitives
+				
+				'some sanity checking!
+				'
+				If prim.mode<>4 Continue
+				
+				If Not prim.POSITION Or prim.POSITION.componentType<>5126 Or prim.POSITION.type<>"VEC3" DebugStop()
+				If Not prim.NORMAL Or prim.NORMAL.componentType<>5126 Or prim.NORMAL.type<>"VEC3" DebugStop()
+				If Not prim.TEXCOORD_0 Or prim.TEXCOORD_0.componentType<>5126 Or prim.TEXCOORD_0.type<>"VEC2" DebugStop()
+				If Not prim.indices Or (prim.indices.componentType<>5123 And prim.indices.componentType<>5125) Or prim.indices.type<>"SCALAR" DebugStop()
+				
+				Local pp:=GetData( prim.POSITION )
+				Local pstride:=prim.POSITION.bufferView.byteStride
+				If Not pstride pstride=12
+				
+				Local np:=GetData( prim.NORMAL )
+				Local nstride:=prim.NORMAL.bufferView.byteStride
+				If Not nstride nstride=12
+				
+				Local tp:=GetData( prim.TEXCOORD_0 )
+				Local tstride:=prim.TEXCOORD_0.bufferView.byteStride
+				If Not tstride tstride=8
+					
+				Local vcount:=prim.POSITION.count
+				
+				Local vertices:=New Vertex3f[vcount],dstvp:=vertices.Data
+				
+				For Local i:=0 Until vcount
+					dstvp[i].position=Cast<Vec3f Ptr>( pp )[0]
+					dstvp[i].normal=Cast<Vec3f Ptr>( np )[0]
+					dstvp[i].texCoord0=Cast<Vec2f Ptr>( tp )[0]
+					'dstvp[i].position.x=-dstvp[i].position.x
+					'dstvp[i].position.y=-dstvp[i].position.y
+					dstvp[i].position.z=-dstvp[i].position.z
+					dstvp[i].normal.z=-dstvp[i].normal.z
+					pp+=pstride
+					np+=nstride
+					tp+=tstride
+				Next
+				
+				Local icount:=prim.indices.count
+				
+				Local indices:=New IndexType[icount],dstip:=indices.Data
+
+				Local ip:=GetData( prim.indices )
+				Local istride:=prim.indices.bufferView.byteStride
+				
+				Local v0:=_loadedMesh.NumVertices
+				
+				If prim.indices.componentType=5123
+					If Not istride istride=2
+					For Local i:=0 Until icount Step 3
+						dstip[i+0]=Cast<UShort Ptr>( ip )[0] + v0
+						dstip[i+2]=Cast<UShort Ptr>( ip )[1] + v0
+						dstip[i+1]=Cast<UShort Ptr>( ip )[2] + v0
+						ip+=istride*3
+					Next
+				Else
+					If Not istride istride=4
+					For Local i:=0 Until icount Step 3
+						dstip[i+0]=Cast<UInt Ptr>( ip )[0] + v0
+						dstip[i+2]=Cast<UInt Ptr>( ip )[1] + v0
+						dstip[i+1]=Cast<UInt Ptr>( ip )[2] + v0
+						ip+=istride*3
+					Next
+				Endif
+				
+				_loadedMesh.AddVertices( vertices )
+				
+				_loadedMesh.AddTriangles( indices )
+					
+				_loadedMaterials.Push( GetMaterial( prim.material ) )
+				
+				Print "Added "+vcount+" vertices, "+icount+" indices."
+				
+			Next
+			
+		Endif
+		
+		For Local child:=Eachin node.children
+			
+			AddMeshes( child )
+		Next
+		
+	End
+	
+End
+
+Public
+
+#rem monkeydoc @hidden
+#End
+Class Gltf2Mojo3dLoader Extends Mojo3dLoader
+
+	Const Instance:=New Gltf2Mojo3dLoader
+
+	Method LoadMesh:Mesh( path:String ) Override
+	
+		If ExtractExt( path ).ToLower()<>".gltf" Return Null
+			
+		Local asset:=Gltf2Asset.Load( path )
+		If Not asset Return Null
+		
+		Local loader:=New Gltf2Loader( asset,ExtractDir( path ) )
+		Local mesh:=loader.LoadMesh()
+		
+		Return mesh
+	End
+
+	Method LoadModel:Model( path:String ) Override
+	
+		If ExtractExt( path ).ToLower()<>".gltf" Return Null
+			
+		Local asset:=Gltf2Asset.Load( path )
+		If Not asset Return Null
+		
+		Local loader:=New Gltf2Loader( asset,ExtractDir( path ) )
+		Local mesh:=loader.LoadModel()
+		
+		Return mesh
+	End
+
+End

+ 128 - 0
modules/mojo3d/graphics/light.monkey2

@@ -0,0 +1,128 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc The LightType enumeration.
+
+Note: Only directional lights are currently fully supported.
+
+| LightType		| Description
+|:--------------|:-----------
+| `Directional`	| Light at infinity.
+| `Point`		| Point light.
+| `Spot`		| Spot light.
+#end
+Enum LightType
+	Directional=0
+	Point=1
+	Spot=2
+End
+
+#rem monkeydoc The Light class.
+#end
+Class Light Extends Entity
+
+	#rem monkeydoc Creates a new light.
+	#end
+	Method New( parent:Entity=Null )
+		Super.New( parent )
+		
+		Type=LightType.Directional
+		Color=Color.White
+		Range=100
+		ShadowsEnabled=True
+		
+		Show()
+	End
+	
+	#rem monkeydoc Copies the light.
+	#end
+	Method Copy:Light( parent:Entity=Null ) Override
+		
+		Local copy:=New Light( Self,parent )
+		
+		CopyComplete( copy )
+		
+		Return copy
+	End
+	
+	#rem monkeydoc Light shadows enabled flag.
+	#end
+	Property ShadowsEnabled:Bool()
+		
+		Return _shadows
+		
+	Setter( shadows:Bool )
+		
+		_shadows=shadows
+	End
+	
+	#rem monkeydoc The light type.
+	#end
+	Property Type:LightType()
+		
+		Return _type
+	
+	Setter( type:LightType )
+		
+		_type=type
+	End
+	
+	#rem monkeydoc The light color.
+	#end
+	Property Color:Color()
+	
+		Return _color
+	
+	Setter( color:Color )
+	
+		_color=color
+	End
+	
+	#rem monkeydoc The light range.
+	#end
+	Property Range:Float()
+	
+		Return _range
+	
+	Setter( range:Float )
+	
+		_range=range
+	End
+	
+	Protected
+
+	#rem monkeydoc @hidden
+	#end	
+	Method New( light:Light,parent:Entity )
+		Super.New( light,parent )
+		
+		Type=light.Type
+		Color=light.Color
+		Range=light.Range
+		
+		Show()
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Method OnShow() Override
+		Scene.Lights.Add( Self )
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Method OnHide() Override
+		Scene.Lights.Remove( Self )
+	End
+
+	Private
+	
+	Field _type:LightType
+	
+	Field _color:Color
+	
+	Field _range:Float
+	
+	Field _shadows:bool
+
+End

+ 22 - 0
modules/mojo3d/graphics/loader.monkey2

@@ -0,0 +1,22 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc @hidden
+#end
+Class Mojo3dLoader
+
+	Const Instances:=New Stack<Mojo3dLoader>
+	
+	Method New()
+		Instances.Push( Self )
+	End
+	
+	Method LoadMesh:Mesh( path:String ) Virtual
+		Return Null
+	End
+	
+	Method LoadModel:Model( path:String ) Virtual
+		Return Null
+	End
+
+End

+ 125 - 0
modules/mojo3d/graphics/material.monkey2

@@ -0,0 +1,125 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc The Material class.
+#end
+Class Material Extends Resource
+	
+	#rem monkeydoc Creates a new material
+	#end
+	Method New( shader:Shader )
+		_shader=shader
+		_uniforms=New UniformBlock( 2 )
+		_blendMode=BlendMode.Opaque
+		_cullMode=CullMode.Back
+		TextureMatrix=New AffineMat3f
+	End
+	
+	#rem monkeydoc Creates a copy of the material.
+	#end
+	Method Copy:Material() Virtual
+		
+		Return New Material( Self )
+	End
+
+	#rem monkeydoc The material shader.
+	#end
+	Property Shader:Shader()
+		
+		Return _shader
+		
+	Setter( shader:Shader )
+	
+		_shader=shader
+	End
+
+	#Rem monkeydoc @hidden The material uniforms.
+	#End
+	Property Uniforms:UniformBlock()
+	
+		Return _uniforms
+	End
+
+	#Rem monkeydoc The material blendmode.
+	#End
+	Property BlendMode:BlendMode()
+		
+		Return _blendMode
+	
+	Setter( mode:BlendMode )
+		
+		_blendMode=mode
+	End
+	
+	#Rem monkeydoc The material cullmode.
+	#End
+	Property CullMode:CullMode()
+		
+		Return _cullMode
+	
+	Setter( mode:CullMode )
+		
+		_cullMode=mode
+	End
+	
+	#rem monkeydoc The material texture matrix.
+	#end
+	Property TextureMatrix:AffineMat3f()
+		
+		Return Uniforms.GetAffineMat3f( "TextureMatrix" )
+		
+	Setter( matrix:AffineMat3f )
+		
+		Uniforms.SetAffineMat3f( "TextureMatrix",matrix )
+	End
+	
+	#rem monkeydoc Translates the texture matrix.
+	#end
+	Method TranslateTextureMatrix( tv:Vec2f )
+		
+		TextureMatrix=TextureMatrix.Translate( tv )
+	End
+	
+	Method TranslateTextureMatrix( tx:Float,ty:Float )
+		
+		TextureMatrix=TextureMatrix.Translate( tx,ty )
+	End
+
+	#rem monkeydoc Rotates the texture matrix.
+	#end
+	Method RotateTextureMatrix( angle:Float )
+		
+		TextureMatrix=TextureMatrix.Rotate( angle )
+	End
+		
+	#rem monkeydoc Scales the texture matrix.
+	#end
+	Method ScaleTextureMatrix( sv:Vec2f )
+		
+		TextureMatrix=TextureMatrix.Scale( sv )
+	End
+	
+	Method ScaleTextureMatrix( sx:Float,sy:Float )
+		
+		TextureMatrix=TextureMatrix.Scale( sx,sy )
+	End
+
+	Protected
+	
+	#rem monkeydoc @hidden
+	#end	
+	Method New( material:Material )
+		_shader=material._shader
+		_uniforms=New UniformBlock( material._uniforms )
+		_blendMode=material._blendMode
+		_cullMode=material._cullMode
+		TextureMatrix=material.TextureMatrix
+	End
+	
+	Private
+	
+	Field _shader:Shader
+	Field _uniforms:UniformBlock
+	Field _blendMode:BlendMode
+	Field _cullMode:CullMode
+End

+ 429 - 0
modules/mojo3d/graphics/mesh.monkey2

@@ -0,0 +1,429 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc The Mesh class.
+#end
+Class Mesh Extends Resource
+	
+	#rem monkeydoc Creates a new mesh.
+	
+	Creates a new empty mesh with 1 logical material.
+	
+	Meshes don't actual contain instances of materials. Instead, mesh triangles are added to 'logical' materials which are effectively just integer indices.
+	
+	Actual materials are stored in models, and can be accessed via the [[Model.Materials]] property.
+	
+	#end
+	Method New()
+		
+		_dirty=Null
+		_bounds=Boxf.EmptyBounds
+		_vbuffer=New VertexBuffer( Vertex3fFormat.Instance,0 )
+		_ibuffers=New Stack<IndexBuffer>
+		AddMaterials( 1 )
+	End
+	
+	Method New( mesh:Mesh )
+		_dirty=mesh._dirty
+		_bounds=mesh._bounds
+		_vbuffer=New VertexBuffer( mesh._vbuffer )
+		For Local ibuffer:=Eachin mesh._ibuffers
+			_ibuffers.Push( New IndexBuffer( ibuffer ) )
+		Next
+	End
+	
+	Method New( vertices:Vertex3f[],triangles:UInt[] )
+		Self.New()
+		
+		AddVertices( vertices )
+		AddTriangles( triangles )
+	End
+
+	#rem monkeydoc Mesh bounding box.
+	#end
+	Property Bounds:Boxf()
+		
+		If _dirty & Dirty.Bounds
+			
+			Local vertices:=Cast<Vertex3f Ptr>( _vbuffer.Data )
+			
+			_bounds=Boxf.EmptyBounds
+			
+			For Local i:=0 Until _vbuffer.Length
+				_bounds|=vertices[i].position
+			Next
+			
+			_dirty&=~Dirty.Bounds
+		Endif
+		
+		Return _bounds
+	End
+	
+	#rem monkeydoc Number of vertices.
+	#end
+	Property NumVertices:Int()
+		
+		Return _vbuffer.Length
+	End
+	
+	#rem monkeydoc Number of materials.
+	
+	This will always be at least one.
+	
+	#end
+	Property NumMaterials:Int()
+		
+		Return _ibuffers.Length
+	End
+	
+	#rem monkeydoc Clears the mesh.
+	
+	Removes all vertices and primitives from the mesh, and resets the number of logical materials to '1'.
+	
+	#end
+	Method Clear()
+		
+		_vbuffer.Clear()
+		
+		For Local ibuffer:=Eachin _ibuffers
+			ibuffer.Clear()
+		Next
+		
+		_ibuffers.Resize( 1 )
+	End
+
+	#rem monkeydoc @hidden
+	#end	
+	Method AddMesh( mesh:Mesh )
+	
+		Local v0:=_vbuffer.Length,i0:=_ibuffers.Length
+	
+		AddVertices( Cast<Vertex3f Ptr>( mesh._vbuffer.Data ),mesh._vbuffer.Length )
+		
+		AddMaterials( mesh._ibuffers.Length )
+		
+		For Local i:=i0 Until _ibuffers.Length
+		
+			Local ibuffer:=mesh._ibuffers[i-i0]
+		
+			AddTriangles( Cast<UInt Ptr>( ibuffer.Data ),ibuffer.Length )
+		Next
+	End
+	
+	#rem monkeydoc Adds vertices to the mesh.
+	#end
+	Method AddVertices( vertices:Vertex3f Ptr,count:Int )
+
+		Local p:=_vbuffer.AddVertices( count )
+		
+		libc.memcpy( p,vertices,count * _vbuffer.Pitch )
+		
+		_dirty|=Dirty.Bounds
+	End
+	
+	Method AddVertices( vertices:Vertex3f[] )
+	
+		AddVertices( vertices.Data,vertices.Length )
+	End
+	
+	#rem monkeydoc Adds a single vertex to the mesh
+	#end
+	Method AddVertex( vertex:Vertex3f )
+		
+		AddVertices( Varptr vertex,1 )
+	End
+	
+	#rem monkeydoc Adds triangles to the mesh.
+	
+	The `materialid` parameter must be greater than or equal to 0 and less than NumMaterials.
+	
+	#end
+	Method AddTriangles( indices:UInt Ptr,count:Int,materialid:Int=0 )
+		
+		Local p:=_ibuffers[materialid].AddIndices( count )
+		
+		libc.memcpy( p,indices,count*4 )
+	End
+	
+	Method AddTriangles( indices:UInt[],materialid:Int=0 )
+	
+		AddTriangles( indices.Data,indices.Length,materialid )
+	End
+
+	#rem  monkeydoc Adds a single triangle the mesh.
+	#end	
+	Method AddTriangle( i0:UInt,i1:UInt,i2:UInt,materialid:Int=0 )
+		
+		AddTriangles( New UInt[]( i0,i1,i2 ),materialid )
+	End
+	
+	#rem monkeydoc Adds materials to the mesh.
+	
+	Adds `count` logical materials to the mesh.
+	
+	WIP! Eventually want to be able to add lines, points etc to meshes, probably via this method...
+	
+	#end
+	Method AddMaterials( count:Int )
+		
+		For Local i:=0 Until count
+			
+			_ibuffers.Push( New IndexBuffer( IndexFormat.UINT32,0 ) )
+		End
+	End
+	
+	#rem monkeydoc Transforms all vertices in the mesh.
+	#end
+	Method TransformVertices( matrix:AffineMat4f )
+		
+		Local vertices:=Cast<Vertex3f Ptr>( _vbuffer.Data )
+		
+		Local cofactor:=matrix.m.Cofactor()
+		
+		For Local i:=0 Until _vbuffer.Length
+		
+			vertices[i].position=matrix * vertices[i].position
+			
+			vertices[i].normal=(cofactor * vertices[i].normal).Normalize()
+			
+			vertices[i].tangent.XYZ=(cofactor * vertices[i].tangent.XYZ).Normalize()
+		Next
+		
+		_vbuffer.Invalidate()
+		
+		_dirty|=Dirty.Bounds
+	End
+	
+	#rem monkeydoc Fits all vertices in the mesh to a box.
+	#end
+	Method FitVertices( box:Boxf,uniform:Bool=True )
+
+		Local bounds:=Bounds
+		
+		Local scale:=box.Size/bounds.Size
+		
+		If uniform scale=New Vec3f( Min( scale.x,Min( scale.y,scale.z ) ) )
+			
+		Local m:=Mat3f.Scaling( scale )
+		
+		Local t:=box.Center - m * bounds.Center
+		
+		TransformVertices( New AffineMat4f( m,t ) )			
+	End
+	
+	#rem monkeydoc Updates mesh normals.
+
+	TODO!
+	
+	Recalculates all vertex normals based on positions.
+
+	#end
+	Method UpdateNormals()
+	
+		RuntimeError( "TODO!" )
+	End
+
+	#rem monkeydoc Updates mesh tangents.
+	
+	Recalculates all vertex tangents based on normals and texcoords.
+	
+	#end
+	Method UpdateTangents()
+		
+		Local vcount:=_vbuffer.Length
+		
+		Local vertices:=Cast<Vertex3f Ptr>( _vbuffer.Data )
+		
+		Local tan1:=New Vec3f[vcount]
+		Local tan2:=New Vec3f[vcount]
+		
+		For Local ibuffer:=Eachin _ibuffers
+			
+			Local icount:=ibuffer.Length
+			Local indices:=Cast<UInt Ptr>( ibuffer.Data )
+		
+			For Local i:=0 Until icount Step 3
+				
+				Local i1:=indices[i+0]
+				Local i2:=indices[i+1]
+				Local i3:=indices[i+2]
+				
+				Local v1:=vertices+i1
+				Local v2:=vertices+i2
+				Local v3:=vertices+i3
+				
+				Local x1:=v2->Tx-v1->Tx
+				Local x2:=v3->Tx-v1->Tx
+				Local y1:=v2->Ty-v1->Ty
+				Local y2:=v3->Ty-v1->Ty
+				Local z1:=v2->Tz-v1->Tz
+				Local z2:=v3->Tz-v1->Tz
+				
+				Local s1:=v2->Sx-v1->Sx
+				Local s2:=v3->Sx-v1->Sx
+				Local t1:=v2->Sy-v1->Sy
+				Local t2:=v3->Sy-v1->Sy
+				
+				Local r:=1.0/(s1*t2-s2*t1)
+				
+				Local sdir:=New Vec3f( (t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r )
+				Local tdir:=New Vec3f( (s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r )
+				
+				tan1[i1]+=sdir
+				tan1[i2]+=sdir
+				tan1[i3]+=sdir
+		
+				tan2[i1]+=tdir
+				tan2[i2]+=tdir
+				tan2[i3]+=tdir
+			Next
+		Next
+	
+		For Local i:=0 Until vcount
+			
+			Local v:=vertices+i
+	
+			Local n:=v->normal,t:=tan1[i]
+			
+			v->tangent.XYZ=( t - n * n.Dot( t ) ).Normalize()
+			
+			v->tangent.w=n.Cross( t ).Dot( tan2[i] ) < 0 ? -1 Else 1
+		Next
+		
+		_vbuffer.Invalidate()
+	End
+	
+	#rem monkeydoc Flips all triangles.
+	#end
+	Method FlipTriangles()
+		
+		For Local ibuffer:=Eachin _ibuffers
+			
+			Local indices:=Cast<UInt Ptr>( ibuffer.Data )
+			
+			For Local i:=0 Until ibuffer.Length Step 3
+				Local t:=indices[i]
+				indices[i]=indices[i+1]
+				indices[i+1]=t
+			Next
+			
+			ibuffer.Invalidate()
+		Next
+		
+	End
+	
+	#rem monkeydoc Scales texture coordinates.
+	#end
+	Method ScaleTexCoords( scale:Vec2f )
+		
+		Local vertices:=Cast<Vertex3f Ptr>( _vbuffer.Data )
+		
+		For Local i:=0 Until _vbuffer.Length
+		
+			vertices[i].texCoord0*=scale
+		Next
+		
+		_vbuffer.Invalidate()
+	End
+
+	#rem monkeydoc Gets all vertices in the mesh.
+	#end	
+	Method GetVertices:Vertex3f[]()
+		
+		Local vertices:=New Vertex3f[ _vbuffer.Length ]
+		
+		libc.memcpy( vertices.Data,_vbuffer.Data,_vbuffer.Length*_vbuffer.Pitch )
+		
+		Return vertices
+	End
+	
+	#rem monkeydoc Get indices for a material id.
+	#end
+	Method GetIndices:UInt[]( materialid:Int )
+	
+		Local ibuffer:=_ibuffers[materialid]
+		
+		Local indices:=New UInt[ ibuffer.Length ]
+		
+		libc.memcpy( indices.Data,ibuffer.Data,ibuffer.Length*4 )
+		
+		Return indices
+	End
+
+	#rem monkeydoc Gets all indices in the mesh.
+	#end	
+	Method GetAllIndices:UInt[]()
+	
+		Local n:=0
+		For Local ibuffer:=Eachin _ibuffers
+			n+=ibuffer.Length
+		Next
+		
+		Local indices:=New UInt[ n ],p:=indices.Data
+		
+		For Local ibuffer:=Eachin _ibuffers
+			
+			libc.memcpy( p,ibuffer.Data,ibuffer.Length*4 )
+		
+			p+=ibuffer.Length
+		Next
+		
+		Return indices
+	End
+	
+	#rem monkeydoc Loads a mesh from a file.
+	
+	On its own, mojo3d can only load gltf2 format mesh and model files.
+	
+	To add more formats, #import the mojo3d-assimp module into your app, eg:
+	
+	```
+	#Import "<mojo3d>"
+	#Import "<mojo3d-assimp>"
+	```
+	
+	This will allow you to load any format supported by the assimp module.
+	
+	However, importing the assimp module into your app will also increase its size.
+	
+	#end
+	Function Load:Mesh( path:String )
+	
+		For Local loader:=Eachin Mojo3dLoader.Instances
+		
+			Local mesh:=loader.LoadMesh( path )
+			If mesh Return mesh
+		
+		Next
+		
+		Return Null
+	End
+	
+	'***** INTERNAL *****
+	
+	#rem monkeydoc @hidden
+	#end
+	Method GetVertexBuffer:VertexBuffer()
+		
+		Return _vbuffer
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method GetIndexBuffers:Stack<IndexBuffer>()
+		
+		Return _ibuffers
+	End
+	
+	Private
+	
+	Enum Dirty
+		Bounds=1
+	End
+	
+	Field _dirty:Dirty
+	Field _bounds:Boxf
+	Field _vbuffer:VertexBuffer
+	Field _ibuffers:Stack<IndexBuffer>
+	
+End

+ 541 - 0
modules/mojo3d/graphics/meshprims.monkey2

@@ -0,0 +1,541 @@
+
+Namespace mojo3d.graphics
+
+Private
+
+Struct TerrainData
+	
+	Field heightMap:Pixmap
+	Field bounds:Boxf
+	Field width:Int
+	Field depth:Int
+	Field iscale:Float
+	Field jscale:Float
+	
+	Method GetPosition:Vec3f( i:Int,j:Int )
+	
+		Local x:=i*iscale
+		Local z:=j*jscale
+	
+		Local y:=heightMap.PixelPtr( i,j )[0]/255.0
+			
+		Return New Vec3f( x,y,z ) * bounds.Size + bounds.min
+	End
+
+	Method GetTexCoord0:Vec2f( i:Int,j:Int )
+	
+		Return New Vec2f( i*iscale,j*jscale )
+	End
+		
+	Method GetNormal:Vec3f( i:Int,j:Int )
+	
+		Local v0:=GetPosition( i,j )
+		Local v1:=GetPosition( i,Min( j+1,depth-1 ) )
+		Local v2:=GetPosition( Min( i+1,width-1 ),j )
+		Local v3:=GetPosition( i,Max( j-1,0 ) )
+		Local v4:=GetPosition( Max( i-1,0 ),j )
+				
+		Local n0:=(v1-v0).Cross(v2-v0).Normalize()
+		Local n1:=(v2-v0).Cross(v3-v0).Normalize()
+		Local n2:=(v3-v0).Cross(v4-v0).Normalize()
+		Local n3:=(v4-v0).Cross(v1-v0).Normalize()
+			
+		Local n:=(n0+n1+n2+n3).Normalize()
+			
+	'		If (i&15)=0 And (j&15)=0 print "n="+v
+			
+	'		DebugAssert( n.y>0 )
+		
+		Return n
+	End
+End
+
+Public
+
+Class Mesh Extension
+	
+	Function CreateRect:Mesh( rect:Rectf )
+
+		Local data:=New Mesh( 
+			New Vertex3f[](
+				New Vertex3f( rect.min.x,rect.max.y,0 ),
+				New Vertex3f( rect.max.x,rect.max.y,0 ),
+				New Vertex3f( rect.max.x,rect.min.y,0 ),
+				New Vertex3f( rect.min.x,rect.min.y,0 ) ),
+			New UInt[](
+				0,1,2,
+				0,2,3 ) )
+		
+		data.UpdateTangents()
+		
+		Return data
+	End
+
+	Function CreateBox:Mesh( box:Boxf,xsegs:Int=1,ysegs:Int=1,zsegs:Int=1 )
+		
+		Local vertices:=New Vertex3f[ ((ysegs+1)*(xsegs+1) + (zsegs+1)*(xsegs+1) + (ysegs+1)*(zsegs+1))*2 ],vp:=vertices.Data
+		
+		For Local q:=-1 To 1 Step 2
+			For Local y:=0 To ysegs
+				For Local x:=0 To xsegs
+					Local vx:=box.Width*x/xsegs+box.min.x
+					Local vy:=box.Height*y/ysegs+box.min.y
+					vp[0]=New Vertex3f( vx,vy,q>0 ? box.max.z Else box.min.z, Float(x)/xsegs,Float(y)/ysegs, 0,0,q )
+					vp+=1
+				Next
+			Next
+			For Local z:=0 To zsegs
+				For Local x:=0 To xsegs
+					Local vx:=box.Width*x/xsegs+box.min.x
+					Local vz:=box.Depth*z/zsegs+box.min.z
+					vp[0]=New Vertex3f( vx,q>0 ? box.max.y Else box.min.y,vz, Float(x)/xsegs,Float(z)/zsegs, 0,q,0 )
+					vp+=1
+				Next
+			Next
+			For Local y:=0 To ysegs
+				For Local z:=0 To zsegs
+					Local vy:=box.Height*y/ysegs+box.min.y
+					Local vz:=box.Depth*z/zsegs+box.min.z
+					vp[0]=New Vertex3f( q>0 ? box.max.x Else box.min.x,vy,vz, Float(z)/zsegs,Float(y)/ysegs, q,0,0 )
+					vp+=1
+				Next
+			Next
+		Next
+		
+		Local indices:=New UInt[ (ysegs*xsegs + zsegs*xsegs + ysegs*zsegs) * 12 ],ip:=indices.Data,v0:=0
+		
+		For Local i:=0 To 1
+			For Local y:=0 Until ysegs
+				For Local x:=0 Until xsegs
+					ip[0]=v0 ; ip[1+i]=v0+xsegs+2 ; ip[2-i]=v0+1
+					ip[3]=v0 ; ip[4+i]=v0+xsegs+1 ; ip[5-i]=v0+xsegs+2
+					ip+=6
+					v0+=1
+				Next
+				v0+=1
+			Next
+			v0+=xsegs+1
+			For Local z:=0 Until zsegs
+				For Local x:=0 Until xsegs
+					ip[0]=v0 ; ip[1+i]=v0+1 ; ip[2-i]=v0+xsegs+2
+					ip[3]=v0 ; ip[4+i]=v0+xsegs+2 ; ip[5-i]=v0+xsegs+1
+					ip+=6
+					v0+=1
+				Next
+				v0+=1
+			Next
+			v0+=xsegs+1
+			For Local y:=0 Until ysegs
+				For Local z:=0 Until zsegs
+					ip[0]=v0 ; ip[1+i]=v0+1 ; ip[2-i]=v0+zsegs+2
+					ip[3]=v0 ; ip[4+i]=v0+zsegs+2 ; ip[5-i]=v0+zsegs+1
+					ip+=6
+					v0+=1
+				Next
+				v0+=1
+			Next
+			v0+=zsegs+1
+		Next
+		
+		Local data:=New Mesh( vertices,indices )
+		
+		data.UpdateTangents()
+		
+		Return data
+	End
+
+	Function CreateSphere:Mesh( radius:float,hsegs:Int=24,vsegs:Int=12 )
+	
+		Local vertices:=New Stack<Vertex3f>
+	
+		For Local i:=0 Until hsegs
+			vertices.Push( New Vertex3f( 0,radius,0, (i+.5)/hsegs,0 ) )
+		Next
+		
+		For Local i:=1 Until vsegs
+			Local pitch:=i*Pi/vsegs-Pi/2
+			For Local j:=0 To hsegs
+				Local yaw:=(j Mod hsegs)*TwoPi/hsegs
+				Local p:=Mat3f.Rotation( pitch,yaw,0 ).k * radius
+				vertices.Push( New Vertex3f( p.x,p.y,p.z, Float(j)/hsegs,Float(i)/vsegs ) )
+			Next
+		Next
+		
+		For Local i:=0 Until hsegs
+			vertices.Push( New Vertex3f( 0,-radius,0, (i+.5)/hsegs,1 ) )
+		Next
+		
+		Local indices:=New Stack<UInt>
+		
+		For Local i:=0 Until hsegs
+			indices.AddAll( New UInt[]( i,i+hsegs+1,i+hsegs ) )
+		Next
+		
+		For Local i:=1 Until vsegs-1
+			For Local j:=0 Until hsegs
+				Local v0:=i*(hsegs+1)+j-1
+				indices.AddAll( New UInt[]( v0,v0+1,v0+hsegs+2 ) )
+				indices.AddAll( New UInt[]( v0,v0+hsegs+2,v0+hsegs+1 ) )
+			Next
+		Next
+		
+		For Local i:=0 Until hsegs
+			Local v0:=(hsegs+1)*(vsegs-1)+i-1
+			indices.AddAll( New UInt[]( v0,v0+1,v0+hsegs+1 ) )
+		Next
+		
+		Local vdata:=vertices.Data
+		For Local i:=0 Until vertices.Length
+			vdata[i].normal=vdata[i].position.Normalize()
+		Next
+		
+		Local data:=New Mesh( vertices.ToArray(),indices.ToArray() )
+		
+		data.UpdateTangents()
+		
+		Return data
+	End
+	
+	Function CreateTorus:Mesh( outerRadius:Float,innerRadius:Float,outerSegs:Int=24,innerSegs:Int=12 )
+		
+		Local vertices:=New Vertex3f[ (outerSegs+1)*(innerSegs+1) ],vp:=vertices.Data
+		
+		For Local outer:=0 To outerSegs
+			
+			Local sweep:=Mat3f.Yaw( outer*TwoPi/outerSegs )
+			
+			For Local inner:=0 To innerSegs
+				
+				Local an:=inner*TwoPi/innerSegs
+				
+				Local cos:=Cos( an ),sin:=Sin( an )
+				
+				Local p:=sweep * New Vec3f( cos * innerRadius + outerRadius,sin * innerRadius,0 )
+				Local t:=New Vec2f( Float(inner)/innerSegs,Float(outer)/outerSegs )
+				Local n:=sweep * New Vec3f( cos,sin,0 )
+				
+				Local v:=New Vertex3f( p,t,n )
+				
+				vp[0]=v
+				vp+=1
+			Next
+		
+		Next
+		
+		Local indices:=New UInt[ outerSegs*innerSegs*6 ],ip:=indices.Data
+		
+		For Local outer:=0 Until outerSegs
+			
+			Local v0:=outer * (innerSegs+1)
+			
+			For Local inner:=0 Until innerSegs
+				
+				ip[0]=v0+innerSegs+1
+				ip[2]=v0+innerSegs+2
+				ip[1]=v0+1
+				
+				ip[3]=v0+innerSegs+1
+				ip[5]=v0+1
+				ip[4]=v0
+				
+				ip+=6
+				v0+=1
+			
+			Next
+		Next
+		
+		Local data:=New Mesh( vertices,indices )
+		
+		data.UpdateTangents()
+		
+		Return data
+	End
+	
+	Function CreateCylinder:Mesh( radius:Float,length:Float,axis:Axis,segs:Int )
+		
+		Local hlength:=length/2
+		
+		Local vertices:=New Stack<Vertex3f>
+		Local triangles:=New Stack<UInt>
+		
+		'tube
+		For Local i:=0 To segs
+			Local yaw:=(i Mod segs) * TwoPi / segs
+			Local v:=New Vec3f( Cos( yaw ) * radius,hlength,Sin( yaw )* radius )
+			Local n:=New Vec3f( v.x,0,v.z ).Normalize()
+			Local tc:=New Vec2f( Float(i)/segs,0 )
+			vertices.Add( New Vertex3f( v,tc,n ) )
+			v.y=-v.y
+			tc.y=1
+			vertices.Add( New Vertex3f( v,tc,n ) )
+		Next
+		For Local i:=0 Until segs
+			triangles.Add( i*2 ) ; triangles.Add( i*2+2 ) ; triangles.Add( i*2+3 )
+			triangles.Add( i*2 ) ; triangles.Add( i*2+3 ) ; triangles.Add( i*2+1 )
+		Next
+		
+		'caps
+		Local v0:=vertices.Length
+		For Local i:=0 Until segs
+			Local yaw:=i * TwoPi / segs
+			Local v:=New Vec3f( Cos( yaw ) * radius,hlength,Sin( yaw ) * radius )
+			Local n:=New Vec3f( 0,1,0 )
+			Local tc:=New Vec2f( v.x*.5+.5,v.z*.5+.5 )
+			vertices.Add( New Vertex3f( v,tc,n ) )
+			v.y=-v.y
+			n.y=-n.y
+			vertices.Add( New Vertex3f( v,tc,n ) )
+		Next
+		For Local i:=1 Until segs-1
+			triangles.Add( v0 ) ; triangles.Add( v0+(i+1)*2 ) ; triangles.Add( v0+i*2 )
+			triangles.Add( v0+1 ) ; triangles.Add( v0+i*2+1 ) ; triangles.Add( v0+(i+1)*2+1 )
+		Next
+		
+		Local mesh:=New Mesh( vertices.ToArray(),triangles.ToArray() )
+		
+		Select axis
+		Case Axis.X
+			mesh.TransformVertices( New AffineMat4f( 0,1,0, 1,0,0, 0,0,1, 0,0,0 ) )
+		Case Axis.Z
+			mesh.TransformVertices( New AffineMat4f( 1,0,0, 0,0,1, 0,-1,0, 0,0,0 ) )
+		End
+		
+		mesh.UpdateTangents()
+		
+		Return mesh
+	End
+	
+	Function CreateCapsule:Mesh( radius:Float,length:Float,axis:Axis,segs:Int )
+		
+		Const HalfPi:=Pi/2
+		
+		Local vertices:=New Stack<Vertex3f>
+		Local triangles:=New Stack<UInt>
+		
+		Local t0:=HalfPi/radius/(length+HalfPi/radius)
+		
+		Local hlength:=length/2
+		
+		'Top hemisphere
+		'
+		For Local i:=0 Until segs
+			vertices.Add( New Vertex3f( 0,hlength+radius,0, (i+.5)/segs,0, 0,1,0 ) )
+		Next
+		For Local j:=1 To segs
+			Local pitch:=j*Pi/(segs*2)-HalfPi
+			For Local i:=0 To segs
+				Local yaw:=(i Mod segs) * TwoPi / segs
+				Local n:=Mat3f.Rotation( pitch,yaw,0 ).k
+				Local v:=n*radius
+				v.y+=hlength
+				vertices.Add( New Vertex3f( v.x,v.y,v.z, Float(i)/Float(segs),Float(j)/Float(segs)*2*t0, n.x,n.y,n.z ) )
+			Next
+		Next
+		For Local i:=0 Until segs
+			triangles.Add( i ) ; triangles.Add( i+segs+1 ) ; triangles.Add( i+segs )
+		Next
+		For Local j:=1 Until segs
+			For Local i:=0 Until segs
+				Local t:=j*(segs+1)+i-1
+				triangles.Add( t ) ; triangles.Add( t+1 ) ; triangles.Add( t+segs+2 )
+				triangles.Add( t ) ; triangles.Add( t+segs+2 ) ; triangles.Add( t+segs+1 )
+			Next
+		Next
+
+		Local v0:=vertices.Length
+		
+		For Local j:=segs Until segs*2
+			Local pitch:=j*Pi/(segs*2)-HalfPi
+			For Local i:=0 To segs
+				Local yaw:=(i Mod segs) * TwoPi / segs
+				Local n:=Mat3f.Rotation( pitch,yaw,0 ).k
+				Local v:=n*radius
+				v.y-=hlength
+				vertices.Add( New Vertex3f( v.x,v.y,v.z, Float(i)/Float(segs),(Float(j)/Float(segs*2)-.5)*2*t0, n.x,n.y,n.z ) )
+			Next
+		Next
+		For Local i:=0 Until segs
+			vertices.Add( New Vertex3f( 0,-hlength-radius,0, (i+.5)/segs,1, 0,-1,0 ) )
+		Next
+
+		For Local j:=0 Until segs-1
+			For Local i:=0 Until segs
+				Local t:=j*(segs+1)+i+v0
+				triangles.Add( t ) ; triangles.Add( t+1 ) ; triangles.Add( t+segs+2 )
+				triangles.Add( t ) ; triangles.Add( t+segs+2 ) ; triangles.Add( t+segs+1 )
+			Next
+		Next
+		
+		For Local i:=0 Until segs
+			Local t:=(segs+1)*(segs-1)+i+v0
+			triangles.Add( t ) ; triangles.Add( t+1 ) ; triangles.Add( t+segs+1 )
+		Next
+		
+		' Join 2 bits together...
+		'
+		For Local i:=0 Until segs
+			Local t:=segs*(segs+1)-1+i
+			triangles.Add( t ) ; triangles.Add( t+1 ) ; triangles.Add( t+segs+2 )
+			triangles.Add( t ) ; triangles.Add( t+segs+2 ) ; triangles.Add( t+segs+1 )
+		Next
+		
+		Local mesh:=New Mesh( vertices.ToArray(),triangles.ToArray() )
+		
+		Select axis
+		Case Axis.X
+			mesh.TransformVertices( New AffineMat4f( 0,1,0, 1,0,0, 0,0,1, 0,0,0 ) )
+		Case Axis.Z
+			mesh.TransformVertices( New AffineMat4f( 1,0,0, 0,0,1, 0,-1,0, 0,0,0 ) )
+		End
+		
+		mesh.UpdateTangents()
+		
+		Return mesh
+	End
+	
+	Function CreateCone:Mesh( radius:Float,length:Float,axis:Axis,segs:Int )
+		
+		Local hlength:=length/2
+		
+		Local vertices:=New Stack<Vertex3f>
+		Local triangles:=New Stack<UInt>
+		
+		For Local i:=0 Until segs
+			vertices.Add( New Vertex3f( 0,hlength,0, (i+.5)/segs,0, 0,1,0 ) )
+		Next
+		For Local i:=0 To segs
+			Local yaw:=(i Mod segs) * TwoPi/segs
+			Local n:=New Vec3f( Cos( yaw ),0,Sin( yaw ) )
+			Local v:=New Vec3f( n.x*radius,-hlength,n.z*radius )
+			Local tc:=New Vec2f( Float(i)/segs,1 )
+			vertices.Add( new Vertex3f( v,tc,n ) )
+		Next
+		For Local i:=0 Until segs
+			triangles.Add( i ) ; triangles.Add( i+segs+1 ) ; triangles.Add( i+segs )
+		Next
+		
+		'cap
+		Local v0:=vertices.Length
+		For Local i:=0 Until segs
+			Local yaw:=i * TwoPi / segs
+			Local n:=New Vec3f( Cos( yaw ),0,Sin( yaw ) )
+			Local v:=New Vec3f( n.x*radius,-hlength,n.z*radius )
+			Local tc:=New Vec2f( n.x*.5+.5,n.z*.5+.5 )
+			vertices.Add( new Vertex3f( v,tc,n ) )
+		Next
+		For Local i:=1 Until segs-1
+			triangles.Add( v0 ) ; triangles.Add( v0+i ) ; triangles.Add( v0+i+1 )
+		Next
+		
+		Local mesh:=New Mesh( vertices.ToArray(),triangles.ToArray() )
+		
+		Select axis
+		Case Axis.X
+			mesh.TransformVertices( New AffineMat4f( 0,1,0, 1,0,0, 0,0,1, 0,0,0 ) )
+		Case Axis.Z
+			mesh.TransformVertices( New AffineMat4f( 1,0,0, 0,0,1, 0,-1,0, 0,0,0 ) )
+		End
+		
+		mesh.UpdateTangents()
+		
+		Return mesh
+	End
+	
+	Function CreateTerrain:Mesh( heightMap:Pixmap,bounds:Boxf )
+
+		Local width:=heightMap.Width
+		Local depth:=heightMap.Height
+		
+		Local data:TerrainData
+		data.heightMap=heightMap
+		data.bounds=bounds
+		data.width=width
+		data.depth=depth
+		data.iscale=1.0/(width-1)
+		data.jscale=1.0/(depth-1)
+		
+		Local vertices:=New Vertex3f[ width*depth ]
+		
+		For Local j:=0 Until depth
+			Local vp:=vertices.Data+j*width
+			For Local i:=0 Until width
+				vp[i].position=data.GetPosition( i,j )
+				vp[i].texCoord0=data.GetTexCoord0( i,j )
+				vp[i].normal=data.GetNormal( i,j )
+			Next
+		Next
+		
+		Local indices:=New UInt[ (width-1)*(depth-1)*6 ]
+		
+		local ip:=indices.Data
+		
+		For Local j:=0 Until depth-1
+			Local v0:=j*width
+			For Local i:=0 Until width-1
+				ip[0]=v0+i ; ip[1]=v0+i+1+width ; ip[2]=v0+i+1
+				ip[3]=v0+i ; ip[4]=v0+i+width ; ip[5]=v0+i+1+width
+				ip+=6
+			Next
+		Next
+		
+		Local mesh:=New Mesh( vertices,indices )
+		
+		mesh.UpdateTangents()
+		
+		Return mesh
+	End
+	
+End
+
+Class Model Extension
+	
+	Function CreateBox:Model( box:Boxf,xsegs:Int,ysegs:Int,zsegs:Int,material:Material,parent:Entity=Null )
+		
+		Local mesh:=mojo3d.graphics.Mesh.CreateBox( box,xsegs,ysegs,zsegs )
+		
+		Return New Model( mesh,material,parent )
+	End
+	
+	Function CreateSphere:Model( radius:Float,hsegs:Int,vsegs:Int,material:Material,parent:Entity=Null )
+		
+		Local mesh:=mojo3d.graphics.Mesh.CreateSphere( radius,hsegs,vsegs )
+		
+		Return New Model( mesh,material,parent )
+	End
+	
+	Function CreateTorus:Model( outerRadius:Float,innerRadius:Float,outerSegs:Int,innerSegs:Int,material:Material,parent:Entity=Null )
+		
+		Local mesh:=mojo3d.graphics.Mesh.CreateTorus( outerRadius,innerRadius,outerSegs,innerSegs )
+		
+		Return New Model( mesh,material,parent )
+	End
+	
+	Function CreateCylinder:Model( radius:Float,length:Float,axis:Axis,segs:Int,material:Material,parent:Entity=null )
+		
+		Local mesh:=mojo3d.graphics.Mesh.CreateCylinder( radius,length,axis,segs )
+		
+		Return New Model( mesh,material,parent )
+	End
+	
+	Function CreateCapsule:Model( radius:Float,length:Float,axis:Axis,segs:Int,material:Material,parent:Entity=null )
+		
+		Local mesh:=mojo3d.graphics.Mesh.CreateCapsule( radius,length,axis,segs )
+		
+		Return New Model( mesh,material,parent )
+	End
+	
+	Function CreateCone:Model( radius:Float,length:Float,axis:Axis,segs:Int,material:Material,parent:Entity=null )
+		
+		Local mesh:=mojo3d.graphics.Mesh.CreateCone( radius,length,axis,segs )
+		
+		Return New Model( mesh,material,parent )
+	End
+	
+	Function CreateTerrain:Model( heightMap:Pixmap,bounds:Boxf,material:Material,parent:Entity=Null )
+		
+		Local mesh:=mojo3d.graphics.Mesh.CreateTerrain( heightMap,bounds )
+		
+		Return New Model( mesh,material,parent )
+	End
+	
+End

+ 195 - 0
modules/mojo3d/graphics/model.monkey2

@@ -0,0 +1,195 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc The Model class.
+#end
+Class Model Extends Entity
+	
+	#rem monkeydoc @hidden
+	#end
+	Struct Bone
+		Field entity:Entity
+		Field offset:AffineMat4f
+	End
+	
+	#rem monkeydoc Creates a new model.
+	#end
+	Method New( parent:Entity=Null )
+		Super.New( parent )
+		
+		Show()
+	End
+	
+	Method New( mesh:Mesh,material:Material,parent:Entity=Null )
+		Self.New( parent )
+		
+		_mesh=mesh
+		
+		_material=material
+	End
+	
+	#rem monkeydoc Copies the model.
+	#end	
+	Method Copy:Model( parent:Entity=Null ) Override
+		
+		Local copy:=New Model( Self,parent )
+		
+		CopyComplete( copy )
+		
+		Return copy
+	End
+
+	#rem monkeydoc The mesh rendered by the model.
+	#end	
+	Property Mesh:Mesh()
+		
+		Return _mesh
+	
+	Setter( mesh:Mesh )
+		
+		_mesh=mesh
+	End
+	
+	#rem monkeydoc The default material to use for rendering.
+	#end
+	Property Material:Material()
+		
+		Return _material
+	
+	Setter( material:Material )
+		
+		_material=material
+	End
+
+	#rem monkeydoc The materials to use for rendering.
+	#end	
+	Property Materials:Material[]()
+		
+		Return _materials
+		
+	Setter( materials:Material[] )
+		
+		_materials=materials
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property Bones:Bone[]()
+		
+		Return _bones
+	
+	Setter( bones:Bone[] )
+		
+		_bones=bones
+	End
+	
+	#rem monkeydoc Loads a model from a file path.
+	
+	On its own, mojo3d can only load gltf2 format mesh and model files.
+	
+	To add more formats, #import the mojo3d-assimp module into your app, eg:
+	
+	```
+	#Import "<mojo3d>"
+	#Import "<mojo3d-assimp>"
+	```
+	
+	This will allow you to load any format supported by the assimp module.
+	
+	However, importing the assimp module into your app will also increase its size.
+	
+	#end
+	Function Load:Model( path:String )
+	
+		For Local loader:=Eachin Mojo3dLoader.Instances
+		
+			Local model:=loader.LoadModel( path )
+			If model Return model
+		
+		Next
+		
+		Return Null
+	
+	End
+
+	#rem monkeydoc @hidden
+	#end	
+	Method OnRender( rq:RenderQueue )
+		
+		If Not _mesh Return
+		
+		Local instance:=Self
+		
+		If _bones
+		
+			instance=Null
+		
+			If _boneMatrices.Length<>_bones.Length _boneMatrices=New Mat4f[ _bones.Length ]
+			
+			For Local i:=0 Until _bones.Length
+				
+				Local bone:=_bones[i]
+				
+				_boneMatrices[i]=New Mat4f( bone.entity.WorldMatrix * bone.offset )
+'				_boneMatrices[i]=New Mat4f( bone.offset * bone.entity.WorldMatrix )
+			Next
+		
+		End
+		
+		Local vbuffer:=_mesh.GetVertexBuffer()
+		
+		Local ibuffers:=_mesh.GetIndexBuffers()
+		
+		For Local i:=0 Until ibuffers.Length
+			
+			Local material:=i<_materials.Length And _materials[i] ? _materials[i] Else _material
+			
+			If Not material DebugStop()
+			
+			Local ibuffer:=ibuffers[i]
+			
+			rq.AddRenderOp( material,vbuffer,ibuffer,instance,_boneMatrices,3,ibuffer.Length/3,0 )
+			
+		Next
+	End
+	
+	Protected
+
+	#rem monkeydoc @hidden
+	#end
+	Method New( model:Model,parent:Entity )
+		Super.New( model,parent )
+		
+		_mesh=model._mesh
+		
+		_material=model._material
+		
+		_materials=model._materials.Slice( 0 )
+		
+		Show()
+	End
+
+	#rem monkeydoc @hidden
+	#end	
+	Method OnShow() Override
+		
+		Scene.Models.Add( Self )
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Method OnHide() Override
+		
+		Scene.Models.Remove( Self )
+	End
+	
+	Private
+	
+	Field _mesh:Mesh
+	Field _material:Material
+	Field _materials:Material[]
+
+	Field _bones:Bone[]
+	Field _boneMatrices:Mat4f[]
+	
+End

+ 70 - 0
modules/mojo3d/graphics/monochromeeffect.monkey2

@@ -0,0 +1,70 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc The MonochromeEffect class.
+#end
+Class MonochromeEffect Extends PostEffect
+
+	#rem monkeydoc Creates a new monochrome effect shader.
+	#end
+	Method New( level:Float=1.0 )
+		
+		_shader=Shader.Open( "monochrome" )
+		
+		_uniforms=New UniformBlock( 2 )
+		
+		Level=level
+	End
+	
+	#rem monkeydoc The effect level.
+	
+	0=no effect, 1=full effect.
+	
+	#end
+	Property Level:Float()
+		
+		Return _uniforms.GetFloat( "Level" )
+	
+	Setter( level:Float )
+		
+		_uniforms.SetFloat( "Level",level )
+	End
+
+	Protected
+	
+	#rem monkeydoc @hidden
+	#end	
+	Method OnRender() Override
+		
+		Local rsize:=Device.Viewport.Size
+		Local rtarget:=Device.RenderTarget
+		Local rtexture:=rtarget.GetColorTexture( 0 )
+		
+		If Not _target Or rsize.x>_target.Size.x Or rsize.y>_target.Size.y
+			
+			SafeDiscard( _target ) ; SafeDiscard( _texture )
+			
+			_texture=New Texture( rsize.x,rsize.y,rtexture.Format,Null )
+			
+			_target=New RenderTarget( New Texture[]( _texture ),Null )
+		End
+					
+		_uniforms.SetTexture( "SourceTexture",rtexture )
+		_uniforms.SetVec2f( "SourceTextureSize",rsize )
+		_uniforms.SetVec2f( "SourceCoordScale",Cast<Vec2f>( rsize )/Cast<Vec2f>( rtexture.Size ) )
+		
+		Device.RenderTarget=_target
+		Device.Shader=_shader
+		Device.BindUniformBlock( _uniforms )
+		
+		RenderQuad()
+	End
+	
+	Private
+	
+	Field _shader:Shader
+	Field _uniforms:UniformBlock
+	Field _target:RenderTarget
+	Field _texture:Texture
+	
+End

+ 205 - 0
modules/mojo3d/graphics/pbrmaterial.monkey2

@@ -0,0 +1,205 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc The PbrMaterial class.
+#end
+Class PbrMaterial Extends Material
+	
+	#rem monkeydoc Creates a new pbr material.
+	
+	All properties default to white or '1' except for emissive factor which defaults to black. If you set an emissive texture, don't forget to set emissive factor to white to 'enable' it.
+	
+	The metalness value should be stored in the 'blue' channel of the metalness texture if the texture has multiple color channels.
+	
+	The roughness value should be stored in the 'green' channel of the metalness texture if the texture has multiple color channels.
+	
+	The occlusion value should be stored in the 'red' channel of the occlusion texture if the texture has multiple color channels.
+	
+	The above last 3 rules allow you to pack metalness, roughness and occlusion into a single texture.
+	
+	#end
+	Method New( boned:Bool=False )
+		Super.New( Shader.Open( boned ? "boned-material" Else "material" ) )
+		
+		ColorTexture=Texture.ColorTexture( Color.White )
+		ColorFactor=Color.White
+		
+		EmissiveTexture=Texture.ColorTexture( Color.White )
+		EmissiveFactor=Color.Black
+	
+		MetalnessTexture=Texture.ColorTexture( Color.White )
+		MetalnessFactor=1.0
+		
+		RoughnessTexture=Texture.ColorTexture( Color.White )
+		RoughnessFactor=1.0
+		
+		OcclusionTexture=Texture.ColorTexture( Color.White )
+		
+		NormalTexture=Texture.ColorTexture( New Color( 0.5,0.5,1.0,0.0 ) )
+	End
+	
+	Method New( material:PbrMaterial )
+		Super.New( material )
+	End
+	
+	Method New( color:Color,metalness:Float=0.0,roughness:Float=1.0 )
+	
+		Self.New()
+		
+		ColorFactor=color
+		MetalnessFactor=metalness
+		RoughnessFactor=roughness
+	End
+	
+	#rem monkeydoc Creates a copy of the pbr material.
+	#end
+	Method Copy:PbrMaterial() Override
+	
+		Return New PbrMaterial( Self )
+	End
+	
+	Property ColorTexture:Texture()
+	
+		Return Uniforms.GetTexture( "ColorTexture" )
+		
+	Setter( texture:Texture )
+	
+		Uniforms.SetTexture( "ColorTexture",texture )
+	End
+	
+	Property ColorFactor:Color()
+	
+		Return Uniforms.GetColor( "ColorFactor" )
+		
+	Setter( color:Color )
+	
+		Uniforms.SetColor( "ColorFactor",color )
+	End
+	
+	Property EmissiveTexture:Texture()
+	
+		Return Uniforms.GetTexture( "EmissiveTexture" )
+		
+	Setter( texture:Texture )
+	
+		Uniforms.SetTexture( "EmissiveTexture",texture )
+	End
+	
+	Property EmissiveFactor:Color()
+	
+		Return Uniforms.GetColor( "EmissiveFactor" )
+		
+	Setter( color:Color )
+	
+		Uniforms.SetColor( "EmissiveFactor",color )
+	End
+	
+	Property MetalnessTexture:Texture()
+	
+		Return Uniforms.GetTexture( "MetalnessTexture" )
+		
+	Setter( texture:Texture )
+	
+		Uniforms.SetTexture( "MetalnessTexture",texture )
+	End
+
+	Property MetalnessFactor:Float()
+	
+		Return Uniforms.GetFloat( "MetalnessFactor" )
+		
+	Setter( factor:Float )
+
+		Uniforms.SetFloat( "MetalnessFactor",factor )
+	End
+	
+	Property RoughnessTexture:Texture()
+	
+		Return Uniforms.GetTexture( "RoughnessTexture" )
+		
+	Setter( texture:Texture )
+	
+		Uniforms.SetTexture( "RoughnessTexture",texture )
+	End
+	
+	Property RoughnessFactor:Float()
+	
+		Return Uniforms.GetFloat( "RoughnessFactor" )
+		
+	Setter( factor:Float )
+	
+		Uniforms.SetFloat( "RoughnessFactor",factor )
+	End
+
+	Property OcclusionTexture:Texture()
+	
+		Return Uniforms.GetTexture( "occlusion" )
+		
+	Setter( texture:Texture )
+	
+		Uniforms.SetTexture( "OcclusionTexture",texture )
+	End
+	
+	Property NormalTexture:Texture()
+	
+		Return Uniforms.GetTexture( "NormalTexture" )
+		
+	Setter( texture:Texture )
+	
+		Uniforms.SetTexture( "NormalTexture",texture )
+	End
+
+	#rem monkeydoc Loads a PbrMaterial from a 'file'.
+	
+	A .pbr file is actually a directory containing a number of textures in png format. These textures are:
+	
+	color.png (required)
+	emissive.png
+	metalness.png
+	roughness.png
+	occlusion.png
+	normal.png
+	
+	#end
+	Function Load:PbrMaterial( path:String )
+		
+		Local flags:TextureFlags=TextureFlags.WrapST|TextureFlags.FilterMipmap
+		
+		
+		Local material:=New PbrMaterial
+		
+		Local texture:=Texture.Load( path+"/color.png",flags )
+		If texture
+			material.ColorTexture=texture
+		Endif
+		
+		texture=Texture.Load( path+"/emissive.png",flags )
+		If texture
+			material.EmissiveTexture=texture
+			material.EmissiveFactor=Color.White
+		Endif
+		
+		texture=Texture.Load( path+"/metalness.png",flags )
+		If texture
+			material.MetalnessTexture=texture
+		Endif
+		
+		texture=Texture.Load( path+"/roughness.png",flags )
+		If texture
+			material.RoughnessTexture=texture
+		Endif
+		
+		texture=Texture.Load( path+"/occlusion.png",flags )
+		If texture
+			material.OcclusionTexture=texture
+		Endif
+		
+		texture=Texture.Load( path+"/normal.png",flags )
+'		If Not texture texture=Texture.Load( path+"/unormal.png",flags|TextureFlags.InvertGreen )
+		If texture
+			material.NormalTexture=texture
+		Endif
+		
+		Return material
+	End
+	
+End

+ 79 - 0
modules/mojo3d/graphics/posteffect.monkey2

@@ -0,0 +1,79 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc The PostEffect class.
+#end
+Class PostEffect
+
+	#rem monkeydoc @hidden
+	#end
+	Method New()
+		
+		Scene.GetCurrent().PostEffects.Add( Self )
+	End
+	
+	#rem monkeydoc Enabled state.
+	
+	Set to true to enable this effect and false to disable.
+	
+	#end
+	Property Enabled:Bool()
+		
+		Return _enabled
+	
+	Setter( enabled:Bool )
+		
+		_enabled=enabled
+	End
+
+	#rem monkeydoc @hidden
+	#end	
+	Method Render( device:GraphicsDevice ) Virtual
+		
+		_device=device
+		
+		OnRender()
+	End
+
+	Protected
+	
+	#rem monkeydoc @hidden
+	#end	
+	Method OnRender() Virtual
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property Device:GraphicsDevice()
+		
+		Return _device
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property SourceRect:Recti()
+		
+		Return _device.Viewport
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property SourceTexture:Texture()
+		
+		Return _device.RenderTarget.GetColorTexture( 0 )
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Method RenderQuad()
+		
+		_device.Render( 4,1,0 )
+	End
+		
+	Private
+	
+	Global _device:GraphicsDevice
+	
+	Field _enabled:Bool=True
+	
+End

+ 18 - 0
modules/mojo3d/graphics/renderable.monkey2

@@ -0,0 +1,18 @@
+
+Namespace mojo3d.graphics
+
+Class Renderable Extends Entity Abstract
+
+	Method New( parent:Entity=Null )
+		Super.New( parent )
+	End
+	
+	Method New( renderable:Renderable,parent:Entity )
+		Super.New( renderable,parent )
+	End
+	
+	'***** INTERNAL *****
+	
+	Method OnRender( device:GraphicsDevice ) Abstract
+	
+End

+ 461 - 0
modules/mojo3d/graphics/renderer.monkey2

@@ -0,0 +1,461 @@
+
+Namespace mojo3d.graphics
+
+#rem
+
+Renderpasses:
+
+0 : render background.
+
+1 : render deferred MRT.
+
+2 : render shadow map.
+
+3 : render deferred light quad.
+
+#end
+
+#rem monkeydoc @hidden
+#end
+Class RenderOp
+	Field material:Material
+	Field vbuffer:VertexBuffer
+	Field ibuffer:IndexBuffer
+	Field instance:Entity
+	Field bones:Mat4f[]
+	Field order:Int
+	Field count:Int
+	Field first:Int
+End
+
+#rem monkeydoc @hidden
+#end
+Class RenderQueue
+	
+	Property OpaqueOps:Stack<RenderOp>()
+		Return _opaqueOps
+	End
+	
+	Property TransparentOps:Stack<RenderOp>()
+		Return _transparentOps
+	End
+	
+	Method Clear()
+		_opaqueOps.Clear()
+		_transparentOps.Clear()
+	End
+	
+	Method AddRenderOp( material:Material,vbuffer:VertexBuffer,ibuffer:IndexBuffer,order:Int,count:Int,first:Int )
+		Local op:=New RenderOp
+		op.material=material
+		op.vbuffer=vbuffer
+		op.ibuffer=ibuffer
+		op.order=order
+		op.count=count
+		op.first=first
+		_opaqueOps.Push( op )
+	End
+	
+	Method AddRenderOp( material:Material,vbuffer:VertexBuffer,ibuffer:IndexBuffer,instance:Entity,order:Int,count:Int,first:Int )
+		Local op:=New RenderOp
+		op.material=material
+		op.vbuffer=vbuffer
+		op.ibuffer=ibuffer
+		op.instance=instance
+		op.order=order
+		op.count=count
+		op.first=first
+		_opaqueOps.Push( op )
+	End
+	
+	Method AddRenderOp( material:Material,vbuffer:VertexBuffer,ibuffer:IndexBuffer,instance:Entity,bones:Mat4f[],order:Int,count:Int,first:Int )
+		Local op:=New RenderOp
+		op.material=material
+		op.vbuffer=vbuffer
+		op.ibuffer=ibuffer
+		op.instance=instance
+		op.bones=bones
+		op.order=order
+		op.count=count
+		op.first=first
+		_opaqueOps.Push( op )
+	End
+	
+	Private
+	
+	Field _opaqueOps:=New Stack<RenderOp>
+	Field _transparentOps:=New Stack<RenderOp>
+	
+End
+
+#rem monkeydoc The Renderer class.
+#end
+Class Renderer
+
+	#rem monkeydoc @hidden
+	#end
+	Method New()
+	
+		_device=New GraphicsDevice( 0,0 )
+		
+		_uniforms=New UniformBlock( 1 )
+		
+		_device.BindUniformBlock( _uniforms )
+
+		_csmSplits=New Float[]( 1,20,60,180,1000 )
+
+		_quadVertices=New VertexBuffer( New Vertex3f[](
+			New Vertex3f( 0,1,0 ),
+			New Vertex3f( 1,1,0 ),
+			New Vertex3f( 1,0,0 ),
+			New Vertex3f( 0,0,0 ) ) )
+			
+		_defaultEnv=Texture.Load( "asset::textures/env_default.jpg",TextureFlags.FilterMipmap|TextureFlags.Cubemap )
+		
+		_skyboxShader=Shader.Open( "skybox" )
+		
+		For Local i:=0 Until _nullBones.Length
+			_nullBones[i]=New Mat4f
+		Next
+	End
+	
+	#rem monkeydoc Size of the cascading shadow map texture.
+	
+	Must be a power of 2 size.
+	
+	Defaults to 4096.
+		
+	#end
+	Property CSMTextureSize:Float()
+		
+		Return _csmSize
+		
+	Setter( size:Float )
+		Assert( Log2( size )=Floor( Log2( size ) ),"CSMTextureSize must be a power of 2" )
+		
+		_csmSize=size
+	End
+	
+	#rem monkeydoc Array containing the Z depths of the cascading shadow map frustum splits.
+	
+	Defaults to Float[]( 1,20,60,180,1000 ).
+		
+	#end
+	Property CSMSplitDepths:Float[]()
+		
+		Return _csmSplits
+	
+	Setter( splits:Float[] )
+		Assert( splits.Length=5,"CSMSplitDepths array must have 5 elements" )
+		
+		_csmSplits=splits
+	End
+	
+	#rem monkeydoc Gets the current renderer.
+	#end
+	Function GetCurrent:Renderer()
+		
+		Global _current:=New DeferredRenderer
+		
+		Return _current
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method Render( scene:Scene,camera:Camera,device:GraphicsDevice )
+		
+		Validate()
+	
+		_renderTarget=device.RenderTarget
+		_renderTargetSize=device.RenderTargetSize
+		_renderViewport=device.Viewport
+		
+		SetScene( scene )
+		
+		SetCamera( camera )
+		
+		OnRender()
+	End
+	
+	'***** INTERNAL *****
+	
+	Protected
+
+	Field _csmSize:=4096
+	Field _csmSplits:=New Float[]( 1,20,60,180,1000 )
+	
+	Field _uniforms:UniformBlock
+	Field _device:GraphicsDevice
+	
+	Field _csmTexture:Texture
+	Field _csmTarget:RenderTarget
+	Field _quadVertices:VertexBuffer
+	Field _skyboxShader:Shader
+	Field _defaultEnv:Texture
+	
+	Field _renderQueue:=New RenderQueue
+	Field _spriteQueue:=New RenderQueue
+	Field _spriteBuffer:=New SpriteBuffer
+	
+	Field _nullBones:=New Mat4f[64]
+	
+	'Per render...
+	'
+	Field _renderTarget:RenderTarget
+	Field _renderTargetSize:Vec2i
+	Field _renderViewport:Recti
+	
+	Field _scene:Scene
+	Field _camera:Camera
+	
+'	Field _projectionMatrix:Mat4f
+'	Field _viewMatrix:AffineMat4f
+'	Field _viewProjectionMatrix:Mat4f
+	
+	Method OnRender() Virtual
+	End
+
+	Method SetScene( scene:Scene )
+	
+		_scene=scene
+		
+		_uniforms.SetFloat( "Time",Now() )
+		_uniforms.SetTexture( "SkyTexture",_scene.SkyTexture )
+		
+		_uniforms.SetVec4f( "ClearColor",_scene.ClearColor )
+		_uniforms.SetVec4f( "AmbientDiffuse",_scene.AmbientLight )
+	
+		_uniforms.SetTexture( "ShadowTexture",_csmTexture )
+		_uniforms.SetVec4f( "ShadowSplits",New Vec4f( _csmSplits[1],_csmSplits[2],_csmSplits[3],_csmSplits[4] ) )
+		
+		Local env:Texture
+		
+		If _scene.SkyTexture
+			env=_scene.SkyTexture
+		Else If _scene.EnvTexture
+			env=_scene.EnvTexture
+		Else
+			env=_defaultEnv
+		Endif
+		
+		_uniforms.SetTexture( "EnvTexture",env )
+		
+		_renderQueue.Clear()
+		
+		For Local model:=Eachin _scene.Models
+			
+			model.OnRender( _renderQueue )
+		Next
+		
+		For Local terrain:=Eachin _scene.Terrains
+			
+			terrain.OnRender( _renderQueue )
+		Next
+	End
+
+	Method SetCamera( camera:Camera )
+	
+		_camera=camera
+		
+		Local envMat:=_camera.WorldMatrix.m
+		Local viewMat:=_camera.InverseWorldMatrix
+		Local projMat:=_camera.ProjectionMatrix
+		Local invProjMat:=-projMat
+			
+		_uniforms.SetMat3f( "EnvMatrix",envMat )
+		_uniforms.SetMat4f( "ProjectionMatrix",projMat )
+		_uniforms.SetMat4f( "InverseProjectionMatrix",invProjMat )
+		_uniforms.SetFloat( "DepthNear",_camera.Near )
+		_uniforms.SetFloat( "DepthFar",_camera.Far )
+		
+		_spriteQueue.Clear()
+		
+		_spriteBuffer.AddSprites( _spriteQueue,_scene.Sprites,_camera )
+	End
+	
+	'MX2_RENDERPASS 0
+	'
+	Method RenderBackground() Virtual
+	
+		If _scene.SkyTexture
+		
+			_device.ColorMask=ColorMask.None
+			_device.DepthMask=True
+			
+			_device.Clear( Null,1.0 )
+			
+			_device.ColorMask=ColorMask.All
+			_device.DepthMask=False
+			_device.DepthFunc=DepthFunc.Always
+			_device.BlendMode=BlendMode.Opaque
+			_device.CullMode=CullMode.None
+			_device.RenderPass=0
+			
+			_device.VertexBuffer=_quadVertices
+			_device.Shader=_skyboxShader
+			_device.Render( 4,1 )
+			
+		Else
+			_device.ColorMask=ColorMask.All
+			_device.DepthMask=True
+		
+			_device.Clear( _scene.ClearColor,1.0 )
+
+		Endif
+		
+	End
+	
+	'MX2_RNDERPASS 1
+	'
+	Method RenderAmbient() Virtual
+		
+		_device.ColorMask=ColorMask.All
+		_device.DepthMask=True
+		_device.DepthFunc=DepthFunc.LessEqual
+		_device.RenderPass=1
+		
+		RenderRenderOps( _renderQueue.OpaqueOps,_camera.InverseWorldMatrix,_camera.ProjectionMatrix )
+	End
+	
+	'MX2_RENDERPASS 0
+	'
+	Method RenderSprites()
+	
+		_device.ColorMask=ColorMask.All
+		_device.DepthMask=False
+		_device.DepthFunc=DepthFunc.Always
+		_device.RenderPass=0
+
+		RenderRenderOps( _spriteQueue.OpaqueOps,_camera.InverseWorldMatrix,_camera.ProjectionMatrix )
+	End
+	
+	'MX2_RENDERPASS 2
+	'
+	Method RenderCSMShadows( light:Light )
+	
+		'Perhaps use a different device for CSM...?
+		'
+		Local t_rtarget:=_device.RenderTarget
+		Local t_viewport:=_device.Viewport
+		Local t_scissor:=_device.Scissor
+
+		_device.RenderTarget=_csmTarget
+		_device.Viewport=New Recti( 0,0,_csmTarget.Size )
+		_device.Scissor=_device.Viewport
+		_device.ColorMask=ColorMask.None
+		_device.DepthMask=True
+		_device.Clear( Null,1.0 )
+		
+		If light.ShadowsEnabled
+		
+			_device.DepthFunc=DepthFunc.LessEqual
+			_device.BlendMode=BlendMode.Opaque
+			_device.CullMode=CullMode.Back
+			_device.RenderPass=2
+	
+			Local invLightMatrix:=light.InverseWorldMatrix
+			Local viewLight:=invLightMatrix * _camera.WorldMatrix
+			
+			For Local i:=0 Until _csmSplits.Length-1
+				
+				Local znear:=_csmSplits[i]
+				Local zfar:=_csmSplits[i+1]
+				
+				Local splitProj:=Mat4f.Perspective( _camera.Fov,_camera.Aspect,znear,zfar )
+							
+				Local invSplitProj:=-splitProj
+				
+				Local bounds:=Boxf.EmptyBounds
+				
+				For Local z:=-1 To 1 Step 2
+					For Local y:=-1 To 1 Step 2
+						For Local x:=-1 To 1 Step 2
+							Local c:=New Vec3f( x,y,z )				'clip coords
+							Local v:=invSplitProj * c				'clip->view
+							Local l:=viewLight * v					'view->light
+							bounds|=l
+						Next
+					Next
+				Next
+				
+				bounds.min.z-=100
+				
+				Local lightProj:=Mat4f.Ortho( bounds.min.x,bounds.max.x,bounds.min.y,bounds.max.y,bounds.min.z,bounds.max.z )
+				
+				'set matrices for next pass...
+				_uniforms.SetMat4f( "ShadowMatrix"+i,lightProj * viewLight )
+				
+				Local size:=_csmTexture.Size,hsize:=size/2
+				
+				Select i
+				Case 0 _device.Viewport=New Recti( 0,0,hsize.x,hsize.y )
+				Case 1 _device.Viewport=New Recti( hsize.x,0,size.x,hsize.y )
+				Case 2 _device.Viewport=New Recti( 0,hsize.y,hsize.x,size.y )
+				Case 3 _device.Viewport=New Recti( hsize.x,hsize.y,size.x,size.y )
+				End
+				
+				_device.Scissor=_device.Viewport
+					
+				RenderRenderOps( _renderQueue.OpaqueOps,invLightMatrix,lightProj )
+			Next
+			
+		Endif
+		
+		_device.RenderTarget=t_rtarget
+		_device.Viewport=t_viewport
+		_device.Scissor=t_scissor
+	End
+
+	Method Validate()
+		
+		If Not _csmTexture Or _csmSize<>_csmTexture.Size.x
+			
+			If _csmTexture _csmTexture.Discard()
+			If _csmTarget _csmTarget.Discard()
+			
+			_csmTexture=New Texture( _csmSize,_csmSize,PixelFormat.Depth32F,TextureFlags.Dynamic )
+			_csmTarget=New RenderTarget( Null,_csmTexture )
+			
+		Endif
+	End
+	
+	Method RenderRenderOps( ops:Stack<RenderOp>,viewMatrix:AffineMat4f,projMatrix:Mat4f )
+		
+		_uniforms.SetMat4f( "ViewMatrix",viewMatrix )
+		_uniforms.SetMat4f( "ProjectionMatrix",projMatrix )
+		_uniforms.SetMat4f( "InverseProjectionMatrix",-projMatrix )
+		
+		For Local op:=Eachin ops
+			
+			Local model:=op.instance
+			
+			Local modelMat:= model ? model.WorldMatrix Else New AffineMat4f
+			Local modelViewMat:=viewMatrix * modelMat
+			Local modelViewProjMat:=projMatrix * modelViewMat
+			Local modelViewNormMat:=~-modelViewMat.m
+				
+			_uniforms.SetMat4f( "ModelMatrix",modelMat )
+			_uniforms.SetMat4f( "ModelViewMatrix",modelViewMat )
+			_uniforms.SetMat4f( "ModelViewProjectionMatrix",modelViewProjMat )
+			_uniforms.SetMat3f( "ModelViewNormalMatrix",modelViewNormMat )
+			
+			If op.bones
+				_uniforms.SetMat4fArray( "BoneMatrices",op.bones )
+			Else
+				_uniforms.SetMat4fArray( "BoneMatrices",_nullBones )
+			End
+			
+			Local material:=op.material
+			
+			_device.Shader=material.Shader
+			_device.BindUniformBlock( material.Uniforms )
+			_device.BlendMode=material.BlendMode
+			_device.CullMode=material.CullMode
+			_device.VertexBuffer=op.vbuffer
+			_device.IndexBuffer=op.ibuffer
+			_device.RenderIndexed( op.order,op.count,op.first )
+			
+		Next
+	End
+
+End

+ 179 - 0
modules/mojo3d/graphics/scene.monkey2

@@ -0,0 +1,179 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc The Scene class.
+#end
+Class Scene
+
+	#rem monkeydoc Creates a new scene.
+	#end
+	Method New()
+		
+		_clearColor=Color.Sky
+
+		_ambientDiffuse=Color.DarkGrey
+	End
+	
+	#rem monkeydoc The sky texture.
+	
+	The sky texture is used to clear the scene. 
+	
+	If there is no sky texture, the clear color is used instead.
+	
+	This must currently be a valid cubemap texture.
+	
+	#end
+	Property SkyTexture:Texture()
+		
+		Return _skytex
+	
+	Setter( skytex:Texture )
+		
+		_skytex=skytex
+	End
+	
+	#rem monkeydoc The environment texture.
+	
+	The environment textures is used to render specular reflections within the scene.
+	
+	If there is no environment texture, the sky texture is used instead.
+		
+	If there is no environment texture and no sky texture, a default internal environment texture is used.
+	
+	This must currently be a valid cubemap texture.
+	
+	#end
+	Property EnvTexture:Texture()
+		
+		Return _envtex
+	
+	Setter( envtex:Texture )
+		
+		_envtex=envtex
+	End
+	
+	#rem monkeydoc The clear color.
+	
+	The clear color is used to clear the scene.
+	
+	The clear color is only used if there is no sky texture.
+	
+	#end
+	Property ClearColor:Color()
+		
+		Return _clearColor
+		
+	Setter( color:Color )
+		
+		_clearColor=color
+	End
+	
+	#rem monkeydoc Ambient diffuse lighting.
+	#end
+	Property AmbientLight:Color()
+		
+		Return _ambientDiffuse
+		
+	Setter( color:Color )
+		
+		_ambientDiffuse=color
+	End
+	
+	#rem monkeydoc Renders the scene to	a canvas.
+	#end
+	Method Render( canvas:Canvas,camera:Camera )
+			
+		camera.Viewport=canvas.Viewport
+			
+		canvas.Flush()
+		
+		Renderer.GetCurrent().Render( Self,camera,canvas.GraphicsDevice )
+	End
+	
+	#rem monkeydoc Gets the current scene.
+	#end
+	Function GetCurrent:Scene()
+		If Not _current _current=New Scene
+			
+		Return _current
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Function SetCurrent( scene:Scene )
+		
+		_current=scene
+	End
+
+	'***** INTERNAL *****
+
+	#rem monkeydoc @hidden
+	#end		
+	Property PostEffects:Stack<PostEffect>()
+		
+		Return _postEffects
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property RootEntities:Stack<Entity>()
+		
+		Return _rootEntities
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property Cameras:Stack<Camera>()
+		
+		Return _cameras
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property Lights:Stack<Light>()
+		
+		Return _lights
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property Models:Stack<Model>()
+		
+		Return _models
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property Terrains:Stack<Terrain>()
+		
+		Return _terrains
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property Sprites:Stack<Sprite>()
+		
+		Return _sprites
+	End
+
+	Private
+	
+	Global _current:Scene
+	
+	Global _defaultEnv:Texture
+	
+	Field _skytex:Texture
+	Field _envtex:Texture
+	Field _clearColor:Color
+	Field _ambientDiffuse:Color
+	Field _postEffects:=New Stack<PostEffect>
+	
+	Field _rootEntities:=New Stack<Entity>
+	
+	Field _cameras:=New Stack<Camera>
+	Field _lights:=New Stack<Light>
+	Field _models:=New Stack<Model>
+	Field _terrains:=New Stack<Terrain>
+	Field _sprites:=New Stack<Sprite>
+			
+End

+ 72 - 0
modules/mojo3d/graphics/shaders/bloom.glsl

@@ -0,0 +1,72 @@
+
+//@renderpasses 0,1,2,3
+
+uniform sampler2D m_SourceTexture;
+uniform vec2 m_SourceTextureSize;
+uniform vec2 m_SourceCoordScale;
+
+varying vec2 v_TexCoord0;
+
+//@vertex
+
+attribute vec2 a_Position;	//0...1 (1=viewport size)
+
+void main(){
+
+	v_TexCoord0=a_Position * m_SourceCoordScale;
+
+	gl_Position=vec4( a_Position * 2.0 - 1.0,-1.0,1.0 );
+}
+
+//@fragment
+
+const float weight0=0.227027,weight1=0.1945946,weight2=0.1216216,weight3=0.054054,weight4=0.016216;
+
+//apparently the same if linear filter enabled in texture...
+//const float weight0=0.2270270270,weight1=0.3162162162,weight2=0.0702702703;
+
+void main(){
+
+#if MX2_RENDERPASS==3
+
+	vec3 result=texture2D( m_SourceTexture,v_TexCoord0 ).rgb;
+
+#else
+
+	vec2 texel_size=1.0/m_SourceTextureSize;
+	
+	vec3 result=texture2D( m_SourceTexture,v_TexCoord0 ).rgb*weight0;
+	
+#if MX2_RENDERPASS==0 || MX2_RENDERPASS==2
+
+	result+=texture2D(m_SourceTexture,v_TexCoord0+vec2(texel_size.x*1.0,0.0)).rgb * weight1;
+	result+=texture2D(m_SourceTexture,v_TexCoord0-vec2(texel_size.x*1.0,0.0)).rgb * weight1;
+	result+=texture2D(m_SourceTexture,v_TexCoord0+vec2(texel_size.x*2.0,0.0)).rgb * weight2;
+	result+=texture2D(m_SourceTexture,v_TexCoord0-vec2(texel_size.x*2.0,0.0)).rgb * weight2;
+	result+=texture2D(m_SourceTexture,v_TexCoord0+vec2(texel_size.x*3.0,0.0)).rgb * weight3;
+	result+=texture2D(m_SourceTexture,v_TexCoord0-vec2(texel_size.x*3.0,0.0)).rgb * weight3;
+	result+=texture2D(m_SourceTexture,v_TexCoord0+vec2(texel_size.x*4.0,0.0)).rgb * weight4;
+	result+=texture2D(m_SourceTexture,v_TexCoord0-vec2(texel_size.x*4.0,0.0)).rgb * weight4;
+	
+#elif MX2_RENDERPASS==1
+
+	result+=texture2D(m_SourceTexture,v_TexCoord0+vec2(0.0,texel_size.y*1.0)).rgb * weight1;
+	result+=texture2D(m_SourceTexture,v_TexCoord0-vec2(0.0,texel_size.y*1.0)).rgb * weight1;
+	result+=texture2D(m_SourceTexture,v_TexCoord0+vec2(0.0,texel_size.y*2.0)).rgb * weight2;
+	result+=texture2D(m_SourceTexture,v_TexCoord0-vec2(0.0,texel_size.y*2.0)).rgb * weight2;
+	result+=texture2D(m_SourceTexture,v_TexCoord0+vec2(0.0,texel_size.y*3.0)).rgb * weight3;
+	result+=texture2D(m_SourceTexture,v_TexCoord0-vec2(0.0,texel_size.y*3.0)).rgb * weight3;
+	result+=texture2D(m_SourceTexture,v_TexCoord0+vec2(0.0,texel_size.y*4.0)).rgb * weight4;
+	result+=texture2D(m_SourceTexture,v_TexCoord0-vec2(0.0,texel_size.y*4.0)).rgb * weight4;
+
+#endif
+
+#if MX2_RENDERPASS==0
+	result=max( result-1.0,0.0 );
+#endif
+
+#endif
+	
+	gl_FragColor=vec4( result,1.0 );
+	
+}

+ 184 - 0
modules/mojo3d/graphics/shaders/boned-material.glsl

@@ -0,0 +1,184 @@
+//@renderpasses 1,2
+
+//material uniforms
+
+uniform mat3 m_TextureMatrix;
+
+//renderer uniforms...
+
+uniform mat4 r_ModelViewMatrix;
+uniform mat4 r_ModelViewProjectionMatrix;
+uniform mat3 r_ModelViewNormalMatrix;
+
+#if MX2_RENDERPASS==1
+
+uniform vec4 r_AmbientDiffuse;
+uniform samplerCube r_EnvTexture;
+uniform mat3 r_EnvMatrix;
+
+//pbr varyings...
+
+varying vec3 v_Position;
+varying vec2 v_TexCoord0;
+varying vec3 v_Normal;
+varying mat3 v_TanMatrix;
+
+#endif
+
+//@vertex
+
+uniform mat4 r_BoneMatrices[64];
+
+//vertex attribs....
+
+attribute vec4 a_Position;
+attribute vec4 a_Weights;
+attribute vec4 a_Bones;
+
+#if MX2_RENDERPASS==1 
+
+attribute vec2 a_TexCoord0;
+attribute vec3 a_Normal;
+attribute vec4 a_Tangent;
+
+#endif
+
+void main(){
+
+	mat4 m0=r_BoneMatrices[ int( a_Bones.x ) ];
+	mat4 m1=r_BoneMatrices[ int( a_Bones.y ) ];
+	mat4 m2=r_BoneMatrices[ int( a_Bones.z ) ];
+	mat4 m3=r_BoneMatrices[ int( a_Bones.a ) ];
+	
+	vec4 b_Position=
+		m0 * a_Position * a_Weights.x +
+		m1 * a_Position * a_Weights.y +
+		m2 * a_Position * a_Weights.z +
+		m3 * a_Position * a_Weights.a;
+		
+#if MX2_RENDERPASS==1
+
+	mat3 n0=mat3( m0[0].xyz,m0[1].xyz,m0[2].xyz );
+	mat3 n1=mat3( m1[0].xyz,m1[1].xyz,m1[2].xyz );
+	mat3 n2=mat3( m2[0].xyz,m2[1].xyz,m2[2].xyz );
+	mat3 n3=mat3( m3[0].xyz,m3[1].xyz,m3[2].xyz );
+
+	vec3 b_Normal=normalize( 
+		n0 * a_Normal * a_Weights.x +
+		n1 * a_Normal * a_Weights.y +
+		n2 * a_Normal * a_Weights.z +
+		n3 * a_Normal * a_Weights.a );
+		
+	vec4 b_Tangent=vec4( normalize( 
+		n0 * a_Tangent.xyz * a_Weights.x +
+		n1 * a_Tangent.xyz * a_Weights.y +
+		n2 * a_Tangent.xyz * a_Weights.z +
+		n3 * a_Tangent.xyz * a_Weights.a ),a_Tangent.w );
+
+	// texture coord0
+	v_TexCoord0=(m_TextureMatrix * vec3(a_TexCoord0,1.0)).st;
+
+	// view space position
+	v_Position=( r_ModelViewMatrix * b_Position ).xyz;
+
+	// viewspace normal
+	v_Normal=r_ModelViewNormalMatrix * b_Normal;
+	
+	// viewspace tangent matrix
+	v_TanMatrix[2]=v_Normal;
+	v_TanMatrix[0]=r_ModelViewNormalMatrix * b_Tangent.xyz;
+	v_TanMatrix[1]=cross( v_TanMatrix[0],v_TanMatrix[2] ) * b_Tangent.a;
+	
+#endif
+	
+	gl_Position=r_ModelViewProjectionMatrix * b_Position;
+}
+
+//@fragment
+
+#if MX2_RENDERPASS==1
+
+void main0( vec3 color,vec3 emissive,float metalness,float roughness,float occlusion,vec3 normal ){
+
+	normal=normalize( v_TanMatrix * normal );
+
+	vec3 color0=vec3( 0.04,0.04,0.04 );
+	
+	vec3 diffuse=color * (1.0-metalness);
+	
+	vec3 specular=(color-color0) * metalness + color0;
+	
+	vec3 rvec=r_EnvMatrix * reflect( v_Position,normal );
+	
+	float lod=textureCube( r_EnvTexture,rvec,10.0 ).a * 255.0 - 10.0;
+	
+	if( lod>0.0 ) lod=textureCube( r_EnvTexture,rvec ).a * 255.0;
+	
+	vec3 env=pow( textureCube( r_EnvTexture,rvec,max( roughness*10.0-lod,0.0 ) ).rgb,vec3( 2.2 ) );
+
+	vec3 vvec=normalize( -v_Position );
+	
+	float ndotv=max( dot( normal,vvec ),0.0 );
+	
+	vec3 fschlick=specular + (1.0-specular) * pow( 1.0-ndotv,5.0 ) * (1.0-roughness);
+
+	vec3 ambdiff=diffuse * r_AmbientDiffuse.rgb;
+		
+	vec3 ambspec=env * fschlick;
+
+	gl_FragData[0]=vec4( min( (ambdiff+ambspec) * occlusion + emissive,8.0 ),1.0 );
+	
+	gl_FragData[1]=vec4( color,metalness );
+	
+	gl_FragData[2]=vec4( normal * 0.5 + 0.5,roughness );
+}
+
+#endif
+
+#if MX2_RENDERPASS==1
+
+uniform sampler2D m_ColorTexture;
+uniform vec4 m_ColorFactor;
+
+uniform sampler2D m_EmissiveTexture;
+uniform vec4 m_EmissiveFactor;
+
+uniform sampler2D m_MetalnessTexture;
+uniform float m_MetalnessFactor;
+
+uniform sampler2D m_RoughnessTexture;
+uniform float m_RoughnessFactor;
+
+uniform sampler2D m_OcclusionTexture;
+
+uniform sampler2D m_NormalTexture;
+
+#endif
+
+void main(){
+
+#if MX2_RENDERPASS==1
+
+	vec3 color=pow( texture2D( m_ColorTexture,v_TexCoord0 ).rgb,vec3( 2.2 ) ) * m_ColorFactor.rgb;
+	
+	vec3 emissive=pow( texture2D( m_EmissiveTexture,v_TexCoord0 ).rgb,vec3( 2.2 ) ) * m_EmissiveFactor.rgb;
+	
+	float metalness=texture2D( m_MetalnessTexture,v_TexCoord0 ).b * m_MetalnessFactor;
+	
+	float roughness=texture2D( m_RoughnessTexture,v_TexCoord0 ).g * m_RoughnessFactor;
+	
+	float occlusion=texture2D( m_OcclusionTexture,v_TexCoord0 ).r;
+
+	vec3 normal=texture2D( m_NormalTexture,v_TexCoord0 ).xyz * 2.0 - 1.0;
+	
+	main0( color,emissive,metalness,roughness,occlusion,normal );
+	
+//	gl_FragColor=vec4( 1.0,0.5,0.0,1.0 );	
+	
+#else
+
+	gl_FragColor=vec4( vec3( gl_FragCoord.z ),1.0 );
+
+#endif
+
+}

+ 26 - 0
modules/mojo3d/graphics/shaders/copy.glsl

@@ -0,0 +1,26 @@
+
+//@renderpasses 0
+
+uniform sampler2D r_SourceTexture;
+
+uniform vec2 r_SourceCoordScale;
+
+varying vec2 v_TexCoord0;
+
+//@vertex
+
+attribute vec2 a_Position;	//0...1 (1=viewport size)
+
+void main(){
+
+	v_TexCoord0=a_Position * r_SourceCoordScale;
+
+	gl_Position=vec4( a_Position * 2.0 - 1.0,-1.0,1.0 );
+}
+
+//@fragment
+
+void main(){
+
+	gl_FragColor=vec4( pow( texture2D( r_SourceTexture,v_TexCoord0 ).rgb,vec3( 1.0/2.2 ) ),1.0 );
+}

+ 159 - 0
modules/mojo3d/graphics/shaders/directional-light.glsl

@@ -0,0 +1,159 @@
+
+//@renderpasses 3
+
+uniform mat4 r_InverseProjectionMatrix;
+
+uniform vec2 r_BufferCoordScale;
+uniform sampler2D r_ColorBuffer;
+uniform sampler2D r_NormalBuffer;
+uniform sampler2D r_DepthBuffer;
+
+uniform float r_DepthNear;
+uniform float r_DepthFar;
+
+uniform mat4 r_LightViewMatrix;
+uniform vec4 r_LightColor;
+uniform float r_LightRange;
+
+uniform sampler2D r_ShadowTexture;
+uniform mat4 r_ShadowMatrix0;
+uniform mat4 r_ShadowMatrix1;
+uniform mat4 r_ShadowMatrix2;
+uniform mat4 r_ShadowMatrix3;
+uniform vec4 r_ShadowSplits;
+
+varying vec2 v_ClipPosition;
+varying vec2 v_TexCoord0;
+
+//@vertex
+
+attribute vec2 a_Position;	//0...1 (1=viewport size)
+
+void main(){
+
+	v_ClipPosition=a_Position * 2.0 - 1.0;
+	
+	v_TexCoord0=a_Position * r_BufferCoordScale;
+	
+	gl_Position=vec4( v_ClipPosition,-1.0,1.0 );
+}
+
+//@fragment
+
+vec3 v_Position;
+vec3 v_Normal;
+
+float viewDepth( float depth ){
+
+	return r_DepthFar * r_DepthNear / ( r_DepthFar + depth * ( r_DepthNear - r_DepthFar ) );
+}
+
+float evalShadow(){
+
+	vec4 vpos=vec4( v_Position + v_Normal * .01,1.0 );
+	vec4 lpos;
+	vec2 off;
+	
+	if( vpos.z<r_ShadowSplits.x ){
+		lpos=r_ShadowMatrix0 * vpos;
+		off=vec2( 0.0,0.0 );
+	}else if( vpos.z<r_ShadowSplits.y ){
+		lpos=r_ShadowMatrix1 * vpos;
+		off=vec2( 0.5,0.0 );
+	}else if( vpos.z<r_ShadowSplits.z ){
+		lpos=r_ShadowMatrix2 * vpos;
+		off=vec2( 0.0,0.5 );
+	}else{
+		lpos=r_ShadowMatrix3 * vpos;
+		off=vec2( 0.5,0.5 );
+	}
+	
+	vec3 spos=lpos.xyz/lpos.w * vec3( 0.25,0.25,0.5 ) + vec3( 0.25,0.25,0.5 );
+
+	spos.z*=0.99;
+	
+	float d=texture2D( r_ShadowTexture,spos.xy+off ).r;
+	
+	if( spos.z>d ) return 0.0;
+	
+	return 1.0;
+}
+
+vec3 evalLight( vec3 color,float metalness,float roughness ){
+
+	vec3 normal=v_Normal;
+	
+	float glosiness=1.0-roughness;
+	
+	vec3 color0=vec3( 0.04,0.04,0.04 );
+	
+	vec3 diffuse=color * (1.0-metalness);
+	
+	vec3 specular=(color-color0) * metalness + color0;
+	
+	//lighting
+	
+	vec3 vvec=normalize( -v_Position );
+	vec3 lvec=normalize( -r_LightViewMatrix[2].xyz );
+	vec3 hvec=normalize( lvec+vvec );
+
+	float spow=pow( 2.0,glosiness * 12.0 );
+//	float spow=pow( 4096.0,glosiness );
+//	float spow=exp2( 12.0 * glosiness + 1.0 );
+
+	float fnorm=(spow+2.0)/8.0;
+	
+	float hdotl=max( dot( hvec,lvec ),0.0 );
+	vec3 fschlick=specular + (1.0-specular) * pow( 1.0-hdotl,5.0 ) * glosiness;
+	
+	float ndotl=max( dot( normal,lvec ),0.0 );
+	float ndoth=max( dot( normal,hvec ),0.0 );
+	
+	vec3 light=r_LightColor.rgb * ndotl;
+	
+	specular=pow( ndoth,spow ) * fnorm * fschlick;
+	
+	return (diffuse+specular) * light;
+}
+
+void main(){
+
+	vec4 color_m=texture2D( r_ColorBuffer,v_TexCoord0 );
+	
+	vec4 normal_r=texture2D( r_NormalBuffer,v_TexCoord0 );
+	
+	float depth=viewDepth( texture2D( r_DepthBuffer,v_TexCoord0 ).r );
+
+	vec4 vpos4=r_InverseProjectionMatrix * vec4( v_ClipPosition,-1.0,1.0 );
+	
+	vec3 vpos=vpos4.xyz/vpos4.w;
+
+	//debug vpos x/y
+	//
+	//if( abs( vpos.x )>=1.0 || abs( vpos.y )>=1.0 ){
+	//	gl_FragColor=vec4( 0.0,0.0,1.0,1.0 );
+	//	return;
+	//}
+	
+	//debug z
+	//
+	//if( abs( vpos.z-r_DepthNear)>0.00001 ){
+	//	gl_FragColor=vec4( 1.0,0.0,0.0,1.0 );
+	//	return;
+	//}
+	
+	v_Position=vpos/vpos.z*depth;
+	
+	v_Normal=normalize( normal_r.xyz * 2.0 - 1.0 );
+	
+	float shadow=evalShadow();
+	
+	shadow=1.0;
+	
+	vec3 light=evalLight( color_m.rgb,color_m.a,normal_r.a );
+	
+	gl_FragColor=vec4( min( light * shadow,8.0 ),1.0 );
+	
+//	gl_FragColor=vec4( light * shadow,1.0 );
+}
+

+ 41 - 0
modules/mojo3d/graphics/shaders/fog.glsl

@@ -0,0 +1,41 @@
+
+//@renderpasses 0
+
+varying vec2 v_TexCoord0;
+
+//@vertex
+
+uniform vec2 r_BufferCoordScale;
+
+attribute vec2 a_Position;	//0...1 (1=viewport size)
+
+void main(){
+
+	v_TexCoord0=a_Position * r_BufferCoordScale;
+	
+	gl_Position=vec4( a_Position * 2.0 - 1.0,-1.0,1.0 );
+}
+
+//@fragment
+
+uniform sampler2D r_DepthBuffer;
+uniform float r_DepthNear;
+uniform float r_DepthFar;
+
+uniform vec4 m_Color;
+uniform float m_Near;
+uniform float m_Far;
+
+float viewDepth( float depth ){
+
+	return r_DepthFar * r_DepthNear / ( r_DepthFar + depth * ( r_DepthNear - r_DepthFar ) );
+}
+
+void main(){
+
+	float depth=viewDepth( texture2D( r_DepthBuffer,v_TexCoord0 ).r );
+	
+	float fog=clamp( (depth-m_Near)/(m_Far-m_Near),0.0,1.0 ) * m_Color.a;
+	
+	gl_FragColor=vec4( m_Color.rgb * fog,fog );	//premultiplied alpha!
+}

+ 153 - 0
modules/mojo3d/graphics/shaders/material.glsl

@@ -0,0 +1,153 @@
+
+//@renderpasses 1,2
+
+//material uniforms
+
+uniform mat3 m_TextureMatrix;
+
+//renderer uniforms...
+
+uniform mat4 r_ModelViewMatrix;
+uniform mat4 r_ModelViewProjectionMatrix;
+uniform mat3 r_ModelViewNormalMatrix;
+
+#if MX2_RENDERPASS==1
+
+uniform vec4 r_AmbientDiffuse;
+uniform samplerCube r_EnvTexture;
+uniform mat3 r_EnvMatrix;
+
+//pbr varyings...
+
+varying vec3 v_Position;
+varying vec2 v_TexCoord0;
+varying vec3 v_Normal;
+varying mat3 v_TanMatrix;
+
+#endif
+
+//@vertex
+
+//vertex attribs....
+
+attribute vec4 a_Position;
+
+#if MX2_RENDERPASS==1 
+
+attribute vec2 a_TexCoord0;
+attribute vec3 a_Normal;
+attribute vec4 a_Tangent;
+
+#endif
+
+void main(){
+
+#if MX2_RENDERPASS==1
+
+	// texture coord0
+	v_TexCoord0=(m_TextureMatrix * vec3(a_TexCoord0,1.0)).st;
+
+	// view space position
+	v_Position=( r_ModelViewMatrix * a_Position ).xyz;
+
+	// viewspace normal
+	v_Normal=r_ModelViewNormalMatrix * a_Normal;
+	
+	// viewspace tangent matrix
+	v_TanMatrix[2]=v_Normal;
+	v_TanMatrix[0]=r_ModelViewNormalMatrix * a_Tangent.xyz;
+	v_TanMatrix[1]=cross( v_TanMatrix[0],v_TanMatrix[2] ) * a_Tangent.a;
+	
+#endif
+	
+	gl_Position=r_ModelViewProjectionMatrix * a_Position;
+}
+
+//@fragment
+
+#if MX2_RENDERPASS==1
+
+void main0( vec3 color,vec3 emissive,float metalness,float roughness,float occlusion,vec3 normal ){
+
+	normal=normalize( v_TanMatrix * normal );
+
+	vec3 color0=vec3( 0.04,0.04,0.04 );
+	
+	vec3 diffuse=color * (1.0-metalness);
+	
+	vec3 specular=(color-color0) * metalness + color0;
+	
+	vec3 rvec=r_EnvMatrix * reflect( v_Position,normal );
+	
+	float lod=textureCube( r_EnvTexture,rvec,10.0 ).a * 255.0 - 10.0;
+	
+	if( lod>0.0 ) lod=textureCube( r_EnvTexture,rvec ).a * 255.0;
+	
+	vec3 env=pow( textureCube( r_EnvTexture,rvec,max( roughness*10.0-lod,0.0 ) ).rgb,vec3( 2.2 ) );
+
+	vec3 vvec=normalize( -v_Position );
+	
+	float ndotv=max( dot( normal,vvec ),0.0 );
+	
+	vec3 fschlick=specular + (1.0-specular) * pow( 1.0-ndotv,5.0 ) * (1.0-roughness);
+
+	vec3 ambdiff=diffuse * r_AmbientDiffuse.rgb;
+		
+	vec3 ambspec=env * fschlick;
+
+	gl_FragData[0]=vec4( min( (ambdiff+ambspec) * occlusion + emissive,8.0 ),1.0 );
+
+	gl_FragData[1]=vec4( color,metalness );
+	
+	gl_FragData[2]=vec4( normal * 0.5 + 0.5,roughness );
+}
+
+#endif
+
+#if MX2_RENDERPASS==1
+
+uniform sampler2D m_ColorTexture;
+uniform vec4 m_ColorFactor;
+
+uniform sampler2D m_EmissiveTexture;
+uniform vec4 m_EmissiveFactor;
+
+uniform sampler2D m_MetalnessTexture;
+uniform float m_MetalnessFactor;
+
+uniform sampler2D m_RoughnessTexture;
+uniform float m_RoughnessFactor;
+
+uniform sampler2D m_OcclusionTexture;
+
+uniform sampler2D m_NormalTexture;
+
+#endif
+
+void main(){
+
+#if MX2_RENDERPASS==1
+
+	vec3 color=pow( texture2D( m_ColorTexture,v_TexCoord0 ).rgb,vec3( 2.2 ) ) * m_ColorFactor.rgb;
+	
+	vec3 emissive=pow( texture2D( m_EmissiveTexture,v_TexCoord0 ).rgb,vec3( 2.2 ) ) * m_EmissiveFactor.rgb;
+	
+	float metalness=texture2D( m_MetalnessTexture,v_TexCoord0 ).b * m_MetalnessFactor;
+	
+	float roughness=texture2D( m_RoughnessTexture,v_TexCoord0 ).g * m_RoughnessFactor;
+	
+	float occlusion=texture2D( m_OcclusionTexture,v_TexCoord0 ).r;
+
+	vec3 normal=texture2D( m_NormalTexture,v_TexCoord0 ).xyz * 2.0 - 1.0;
+	
+	main0( color,emissive,metalness,roughness,occlusion,normal );
+	
+//	gl_FragColor=vec4( 1.0,0.5,0.0,1.0 );	
+	
+#else
+
+	gl_FragColor=vec4( vec3( gl_FragCoord.z ),1.0 );
+
+#endif
+
+}

+ 31 - 0
modules/mojo3d/graphics/shaders/monochrome.glsl

@@ -0,0 +1,31 @@
+//@renderpasses 0
+
+uniform vec2 m_SourceCoordScale;
+
+varying vec2 v_TexCoord0;
+
+//@vertex
+
+attribute vec2 a_Position;	//0...1 (1=viewport size)
+
+void main(){
+
+	v_TexCoord0=a_Position * m_SourceCoordScale;
+
+	gl_Position=vec4( a_Position * 2.0 - 1.0,-1.0,1.0 );
+}
+
+//@fragment
+
+uniform sampler2D m_SourceTexture;
+
+uniform float m_Level;
+
+void main(){
+
+	vec4 color=texture2D( m_SourceTexture,v_TexCoord0 );
+
+	float i=(color.r+color.g+color.b)/3.0;
+	
+	gl_FragColor=vec4( mix( color.rgb,vec3( i ),m_Level ),color.a );
+}

+ 118 - 0
modules/mojo3d/graphics/shaders/point-light.glsl

@@ -0,0 +1,118 @@
+
+//@renderpasses 2
+
+uniform mat4 r_InverseProjectionMatrix;
+
+uniform vec2 r_BufferCoordScale;
+uniform sampler2D r_ColorBuffer;
+uniform sampler2D r_NormalBuffer;
+uniform sampler2D r_DepthBuffer;
+
+uniform float r_DepthNear;
+uniform float r_DepthFar;
+
+uniform mat4 r_LightViewMatrix;
+uniform vec4 r_LightColor;
+uniform float r_LightRange;
+
+varying vec2 v_ClipPosition;
+varying vec2 v_TexCoord0;
+
+//@vertex
+
+attribute vec2 a_Position;
+	
+void main(){
+
+	v_ClipPosition=a_Position * 2.0 - 1.0;
+	
+	v_TexCoord0=a_Position * r_BufferCoordScale;
+	
+	gl_Position=vec4( v_ClipPosition,-1.0,1.0 );
+}
+
+//@fragment
+
+vec3 v_Position;
+vec3 v_Normal;
+
+float viewDepth( float depth ){
+
+	return r_DepthFar * r_DepthNear / ( r_DepthFar + depth * ( r_DepthNear - r_DepthFar ) );
+}
+
+vec3 evalLight( vec3 color,float metalness,float roughness ){
+
+	vec3 normal=v_Normal;
+	
+	float glosiness=1.0-roughness;
+	
+	vec3 color0=vec3( 0.04,0.04,0.04 );
+	
+	vec3 diffuse=color * (1.0-metalness);
+	
+	vec3 specular=(color-color0) * metalness + color0;
+	
+	//lighting
+	
+	vec3 lightDir=r_LightViewMatrix[3].xyz-v_Position;
+
+	//Cool! https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/
+	//	
+	float atten=1.0/( pow( length( lightDir/r_LightRange ),2.0 ) );
+	
+	vec3 vvec=normalize( -v_Position );
+	vec3 lvec=normalize( lightDir );
+	vec3 hvec=normalize( lvec+vvec );
+
+	float spow=pow( 2.0,glosiness * 12.0 );
+//	float spow=pow( 4096.0,glosiness );
+//	float spow=exp2( 12.0 * glosiness + 1.0 );
+
+	float fnorm=(spow+2.0)/8.0;
+	
+	float hdotl=max( dot( hvec,lvec ),0.0 );
+	vec3 fschlick=specular + (1.0-specular) * pow( 1.0-hdotl,5.0 ) * glosiness;
+	
+	float ndotl=max( dot( normal,lvec ),0.0 );
+	float ndoth=max( dot( normal,hvec ),0.0 );
+	
+	vec3 lightColor=r_LightColor.rgb * ndotl * atten;
+	
+	specular=pow( ndoth,spow ) * fnorm * fschlick;
+	
+	return (diffuse+specular) * lightColor;
+}
+
+void main(){
+
+	vec4 color_m=texture2D( r_ColorBuffer,v_TexCoord0 );
+	
+	vec4 normal_r=texture2D( r_NormalBuffer,v_TexCoord0 );
+	
+	float depth=viewDepth( texture2D( r_DepthBuffer,v_TexCoord0 ).r );
+
+	vec4 vpos4=r_InverseProjectionMatrix * vec4( v_ClipPosition,-1.0,1.0 );
+	
+	vec3 vpos=vpos4.xyz/vpos4.w;
+
+	/*	
+	if( abs( vpos.z-r_DepthNear)>0.00001 ){
+		gl_FragColor=vec4( 1.0,0.0,0.0,1.0 );
+		return;
+	}
+	if( abs( vpos.x )>=1.0 || abs( vpos.y )>=1.0 ){
+		gl_FragColor=vec4( 0.0,1.0,0.0,1.0 );
+		return;
+	}
+	*/
+	
+	v_Position=vpos/vpos.z*depth;
+	
+	v_Normal=normalize( normal_r.xyz * 2.0 - 1.0 );
+	
+	vec3 light=evalLight( color_m.rgb,color_m.a,normal_r.a );
+	
+	gl_FragColor=vec4( min( light,8.0 ),1.0 );
+}
+

+ 32 - 0
modules/mojo3d/graphics/shaders/skybox.glsl

@@ -0,0 +1,32 @@
+
+//@renderpasses 0
+
+varying vec2 v_ClipPosition;
+
+//@vertex
+
+attribute vec2 a_Position;	//0...1
+
+void main(){
+
+	v_ClipPosition=a_Position * 2.0 - 1.0;
+
+	gl_Position=vec4( v_ClipPosition,0.0,1.0 );
+}
+
+//@fragment
+
+uniform mat3 r_EnvMatrix;
+
+uniform samplerCube r_SkyTexture;
+
+uniform mat4 r_InverseProjectionMatrix;
+
+void main(){
+
+	vec4 clip=r_InverseProjectionMatrix * vec4( v_ClipPosition,0.0,1.0 );
+
+	vec3 tv=r_EnvMatrix * (clip.xyz/clip.w);
+	
+	gl_FragColor=vec4( pow( textureCube( r_SkyTexture,tv ).rgb,vec3( 2.2 ) ),1.0 );
+}

+ 41 - 0
modules/mojo3d/graphics/shaders/sprite3d.glsl

@@ -0,0 +1,41 @@
+//@renderpasses 0
+
+//material uniforms
+
+uniform mat3 m_TextureMatrix;
+
+//renderer uniforms...
+
+uniform mat4 r_ModelViewProjectionMatrix;
+
+//varyings...
+
+varying vec2 v_TexCoord0;
+
+//@vertex
+
+attribute vec4 a_Position;
+
+attribute vec2 a_TexCoord0;
+
+void main(){
+
+	v_TexCoord0=(m_TextureMatrix * vec3(a_TexCoord0,1.0)).st;
+	
+	gl_Position=r_ModelViewProjectionMatrix * a_Position;
+}
+
+//@fragment
+
+uniform sampler2D m_ColorTexture;
+
+uniform vec4 m_ColorFactor;
+
+void main(){
+
+	vec3 color=pow( texture2D( m_ColorTexture,v_TexCoord0 ).rgb,vec3( 2.2 ) ) * m_ColorFactor.rgb;
+	
+	float alpha=texture2D( m_ColorTexture,v_TexCoord0 ).a * m_ColorFactor.a;
+	
+	gl_FragColor=vec4( color,alpha );
+}

+ 154 - 0
modules/mojo3d/graphics/shaders/terrain.glsl

@@ -0,0 +1,154 @@
+//@renderpasses 0,1
+
+//renderer uniforms...
+
+uniform mat4 r_ModelViewMatrix;
+uniform mat4 r_ModelViewProjectionMatrix;
+uniform mat3 r_ModelViewNormalMatrix;
+
+#if MX2_RENDERPASS==0
+
+uniform vec4 r_AmbientDiffuse;
+uniform samplerCube r_EnvTexture;
+uniform mat3 r_EnvMatrix;
+
+//pbr varyings...
+
+varying vec3 v_Position;
+varying vec2 v_TexCoord0;
+varying vec3 v_Normal;
+varying mat3 v_TanMatrix;
+
+#endif
+
+//@vertex
+
+//vertex attribs....
+
+attribute vec4 a_Position;
+
+#if MX2_RENDERPASS==0 
+
+attribute vec2 a_TexCoord0;
+attribute vec3 a_Normal;
+attribute vec4 a_Tangent;
+
+#endif
+
+void main(){
+
+#if MX2_RENDERPASS==0
+
+	// view space position
+	v_Position=( r_ModelViewMatrix * a_Position ).xyz;
+
+	// texture coord0
+	v_TexCoord0=a_TexCoord0;
+
+	// viewspace normal
+	v_Normal=r_ModelViewNormalMatrix * a_Normal;
+	
+	// viewspace tangent matrix
+	v_TanMatrix[2]=v_Normal;
+	v_TanMatrix[0]=r_ModelViewNormalMatrix * a_Tangent.xyz;
+	v_TanMatrix[1]=cross( v_TanMatrix[0],v_TanMatrix[2] ) * a_Tangent.a;
+	
+#endif
+	
+	gl_Position=r_ModelViewProjectionMatrix * a_Position;
+}
+
+//@fragment
+
+#if MX2_RENDERPASS==0
+
+void main0( vec3 color,vec3 emissive,float metalness,float roughness,float occlusion,vec3 normal ){
+
+	normal=normalize( v_TanMatrix * normal );
+
+	vec3 color0=vec3( 0.04,0.04,0.04 );
+	
+	vec3 diffuse=color * (1.0-metalness);
+	
+	vec3 specular=(color-color0) * metalness + color0;
+	
+	vec3 rvec=r_EnvMatrix * reflect( v_Position,normal );
+	
+	float lod=textureCube( r_EnvTexture,rvec,10.0 ).a * 255.0 - 10.0;
+	
+	if( lod>0.0 ) lod=textureCube( r_EnvTexture,rvec ).a * 255.0;
+	
+	vec3 env=pow( textureCube( r_EnvTexture,rvec,max( roughness*10.0-lod,0.0 ) ).rgb,vec3( 2.2 ) );
+
+	vec3 vvec=normalize( -v_Position );
+	
+	float ndotv=max( dot( normal,vvec ),0.0 );
+	
+	vec3 fschlick=specular + (1.0-specular) * pow( 1.0-ndotv,5.0 ) * (1.0-roughness);
+
+	vec3 ambdiff=diffuse * r_AmbientDiffuse.rgb;
+		
+	vec3 ambspec=env * fschlick;
+
+	gl_FragData[0]=vec4( min( (ambdiff+ambspec) * occlusion + emissive,8.0 ),1.0 );
+	
+	gl_FragData[1]=vec4( color,metalness );
+	
+	gl_FragData[2]=vec4( normal * 0.5 + 0.5,roughness );
+}
+
+#endif
+
+#if MX2_RENDERPASS==0
+
+uniform sampler2D m_BlendTexture;
+
+uniform sampler2D m_ColorTexture0;
+uniform sampler2D m_ColorTexture1;
+uniform sampler2D m_ColorTexture2;
+uniform sampler2D m_ColorTexture3;
+
+uniform sampler2D m_NormalTexture0;
+uniform sampler2D m_NormalTexture1;
+uniform sampler2D m_NormalTexture2;
+uniform sampler2D m_NormalTexture3;
+
+#endif
+
+void main(){
+
+#if MX2_RENDERPASS==0
+
+	vec4 blend=texture2D( m_BlendTexture,v_TexCoord0 ).rgb
+	
+	vec3 color0=pow( texture2D( m_ColorTexture0,v_TexCoord0 ).rgb,vec3( 2.2 ) ) * blend.r;
+	vec3 color1=pow( texture2D( m_ColorTexture1,v_TexCoord0 ).rgb,vec3( 2.2 ) ) * blend.g;
+	vec3 color2=pow( texture2D( m_ColorTexture2,v_TexCoord0 ).rgb,vec3( 2.2 ) ) * blend.b;
+	vec3 color3=pow( texture2D( m_ColorTexture3,v_TexCoord0 ).rgb,vec3( 2.2 ) ) * blend.a;
+	
+	vec3 normal0=( texture2D( m_NormalTexture0,v_TexCoord0 ).xyz * 2.0 - 1.0 ) * blend.r;
+	vec3 normal1=( texture2D( m_NormalTexture1,v_TexCoord0 ).xyz * 2.0 - 1.0 ) * blend.g;
+	vec3 normal2=( texture2D( m_NormalTexture2,v_TexCoord0 ).xyz * 2.0 - 1.0 ) * blend.b;
+	vec3 normal3=( texture2D( m_NormalTexture3,v_TexCoord0 ).xyz * 2.0 - 1.0 ) * blend.a;
+	
+	vec3 color=color0+color1+color2+color3;
+	
+	vec3 normal=normalize( normal0+normal1+normal2+normal3 );
+	
+	vec3 emissive=vec3( 0.0 );
+	
+	float metalness=0.0;
+	
+	float roughness=1.0;
+	
+	float occlusion=1.0;
+
+	main0( color,emissive,metalness,roughness,occlusion,normal );
+	
+#else
+
+	gl_FragColor=vec4( vec3( gl_FragCoord.z ),1.0 );
+
+#endif
+
+}

+ 153 - 0
modules/mojo3d/graphics/shaders/water.glsl

@@ -0,0 +1,153 @@
+
+//@renderpasses 1,2
+
+//material uniforms
+
+uniform mat3 m_TextureMatrix;
+
+//renderer uniforms
+
+uniform mat4 r_ModelViewMatrix;
+uniform mat4 r_ModelViewProjectionMatrix;
+uniform mat3 r_ModelViewNormalMatrix;
+
+#if MX2_RENDERPASS==1
+
+uniform vec4 r_AmbientDiffuse;
+uniform samplerCube r_EnvTexture;
+uniform mat3 r_EnvMatrix;
+
+//pbr varyings...
+
+varying vec3 v_Position;
+varying vec2 v_TexCoord0;
+varying vec3 v_Normal;
+varying mat3 v_TanMatrix;
+
+#endif
+
+//@vertex
+
+//vertex attribs....
+
+attribute vec4 a_Position;
+
+#if MX2_RENDERPASS==1 
+
+attribute vec2 a_TexCoord0;
+attribute vec3 a_Normal;
+attribute vec4 a_Tangent;
+
+#endif
+
+void main(){
+
+#if MX2_RENDERPASS==1
+
+	// texture coord0
+	v_TexCoord0=(m_TextureMatrix * vec3(a_TexCoord0,1.0)).st;
+
+	// view space position
+	v_Position=( r_ModelViewMatrix * a_Position ).xyz;
+
+	// viewspace normal
+	v_Normal=r_ModelViewNormalMatrix * a_Normal;
+	
+	// viewspace tangent matrix
+	v_TanMatrix[2]=v_Normal;
+	v_TanMatrix[0]=r_ModelViewNormalMatrix * a_Tangent.xyz;
+	v_TanMatrix[1]=cross( v_TanMatrix[0],v_TanMatrix[2] ) * a_Tangent.a;
+	
+#endif
+	
+	gl_Position=r_ModelViewProjectionMatrix * a_Position;
+}
+
+//@fragment
+
+#if MX2_RENDERPASS==1
+
+void main0( vec3 color,vec3 emissive,float metalness,float roughness,float occlusion,vec3 normal ){
+
+	normal=normalize( v_TanMatrix * normal );
+
+	vec3 color0=vec3( 0.04,0.04,0.04 );
+	
+	vec3 diffuse=color * (1.0-metalness);
+	
+	vec3 specular=(color-color0) * metalness + color0;
+	
+	vec3 rvec=r_EnvMatrix * reflect( v_Position,normal );
+	
+	float lod=textureCube( r_EnvTexture,rvec,10.0 ).a * 255.0 - 10.0;
+	
+	if( lod>0.0 ) lod=textureCube( r_EnvTexture,rvec ).a * 255.0;
+	
+	vec3 env=pow( textureCube( r_EnvTexture,rvec,max( roughness*10.0-lod,0.0 ) ).rgb,vec3( 2.2 ) );
+
+	vec3 vvec=normalize( -v_Position );
+	
+	float ndotv=max( dot( normal,vvec ),0.0 );
+	
+	vec3 fschlick=specular + (1.0-specular) * pow( 1.0-ndotv,5.0 ) * (1.0-roughness);
+
+	vec3 ambdiff=diffuse * r_AmbientDiffuse.rgb;
+		
+	vec3 ambspec=env * fschlick;
+
+	gl_FragData[0]=vec4( min( (ambdiff+ambspec) * occlusion + emissive,8.0 ),1.0 );
+	
+	gl_FragData[1]=vec4( color,metalness );
+	
+	gl_FragData[2]=vec4( normal * 0.5 + 0.5,roughness );
+}
+
+#endif
+
+#if MX2_RENDERPASS==1
+
+uniform float r_Time;
+
+uniform sampler2D m_ColorTexture;
+uniform vec4 m_ColorFactor;
+
+uniform float m_Metalness;
+uniform float m_Roughness;
+
+uniform sampler2D m_NormalTexture0;
+uniform sampler2D m_NormalTexture1;
+
+uniform vec2 m_Velocity0;
+uniform vec2 m_Velocity1;
+
+#endif
+
+void main(){
+
+#if MX2_RENDERPASS==1
+
+	vec3 color=pow( texture2D( m_ColorTexture,v_TexCoord0 ).rgb,vec3( 2.2 ) ) * m_ColorFactor.rgb;
+
+	vec3 normal0=texture2D( m_NormalTexture0,v_TexCoord0 + m_Velocity0 * r_Time ).xyz * 2.0 - 1.0;
+	
+	vec3 normal1=texture2D( m_NormalTexture1,v_TexCoord0 + m_Velocity1 * r_Time ).xyz * 2.0 - 1.0;
+	
+	vec3 normal=normalize( normal0+normal1 );
+	
+	vec3 emissive=vec3( 0.0 );
+	
+	float metalness=m_Metalness;
+	
+	float roughness=m_Roughness;
+	
+	float occlusion=1.0;
+
+	main0( color,emissive,metalness,roughness,occlusion,normal );
+	
+#else
+
+	gl_FragColor=vec4( vec3( gl_FragCoord.z ),1.0 );
+
+#endif
+
+}

+ 122 - 0
modules/mojo3d/graphics/sprite.monkey2

@@ -0,0 +1,122 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc SpriteMode enumeration.
+
+| LightType		| Description
+|:--------------|:-----------
+| `Billboard`	| Sprite always faces the camera, eg: like a lens flare.
+| `Upright`		| Sprite faces the camera but remains upright, eg: like a tree.
+
+#end
+Enum SpriteMode
+	Billboard=1
+	Upright=2
+End
+
+#rem monkeydoc The Sprite class.
+#end
+Class Sprite Extends Entity
+	
+	#rem monkeydoc Creates a new sprite.
+	#end
+	Method New( parent:Entity=Null )
+		Super.New( parent )
+
+		Show()
+	End
+
+	Method New( material:Material,parent:Entity=Null )
+		Self.New( parent )
+		
+		_material=material
+	End
+
+	#rem monkeydoc Copies the sprite.
+	#end	
+	Method Copy:Sprite( parent:Entity=Null ) Override
+		
+		Local copy:=New Sprite( Self,parent )
+		
+		CopyComplete( copy )
+		
+		Return copy
+	End
+
+	#rem monkeydoc Material used to render the sprite.
+	
+	This must currently be an instance of a SpriteMaterial.
+	
+	#end	
+	Property Material:Material()
+		
+		Return _material
+	
+	Setter( material:Material )
+		
+		_material=material
+	End
+	
+	#rem monkeydoc Sprite handle.
+	
+	Defaults to 0.5,0.5.
+	
+	#end
+	Property Handle:Vec2f()
+		
+		Return _handle
+	
+	Setter( handle:Vec2f )
+		
+		_handle=handle
+	End
+	
+	#rem monkeydoc Sprite mode.
+	
+	Defaults to SpriteMode.Billboard.
+	
+	#end
+	Property Mode:SpriteMode()
+		
+		Return _mode
+		
+	Setter( mode:SpriteMode )
+		
+		_mode=mode
+	End
+	
+	Protected
+
+	#rem monkeydoc @hidden
+	#End		
+	Method New( sprite:Sprite,parent:Entity )
+		Super.New( sprite,parent )
+		
+		_material=sprite._material
+		_handle=sprite._handle
+		_mode=sprite._mode
+		
+		Show()
+	End
+	
+	#rem monkeydoc @hidden
+	#End		
+	Method OnShow() Override
+		
+		Scene.Sprites.Add( Self )
+	End
+	
+	#rem monkeydoc @hidden
+	#End		
+	Method OnHide() Override
+		
+		Scene.Sprites.Remove( Self )
+	End
+	
+	Private
+	
+	Field _material:Material
+	Field _handle:Vec2f=New Vec2f( .5,.5 )
+	Field _mode:SpriteMode=Null
+	
+End

+ 93 - 0
modules/mojo3d/graphics/spritebuffer.monkey2

@@ -0,0 +1,93 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc @hidden
+#end
+Class SpriteBuffer
+	
+	Method New()
+		
+		If _spriteVertices Return
+		
+		_spriteVertices=New VertexBuffer( Vertex3fFormat.Instance,0 )
+		
+		_spriteIndices=New IndexBuffer( IndexFormat.UINT32,0 )
+	End
+	
+	Method AddSprites( rq:RenderQueue,sprites:Stack<Sprite>,camera:Camera )
+		
+		If sprites.Empty Return
+		
+		Local n:=sprites.Length
+
+		_spriteVertices.Clear()
+		Local vp:=Cast<Vertex3f Ptr>( _spriteVertices.AddVertices( n*4 ) )
+		
+		If n>_spriteIndices.Length/6
+			Local i:=_spriteIndices.Length/6
+			Local ip:=Cast<UInt Ptr>( _spriteIndices.AddIndices( (n-i)*6 ) )
+			For Local j:=i Until n
+				ip[0]=j*4
+				ip[1]=j*4+1
+				ip[2]=j*4+2
+				ip[3]=j*4
+				ip[4]=j*4+2
+				ip[5]=j*4+3
+				ip+=6
+			Next
+		Endif
+		
+		sprites.Sort( Lambda:Int( x:Sprite,y:Sprite )
+			Return camera.WorldPosition.Distance( y.WorldPosition ) <=> camera.WorldPosition.Distance( x.WorldPosition )
+		End )
+		
+		Local cmaterial:=sprites[0].Material
+		Local i0:=0,i:=0
+		
+		For Local sprite:=Eachin sprites
+			
+			Local material:=sprite.Material
+			If material<>cmaterial
+				rq.AddRenderOp( cmaterial,_spriteVertices,_spriteIndices,Null,3,(i-i0)*2,i0*6 )
+				cmaterial=material
+				i0=i
+			Endif
+			
+			Local r:=camera.WorldBasis
+			
+			Select sprite.Mode
+			Case SpriteMode.Upright
+	
+				r.j=New Vec3f( 0,1,0 ) ; r.i=r.j.Cross( r.k ).Normalize()
+			End
+			
+			Local matrix:=New AffineMat4f( r.Scale( sprite.WorldScale ),sprite.WorldPosition )
+			
+			Local handle:=sprite.Handle
+			
+			vp[0].position=matrix * New Vec3f( -handle.x,1-handle.y,0 )
+			vp[0].texCoord0=New Vec2f( 0,0 )
+			
+			vp[1].position=matrix * New Vec3f( 1-handle.x,1-handle.y,0 )
+			vp[1].texCoord0=New Vec2f( 1,0 )
+
+			vp[2].position=matrix * New Vec3f( 1-handle.x,-handle.y,0 )
+			vp[2].texCoord0=New Vec2f( 1,1 )
+			
+			vp[3].position=matrix * New Vec3f( -handle.x,-handle.y,0 )
+			vp[3].texCoord0=New Vec2f( 0,1 )
+			
+			vp+=4
+			i+=1
+		Next
+		
+		rq.AddRenderOp( cmaterial,_spriteVertices,_spriteIndices,Null,3,(i-i0)*2,i0*6 )
+	End
+
+	Private
+	
+	Field _spriteVertices:VertexBuffer
+	
+	Field _spriteIndices:IndexBuffer
+	
+End

+ 68 - 0
modules/mojo3d/graphics/spritematerial.monkey2

@@ -0,0 +1,68 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc The SpriteMaterial class.
+#end
+Class SpriteMaterial Extends Material
+	
+	#rem monkeydoc Creates a new sprite material.
+	#end	
+	Method New()
+		Super.New( Shader.Open( "sprite3d" ) )
+		
+		BlendMode=BlendMode.Alpha
+		CullMode=CullMode.None
+		
+		ColorTexture=Texture.ColorTexture( Color.White )
+		ColorFactor=Color.White
+	End
+	
+	#rem monkeydoc Creates a copy of the sprite material.
+	#end
+	Method Copy:SpriteMaterial() Override
+	
+		Return New SpriteMaterial( Self )
+	End
+	
+	Property ColorTexture:Texture()
+		
+		Return Uniforms.GetTexture( "ColorTexture" )
+	
+	Setter( texture:Texture )
+		
+		Uniforms.SetTexture( "ColorTexture",texture )
+	End
+	
+	Property ColorFactor:Color()
+	
+		Return Uniforms.GetColor( "ColorFactor" )
+		
+	Setter( color:Color )
+	
+		Uniforms.SetColor( "ColorFactor",color )
+	End
+
+	#rem monkeydoc Loads a sprite material from an image file.
+	#end	
+	Function Load:SpriteMaterial( path:String )
+		
+		Local texture:=Texture.Load( path,TextureFlags.FilterMipmap )
+		If Not texture texture=Texture.ColorTexture( Color.Magenta )
+		
+		Local material:=New SpriteMaterial
+		material.ColorTexture=texture
+		
+		Return material
+	End
+	
+	Protected
+	
+	#rem monkeydoc @hidden
+	#end
+	Method New( material:SpriteMaterial )
+	
+		Super.New( material )
+	End
+	
+End
+

+ 198 - 0
modules/mojo3d/graphics/terrain.monkey2

@@ -0,0 +1,198 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc The Terrain class.
+#end
+Class Terrain Extends Entity
+
+	#rem monkeydoc Creates a new terrain.
+	#end	
+	Method New( heightMap:Pixmap,bounds:Boxf,material:Material,parent:Entity=null )
+		Super.New( parent )
+		
+		Local cellSize:=128
+		
+		Assert( heightMap.Width=heightMap.Height,"Terrain heightmap must be square" )
+		
+		Assert( Log2( heightMap.Width )=Floor( Log2( heightMap.Width ) ),"Terrain heightmap size must be power of 2" )
+
+		Assert( Log2( cellSize )=Floor( Log2( cellSize ) ),"Terrain cell size must be power of 2" )
+		
+		Assert( heightMap.Width>=cellSize,"Terrain heightmap size must be greater than cell size" )
+		
+		_heightMap=heightMap
+		_size=heightMap.Width
+		_bounds=bounds
+		_material=material
+		_cellSize=cellSize
+		
+		Init()
+		
+		Show()
+	End
+	
+	Method OnRender( rq:RenderQueue )
+		
+		Local count:=_ibuffer.Length/3
+		
+		For Local vbuffer:=Eachin _vbuffers
+
+			rq.AddRenderOp( _material,vbuffer,_ibuffer,Self,3,count,0 )
+			
+		Next
+	End
+	
+	Protected
+	
+	Method OnShow() Override
+		
+		Scene.Terrains.Add( Self )
+	End
+	
+	Method OnHide() Override
+		
+		Scene.Terrains.Remove( Self )
+	End
+	
+	Private
+	
+	Field _heightMap:Pixmap
+	Field _bounds:Boxf
+	Field _material:Material
+	
+	Field _cellSize:Int
+	Field _size:Int
+	
+	Field _ibuffer:IndexBuffer
+	Field _vbuffers:VertexBuffer[]
+	
+	Method GetPosition:Vec3f( i:Int,j:Int )
+		
+		i=Clamp( i,0,_size-1 )
+		j=Clamp( j,0,_size-1 )
+		
+		Local x:=Float(i)/Float(_size-1)
+		Local z:=Float(j)/Float(_size-1)
+		
+		Local y:=_heightMap.PixelPtr( i,j )[0]/255.0
+		
+		Return New Vec3f( x,y,z ) * _bounds.Size+_bounds.min
+	End
+
+	Method GetTexCoord0:Vec2f( i:Int,j:Int )
+	
+		Local x:=Float(i)/Float(_size-1)
+		Local z:=Float(j)/Float(_size-1)
+
+		Return New Vec2f( x,z )
+	End
+	
+	Method GetNormal:Vec3f( i:Int,j:Int )
+
+		Local v0:=GetPosition( i,j )
+		Local v1:=GetPosition( i,Min( j+1,_size-1 ) )
+		Local v2:=GetPosition( Min( i+1,_size-1 ),j )
+		Local v3:=GetPosition( i,Max( j-1,0 ) )
+		Local v4:=GetPosition( Max( i-1,0 ),j )
+				
+		Local n0:=(v1-v0).Cross(v2-v0).Normalize()
+		Local n1:=(v2-v0).Cross(v3-v0).Normalize()
+		Local n2:=(v3-v0).Cross(v4-v0).Normalize()
+		Local n3:=(v4-v0).Cross(v1-v0).Normalize()
+			
+		Local n:=(n0+n1+n2+n3).Normalize()
+			
+'		If (i&15)=0 And (j&15)=0 print "n="+v
+			
+'		DebugAssert( n.y>0 )
+		
+		Return n
+	End
+	
+	Function GetIndexBuffer:IndexBuffer( cellSize:Int )
+		
+		Global _ibuffers:=New IntMap<IndexBuffer>
+				
+		If _ibuffers.Contains( cellSize ) Return _ibuffers[cellSize]
+	
+		Local indices:=New Uint[ cellSize*cellSize*6 ],ip:=indices.Data
+				
+		For Local j:=0 Until cellSize
+		
+			Local k:=j*(cellSize+1)
+		
+			For Local i:=k Until k+cellSize
+			
+				Local v0:=i,v1:=i+cellSize+1,v2:=i+cellSize+2,v3:=i+1
+			
+				If (j~(i-k)) & 1
+	 				ip[0]=v1 ; ip[1]=v2 ; ip[2]=v3
+	 				ip[3]=v1 ; ip[4]=v3 ; ip[5]=v0
+				Else
+					ip[0]=v2 ; ip[1]=v3 ; ip[2]=v0
+					ip[3]=v2 ; ip[4]=v0 ; ip[5]=v1
+				Endif
+				
+				ip+=6
+			Next
+		Next
+		
+		Local ibuffer:=New IndexBuffer( indices )
+		
+		_ibuffers[cellSize]=ibuffer
+		
+		Return ibuffer
+	End
+	
+	Method Init()
+		
+		_ibuffer=GetIndexBuffer( _cellSize )
+		
+		Local vbuffers:=New Stack<VertexBuffer>
+		
+		Local vertices:=New Stack<Vertex3f>
+		
+		For Local j0:=0 Until _size Step _cellSize
+		
+			Local j1:=j0+_cellSize
+
+			For Local i0:=0 Until _size Step _cellSize
+			
+				Local i1:=i0+_cellSize
+				
+				Local v0:=vertices.Length
+				
+				For Local j:=j0 To j1
+				
+					For Local i:=i0 To i1
+					
+						Local p:=GetPosition( i,j )
+						
+						Local t0:=GetTexCoord0( i,j )
+						
+						Local n:=GetNormal( i,j )
+						
+						Local v:=New Vertex3f( p,t0,n )
+						
+						vertices.Push( v )
+					
+					Next
+					
+				Next
+				
+				UpdateTangents( vertices.Data.Data,vertices.Length,Cast<UInt Ptr>( _ibuffer.Data ),_ibuffer.Length )
+				
+				Local vbuffer:=New VertexBuffer( vertices.ToArray() )
+
+				vbuffers.Push( vbuffer )
+				
+				vertices.Clear()
+			Next
+			
+		Next
+		
+		_vbuffers=vbuffers.ToArray()
+
+	End
+	
+End

+ 71 - 0
modules/mojo3d/graphics/terrainmaterial.monkey2

@@ -0,0 +1,71 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc The TerrainMaterial class.
+#end
+Class TerrainMaterial Extends Material
+
+	#rem monkeydoc Creates a new terrain material.
+	#end
+	Method New()
+		Super.New( Shader.Open( "terrain" ) )
+		
+		BlendTexture=Texture.ColorTexture( Color.Black )
+		
+		ColorTextures=New Texture[](
+			Texture.ColorTexture( Color.White ),
+			Texture.ColorTexture( Color.White ),
+			Texture.ColorTexture( Color.White ),
+			Texture.ColorTexture( Color.White ) )
+			
+		NormalTextures=New Texture[]( 
+			Texture.FlatNormal(),
+			Texture.FlatNormal(),
+			Texture.FlatNormal(),
+			Texture.FlatNormal() )
+	End
+	
+	Property BlendTexture:Texture()
+		
+		Return Uniforms.GetTexture( "BlendTexture" )
+		
+	Setter( texture:Texture )
+		
+		Uniforms.SetTexture( "BlendTexture",texture )
+	End
+	
+	Property ColorTextures:Texture[]()
+		
+		Return New Texture[](
+			Uniforms.GetTexture( "ColorTexture0" ),
+			Uniforms.GetTexture( "ColorTexture1" ),
+			Uniforms.GetTexture( "ColorTexture2" ),
+			Uniforms.GetTexture( "ColorTexture3" ) )
+	
+	Setter( textures:Texture[] )
+		Assert( textures.Length=4,"ColorTextures length must be 4" )
+		
+		Uniforms.SetTexture( "ColorTexture0",textures[0] )
+		Uniforms.SetTexture( "ColorTexture1",textures[1] )
+		Uniforms.SetTexture( "ColorTexture2",textures[2] )
+		Uniforms.SetTexture( "ColorTexture3",textures[3] )
+	End
+	
+	Property NormalTextures:Texture[]()
+		
+		Return New Texture[](
+			Uniforms.GetTexture( "NormalTexture0" ),
+			Uniforms.GetTexture( "NormalTexture1" ),
+			Uniforms.GetTexture( "NormalTexture2" ),
+			Uniforms.GetTexture( "NormalTexture3" ) )
+	
+	Setter( textures:Texture[] )
+		Assert( textures.Length=4,"NormalTextures length must be 4" )
+		
+		Uniforms.SetTexture( "NormalTexture0",textures[0] )
+		Uniforms.SetTexture( "NormalTexture1",textures[1] )
+		Uniforms.SetTexture( "NormalTexture2",textures[2] )
+		Uniforms.SetTexture( "NormalTexture3",textures[3] )
+	End
+	
+End

BIN
modules/mojo3d/graphics/textures/env_default.jpg


+ 59 - 0
modules/mojo3d/graphics/util3d.monkey2

@@ -0,0 +1,59 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc @hidden
+#end
+Function UpdateTangents( vertices:Vertex3f Ptr,vcount:Int,indices:UInt Ptr,icount:Int )
+	
+	Local tan1:=New Vec3f[vcount]
+	Local tan2:=New Vec3f[vcount]
+
+	For Local i:=0 Until icount Step 3
+
+		Local i1:=indices[i+0]
+		Local i2:=indices[i+1]
+		Local i3:=indices[i+2]
+
+		Local v1:=vertices+i1
+		Local v2:=vertices+i2
+		Local v3:=vertices+i3
+
+		Local x1:=v2->Tx-v1->Tx
+		Local x2:=v3->Tx-v1->Tx
+		Local y1:=v2->Ty-v1->Ty
+		Local y2:=v3->Ty-v1->Ty
+		Local z1:=v2->Tz-v1->Tz
+		Local z2:=v3->Tz-v1->Tz
+
+		Local s1:=v2->Sx-v1->Sx
+		Local s2:=v3->Sx-v1->Sx
+		Local t1:=v2->Sy-v1->Sy
+		Local t2:=v3->Sy-v1->Sy
+
+		Local r:=1.0/(s1*t2-s2*t1)
+
+		Local sdir:=New Vec3f( (t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r )
+		Local tdir:=New Vec3f( (s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r )
+
+		tan1[i1]+=sdir
+		tan1[i2]+=sdir
+		tan1[i3]+=sdir
+
+		tan2[i1]+=tdir
+		tan2[i2]+=tdir
+		tan2[i3]+=tdir
+	Next
+
+	For Local i:=0 Until vcount
+	
+		Local v:=vertices+i
+	
+		Local n:=v->normal,t:=tan1[i]
+	
+		v->tangent.XYZ=( t - n * n.Dot( t ) ).Normalize()
+	
+		v->tangent.w=n.Cross( t ).Dot( tan2[i] ) < 0 ? -1 Else 1
+	Next
+
+End
+	

+ 99 - 0
modules/mojo3d/graphics/watermaterial.monkey2

@@ -0,0 +1,99 @@
+
+Namespace mojo3d.graphics
+
+#rem monkeydoc The WaterMaterial class.
+#end
+Class WaterMaterial Extends Material
+	
+	#rem monkeydoc Creates a new water material.
+	#end
+	Method New()
+		Super.New( Shader.Open( "water" ) )
+		
+		ColorTexture=Texture.ColorTexture( Color.SeaGreen )
+		ColorFactor=Color.White
+		
+		Metalness=0
+		Roughness=0
+		
+		Local normal:=Texture.ColorTexture( New Color( .5,.5,1 ) )
+		
+		NormalTextures=New Texture[]( normal,normal )
+		
+		Velocities=New Vec2f[]( New Vec2f( 0,0 ),New Vec2f( 0,0 ) )
+	End
+	
+	#rem monkeydoc Creates a copy of the water material.
+	#end
+	Method Copy:WaterMaterial() Override
+	
+		Return New WaterMaterial( Self )
+	End
+	
+	Property ColorTexture:Texture()
+		
+		Return Uniforms.GetTexture( "ColorTexture" )
+	
+	Setter( texture:Texture )
+		
+		Uniforms.SetTexture( "ColorTexture",texture )
+	End
+	
+	Property ColorFactor:Color()
+		
+		Return Uniforms.GetColor( "ColorFactor" )
+	
+	Setter( factor:Color )
+		
+		Uniforms.SetColor( "ColorFactor",factor )
+	End
+	
+	Property Metalness:Float()
+		
+		Return Uniforms.GetFloat( "Metalness" )
+	
+	Setter( metalness:Float )
+		
+		Uniforms.SetFloat( "Metalness",metalness )
+	End
+	
+	property Roughness:Float()
+		
+		Return Uniforms.GetFloat( "Roughness" )
+	
+	Setter( roughness:Float )
+		
+		Uniforms.SetFloat( "Roughness",roughness )
+	End
+	
+	Property NormalTextures:Texture[]()
+		
+		Return New Texture[]( Uniforms.GetTexture( "NormalTexture0" ),Uniforms.GetTexture( "NormalTexture1" ) )
+	
+	Setter( textures:Texture[] )
+		Assert( textures.Length=2,"NormalTextures array must havre length 2" )
+		
+		Uniforms.SetTexture( "NormalTexture0",textures[0] )
+		Uniforms.SetTexture( "NormalTexture1",textures[1] )
+	End
+	
+	Property Velocities:Vec2f[]()
+		
+		Return New Vec2f[]( Uniforms.GetVec2f( "Velocity0" ),Uniforms.GetVec2f( "Velocity1" ) )
+	
+	Setter( velocities:Vec2f[] )
+		Assert( velocities.Length=2,"Velocities array must have length 2" )
+		
+		Uniforms.SetVec2f( "Velocity0",velocities[0] )
+		Uniforms.SetVec2f( "Velocity1",velocities[1] )
+	End
+
+	Protected
+		
+	#rem monkeydoc @hidden
+	#end
+	Method New( material:WaterMaterial )
+		Super.New( material )
+	End
+	
+End

+ 8 - 0
modules/mojo3d/module.json

@@ -0,0 +1,8 @@
+{
+	"module":"mojo3d",
+	"about":"Monkey2 3d support",
+	"author":"Mark Sibly",
+	"version":"1.0.0",
+	"support":"http://monkeycoder.co.nz",
+	"depends":["std","mojo"]
+}

+ 44 - 0
modules/mojo3d/mojo3d.monkey2

@@ -0,0 +1,44 @@
+
+Namespace mojo3d
+
+#Import "<std>"
+#Import "<mojo>"
+
+#Import "graphics/shaders/@/shaders"
+#Import "graphics/textures/@/textures"
+
+Using std..
+Using mojo..
+Using mojo3d..
+Using gles20..
+
+#Import "graphics/util3d"
+#Import "graphics/mesh"
+#Import "graphics/meshprims"
+#Import "graphics/entity"
+#Import "graphics/camera"
+#Import "graphics/light"
+#Import "graphics/model"
+#Import "graphics/terrain"
+#Import "graphics/sprite"
+#Import "graphics/spritebuffer"
+#Import "graphics/scene"
+#Import "graphics/loader.monkey2"
+#Import "graphics/gltf2"
+#Import "graphics/gltf2loader"
+
+#Import "graphics/animation"
+#Import "graphics/animator"
+
+#Import "graphics/renderer"
+#Import "graphics/deferredrenderer"
+
+#Import "graphics/posteffect"
+#Import "graphics/bloomeffect"
+#Import "graphics/fogeffect"
+#Import "graphics/monochromeeffect"
+
+#Import "graphics/material"
+#Import "graphics/pbrmaterial"
+#Import "graphics/spritematerial"
+#Import "graphics/watermaterial"

BIN
modules/mojo3d/tests/assets/Acadia-Tree-Sprite.png


BIN
modules/mojo3d/tests/assets/Monkey2logo_64.png


+ 238 - 0
modules/mojo3d/tests/assets/duck.gltf/Duck.gltf

@@ -0,0 +1,238 @@
+{
+    "asset": {
+        "generator": "COLLADA2GLTF",
+        "version": "2.0"
+    },
+    "scene": 0,
+    "scenes": [
+        {
+            "nodes": [
+                0
+            ]
+        }
+    ],
+    "nodes": [
+        {
+            "children": [
+                3,
+                2,
+                1
+            ],
+            "matrix": [
+                0.009999999776482582,
+                0.0,
+                0.0,
+                0.0,
+                0.0,
+                0.009999999776482582,
+                0.0,
+                0.0,
+                0.0,
+                0.0,
+                0.009999999776482582,
+                0.0,
+                0.0,
+                0.0,
+                0.0,
+                1.0
+            ]
+        },
+        {
+            "matrix": [
+                -0.9546916484832764,
+                0.2181433141231537,
+                -0.2024286538362503,
+                0.0,
+                0.014671952463686468,
+                0.7138853073120117,
+                0.7001089453697205,
+                0.0,
+                0.2972349226474762,
+                0.6654181480407715,
+                -0.6847409009933472,
+                0.0,
+                148.6540069580078,
+                183.6720123291016,
+                -292.1790161132813,
+                1.0
+            ]
+        },
+        {
+            "matrix": [
+                -0.7289686799049377,
+                0.0,
+                -0.6845470666885376,
+                0.0,
+                -0.4252049028873444,
+                0.7836934328079224,
+                0.4527972936630249,
+                0.0,
+                0.5364750623703003,
+                0.6211478114128113,
+                -0.571287989616394,
+                0.0,
+                400.1130065917969,
+                463.2640075683594,
+                -431.0780334472656,
+                1.0
+            ],
+            "camera": 0
+        },
+        {
+            "mesh": 0
+        }
+    ],
+    "cameras": [
+        {
+            "perspective": {
+                "aspectRatio": 1.5,
+                "yfov": 0.6605925559997559,
+                "zfar": 10000.0,
+                "znear": 1.0
+            },
+            "type": "perspective"
+        }
+    ],
+    "meshes": [
+        {
+            "primitives": [
+                {
+                    "attributes": {
+                        "NORMAL": 1,
+                        "POSITION": 2,
+                        "TEXCOORD_0": 3
+                    },
+                    "indices": 0,
+                    "mode": 4,
+                    "material": 0
+                }
+            ],
+            "name": "LOD3spShape"
+        }
+    ],
+    "accessors": [
+        {
+            "bufferView": 0,
+            "byteOffset": 0,
+            "componentType": 5123,
+            "count": 12636,
+            "max": [
+                2398
+            ],
+            "min": [
+                0
+            ],
+            "type": "SCALAR"
+        },
+        {
+            "bufferView": 1,
+            "byteOffset": 0,
+            "componentType": 5126,
+            "count": 2399,
+            "max": [
+                0.9995989799499512,
+                0.999580979347229,
+                0.9984359741210938
+            ],
+            "min": [
+                -0.9990839958190918,
+                -1.0,
+                -0.9998319745063782
+            ],
+            "type": "VEC3"
+        },
+        {
+            "bufferView": 1,
+            "byteOffset": 28788,
+            "componentType": 5126,
+            "count": 2399,
+            "max": [
+                96.17990112304688,
+                163.97000122070313,
+                53.92519760131836
+            ],
+            "min": [
+                -69.29850006103516,
+                9.929369926452637,
+                -61.32819747924805
+            ],
+            "type": "VEC3"
+        },
+        {
+            "bufferView": 2,
+            "byteOffset": 0,
+            "componentType": 5126,
+            "count": 2399,
+            "max": [
+                0.9833459854125976,
+                0.9800369739532472
+            ],
+            "min": [
+                0.026409000158309938,
+                0.01996302604675293
+            ],
+            "type": "VEC2"
+        }
+    ],
+    "materials": [
+        {
+            "pbrMetallicRoughness": {
+                "baseColorTexture": {
+                    "index": 0
+                }
+            },
+            "emissiveFactor": [
+                0.0,
+                0.0,
+                0.0
+            ]
+        }
+    ],
+    "textures": [
+        {
+            "sampler": 0,
+            "source": 0
+        }
+    ],
+    "images": [
+        {
+            "uri": "DuckCM.png"
+        }
+    ],
+    "samplers": [
+        {
+            "magFilter": 9729,
+            "minFilter": 9986,
+            "wrapS": 10497,
+            "wrapT": 10497
+        }
+    ],
+    "bufferViews": [
+        {
+            "buffer": 0,
+            "byteOffset": 76768,
+            "byteLength": 25272,
+            "target": 34963
+        },
+        {
+            "buffer": 0,
+            "byteOffset": 0,
+            "byteLength": 57576,
+            "byteStride": 12,
+            "target": 34962
+        },
+        {
+            "buffer": 0,
+            "byteOffset": 57576,
+            "byteLength": 19192,
+            "byteStride": 8,
+            "target": 34962
+        }
+    ],
+    "buffers": [
+        {
+            "byteLength": 102040,
+            "uri": "Duck0.bin"
+        }
+    ]
+}

BIN
modules/mojo3d/tests/assets/duck.gltf/Duck0.bin


BIN
modules/mojo3d/tests/assets/duck.gltf/DuckCM.png


BIN
modules/mojo3d/tests/assets/heightmap_256.BMP


BIN
modules/mojo3d/tests/assets/miramar-skybox.jpg


+ 1 - 0
modules/mojo3d/tests/assets/mossy-ground1.pbr/About these PBR files.txt

@@ -0,0 +1 @@
+These texture files were created by FreePBR.com and may be used freely in your video games and 3d work at no cost. They may not however be redistributed on other websites or anywhere else other than FreePBR.com. We think that is more than fair. :) We also would greatly appreciate it if some sorrt of credit was given if you do indeed use these textures in a published game. Other than that, keep on creating and have fun. :)

BIN
modules/mojo3d/tests/assets/mossy-ground1.pbr/color.png


BIN
modules/mojo3d/tests/assets/mossy-ground1.pbr/height.png


BIN
modules/mojo3d/tests/assets/mossy-ground1.pbr/metalness.png


BIN
modules/mojo3d/tests/assets/mossy-ground1.pbr/normal.png


BIN
modules/mojo3d/tests/assets/mossy-ground1.pbr/occlusion.png


BIN
modules/mojo3d/tests/assets/mossy-ground1.pbr/roughness.png


+ 507 - 0
modules/mojo3d/tests/assets/spheres.gltf/MetalRoughSpheres.gltf

@@ -0,0 +1,507 @@
+{
+    "asset": {
+        "copyright": "Copyright 2017 Analytical Graphics, Inc, CC-BY 4.0 https://creativecommons.org/licenses/by/4.0/ - Model and textures by Ed Mackey.",
+        "generator": "COLLADA2GLTF with hand-edits",
+        "version": "2.0"
+    },
+    "scene": 0,
+    "scenes": [
+        {
+            "nodes": [
+                0
+            ]
+        }
+    ],
+    "nodes": [
+        {
+            "children": [
+                5,
+                4,
+                3,
+                2,
+                1
+            ],
+            "matrix": [
+               -0.4,  0.0,  0.0,  0.0,
+                0.0,  0.0,  0.4,  0.0,
+                0.0,  0.4,  0.0,  0.0,
+                0.0,  0.0,  0.0,  1.0
+            ]
+        },
+        {
+            "mesh": 0
+        },
+        {
+            "mesh": 1
+        },
+        {
+            "mesh": 2
+        },
+        {
+            "mesh": 3
+        },
+        {
+            "mesh": 4
+        }
+    ],
+    "meshes": [
+        {
+            "primitives": [
+                {
+                    "attributes": {
+                        "NORMAL": 1,
+                        "POSITION": 2,
+                        "TEXCOORD_0": 3
+                    },
+                    "indices": 0,
+                    "mode": 4,
+                    "material": 0
+                }
+            ],
+            "name": "Spheres.004"
+        },
+        {
+            "primitives": [
+                {
+                    "attributes": {
+                        "NORMAL": 5,
+                        "POSITION": 6,
+                        "TEXCOORD_0": 7
+                    },
+                    "indices": 4,
+                    "mode": 4,
+                    "material": 0
+                }
+            ],
+            "name": "Spheres.003"
+        },
+        {
+            "primitives": [
+                {
+                    "attributes": {
+                        "NORMAL": 9,
+                        "POSITION": 10,
+                        "TEXCOORD_0": 11
+                    },
+                    "indices": 8,
+                    "mode": 4,
+                    "material": 0
+                }
+            ],
+            "name": "Spheres.002"
+        },
+        {
+            "primitives": [
+                {
+                    "attributes": {
+                        "NORMAL": 13,
+                        "POSITION": 14,
+                        "TEXCOORD_0": 15
+                    },
+                    "indices": 12,
+                    "mode": 4,
+                    "material": 0
+                }
+            ],
+            "name": "Spheres.001"
+        },
+        {
+            "primitives": [
+                {
+                    "attributes": {
+                        "NORMAL": 17,
+                        "POSITION": 18,
+                        "TEXCOORD_0": 19
+                    },
+                    "indices": 16,
+                    "mode": 4,
+                    "material": 0
+                }
+            ],
+            "name": "Spheres"
+        }
+    ],
+    "accessors": [
+        {
+            "bufferView": 0,
+            "byteOffset": 2642016,
+            "componentType": 5123,
+            "count": 184320,
+            "max": [
+                31331
+            ],
+            "min": [
+                0
+            ],
+            "type": "SCALAR"
+        },
+        {
+            "bufferView": 1,
+            "byteOffset": 5389968,
+            "componentType": 5126,
+            "count": 31332,
+            "max": [
+                0.9999999403953552,
+                1.0,
+                1.0
+            ],
+            "min": [
+                -0.9999999403953552,
+                -1.0,
+                -1.0
+            ],
+            "type": "VEC3"
+        },
+        {
+            "bufferView": 1,
+            "byteOffset": 5765952,
+            "componentType": 5126,
+            "count": 31332,
+            "max": [
+                -8.0,
+                9.0,
+                10.0
+            ],
+            "min": [
+                -10.0,
+                -1.0,
+                -7.0
+            ],
+            "type": "VEC3"
+        },
+        {
+            "bufferView": 2,
+            "byteOffset": 1796656,
+            "componentType": 5126,
+            "count": 31332,
+            "max": [
+                0.1278132051229477,
+                0.7597609758377075
+            ],
+            "min": [
+                0.03436123952269554,
+                0.013921022415161133
+            ],
+            "type": "VEC2"
+        },
+        {
+            "bufferView": 0,
+            "byteOffset": 0,
+            "componentType": 5123,
+            "count": 368640,
+            "max": [
+                62663
+            ],
+            "min": [
+                0
+            ],
+            "type": "SCALAR"
+        },
+        {
+            "bufferView": 1,
+            "byteOffset": 0,
+            "componentType": 5126,
+            "count": 62664,
+            "max": [
+                0.9999999403953552,
+                1.0,
+                1.0
+            ],
+            "min": [
+                -0.9999999403953552,
+                -1.0,
+                -1.0
+            ],
+            "type": "VEC3"
+        },
+        {
+            "bufferView": 1,
+            "byteOffset": 751968,
+            "componentType": 5126,
+            "count": 62664,
+            "max": [
+                -2.0,
+                9.0,
+                10.0
+            ],
+            "min": [
+                -7.0,
+                -1.0,
+                -7.0
+            ],
+            "type": "VEC3"
+        },
+        {
+            "bufferView": 2,
+            "byteOffset": 0,
+            "componentType": 5126,
+            "count": 62664,
+            "max": [
+                0.4161497056484223,
+                0.7597609758377075
+            ],
+            "min": [
+                0.15740810334682465,
+                0.012456059455871582
+            ],
+            "type": "VEC2"
+        },
+        {
+            "bufferView": 0,
+            "byteOffset": 1904736,
+            "componentType": 5123,
+            "count": 368640,
+            "max": [
+                62663
+            ],
+            "min": [
+                0
+            ],
+            "type": "SCALAR"
+        },
+        {
+            "bufferView": 1,
+            "byteOffset": 3886032,
+            "componentType": 5126,
+            "count": 62664,
+            "max": [
+                0.9999999403953552,
+                1.0,
+                1.0
+            ],
+            "min": [
+                -0.9999999403953552,
+                -1.0,
+                -1.0
+            ],
+            "type": "VEC3"
+        },
+        {
+            "bufferView": 1,
+            "byteOffset": 4638000,
+            "componentType": 5126,
+            "count": 62664,
+            "max": [
+                4.0,
+                9.0,
+                10.0
+            ],
+            "min": [
+                -1.0,
+                -1.0,
+                -7.0
+            ],
+            "type": "VEC3"
+        },
+        {
+            "bufferView": 2,
+            "byteOffset": 1295344,
+            "componentType": 5126,
+            "count": 62664,
+            "max": [
+                0.7028239369392395,
+                0.7636672854423523
+            ],
+            "min": [
+                0.4482637047767639,
+                0.015471160411834717
+            ],
+            "type": "VEC2"
+        },
+        {
+            "bufferView": 0,
+            "byteOffset": 1167456,
+            "componentType": 5123,
+            "count": 368640,
+            "max": [
+                62663
+            ],
+            "min": [
+                0
+            ],
+            "type": "SCALAR"
+        },
+        {
+            "bufferView": 1,
+            "byteOffset": 2382096,
+            "componentType": 5126,
+            "count": 62664,
+            "max": [
+                0.9999999403953552,
+                1.0,
+                1.0
+            ],
+            "min": [
+                -0.9999999403953552,
+                -1.0,
+                -1.0
+            ],
+            "type": "VEC3"
+        },
+        {
+            "bufferView": 1,
+            "byteOffset": 3134064,
+            "componentType": 5126,
+            "count": 62664,
+            "max": [
+                10.0,
+                9.0,
+                10.0
+            ],
+            "min": [
+                5.0,
+                -1.0,
+                -7.0
+            ],
+            "type": "VEC3"
+        },
+        {
+            "bufferView": 2,
+            "byteOffset": 794032,
+            "componentType": 5126,
+            "count": 62664,
+            "max": [
+                0.9852716326713562,
+                0.7671433687210083
+            ],
+            "min": [
+                0.7233805060386658,
+                0.016381680965423585
+            ],
+            "type": "VEC2"
+        },
+        {
+            "bufferView": 0,
+            "byteOffset": 737280,
+            "componentType": 5123,
+            "count": 215088,
+            "max": [
+                36589
+            ],
+            "min": [
+                0
+            ],
+            "type": "SCALAR"
+        },
+        {
+            "bufferView": 1,
+            "byteOffset": 1503936,
+            "componentType": 5126,
+            "count": 36590,
+            "max": [
+                0.9999999403953552,
+                1.0,
+                1.0
+            ],
+            "min": [
+                -0.9999999403953552,
+                -1.0,
+                -1.0
+            ],
+            "type": "VEC3"
+        },
+        {
+            "bufferView": 1,
+            "byteOffset": 1943016,
+            "componentType": 5126,
+            "count": 36590,
+            "max": [
+                11.874730110168456,
+                9.0,
+                10.969940185546877
+            ],
+            "min": [
+                -12.186589241027832,
+                -1.0,
+                -12.35944938659668
+            ],
+            "type": "VEC3"
+        },
+        {
+            "bufferView": 2,
+            "byteOffset": 501312,
+            "componentType": 5126,
+            "count": 36590,
+            "max": [
+                0.9869875311851502,
+                0.9988328814506532
+            ],
+            "min": [
+                0.0013856289442628625,
+                0.016568005084991456
+            ],
+            "type": "VEC2"
+        }
+    ],
+    "materials": [
+        {
+            "pbrMetallicRoughness": {
+                "baseColorTexture": {
+                    "index": 0
+                },
+                "metallicRoughnessTexture": {
+                    "index": 1
+                }
+            },
+            "emissiveFactor": [
+                0.0,
+                0.0,
+                0.0
+            ]
+        }
+    ],
+    "textures": [
+        {
+            "sampler": 0,
+            "source": 0
+        },
+        {
+            "sampler": 0,
+            "source": 1
+        }
+    ],
+    "images": [
+        {
+            "uri": "Spheres_BaseColor.png"
+        },
+        {
+            "uri": "Spheres_MetalRough.png"
+        }
+    ],
+    "samplers": [
+        {
+            "magFilter": 9729,
+            "minFilter": 9986,
+            "wrapS": 33071,
+            "wrapT": 33071
+        }
+    ],
+    "bufferViews": [
+        {
+            "buffer": 0,
+            "byteOffset": 8189248,
+            "byteLength": 3010656,
+            "target": 34963
+        },
+        {
+            "buffer": 0,
+            "byteOffset": 0,
+            "byteLength": 6141936,
+            "byteStride": 12,
+            "target": 34962
+        },
+        {
+            "buffer": 0,
+            "byteOffset": 6141936,
+            "byteLength": 2047312,
+            "byteStride": 8,
+            "target": 34962
+        }
+    ],
+    "buffers": [
+        {
+            "byteLength": 11199904,
+            "uri": "MetalRoughSpheres0.bin"
+        }
+    ]
+}

BIN
modules/mojo3d/tests/assets/spheres.gltf/MetalRoughSpheres0.bin


BIN
modules/mojo3d/tests/assets/spheres.gltf/Spheres_BaseColor.png


BIN
modules/mojo3d/tests/assets/spheres.gltf/Spheres_MetalRough.png


BIN
modules/mojo3d/tests/assets/terrain_256.png


BIN
modules/mojo3d/tests/assets/water1.jpg


BIN
modules/mojo3d/tests/assets/water_normal0.png


BIN
modules/mojo3d/tests/assets/water_normal1.png


+ 79 - 0
modules/mojo3d/tests/donut.monkey2

@@ -0,0 +1,79 @@
+
+Namespace myapp
+
+#Import "<std>"
+#Import "<mojo>"
+#Import "<mojo3d>"
+
+#Import "assets/"
+
+#Import "util"
+
+Using std..
+Using mojo..
+Using mojo3d..
+
+Class MyWindow Extends Window
+	
+	Field _scene:Scene
+	
+	Field _camera:Camera
+	
+	Field _light:Light
+	
+	Field _donut:Model
+	
+	Method New( title:String="Simple mojo app",width:Int=640,height:Int=480,flags:WindowFlags=WindowFlags.Resizable )
+
+		Super.New( title,width,height,flags )
+		
+		_scene=Scene.GetCurrent()
+		
+		_scene.ClearColor=Color.Sky
+		
+		'create camera
+		'
+		_camera=New Camera
+		_camera.Near=.1
+		_camera.Far=100
+		_camera.Move( 0,10,-10 )
+		
+		'create light
+		'
+		_light=New Light
+		_light.RotateX( Pi/2 )	'aim directional light 'down' - Pi/2=90 degrees.
+		
+		'create donut - metallic silver...
+		
+		Local material:=New PbrMaterial( Color.Silver,1,0.5 )
+		
+		_donut=Model.CreateTorus( 2,.5,48,24,material )
+		
+		_donut.Move( 0,10,0 )
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		RequestRender()
+		
+		If Keyboard.KeyHit( Key.Space ) _donut.Visible=Not _donut.Visible
+		
+		_donut.Rotate( .01,.02,.03 )
+		
+		util.Fly( _camera,Self )
+		
+		_scene.Render( canvas,_camera )
+		
+		canvas.DrawText( "Width="+Width+", Height="+Height+", FPS="+App.FPS,0,0 )
+	End
+	
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MyWindow
+	
+	App.Run()
+End

+ 144 - 0
modules/mojo3d/tests/ducks.monkey2

@@ -0,0 +1,144 @@
+
+Namespace myapp
+
+#Import "<std>"
+#Import "<mojo>"
+#Import "<mojo3d>"
+
+#Import "assets/"
+
+#Import "util"
+
+Using std..
+Using mojo..
+Using mojo3d..
+
+Class MyWindow Extends Window
+	
+	Field _scene:Scene
+	
+	Field _camera:Camera
+	
+	Field _light:Light
+	
+	Field _ground:Model
+	
+	Field _ducks:=New Stack<Model>
+	
+	Field _fog:FogEffect
+	
+	Field _monochrome:MonochromeEffect
+	
+	Method New( title:String="Simple mojo app",width:Int=640,height:Int=480,flags:WindowFlags=WindowFlags.Resizable )
+
+		Super.New( title,width,height,flags )
+		
+		Print gles20.glGetString( gles20.GL_EXTENSIONS ).Replace( " ","~n" )
+		
+		'create scene
+		'		
+		_scene=Scene.GetCurrent()
+
+		'fog effect
+		'		
+		_fog=New FogEffect
+		_fog.Color=Color.Sky
+		_fog.Near=0
+		_fog.Far=100
+		
+		'monochrome effect - hit space to toggle
+		'
+		_monochrome=New MonochromeEffect
+		_monochrome.Enabled=False
+		
+		'_scene.SkyTexture=Texture.Load( "asset::miramar-skybox.jpg",TextureFlags.FilterMipmap|TextureFlags.Cubemap )
+		
+		'create camera
+		'
+		_camera=New Camera
+		_camera.Near=.1
+		_camera.Far=100
+		_camera.Move( 0,15,-20 )
+		
+		'create light
+		'
+		_light=New Light
+		_light.RotateX( Pi/2 )	'aim directional light downwards - Pi/2=90 degrees.
+		
+		'create ground
+		'
+		_ground=Model.CreateBox( New Boxf( -50,-1,-50,50,0,50 ),1,1,1,New PbrMaterial( Color.Green,0,1 ) )
+		
+		'create ducks
+		'		
+		Local duck:=Model.Load( "asset::duck.gltf/Duck.gltf" )
+		duck.Mesh.FitVertices( New Boxf( -1,1 ) )
+		duck.Mesh.TransformVertices( Mat4f.Rotation( 0,Pi/2,0 ) )
+		
+		Local root:=duck.Copy()
+		root.Move( 0,10,0 )
+		root.Scale=New Vec3f( 3 )
+		
+		_ducks.Push( root )
+		
+		For Local m:=0.0 To 1.0 Step .125
+		
+			For Local i:=0.0 Until 360.0 Step 24
+			
+				Local copy:=duck.Copy( root )
+				
+				copy.RotateY( i*TwoPi/360 )
+				
+				copy.Move( 0,0,6+m*16 )
+				
+				For Local j:=0 Until copy.Materials.Length
+				
+					Local material:=Cast<PbrMaterial>( copy.Materials[j].Copy() )
+					
+					material.MetalnessFactor=m
+					material.RoughnessFactor=i/360.0
+					
+					copy.Materials[j]=material
+				Next
+				
+				_ducks.Push( copy )
+			
+			Next
+		Next
+		
+		duck.Destroy()
+
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+
+		RequestRender()
+		
+		'Space to toggle monochrome mode!
+		If Keyboard.KeyHit( Key.Space ) _monochrome.Enabled=Not _monochrome.Enabled
+			
+		'_monochrome.Level=Sin( Now()*3 ) * .5 + .5
+		
+		For Local duck:=Eachin _ducks
+			duck.Rotate( 0,.01,0 )
+		Next
+		
+		util.Fly( _camera,Self )
+		
+		_scene.Render( canvas,_camera )
+
+		canvas.Scale( Width/640.0,Height/480.0 )
+		
+		canvas.DrawText( "Width="+Width+", Height="+Height+", FPS="+App.FPS,0,0 )
+	End
+	
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MyWindow
+	
+	App.Run()
+End

+ 84 - 0
modules/mojo3d/tests/pbrspheres.monkey2

@@ -0,0 +1,84 @@
+
+Namespace myapp
+
+#Import "<std>"
+#Import "<mojo>"
+#Import "<mojo3d>"
+
+#Import "assets/"
+
+#Import "util"
+
+Using std..
+Using mojo..
+Using mojo3d..
+
+Class MyWindow Extends Window
+	
+	Field _scene:Scene
+	
+	Field _camera:Camera
+	
+	Field _light:Light
+	
+	Field _ground:Model
+	
+	Field _spheres:Model
+	
+	Method New( title:String="Simple mojo app",width:Int=640,height:Int=480,flags:WindowFlags=WindowFlags.Resizable )
+
+		Super.New( title,width,height,flags )
+		
+		_scene=Scene.GetCurrent()
+		
+		_scene.SkyTexture=Texture.Load( "asset::miramar-skybox.jpg",TextureFlags.FilterMipmap|TextureFlags.Cubemap )
+		
+		'create camera
+		'
+		_camera=New Camera
+		_camera.Near=.1
+		_camera.Far=100
+		_camera.Move( 0,10,-10 )
+		
+		'create light
+		'
+		_light=New Light
+		_light.RotateX( Pi/2 )	'aim directional light 'down' - Pi/2=90 degrees.
+		
+		'create ground
+		'
+		Local mesh:=Mesh.CreateBox( New Boxf( -50,-1,-50,50,0,50 ),1,1,1 )
+		
+		Local material:=New PbrMaterial( Color.Green,0,.5 )
+		
+		_ground=New Model( mesh,material )
+		
+		'create spheres
+		
+		_spheres=Model.Load( "asset::spheres.gltf/MetalRoughSpheres.gltf" )
+		
+		_spheres.Move( 0,10,0 )
+		
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		RequestRender()
+		
+		util.Fly( _camera,Self )
+		
+		_scene.Render( canvas,_camera )
+		
+		canvas.DrawText( "Width="+Width+", Height="+Height+", FPS="+App.FPS,0,Height-16 )
+	End
+	
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MyWindow
+	
+	App.Run()
+End

+ 93 - 0
modules/mojo3d/tests/sprites.monkey2

@@ -0,0 +1,93 @@
+
+Namespace myapp
+
+#Import "<std>"
+#Import "<mojo>"
+#Import "<mojo3d>"
+
+#Import "assets/"
+
+#Import "util"
+
+Using std..
+Using mojo..
+Using mojo3d..
+
+Class MyWindow Extends Window
+	
+	Field _scene:Scene
+	
+	Field _camera:Camera
+	
+	Field _light:Light
+	
+	Field _ground:Model
+	
+	Field _sprites:=New Stack<Sprite>
+	
+	Method New( title:String="Simple mojo app",width:Int=640,height:Int=480,flags:WindowFlags=WindowFlags.Resizable )
+
+		Super.New( title,width,height,flags )
+		
+		_scene=Scene.GetCurrent()
+		
+		_scene.SkyTexture=Texture.Load( "asset::miramar-skybox.jpg",TextureFlags.FilterMipmap|TextureFlags.Cubemap )
+		
+		'create camera
+		'
+		_camera=New Camera
+		_camera.Near=.1
+		_camera.Far=100
+		_camera.Move( 0,10,-10 )
+		
+		'create light
+		'
+		_light=New Light
+		_light.RotateX( Pi/2 )	'aim directional light 'down' - Pi/2=90 degrees.
+		
+		'create ground
+		'
+		_ground=Model.CreateBox( New Boxf( -50,-1,-50,50,0,50 ),1,1,1,New PbrMaterial( Color.Green,0,.5 ) )
+		
+		'create sprites
+		'
+		Local material:=SpriteMaterial.Load( "asset::Acadia-Tree-Sprite.png" )
+		
+		For Local i:=0 Until 1000
+			
+			Local sprite:=New Sprite( material )
+			
+			sprite.Move( Rnd(-50,50),0,Rnd(-50,50) )
+			
+			sprite.Scale=New Vec3f( Rnd(2,3),Rnd(3,5),1 )
+			
+			sprite.Handle=New Vec2f( .5,0 )
+			
+			sprite.Mode=SpriteMode.Upright
+
+			_sprites.Push( sprite )
+		Next
+		
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		RequestRender()
+		
+		util.Fly( _camera,Self )
+		
+		_scene.Render( canvas,_camera )
+		
+		canvas.DrawText( "Width="+Width+", Height="+Height+", FPS="+App.FPS,0,0 )
+	End
+	
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MyWindow
+	
+	App.Run()
+End

+ 78 - 0
modules/mojo3d/tests/terraintest.monkey2

@@ -0,0 +1,78 @@
+
+Namespace myapp
+
+#Import "<std>"
+#Import "<mojo>"
+#Import "<mojo3d>"
+
+#Import "assets/"
+
+#Import "util"
+
+Using std..
+Using mojo..
+Using mojo3d..
+
+Class MyWindow Extends Window
+	
+	Field _scene:Scene
+	
+	Field _fog:FogEffect
+	
+	Field _camera:Camera
+	
+	Field _light:Light
+	
+	Field _material:Material
+	
+	Field _terrain:Terrain
+	
+	Method New( title:String="Simple mojo app",width:Int=640,height:Int=480,flags:WindowFlags=WindowFlags.Resizable )
+
+		Super.New( title,width,height,flags )
+		
+		_scene=Scene.GetCurrent()
+		
+		_fog=New FogEffect( Color.Sky,480,512 )
+		
+		'create camera
+		'
+		_camera=New Camera
+		_camera.Near=1
+		_camera.Far=512
+		_camera.Move( 0,66,0 )
+		
+		'create light
+		'
+		_light=New Light
+		_light.RotateX( Pi/2 )	'aim directional light 'down' - Pi/2=90 degrees.
+		
+		_material=PbrMaterial.Load( "asset::mossy-ground1.pbr" )
+		_material.ScaleTextureMatrix( 32,32 )
+		
+		Local heightMap:=Pixmap.Load( "asset::heightmap_256.BMP" )
+		
+		_terrain=New Terrain( heightMap,New Boxf( -512,0,-512,512,64,512 ),_material )
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		RequestRender()
+		
+		util.Fly( _camera,Self )
+		
+		_scene.Render( canvas,_camera )
+		
+		canvas.DrawText( "Width="+Width+", Height="+Height+", FPS="+App.FPS,0,0 )
+	End
+	
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MyWindow
+	
+	App.Run()
+End

+ 40 - 0
modules/mojo3d/tests/util.monkey2

@@ -0,0 +1,40 @@
+
+Namespace util
+
+Function Fly( entity:Entity,view:View )
+
+	If Keyboard.KeyDown( Key.Up )
+		entity.RotateX( .1 )
+	Else If Keyboard.KeyDown( Key.Down )
+		entity.RotateX( -.1 )
+	Endif
+	
+	If Keyboard.KeyDown( Key.Q )
+		entity.RotateZ( .1 )
+	Else If Keyboard.KeyDown( Key.W )
+		entity.RotateZ( -.1 )
+	Endif
+	
+	If Keyboard.KeyDown( Key.Left )
+		entity.RotateY( .1,True )
+	Else If Keyboard.KeyDown( Key.Right )
+		entity.RotateY( -.1,True )
+	Endif
+
+	If Mouse.ButtonDown( MouseButton.Left )
+		If Mouse.X<view.Width/3
+			entity.RotateY( .1,True )
+		Else If Mouse.X>view.Width/3*2
+			entity.RotateY( -.1,True )
+		Else
+			entity.Move( New Vec3f( 0,0,.1 ) )
+		Endif
+	Endif
+	
+	If Keyboard.KeyDown( Key.A )
+		entity.MoveZ( .1 )	'( New Vec3f( 0,0,.1 ) )
+	Else If Keyboard.KeyDown( Key.Z )
+		entity.MoveZ( -.1 )	'( New Vec3f( 0,0,-.1 ) )
+	Endif
+		
+End

+ 87 - 0
modules/mojo3d/tests/water.monkey2

@@ -0,0 +1,87 @@
+
+Namespace myapp
+
+#Import "<std>"
+#Import "<mojo>"
+#Import "<mojo3d>"
+
+#Import "assets/"
+
+#Import "util"
+
+Using std..
+Using mojo..
+Using mojo3d..
+
+Class MyWindow Extends Window
+	
+	Field _scene:Scene
+	
+	Field _camera:Camera
+	
+	Field _light:Light
+	
+	Field _water:Model
+	
+	Method New( title:String="Simple mojo app",width:Int=640,height:Int=480,flags:WindowFlags=WindowFlags.Resizable )
+
+		Super.New( title,width,height,flags )
+		
+		_scene=Scene.GetCurrent()
+		
+		_scene.SkyTexture=Texture.Load( "asset::miramar-skybox.jpg",TextureFlags.FilterMipmap|TextureFlags.Cubemap )
+		
+		'create camera
+		'
+		_camera=New Camera
+		_camera.Near=.1
+		_camera.Far=100
+		_camera.Move( 0,10,-10 )
+		
+		'create light
+		'
+		_light=New Light
+		_light.RotateX( Pi/2 )	'aim directional light 'down' - Pi/2=90 degrees.
+		
+		'create water material
+		'
+		Local waterMaterial:=New WaterMaterial
+		
+		waterMaterial.ScaleTextureMatrix( 10,10 )
+		waterMaterial.ColorTexture=Texture.ColorTexture( Color.SeaGreen )
+		waterMaterial.Roughness=0
+		
+		waterMaterial.NormalTextures=New Texture[]( 
+			Texture.Load( "asset::water_normal0.png",TextureFlags.WrapST|TextureFlags.FilterMipmap ),
+			Texture.Load( "asset::water_normal1.png",TextureFlags.WrapST|TextureFlags.FilterMipmap ) )
+		
+		waterMaterial.Velocities=New Vec2f[]( 
+			New Vec2f( .01,.03 ),
+			New Vec2f( .02,.05 ) )
+		
+		'create water
+		'
+		_water=Model.CreateBox( New Boxf( -50,-1,-50,50,0,50 ),1,1,1,waterMaterial )
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		RequestRender()
+		
+		util.Fly( _camera,Self )
+		
+		_scene.Render( canvas,_camera )
+		
+		canvas.DrawText( "Width="+Width+", Height="+Height+", FPS="+App.FPS,0,0 )
+	End
+	
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MyWindow
+	
+	App.Run()
+End