Browse Source

Added support for gltf2 animations + bouncyball and walker tests.

Mark Sibly 7 years ago
parent
commit
fe712d45b7

+ 8 - 0
modules/mojo3d/components/animation.monkey2

@@ -249,11 +249,19 @@ Class AnimationKey<T>
 	Property Time:Float()
 	Property Time:Float()
 		
 		
 		Return _time
 		Return _time
+		
+	Setter( time:Float )
+			
+		_time=time
 	End
 	End
 	
 	
 	Property Value:T()
 	Property Value:T()
 		
 		
 		Return _value
 		Return _value
+	
+	Setter( value:T )
+		
+		_value=value
 	End
 	End
 
 
 	Private
 	Private

+ 33 - 0
modules/mojo3d/components/animator.monkey2

@@ -92,6 +92,11 @@ Class Animator Extends Component
 		Return Null
 		Return Null
 	End
 	End
 	
 	
+	Method Animate( index:Int=0,transition:Float=0.0,finished:Void()=Null )
+		
+		Animate( _animations[index],transition,finished )
+	End
+		
 	Method Animate( name:String,transition:Float=0.0,finished:Void()=Null )
 	Method Animate( name:String,transition:Float=0.0,finished:Void()=Null )
 		
 		
 		Animate( FindAnimation( name ),transition,finished )
 		Animate( FindAnimation( name ),transition,finished )
@@ -209,6 +214,32 @@ Class Animator Extends Component
 			Local chan0:=playing0 ? playing0.Channels[i] Else Null
 			Local chan0:=playing0 ? playing0.Channels[i] Else Null
 			Local chan1:=playing1 ? playing1.Channels[i] Else Null
 			Local chan1:=playing1 ? playing1.Channels[i] Else Null
 			
 			
+			If chan0?.PositionKeys
+				If chan1?.PositionKeys
+					_skeleton[i].LocalPosition=chan0.GetPosition( time0 ).Blend( chan1.GetPosition( time1 ),alpha )
+				Else
+					_skeleton[i].LocalPosition=chan0.GetPosition( time0 )
+				Endif
+			Endif
+			
+			If chan0?.RotationKeys
+				If chan1?.RotationKeys
+					_skeleton[i].LocalBasis=chan0.GetRotation( time0 ).Slerp( chan1.GetRotation( time1 ),alpha )
+				Else
+					_skeleton[i].LocalBasis=chan0.GetRotation( time0 )
+				Endif
+			Endif
+
+			If chan0?.ScaleKeys
+				If chan1?.ScaleKeys
+					_skeleton[i].LocalScale=chan0.GetScale( time0 ).Blend( chan1.GetScale( time1 ),alpha )
+				Else
+					_skeleton[i].LocalScale=chan0.GetScale( time0 )
+				Endif
+			Endif
+
+			#rem
+			
 			If playing0 And playing1
 			If playing0 And playing1
 				
 				
 				Local pos0:=chan0 ? chan0.GetPosition( time0 ) Else New Vec3f
 				Local pos0:=chan0 ? chan0.GetPosition( time0 ) Else New Vec3f
@@ -244,6 +275,8 @@ Class Animator Extends Component
 				_skeleton[i].LocalScale=scl1
 				_skeleton[i].LocalScale=scl1
 			
 			
 			Endif
 			Endif
+			
+			#end
 		
 		
 		Next
 		Next
 	End
 	End

+ 0 - 325
modules/mojo3d/geometry/gltf2loader.monkey2

@@ -1,325 +0,0 @@
-
-Namespace mojo3d.gltf2
-
-Private
-
-Class Gltf2Loader
-	
-	Method New( asset:Gltf2Asset,dir:String )
-		
-		_asset=asset
-		_dir=dir
-	End
-	
-	Method LoadMesh:Mesh()
-		
-		Local mesh:=New Mesh
-		
-		For Local node:=Eachin _asset.scenes[0].nodes
-			
-			LoadMesh( node,mesh,Null )
-		Next
-		
-		mesh.UpdateTangents()
-		
-		Return mesh
-	End
-
-	Method LoadModel:Model()
-		
-		Local mesh:=New Mesh
-		
-		Local materials:=New Stack<Material>
-		
-		For Local node:=Eachin _asset.scenes[0].nodes
-			
-			LoadMesh( node,mesh,materials )
-		Next
-		
-		mesh.UpdateTangents()
-		
-		Local model:=New Model
-		
-		model.Mesh=mesh
-		
-		model.Materials=materials.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>
-	
-	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,textured:Bool )
-		
-		If Not material Return New PbrMaterial( Color.Magenta )
-		
-		If _materialCache.Contains( material ) Return _materialCache[material]
-		
-		Local colorTexture:Texture
-		Local metallicRoughnessTexture:Texture
-		Local occlusionTexture:Texture
-		Local emissiveTexture:Texture
-		Local normalTexture:Texture
-		
-		If textured
-			colorTexture=GetTexture( material.baseColorTexture )
-			metallicRoughnessTexture=GetTexture( material.metallicRoughnessTexture )
-			occlusionTexture=GetTexture( material.occlusionTexture )
-			emissiveTexture=GetTexture( material.emissiveTexture )
-			normalTexture=GetTexture( material.normalTexture )
-		Endif
-			
-		Local mat:=New PbrMaterial
-
-		mat.ColorFactor=New Color( material.baseColorFactor.x,material.baseColorFactor.y,material.baseColorFactor.z )
-		mat.MetalnessFactor=material.metallicFactor
-		mat.RoughnessFactor=material.roughnessFactor
-
-		If colorTexture mat.ColorTexture=colorTexture
-		If metallicRoughnessTexture mat.MetalnessTexture=metallicRoughnessTexture ; mat.RoughnessTexture=metallicRoughnessTexture
-		If occlusionTexture mat.OcclusionTexture=occlusionTexture
-		If emissiveTexture 
-			mat.EmissiveTexture=emissiveTexture
- 			If material.emissiveFactor<>Null
-				mat.EmissiveFactor=New Color( material.emissiveFactor.x,material.emissiveFactor.y,material.emissiveFactor.z )
-			Else
-				mat.EmissiveFactor=Color.White
-			Endif
-		Else If material.emissiveFactor<>Null
-			mat.EmissiveTexture=Texture.ColorTexture( Color.White )
-			mat.EmissiveFactor=New Color( material.emissiveFactor.x,material.emissiveFactor.y,material.emissiveFactor.z )
-		Endif
-		
-		If normalTexture mat.NormalTexture=normalTexture
-			
-		_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 LoadMesh( node:Gltf2Node,mesh:Mesh,materials:Stack<Material> )
-		
-		If node.mesh
-			
-'			Print "mesh="+node.mesh.name
-			
-			Local matrix:=New AffineMat4f( GetMatrix( node ) )
-
-			Local cofactor:=matrix.m.Cofactor()
-			
-			For Local prim:=Eachin node.mesh.primitives
-				
-				'some sanity checking!
-				'
-				If prim.mode<>4
-					Print "Gltf invalid mesh mode:"+prim.mode
-					Continue
-				Endif
-				
-				If Not prim.indices
-					Print "Gltf mesh has no indices"
-					Continue
-				Endif
-
-				If prim.POSITION.componentType<>5126 Or prim.POSITION.type<>"VEC3"
-					Print "Gltf invalid nesh POSITION data"
-					Continue
-				Endif
-				
-				If Not prim.indices Or (prim.indices.componentType<>5123 And prim.indices.componentType<>5125) Or prim.indices.type<>"SCALAR" 
-					Print "Gltf invalid mesh indices data"
-					Continue
-				Endif
-				
-				Local pp:=GetData( prim.POSITION )
-				Local pstride:=prim.POSITION.bufferView.byteStride
-				If Not pstride pstride=12
-					
-				Local np:UByte Ptr,nstride:Int
-				If prim.NORMAL
-					If prim.NORMAL.componentType=5126 And prim.NORMAL.type="VEC3"
-						np=GetData( prim.NORMAL )
-						nstride=prim.NORMAL.bufferView.byteStride
-						If Not nstride nstride=12
-					Endif
-				Endif
-				
-				Local tp:UByte Ptr,tstride:Int
-				If prim.TEXCOORD_0 
-					If prim.TEXCOORD_0.componentType=5126 And prim.TEXCOORD_0.type="VEC2"
-						tp=GetData( prim.TEXCOORD_0 )
-						tstride=prim.TEXCOORD_0.bufferView.byteStride
-						If Not tstride tstride=8
-					Endif
-				Endif
-				
-				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].position.z=-dstvp[i].position.z
-					dstvp[i].position=matrix * dstvp[i].position
-					pp+=pstride
-					
-					If np
-						dstvp[i].normal=Cast<Vec3f Ptr>( np )[0]
-						dstvp[i].normal.z=-dstvp[i].normal.z
-						dstvp[i].normal=cofactor * dstvp[i].normal
-						np+=nstride
-					Endif
-					
-					If tp
-						dstvp[i].texCoord0=Cast<Vec2f Ptr>( tp )[0]
-						tp+=tstride
-					Endif
-				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:=mesh.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
-				
-				mesh.AddVertices( vertices )
-				
-				If materials
-					
-					mesh.AddTriangles( indices,mesh.NumMaterials )
-
-					materials.Push( GetMaterial( prim.material,tp<>Null ) )
-					
-				Else
-					
-					mesh.AddTriangles( indices,0 )
-					
-				Endif
-					
-			Next
-			
-		Endif
-		
-		For Local child:=Eachin node.children
-			
-			LoadMesh( child,mesh,materials )
-		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
-
-	Method LoadBonedModel:Model( path:String ) Override
-		
-		Return Null
-	End
-
-End

+ 29 - 19
modules/mojo3d/geometry/mesh.monkey2

@@ -101,25 +101,6 @@ Class Mesh Extends Resource
 
 
 		InvalidateVertices()
 		InvalidateVertices()
 	End
 	End
-#rem
-	#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
-#end
 
 
 	#rem monkeydoc Sets a range of vertices.
 	#rem monkeydoc Sets a range of vertices.
 	#end
 	#end
@@ -303,6 +284,35 @@ Class Mesh Extends Resource
 		Return first
 		Return first
 	End
 	End
 	
 	
+	#rem monkeydoc Adds a mesh to this mesh.
+	#end
+	Method AddMesh( mesh:Mesh,materialid:Int=0 )
+		
+		Local v0:=_vertices.Length
+		
+		AddVertices( mesh._vertices.Data.Data,mesh._vertices.Length )
+		
+		For Local material:=Eachin mesh._materials
+			
+			Local count:=material.indices.Length
+			
+			Local mindices:=material.indices.Data
+			
+			If v0
+				Local indices:=New UInt[count]
+				For Local i:=0 Until count
+					indices[i]=mindices[i]+v0
+				Next
+				mindices=indices
+			Endif
+			
+			AddTriangles( mindices.Data,count,materialid )
+			
+			materialid+=1
+		Next
+	
+	End
+	
 	#rem monkeydoc Transforms all vertices in the mesh.
 	#rem monkeydoc Transforms all vertices in the mesh.
 	#end
 	#end
 	Method TransformVertices( matrix:AffineMat4f )
 	Method TransformVertices( matrix:AffineMat4f )

+ 155 - 6
modules/mojo3d/geometry/gltf2.monkey2 → modules/mojo3d/loader/gltf2.monkey2

@@ -1,6 +1,14 @@
 
 
 Namespace mojo3d.gltf2
 Namespace mojo3d.gltf2
 
 
+Const GLTF_BYTE:=5120
+Const GLTF_UNSIGNED_BYTE:=5121
+Const GLTF_SHORT:=5122
+Const GLTF_UNSIGNED_SHORT:=5123
+Const GLTF_INT:=5124
+Const GLTF_UNSIGNED_INT:=5125
+Const GLTF_FLOAT:=5126
+
 #rem monkeydoc @hidden
 #rem monkeydoc @hidden
 #end
 #end
 Class Gltf2Buffer
 Class Gltf2Buffer
@@ -75,6 +83,8 @@ Class Gltf2Primitive
 	Field NORMAL:Gltf2Accessor
 	Field NORMAL:Gltf2Accessor
 	Field TANGENT:Gltf2Accessor
 	Field TANGENT:Gltf2Accessor
 	Field TEXCOORD_0:Gltf2Accessor
 	Field TEXCOORD_0:Gltf2Accessor
+	Field JOINTS_0:Gltf2Accessor
+	Field WEIGHTS_0:Gltf2Accessor
 	Field indices:Gltf2Accessor
 	Field indices:Gltf2Accessor
 	Field material:Gltf2Material
 	Field material:Gltf2Material
 	Field mode:Int
 	Field mode:Int
@@ -101,6 +111,39 @@ Class Gltf2Node
 	Field hasMatrix:Bool
 	Field hasMatrix:Bool
 End
 End
 
 
+#rem monkeydoc @hidden
+#end
+Class Gltf2Animation
+	Field name:String
+	Field channels:Gltf2AnimationChannel[]
+End
+
+#rem monkeydoc @hidden
+#end
+Class Gltf2AnimationChannel
+	
+	Field sampler:Gltf2AnimationSampler	'for shared samplers (nice).
+	Field targetNode:Gltf2Node
+	Field targetPath:String
+End
+
+#rem monkeydoc @hidden
+#end
+Class Gltf2AnimationSampler
+	
+	Field input:Gltf2Accessor	'time
+	Field output:Gltf2Accessor	'post/rot etc
+	Field interpolation:String
+End
+
+#rem monkeydoc @hidden
+#end
+Class Gltf2Skin
+	Field inverseBindMatrices:Gltf2Accessor	'array of mat4fs
+	Field joints:Gltf2Node[]
+	Field skeleton:Gltf2Node
+End
+
 #rem monkeydoc @hidden
 #rem monkeydoc @hidden
 #end
 #end
 Class Gltf2Scene
 Class Gltf2Scene
@@ -121,7 +164,10 @@ Class Gltf2Asset
 	Field materials:Gltf2Material[]
 	Field materials:Gltf2Material[]
 	Field meshes:Gltf2Mesh[]
 	Field meshes:Gltf2Mesh[]
 	Field nodes:Gltf2Node[]
 	Field nodes:Gltf2Node[]
+	Field animations:Gltf2Animation[]
+	Field skins:Gltf2Skin[]
 	Field scenes:Gltf2Scene[]
 	Field scenes:Gltf2Scene[]
+	Field scene:Gltf2Scene
 	
 	
 	Function Load:Gltf2Asset( path:String )
 	Function Load:Gltf2Asset( path:String )
 		
 		
@@ -156,10 +202,10 @@ Class Gltf2Asset
 	
 	
 	Method GetMat4f:Mat4f( jval:JsonArray )
 	Method GetMat4f:Mat4f( jval:JsonArray )
 		Return New Mat4f(
 		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) ) )
+			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
 	End
 	
 	
 	Method LoadBuffers:Bool()
 	Method LoadBuffers:Bool()
@@ -395,6 +441,12 @@ Class Gltf2Asset
 				If jattribs.Contains( "TEXCOORD_0" )
 				If jattribs.Contains( "TEXCOORD_0" )
 					prim.TEXCOORD_0=accessors[jattribs.GetNumber( "TEXCOORD_0" )]
 					prim.TEXCOORD_0=accessors[jattribs.GetNumber( "TEXCOORD_0" )]
 				Endif
 				Endif
+				If jattribs.Contains( "JOINTS_0" )
+					prim.JOINTS_0=accessors[jattribs.GetNumber( "JOINTS_0" )]
+				Endif
+				If jattribs.Contains( "WEIGHTS_0" )
+					prim.WEIGHTS_0=accessors[jattribs.GetNumber( "WEIGHTS_0" )]
+				Endif
 				If jprim.Contains( "indices" )
 				If jprim.Contains( "indices" )
 					prim.indices=accessors[jprim.GetNumber( "indices" )]
 					prim.indices=accessors[jprim.GetNumber( "indices" )]
 				Endif
 				Endif
@@ -413,7 +465,102 @@ Class Gltf2Asset
 		
 		
 		Return True
 		Return True
 	End
 	End
+	
+	Method LoadSkins:Bool()
+		
+		Local jskins:=root.GetArray( "skins" )
+		If Not jskins Return True
+		
+		skins=New Gltf2Skin[jskins.Length]
+		
+		For Local i:=0 Until skins.Length
+			
+			Local jskin:=jskins.GetObject( i )
+			
+			Local skin:=New Gltf2Skin
+			skins[i]=skin
+			
+			skin.inverseBindMatrices=accessors[jskin.GetNumber( "inverseBindMatrices" )]
+			
+			If jskin.Contains( "skeleton" )
+				
+				skin.skeleton=nodes[jskin.GetNumber("skeleton")]
+			Endif
+			
+			Local jjoints:=jskin.GetArray( "joints" )
+			
+			skin.joints=New Gltf2Node[jjoints.Length]
+			
+			For Local i:=0 Until jjoints.Length
+				
+				skin.joints[i]=nodes[jjoints.GetNumber(i)]
+			Next
+		
+		Next
+		
+		Return True
+		
+	End
+
+	Method LoadAnimations:Bool()
+		
+		Local janimations:=root.GetArray( "animations" )
+		If Not janimations Return True
+		
+		animations=New Gltf2Animation[ janimations.Length ]
+		
+		For Local i:=0 Until animations.Length
+			
+			Local animation:=New Gltf2Animation
+			animations[i]=animation
 
 
+			Local janimation:=janimations.GetObject( i )
+			
+			animation.name=janimation.GetString( "name" )
+
+			Local jsamplers:=janimation.GetArray( "samplers" )
+			
+			Local samplers:=New Gltf2AnimationSampler[ jsamplers.Length ]
+			
+			For Local i:=0 Until samplers.Length
+				
+				Local sampler:=New Gltf2AnimationSampler
+				samplers[i]=sampler
+				
+				Local jsampler:=jsamplers.GetObject( i )
+				
+				sampler.input=accessors[jsampler.GetNumber( "input" )]
+				sampler.output=accessors[jsampler.GetNumber( "output" )]
+				sampler.interpolation=jsampler.GetString( "interpolation" )
+			
+			Next
+			
+			Local jchannels:=janimation.GetArray( "channels" )
+			
+			animation.channels=New Gltf2AnimationChannel[ jchannels.Length ]
+			
+			For Local i:=0 Until animation.channels.Length
+				
+				Local channel:=New Gltf2AnimationChannel
+				animation.channels[i]=channel
+				
+				Local jchannel:=jchannels.GetObject( i )
+				
+				channel.sampler=samplers[jchannel.GetNumber( "sampler" )]
+				
+				Local jtarget:=jchannel.GetObject( "target" )
+				
+				channel.targetNode=nodes[jtarget.GetNumber( "node" )]
+				channel.targetPath=jtarget.GetString( "path" )
+			
+			Next
+			
+		Next
+		
+		Return True
+		
+	End
+	
 	Method LoadNodes:Bool()
 	Method LoadNodes:Bool()
 
 
 		Local jnodes:=root.GetArray( "nodes" )
 		Local jnodes:=root.GetArray( "nodes" )
@@ -448,7 +595,6 @@ Class Gltf2Asset
 			
 			
 			If jnode.Contains( "translation" )
 			If jnode.Contains( "translation" )
 				node.translation=GetVec3f( jnode.GetArray( "translation" ) )
 				node.translation=GetVec3f( jnode.GetArray( "translation" ) )
-				node.translation.z=-node.translation.z
 			Endif
 			Endif
 
 
 			If jnode.Contains( "rotation" )
 			If jnode.Contains( "rotation" )
@@ -461,7 +607,6 @@ Class Gltf2Asset
 				
 				
 			If jnode.Contains( "matrix" )
 			If jnode.Contains( "matrix" )
 				node.matrix=GetMat4f( jnode.GetArray( "matrix" ) )
 				node.matrix=GetMat4f( jnode.GetArray( "matrix" ) )
-				node.matrix.t.z=-node.matrix.t.z
 			Else
 			Else
 				node.matrix=Mat4f.Translation( node.translation ) * Mat4f.Rotation( node.rotation ) * Mat4f.Scaling( node.scale )
 				node.matrix=Mat4f.Translation( node.translation ) * Mat4f.Rotation( node.rotation ) * Mat4f.Scaling( node.scale )
 			Endif
 			Endif
@@ -500,6 +645,8 @@ Class Gltf2Asset
 		
 		
 		Next
 		Next
 		
 		
+		scene=scenes[root.GetNumber( "scene" )]
+		
 		Return True
 		Return True
 	End
 	End
 	
 	
@@ -520,6 +667,8 @@ Class Gltf2Asset
 		If Not LoadMaterials() Return False
 		If Not LoadMaterials() Return False
 		If Not LoadMeshes() Return False
 		If Not LoadMeshes() Return False
 		If Not LoadNodes() Return False
 		If Not LoadNodes() Return False
+		If Not LoadAnimations() Return False
+		If Not LoadSkins() Return False
 		If Not LoadScenes() Return False
 		If Not LoadScenes() Return False
 		
 		
 		Return True
 		Return True

+ 651 - 0
modules/mojo3d/loader/gltf2loader.monkey2

@@ -0,0 +1,651 @@
+
+Namespace mojo3d.gltf2
+
+Private
+
+Class Gltf2Loader
+	
+	Method New( asset:Gltf2Asset,dir:String )
+		
+		_asset=asset
+		_dir=dir
+	End
+	
+	Method LoadMesh:Mesh()
+		
+		Local mesh:=New Mesh
+		
+		For Local node:=Eachin _asset.scene.nodes
+
+			LoadMesh( node,mesh,Null )
+		Next
+		
+		Return mesh
+	End
+	
+	Method LoadModel:Model()
+
+		Local mesh:=New Mesh
+		
+		Local materials:=New Stack<Material>
+		
+		For Local node:=Eachin _asset.scene.nodes
+		
+			LoadMesh( node,mesh,materials )
+		Next
+		
+		Local model:=New Model
+		
+		model.Mesh=mesh
+		
+		model.Materials=materials.ToArray()
+		
+		Return model
+	End
+	
+	Method LoadBonedModel:Model()
+		
+		Local pivot:=New Model
+		
+		For Local node:=Eachin _asset.scene.nodes
+			
+			LoadBonedModel( node,pivot )
+		Next
+		
+		LoadAnimator( pivot )
+		
+		LoadBones()
+		
+		For Local node:=Eachin _asset.scene.nodes
+			
+			LoadBonedMesh( node )
+		Next
+		
+		Return pivot
+	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 _nodeIds:=New Map<Gltf2Node,Int>
+	Field _entityIds:=New Map<Entity,Int>
+	Field _entities:=New Stack<Entity>
+	
+	Field _bones:Model.Bone[]
+	
+	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,textured:Bool,boned:Bool )
+		
+		If Not material Return New PbrMaterial( Color.Magenta,0,1,boned )
+		
+		If _materialCache.Contains( material ) Return _materialCache[material]
+		
+		Local colorTexture:Texture
+		Local metallicRoughnessTexture:Texture
+		Local occlusionTexture:Texture
+		Local emissiveTexture:Texture
+		Local normalTexture:Texture
+		
+		If textured
+			colorTexture=GetTexture( material.baseColorTexture )
+			metallicRoughnessTexture=GetTexture( material.metallicRoughnessTexture )
+			occlusionTexture=GetTexture( material.occlusionTexture )
+			emissiveTexture=GetTexture( material.emissiveTexture )
+			normalTexture=GetTexture( material.normalTexture )
+		Endif
+			
+		Local mat:=New PbrMaterial( boned )
+
+		mat.ColorFactor=New Color( material.baseColorFactor.x,material.baseColorFactor.y,material.baseColorFactor.z )
+		mat.MetalnessFactor=material.metallicFactor
+		mat.RoughnessFactor=material.roughnessFactor
+
+		If colorTexture mat.ColorTexture=colorTexture
+		If metallicRoughnessTexture mat.MetalnessTexture=metallicRoughnessTexture ; mat.RoughnessTexture=metallicRoughnessTexture
+		If occlusionTexture mat.OcclusionTexture=occlusionTexture
+		If emissiveTexture 
+			mat.EmissiveTexture=emissiveTexture
+ 			If material.emissiveFactor<>Null
+				mat.EmissiveFactor=New Color( material.emissiveFactor.x,material.emissiveFactor.y,material.emissiveFactor.z )
+			Else
+				mat.EmissiveFactor=Color.White
+			Endif
+		Else If material.emissiveFactor<>Null
+			mat.EmissiveTexture=Texture.ColorTexture( Color.White )
+			mat.EmissiveFactor=New Color( material.emissiveFactor.x,material.emissiveFactor.y,material.emissiveFactor.z )
+		Endif
+		
+		If normalTexture mat.NormalTexture=normalTexture
+			
+		_materialCache[material]=mat
+		Return mat
+	End
+
+	Method FlipMatrix:Mat4f( matrix:Mat4f )
+		matrix.i.z=-matrix.i.z
+		matrix.j.z=-matrix.j.z
+		matrix.k.x=-matrix.k.x
+		matrix.k.y=-matrix.k.y
+		matrix.t.z=-matrix.t.z
+		Return matrix
+	End
+	
+	Method GetMatrix:Mat4f( node:Gltf2Node )
+		
+		Local matrix:=FlipMatrix( node.matrix )
+		
+		Return node.parent ? GetMatrix( node.parent ) * matrix Else matrix
+	End
+	
+	Method LoadPrimitive:Mesh( prim:Gltf2Primitive )
+		
+		'some sanity checking!
+		'
+		If prim.mode<>4
+			Print "Gltf invalid mesh mode:"+prim.mode
+			Return Null
+		Endif
+		
+		If Not prim.indices
+			Print "Gltf mesh has no indices"
+			Return Null
+		Endif
+
+		If prim.POSITION.componentType<>GLTF_FLOAT Or prim.POSITION.type<>"VEC3"
+			Print "Gltf nesh has invalid POSITION data type"
+			Return Null
+		Endif
+		
+		If Not prim.indices Or (prim.indices.componentType<>GLTF_UNSIGNED_SHORT And prim.indices.componentType<>GLTF_UNSIGNED_INT) Or prim.indices.type<>"SCALAR" 
+			Print "Gltf mesh has invalid index data"
+			Return Null
+		Endif
+		
+		Local vcount:=prim.POSITION.count
+		Local vertices:=New Vertex3f[vcount]
+
+		Local datap:=GetData( prim.POSITION )
+		Local stride:=prim.POSITION.bufferView.byteStride ?Else 12
+		For Local i:=0 Until vcount
+			vertices[i].position=Cast<Vec3f Ptr>( datap )[0]
+			vertices[i].position.z=-vertices[i].position.z
+			datap+=stride
+		Next
+		
+		If prim.NORMAL
+			If prim.NORMAL.componentType=GLTF_FLOAT And prim.NORMAL.type="VEC3"
+'				Print "Gltf2 primitive has normals"
+				Local datap:=GetData( prim.NORMAL )
+				Local stride:=prim.NORMAL.bufferView.byteStride ?Else 12
+				For Local i:=0 Until vcount
+					vertices[i].normal=Cast<Vec3f Ptr>( datap )[0]
+					vertices[i].normal.z=-vertices[i].normal.z
+					datap+=stride
+				Next
+			Endif
+		Endif
+		
+		If prim.TEXCOORD_0
+			If prim.TEXCOORD_0.componentType=GLTF_FLOAT And prim.TEXCOORD_0.type="VEC2"
+'				Print "Gltf2 primitive has texcoords"
+				Local datap:=GetData( prim.TEXCOORD_0 )
+				Local stride:=prim.TEXCOORD_0.bufferView.byteStride ?Else 8
+				For Local i:=0 Until vcount
+					vertices[i].texCoord0=Cast<Vec2f Ptr>( datap )[0]
+					datap+=stride
+				Next
+			Endif
+		Endif
+		
+		If prim.JOINTS_0
+			Local datap:=GetData( prim.JOINTS_0 )
+			If prim.JOINTS_0.componentType=GLTF_UNSIGNED_SHORT And prim.JOINTS_0.type="VEC4"
+'				Print "Primitive has ushort joints"
+				Local stride:=prim.JOINTS_0.bufferView.byteStride ?Else 8
+				For Local i:=0 Until vcount
+					Local cptr:=Cast<UShort Ptr>( datap )
+					vertices[i].bones=cptr[3] Shl 24 | cptr[2] Shl 16 | cptr[1] Shl 8 | cptr[0]
+					datap+=stride
+				Next
+			Else If prim.JOINTS_0.componentType=GLTF_UNSIGNED_BYTE And prim.JOINTS_0.type="VEC4"
+'				Print "Primitive has ubyte joints"
+				Local stride:=prim.JOINTS_0.bufferView.byteStride ?Else 4
+				For Local i:=0 Until vcount
+					Local cptr:=Cast<UByte Ptr>( datap )
+					vertices[i].bones=cptr[3] Shl 24 | cptr[2] Shl 16 | cptr[1] Shl 8 | cptr[0]
+					datap+=stride
+				Next
+			Endif
+		Endif
+		
+		If prim.WEIGHTS_0
+			Local datap:=GetData( prim.WEIGHTS_0 )
+			If prim.WEIGHTS_0.componentType=GLTF_FLOAT And prim.WEIGHTS_0.type="VEC4"
+'			Print "Primitive has float weights"
+				Local stride:=prim.WEIGHTS_0.bufferView.byteStride ?Else 16
+				For Local i:=0 Until vcount
+					vertices[i].weights=Cast<Vec4f Ptr>( datap )[0]
+					datap+=stride
+				Next
+			Endif
+		Endif
+		
+		Local icount:=prim.indices.count
+		Local indices:=New IndexType[icount]
+
+		datap=GetData( prim.indices )
+		stride=prim.indices.bufferView.byteStride
+		If prim.indices.componentType=GLTF_UNSIGNED_SHORT
+			stride=stride ?Else 2
+			For Local i:=0 Until icount Step 3
+				indices[i+0]=Cast<UShort Ptr>( datap )[0]
+				indices[i+2]=Cast<UShort Ptr>( datap )[1]
+				indices[i+1]=Cast<UShort Ptr>( datap )[2]
+				datap+=stride*3
+			Next
+		Else
+			stride=stride ?Else 4
+			For Local i:=0 Until icount Step 3
+				indices[i+0]=Cast<UInt Ptr>( datap )[0]
+				indices[i+2]=Cast<UInt Ptr>( datap )[1]
+				indices[i+1]=Cast<UInt Ptr>( datap )[2]
+				datap+=stride*3
+			Next
+		Endif
+		
+		Return New Mesh( vertices,indices )
+	End
+	
+	Method LoadMesh( node:Gltf2Node,mesh:Mesh,materials:Stack<Material> )
+		
+		If node.mesh
+			
+			Local matrix:=New AffineMat4f( GetMatrix( node ) )
+
+			For Local prim:=Eachin node.mesh.primitives
+				
+				Local pmesh:=LoadPrimitive( prim )
+				
+				pmesh.TransformVertices( matrix )
+				
+				If materials
+					
+					Local textured:=prim.TEXCOORD_0<>Null
+					
+					materials.Add( GetMaterial( prim.material,textured,False ) )
+					
+					mesh.AddMesh( pmesh,mesh.NumMaterials )
+				Else
+				
+					mesh.AddMesh( pmesh )
+				Endif
+				
+			Next
+			
+		Endif
+		
+		For Local child:=Eachin node.children
+			
+			LoadMesh( child,mesh,materials )
+		Next
+	
+	End
+	
+	Method LoadBonedModel:Model( node:Gltf2Node,parent:Model )
+
+		Local model:=New Model( parent )
+		
+		model.Name=node.name
+		
+		model.LocalMatrix=New AffineMat4f( FlipMatrix( node.matrix ) )
+		
+		Local id:=_entities.Length
+		_entities.Add( model )
+		_entityIds[model]=id
+		_nodeIds[node]=id
+		
+		For Local child:=Eachin node.children
+			
+			LoadBonedModel( child,model )
+		Next
+		
+		Return model
+	End
+	
+	Method LoadBonedMesh( node:Gltf2Node )
+		
+		If node.mesh
+
+			Local mesh:=New Mesh
+			
+			Local materials:=New Stack<Material>
+			
+			For Local prim:=Eachin node.mesh.primitives
+				
+				Local pmesh:=LoadPrimitive( prim )
+				
+				mesh.AddMesh( pmesh,mesh.NumMaterials )
+				
+				Local boned:=prim.JOINTS_0<>Null
+				
+				Local textured:=prim.TEXCOORD_0<>Null
+				
+				materials.Add( GetMaterial( prim.material,textured,boned ) )
+			Next
+			
+			Local model:=Cast<Model>( _entities[_nodeIds[node]] )
+		
+			model.Mesh=mesh
+			
+			model.Materials=materials.ToArray()
+			
+			If node.mesh.primitives[0].JOINTS_0<>Null
+				
+				model.Bones=_bones
+			Endif
+			
+		Endif
+		
+		For Local child:=Eachin node.children
+			
+			LoadBonedMesh( child )
+		Next
+	End
+	
+	Method LoadAnimation:Animation( ganimation:Gltf2Animation )
+		
+		Local gchannels:=ganimation.channels
+		
+		Local posKeys:=New Stack<PositionKey>[_entities.Length]
+		Local rotKeys:=New Stack<RotationKey>[_entities.Length]
+		Local sclKeys:=New Stack<ScaleKey>[_entities.Length]
+		
+		Local minTime:=10000.0,maxTime:=0.0
+		
+		For Local i:=0 Until gchannels.Length
+			
+			Local gchannel:=gchannels[i]
+			
+			If Not _nodeIds.Contains( gchannel.targetNode )
+				Print "Gltf2Loader: gchannel.targetNode not found"
+				Continue
+			Endif
+			
+			Local entityId:=_nodeIds[gchannel.targetNode]
+
+			Local sampler:=gchannel.sampler
+			
+			Local n:=sampler.input.count
+			If Not n Continue
+			
+			If n<>sampler.output.count
+				Print "Gltf2Loader: sampler.input.count<>sampler.output.count"
+				continue
+			End
+			
+			Local timep:=GetData( sampler.input )
+			Local timeStride:=sampler.input.bufferView.byteStride ?Else 4
+
+			minTime=Min( minTime,Cast<Float Ptr>(timep)[0] )
+			maxTime=Max( maxTime,Cast<Float Ptr>(timep+(n-1)*timeStride)[0] )
+				
+			Select gchannel.targetPath
+			Case "translation"
+				
+				Local keys:=New Stack<PositionKey>
+				If posKeys[ entityId ] Print "OOPS!"
+				posKeys[entityId]=keys
+
+				Local datap:=GetData( sampler.output )
+				Local dataStride:=sampler.output.bufferView.byteStride ?Else 12
+				
+				For Local i:=0 Until n
+
+					Local v:=Cast<Vec3f Ptr>( datap )[0]
+					
+					v.z=-v.z
+					
+					keys.Add( New PositionKey( Cast<Float Ptr>( timep )[0],v ) )
+					
+					timep+=timeStride
+					datap+=dataStride
+				Next
+			
+			Case "rotation"
+
+				Local keys:=New Stack<RotationKey>
+				If rotKeys[entityId] Print "OOPS!"
+				rotKeys[entityId]=keys
+				
+				Local datap:=GetData( sampler.output )
+				Local dataStride:=sampler.output.bufferView.byteStride ?Else 16
+				
+				For Local i:=0 Until n
+
+					Local q:=Cast<Quatf Ptr>( datap )[0]
+					
+					q.v.x=-q.v.x
+					q.v.y=-q.v.y
+					q.w=-q.w
+					
+					keys.Add( New RotationKey( Cast<Float Ptr>( timep )[0],q ) )
+					
+					timep+=timeStride
+					datap+=dataStride
+				Next
+				
+			Case "scale"
+				
+				Local keys:=New Stack<ScaleKey>
+				If sclKeys[entityId] Print "OOPS!"
+				sclKeys[entityId]=keys
+				
+				Local datap:=GetData( sampler.output )
+				Local dataStride:=sampler.output.bufferView.byteStride ?Else 12
+				
+				For Local i:=0 Until n
+
+					Local v:=Cast<Vec3f Ptr>( datap )[0]
+					
+					keys.Add( New ScaleKey( Cast<Float Ptr>( timep )[0],v ) )
+					
+					timep+=timeStride
+					datap+=dataStride
+				Next
+				
+			Default
+				
+				Print "targetPath="+gchannel.targetPath+"?????"
+			
+			End
+			
+		Next
+		
+		Local duration:=maxTime-minTime
+		
+'		Print "animation:"+ganimation.name+" duration:"+duration
+		
+		Local channels:=New AnimationChannel[_entities.Length]
+		
+		For Local i:=0 Until channels.Length
+			
+			Local pkeys:=posKeys[i]?.ToArray()
+			Local rkeys:=rotKeys[i]?.ToArray()
+			Local skeys:=sclKeys[i]?.ToArray()
+			
+			For Local key:=Eachin pkeys
+				key.Time-=minTime
+			End
+			For Local key:=Eachin rkeys
+				key.Time-=minTime
+			End
+			For Local key:=Eachin skeys
+				key.Time-=minTime
+			End
+			
+'			If Not pkeys And Not rkeys And Not skeys 
+'				Print "Yo1"
+'			Else If Not pkeys Or Not rkeys Or Not skeys 
+'				Print "Yo2"
+'			Endif
+			
+'			Print "channel:"+_entities[i].Name+" keys:"+pkeys.Length
+			
+			channels[i]=New AnimationChannel( pkeys,rkeys,skeys )
+		Next
+		
+		Local animation:=New Animation( ganimation.name,channels,duration,1,AnimationMode.Looping )
+		
+		Return animation
+	End
+	
+	Method LoadAnimator:Animator( model:Model )
+		
+		If Not _asset.animations Return Null
+		
+		Local animations:=New Animation[_asset.animations.Length]
+		
+		For Local i:=0 Until animations.Length
+			
+			animations[i]=LoadAnimation( _asset.animations[i] )
+		Next
+		
+		Local animator:=New Animator( model )
+
+		animator.Animations.AddAll( animations )
+		
+		animator.Skeleton=_entities.ToArray()
+		
+		Return animator
+	End
+	
+	Method LoadBones()
+		
+		If Not _asset.skins Return
+		
+		Local skin:=_asset.skins[0]
+		
+		Local datap:=GetData( skin.inverseBindMatrices )
+		Local stride:=skin.inverseBindMatrices.bufferView.byteStride ?Else 64
+		Local n:=skin.inverseBindMatrices.count
+		
+		If n<>skin.joints.Length
+			Print "Invalid skin"
+			Return
+		Endif
+		
+		_bones=New Model.Bone[n]
+		
+'		Print "Loading "+n+" bones"
+		
+		For Local i:=0 Until n
+			
+			Local entity:=_entities[ _nodeIds[skin.joints[i]] ]
+			Local matrix:=FlipMatrix( Cast<Mat4f Ptr>( datap )[0] )
+			
+			_bones[i].entity=entity
+			_bones[i].offset=New AffineMat4f( matrix )
+			
+			datap+=stride
+		Next
+	End
+	
+End
+
+Public
+
+#rem monkeydoc @hidden
+#End
+Class Gltf2Mojo3dLoader Extends Mojo3dLoader
+
+	Const Instance:=New Gltf2Mojo3dLoader
+
+	Method LoadMesh:Mesh( path:String ) Override
+	
+		Local loader:=Open( path )
+		If Not loader Return Null
+
+		Local mesh:=loader.LoadMesh()
+		
+		Return mesh
+	End
+
+	Method LoadModel:Model( path:String ) Override
+	
+		Local loader:=Open( path )
+		If Not loader Return Null
+
+		Local model:=loader.LoadModel()
+		
+		Return model
+	End
+
+	Method LoadBonedModel:Model( path:String ) Override
+
+		Local loader:=Open( path )
+		If Not loader Return Null
+
+		Local model:=loader.LoadBonedModel()
+		
+		Return model
+	End
+	
+	Private
+	
+	Method Open:Gltf2Loader( path:String )
+		
+		If ExtractExt( path ).ToLower()<>".gltf" Return Null
+			
+		Local asset:=Gltf2Asset.Load( path )
+		If Not asset Return Null
+		
+		Local loader:=New Gltf2Loader( asset,ExtractDir( path ) )
+		
+		Return loader
+	End
+
+End

+ 0 - 0
modules/mojo3d/geometry/loader.monkey2 → modules/mojo3d/loader/loader.monkey2


+ 5 - 4
modules/mojo3d/mojo3d.monkey2

@@ -43,12 +43,13 @@ Namespace mojo3d
 #Import "scene/entityexts"
 #Import "scene/entityexts"
 #Import "scene/scene"
 #Import "scene/scene"
 
 
-#Import "geometry/loader"
-#Import "geometry/gltf2"
-#Import "geometry/gltf2loader"
+#Import "loader/loader"
+#Import "loader/gltf2"
+#Import "loader/gltf2loader"
+
 #Import "geometry/mesh"
 #Import "geometry/mesh"
 #Import "geometry/meshprims"
 #Import "geometry/meshprims"
-#Import "geometry/util3d"
+'#Import "geometry/util3d"
 
 
 Using std..
 Using std..
 Using mojo..
 Using mojo..

+ 11 - 0
modules/mojo3d/scene/entity.monkey2

@@ -180,6 +180,7 @@ Class Entity Extends DynamicObject
 	Setter( matrix:AffineMat4f )
 	Setter( matrix:AffineMat4f )
 		
 		
 		Local scale:=matrix.m.GetScaling()
 		Local scale:=matrix.m.GetScaling()
+		
 		Basis=matrix.m.Scale( 1/scale.x,1/scale.y,1/scale.z )
 		Basis=matrix.m.Scale( 1/scale.x,1/scale.y,1/scale.z )
 		Position=matrix.t
 		Position=matrix.t
 		Scale=scale
 		Scale=scale
@@ -256,6 +257,16 @@ Class Entity Extends DynamicObject
 		Endif
 		Endif
 		
 		
 		Return _M
 		Return _M
+		
+	Setter( matrix:AffineMat4f )
+		
+		Local scale:=matrix.m.GetScaling()
+		
+		LocalBasis=matrix.m.Scale( 1/scale.x,1/scale.y,1/scale.z )
+		LocalPosition=matrix.t
+		LocalScale=scale
+		
+		Invalidate()
 	End
 	End
 
 
 	#rem monkeydoc Local space position.
 	#rem monkeydoc Local space position.

BIN
modules/mojo3d/tests/assets/bouncyball/bouncyball.bin


+ 589 - 0
modules/mojo3d/tests/assets/bouncyball/bouncyball.gltf

@@ -0,0 +1,589 @@
+{
+  "accessors": [
+    {
+      "bufferView": 0,
+      "byteOffset": 0,
+      "componentType": 5126,
+      "count": 50,
+      "max": [
+        0.129409536719322,
+        0.5,
+        0.129409536719322
+      ],
+      "min": [
+        -0.129409536719322,
+        -0.5,
+        -0.129409536719322
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 0,
+      "byteOffset": 600,
+      "componentType": 5126,
+      "count": 50,
+      "max": [
+        0.268908590078354,
+        1,
+        0.268908590078354
+      ],
+      "min": [
+        -0.268908590078354,
+        -1,
+        -0.268908560276031
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 0,
+      "componentType": 5123,
+      "count": 144,
+      "max": [
+        49
+      ],
+      "min": [
+        0
+      ],
+      "type": "SCALAR"
+    },
+    {
+      "bufferView": 0,
+      "byteOffset": 1200,
+      "componentType": 5126,
+      "count": 204,
+      "max": [
+        0.5,
+        0.482962906360626,
+        0.5
+      ],
+      "min": [
+        -0.5,
+        -0.482962906360626,
+        -0.5
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 0,
+      "byteOffset": 3648,
+      "componentType": 5126,
+      "count": 204,
+      "max": [
+        0.999999940395355,
+        0.963165760040283,
+        0.999999940395355
+      ],
+      "min": [
+        -1,
+        -0.963165700435638,
+        -1
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 288,
+      "componentType": 5123,
+      "count": 720,
+      "max": [
+        203
+      ],
+      "min": [
+        0
+      ],
+      "type": "SCALAR"
+    },
+    {
+      "bufferView": 0,
+      "byteOffset": 6096,
+      "componentType": 5126,
+      "count": 204,
+      "max": [
+        0.5,
+        0.482962906360626,
+        0.5
+      ],
+      "min": [
+        -0.5,
+        -0.482962906360626,
+        -0.5
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 0,
+      "byteOffset": 8544,
+      "componentType": 5126,
+      "count": 204,
+      "max": [
+        0.999999940395355,
+        0.963165760040283,
+        0.999999940395355
+      ],
+      "min": [
+        -1,
+        -0.963165760040283,
+        -1
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 1728,
+      "componentType": 5123,
+      "count": 720,
+      "max": [
+        203
+      ],
+      "min": [
+        0
+      ],
+      "type": "SCALAR"
+    },
+    {
+      "bufferView": 1,
+      "byteOffset": 0,
+      "componentType": 5126,
+      "count": 20,
+      "max": [
+        0.9916666666666667
+      ],
+      "min": [
+        0.041666666666666664
+      ],
+      "type": "SCALAR"
+    },
+    {
+      "bufferView": 1,
+      "byteOffset": 80,
+      "componentType": 5126,
+      "count": 20,
+      "max": [
+        0,
+        2.7406444549560547,
+        0
+      ],
+      "min": [
+        0,
+        0.2996174097061157,
+        0
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 1,
+      "byteOffset": 320,
+      "componentType": 5126,
+      "count": 20,
+      "max": [
+        1.3701729774475098,
+        1.5768864154815674,
+        1.3701729774475098
+      ],
+      "min": [
+        0.7761592268943787,
+        0.61479252576828,
+        0.7761592268943787
+      ],
+      "type": "VEC3"
+    }
+  ],
+  "animations": [
+    {
+      "channels": [
+        {
+          "sampler": 0,
+          "target": {
+            "node": 0,
+            "path": "scale"
+          }
+        },
+        {
+          "sampler": 1,
+          "target": {
+            "node": 0,
+            "path": "translation"
+          }
+        }
+      ],
+      "name": "Main",
+      "samplers": [
+        {
+          "input": 9,
+          "interpolation": "LINEAR",
+          "output": 11
+        },
+        {
+          "input": 9,
+          "interpolation": "LINEAR",
+          "output": 10
+        }
+      ]
+    }
+  ],
+  "asset": {
+    "generator": "qtek fbx2gltf",
+    "version": "2.0"
+  },
+  "bufferViews": [
+    {
+      "buffer": 0,
+      "byteLength": 10992,
+      "byteOffset": 0,
+      "target": 34962
+    },
+    {
+      "buffer": 0,
+      "byteLength": 560,
+      "byteOffset": 10992,
+      "target": 34962
+    },
+    {
+      "buffer": 0,
+      "byteLength": 3168,
+      "byteOffset": 11552,
+      "target": 34963
+    }
+  ],
+  "buffers": [
+    {
+      "byteLength": 14720,
+      "uri": "bouncyball.bin"
+    }
+  ],
+  "materials": [
+    {
+      "name": "Blue",
+      "emissiveFactor": [
+        0,
+        0,
+        0
+      ],
+      "pbrMetallicRoughness": {
+        "baseColorFactor": [
+          0,
+          0.0392156862745098,
+          0.9019607843137255,
+          1
+        ],
+        "metallicFactor": 0,
+        "roughnessFactor": 0.8213901449934544
+      }
+    },
+    {
+      "name": "Yellow",
+      "emissiveFactor": [
+        0,
+        0,
+        0
+      ],
+      "pbrMetallicRoughness": {
+        "baseColorFactor": [
+          0.9019607843137255,
+          0.9019607843137255,
+          0,
+          1
+        ],
+        "metallicFactor": 0,
+        "roughnessFactor": 0.8213901449934544
+      }
+    },
+    {
+      "name": "Red",
+      "emissiveFactor": [
+        0,
+        0,
+        0
+      ],
+      "pbrMetallicRoughness": {
+        "baseColorFactor": [
+          0.9019607843137255,
+          0,
+          0,
+          1
+        ],
+        "metallicFactor": 0,
+        "roughnessFactor": 0.8213901449934544
+      }
+    }
+  ],
+  "meshes": [
+    {
+      "name": "sphere_object1_Material0",
+      "primitives": [
+        {
+          "attributes": {
+            "NORMAL": 1,
+            "POSITION": 0
+          },
+          "indices": 2,
+          "material": 0
+        },
+        {
+          "attributes": {
+            "NORMAL": 4,
+            "POSITION": 3
+          },
+          "indices": 5,
+          "material": 1
+        },
+        {
+          "attributes": {
+            "NORMAL": 7,
+            "POSITION": 6
+          },
+          "indices": 8,
+          "material": 2
+        }
+      ]
+    }
+  ],
+  "nodes": [
+    {
+      "matrix": [
+        1.3701729774475098,
+        0,
+        0,
+        0,
+        0,
+        0.61479252576828,
+        0,
+        0,
+        0,
+        0,
+        1.3701729774475098,
+        0,
+        0,
+        0.2996174097061157,
+        0,
+        1
+      ],
+      "mesh": 0,
+      "name": "sphere_object1"
+    }
+  ],
+  "scene": 0,
+  "scenes": [
+    {
+      "nodes": [
+        0
+      ]
+    }
+  ],
+  "extras": {
+    "qtekModelViewerConfig": {
+      "preZ": true,
+      "materials": [
+        {
+          "name": "Blue",
+          "color": "#000ae6",
+          "emission": "#000000",
+          "alpha": 1,
+          "alphaCutoff": 0,
+          "emissionIntensity": 1,
+          "uvRepeat": {
+            "0": 1,
+            "1": 1
+          },
+          "parallaxOcclusionScale": 0.02,
+          "diffuseMap": "",
+          "normalMap": "",
+          "parallaxOcclusionMap": "",
+          "emissiveMap": "",
+          "metalness": 0,
+          "roughness": 0.8213901449934544,
+          "metalnessMap": "",
+          "roughnessMap": "",
+          "type": "pbrMetallicRoughness",
+          "targetMeshes": [
+            "sphere_object1_Material0"
+          ]
+        },
+        {
+          "name": "Yellow",
+          "color": "#e6e600",
+          "emission": "#000000",
+          "alpha": 1,
+          "alphaCutoff": 0,
+          "emissionIntensity": 1,
+          "uvRepeat": {
+            "0": 1,
+            "1": 1
+          },
+          "parallaxOcclusionScale": 0.02,
+          "diffuseMap": "",
+          "normalMap": "",
+          "parallaxOcclusionMap": "",
+          "emissiveMap": "",
+          "metalness": 0,
+          "roughness": 0.8213901449934544,
+          "metalnessMap": "",
+          "roughnessMap": "",
+          "type": "pbrMetallicRoughness",
+          "targetMeshes": [
+            "sphere_object1_Material0$1"
+          ]
+        },
+        {
+          "name": "Red",
+          "color": "#e60000",
+          "emission": "#000000",
+          "alpha": 1,
+          "alphaCutoff": 0,
+          "emissionIntensity": 1,
+          "uvRepeat": {
+            "0": 1,
+            "1": 1
+          },
+          "parallaxOcclusionScale": 0.02,
+          "diffuseMap": "",
+          "normalMap": "",
+          "parallaxOcclusionMap": "",
+          "emissiveMap": "",
+          "metalness": 0,
+          "roughness": 0.8213901449934544,
+          "metalnessMap": "",
+          "roughnessMap": "",
+          "type": "pbrMetallicRoughness",
+          "targetMeshes": [
+            "sphere_object1_Material0$2"
+          ]
+        }
+      ],
+      "takes": [],
+      "textureFlipY": false,
+      "zUpToYUp": false,
+      "shadow": true,
+      "environment": "auto",
+      "viewControl": {
+        "center": [
+          1.0690909624099731,
+          -1.1321967840194702,
+          0.9232351183891296
+        ],
+        "alpha": 10.229259828771838,
+        "beta": 33.79452028477864,
+        "distance": 47.03251490338545
+      },
+      "ground": {
+        "show": true
+      },
+      "mainLight": {
+        "shadow": true,
+        "shadowQuality": "medium",
+        "intensity": 0.8,
+        "color": "#fff",
+        "alpha": 45,
+        "beta": 45,
+        "$padAngle": [
+          0.25,
+          0.5
+        ]
+      },
+      "secondaryLight": {
+        "shadow": false,
+        "shadowQuality": "medium",
+        "intensity": 0,
+        "color": "#fff",
+        "alpha": 60,
+        "beta": -50,
+        "$padAngle": [
+          -0.2777777777777778,
+          0.6666666666666666
+        ]
+      },
+      "tertiaryLight": {
+        "shadow": false,
+        "shadowQuality": "medium",
+        "intensity": 0,
+        "color": "#fff",
+        "alpha": 89,
+        "beta": 0,
+        "$padAngle": [
+          0,
+          0.9888888888888889
+        ]
+      },
+      "ambientLight": {
+        "intensity": 0,
+        "color": "#fff"
+      },
+      "ambientCubemapLight": {
+        "texture": "./asset/texture/Barce_Rooftop_C.hdr",
+        "$texture": "Barce_Rooftop_C",
+        "$textureOptions": [
+          "pisa",
+          "Barce_Rooftop_C",
+          "Factory_Catwalk",
+          "Grand_Canyon_C",
+          "Ice_Lake",
+          "Hall",
+          "Old_Industrial_Hall"
+        ],
+        "exposure": 3,
+        "diffuseIntensity": 0.2,
+        "specularIntensity": 0.2,
+        "$intensity": 0.2
+      },
+      "postEffect": {
+        "enable": true,
+        "bloom": {
+          "enable": true,
+          "intensity": 0.1
+        },
+        "depthOfField": {
+          "enable": false,
+          "focalDistance": 4,
+          "focalRange": 1,
+          "blurRadius": 5,
+          "fstop": 10,
+          "quality": "medium",
+          "$qualityOptions": [
+            "low",
+            "medium",
+            "high",
+            "ultra"
+          ]
+        },
+        "screenSpaceAmbientOcclusion": {
+          "enable": false,
+          "radius": 1.5,
+          "quality": "medium",
+          "intensity": 1,
+          "$qualityOptions": [
+            "low",
+            "medium",
+            "high",
+            "ultra"
+          ]
+        },
+        "screenSpaceReflection": {
+          "enable": false,
+          "quality": "medium",
+          "maxRoughness": 0.8,
+          "$qualityOptions": [
+            "low",
+            "medium",
+            "high",
+            "ultra"
+          ]
+        },
+        "colorCorrection": {
+          "enable": true,
+          "exposure": 0,
+          "brightness": 0,
+          "contrast": 1,
+          "saturation": 1,
+          "lookupTexture": ""
+        },
+        "FXAA": {
+          "enable": false
+        }
+      }
+    }
+  },
+  "extensionsUsed": [
+    "KHR_materials_pbrSpecularGlossiness"
+  ]
+}

BIN
modules/mojo3d/tests/assets/walker/walk.bin


+ 2470 - 0
modules/mojo3d/tests/assets/walker/walk.gltf

@@ -0,0 +1,2470 @@
+{
+  "accessors": [
+    {
+      "bufferView": 0,
+      "byteOffset": 0,
+      "componentType": 5126,
+      "count": 956,
+      "max": [
+        0.307114273309708,
+        1.88036692142487,
+        0.301835179328918
+      ],
+      "min": [
+        -0.307114273309708,
+        0.00452733971178532,
+        -0.307114273309708
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 0,
+      "byteOffset": 11472,
+      "componentType": 5126,
+      "count": 956,
+      "max": [
+        0.125,
+        0.375
+      ],
+      "min": [
+        0.125,
+        0.375
+      ],
+      "type": "VEC2"
+    },
+    {
+      "bufferView": 0,
+      "byteOffset": 19120,
+      "componentType": 5123,
+      "count": 956,
+      "max": [
+        12,
+        12,
+        11,
+        10
+      ],
+      "min": [
+        0,
+        0,
+        0,
+        0
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 0,
+      "byteOffset": 26768,
+      "componentType": 5126,
+      "count": 956,
+      "max": [
+        1,
+        0.916199564933777,
+        0.843633890151978,
+        0.322161793708801
+      ],
+      "min": [
+        0.0000332839408656582,
+        0,
+        0,
+        0
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 3,
+      "byteOffset": 0,
+      "componentType": 5123,
+      "count": 4320,
+      "max": [
+        955
+      ],
+      "min": [
+        0
+      ],
+      "type": "SCALAR"
+    },
+    {
+      "bufferView": 0,
+      "byteOffset": 42064,
+      "componentType": 5126,
+      "count": 59,
+      "max": [
+        0.297124952077866,
+        1.87145125865936,
+        0.291844874620438
+      ],
+      "min": [
+        -0.297124952077866,
+        0.00624265894293785,
+        -0.297124952077866
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 0,
+      "byteOffset": 42772,
+      "componentType": 5126,
+      "count": 59,
+      "max": [
+        0.125,
+        0.125
+      ],
+      "min": [
+        0.125,
+        0.125
+      ],
+      "type": "VEC2"
+    },
+    {
+      "bufferView": 0,
+      "byteOffset": 43244,
+      "componentType": 5123,
+      "count": 59,
+      "max": [
+        12,
+        12,
+        11,
+        10
+      ],
+      "min": [
+        0,
+        0,
+        0,
+        0
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 0,
+      "byteOffset": 43716,
+      "componentType": 5126,
+      "count": 59,
+      "max": [
+        1,
+        0.862461030483246,
+        0.787990570068359,
+        0.279946267604828
+      ],
+      "min": [
+        0.00219220668077469,
+        0,
+        0,
+        0
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 3,
+      "byteOffset": 8640,
+      "componentType": 5123,
+      "count": 312,
+      "max": [
+        58
+      ],
+      "min": [
+        0
+      ],
+      "type": "SCALAR"
+    },
+    {
+      "bufferView": 1,
+      "byteOffset": 0,
+      "componentType": 5126,
+      "count": 13,
+      "max": [
+        0.7071069616676381,
+        0.7065039792525689,
+        0.18352295839579968,
+        0,
+        1.297598889720983e-36,
+        0.2595406240953048,
+        0.9991472703742983,
+        0,
+        1.00000001242492,
+        0.7065039938963843,
+        0.18352292110010368,
+        0,
+        2.3755418781675774e-7,
+        -5.071771698169818e-15,
+        1.60000004629283,
+        1
+      ],
+      "min": [
+        -0.7071069801562327,
+        -0.9999999688522121,
+        -0.18352298509004772,
+        0,
+        -1.0288886087161426e-7,
+        -0.08012979196722309,
+        -1.00000002106023,
+        0,
+        -0.7071069937475054,
+        -0.7065039378907493,
+        -0.18352295945499197,
+        0,
+        -5.58189149511033e-15,
+        -0.5467795687954656,
+        -1.5979803382428897,
+        1
+      ],
+      "type": "MAT4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 0,
+      "componentType": 5126,
+      "count": 28,
+      "max": [
+        1.4416666666666669
+      ],
+      "min": [
+        0.041666666666666664
+      ],
+      "type": "SCALAR"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 112,
+      "componentType": 5126,
+      "count": 28,
+      "max": [
+        0.3787504732608795,
+        0.059866804629564285,
+        -0.19540420174598694
+      ],
+      "min": [
+        0.10466296225786209,
+        1.066356958290271e-8,
+        -0.19540420174598694
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 448,
+      "componentType": 5126,
+      "count": 28,
+      "max": [
+        0,
+        0,
+        0,
+        1
+      ],
+      "min": [
+        0,
+        0,
+        0,
+        1
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 896,
+      "componentType": 5126,
+      "count": 28,
+      "max": [
+        1,
+        1,
+        1
+      ],
+      "min": [
+        1,
+        1,
+        1
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 1232,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        1.4416666666666669
+      ],
+      "min": [
+        0.041666666666666664
+      ],
+      "type": "SCALAR"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 1348,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        0.10212353616952896,
+        0.05983922630548477,
+        -0.19540409743785858
+      ],
+      "min": [
+        -0.17865866422653198,
+        1.066356958290271e-8,
+        -0.19540409743785858
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 1696,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        0,
+        0,
+        0,
+        1
+      ],
+      "min": [
+        0,
+        0,
+        0,
+        1
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 2160,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        1,
+        1,
+        1
+      ],
+      "min": [
+        1,
+        1,
+        1
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 2508,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        0.10212364047765732,
+        0.06039157137274742,
+        0.08743858337402344
+      ],
+      "min": [
+        -0.17833927273750305,
+        1.066356958290271e-8,
+        0.08743858337402344
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 2856,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        0,
+        0,
+        0,
+        1
+      ],
+      "min": [
+        0,
+        0,
+        0,
+        1
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 3320,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        1,
+        1,
+        1
+      ],
+      "min": [
+        1,
+        1,
+        1
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 3668,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        0.3882029056549072,
+        0.059866804629564285,
+        0.08743848651647568
+      ],
+      "min": [
+        0.10466305911540985,
+        1.066356958290271e-8,
+        0.08743848651647568
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 4016,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        0,
+        0,
+        0,
+        1
+      ],
+      "min": [
+        0,
+        0,
+        0,
+        1
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 4480,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        1,
+        1,
+        1
+      ],
+      "min": [
+        1,
+        1,
+        1
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 4828,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        0,
+        1.7387977838516235,
+        0.03374987840652466
+      ],
+      "min": [
+        0,
+        1.71110200881958,
+        -0.0334467813372612
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 5176,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        0.0256829595709123,
+        0.0005007423353603812,
+        0.00049700447537215,
+        0.9999997557907885
+      ],
+      "min": [
+        -0.01785916289340164,
+        0.00047956127536375424,
+        0.0004756569971589696,
+        0.9996698998145406
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 5640,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        1.0000000000000002,
+        1.0000000000000002,
+        1.0000000000000002
+      ],
+      "min": [
+        0.9999999999999999,
+        0.9999999999999998,
+        0.9999999999999998
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 5988,
+      "componentType": 5126,
+      "count": 2,
+      "max": [
+        1.4416666666666669
+      ],
+      "min": [
+        0.041666666666666664
+      ],
+      "type": "SCALAR"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 5996,
+      "componentType": 5126,
+      "count": 2,
+      "max": [
+        0.08011647313833238,
+        -0.06073546037077904,
+        0.08011647313833237
+      ],
+      "min": [
+        0.08011647313833235,
+        -0.06073546037077904,
+        0.08011647313833237
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 6020,
+      "componentType": 5126,
+      "count": 2,
+      "max": [
+        3.75530232012281e-18,
+        -0.38268343236508967,
+        9.066101792051642e-18,
+        0.9238795325112868
+      ],
+      "min": [
+        -9.388255800307029e-19,
+        -0.38268343236508967,
+        -2.266525448012911e-18,
+        0.9238795325112868
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 6052,
+      "componentType": 5126,
+      "count": 2,
+      "max": [
+        1.0000000000000002,
+        1,
+        1.0000000000000002
+      ],
+      "min": [
+        0.9999999999999999,
+        1,
+        0.9999999999999999
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 6076,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        1.4333333333333336
+      ],
+      "min": [
+        0.03333333333333333
+      ],
+      "type": "SCALAR"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 6192,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        -0.34327567463483477,
+        -0.5646079643917237,
+        -0.3825899012579683,
+        0.6209772332146184
+      ],
+      "min": [
+        -0.3963100561429893,
+        -0.5940892308030855,
+        -0.42289289031820115,
+        0.5858613604175246
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 6656,
+      "componentType": 5126,
+      "count": 28,
+      "max": [
+        1.4500000000000002
+      ],
+      "min": [
+        0.1
+      ],
+      "type": "SCALAR"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 6768,
+      "componentType": 5126,
+      "count": 28,
+      "max": [
+        -0.11154751992319904,
+        -2.227614974576578e-9,
+        -9.924262985301493e-9,
+        0.9937591009892606
+      ],
+      "min": [
+        -0.19375562757825307,
+        -3.0630453960360002e-9,
+        -1.0144504604253384e-8,
+        0.9810498238019093
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 7216,
+      "componentType": 5126,
+      "count": 28,
+      "max": [
+        -0.061867483469879365,
+        6.7151424641743684e-9,
+        -5.561533723753888e-9,
+        0.9980843724300587
+      ],
+      "min": [
+        -0.17346961800430458,
+        6.048687328187861e-9,
+        -6.2799025750352286e-9,
+        0.9848392212079292
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 7664,
+      "componentType": 5126,
+      "count": 2,
+      "max": [
+        -0.08011647313833237,
+        -0.060735460370778815,
+        0.08011647313833237
+      ],
+      "min": [
+        -0.08011647313833238,
+        -0.06073546037077904,
+        0.08011647313833237
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 7688,
+      "componentType": 5126,
+      "count": 2,
+      "max": [
+        8.000390764101652e-17,
+        0.9238795325112867,
+        8.000390764101652e-17,
+        -0.3826834323650898
+      ],
+      "min": [
+        -3.3138703587753536e-17,
+        0.9238795325112867,
+        3.3138703587753536e-17,
+        -0.3826834323650898
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 7720,
+      "componentType": 5126,
+      "count": 2,
+      "max": [
+        1.0000000000000002,
+        1,
+        1.0000000000000002
+      ],
+      "min": [
+        0.9999999999999999,
+        1,
+        0.9999999999999999
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 7744,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        -0.35032506818952497,
+        -0.5181793742259639,
+        -0.34669574095674915,
+        0.6143522840977433
+      ],
+      "min": [
+        -0.43733678030753825,
+        -0.6164970852371117,
+        -0.48079020248750415,
+        0.5548516194390245
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 8208,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        -0.1179497210369631,
+        7.2960567370699746e-9,
+        -1.1566850091859941e-8,
+        0.9930195684412783
+      ],
+      "min": [
+        -0.2182250838808589,
+        6.083173761707528e-9,
+        -1.2248243155057646e-8,
+        0.9758984643728014
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 8672,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        -0.06883043527648022,
+        4.375810562473946e-8,
+        -9.039760573008193e-9,
+        0.9976283732831821
+      ],
+      "min": [
+        -0.2104509740317789,
+        4.202087980935973e-8,
+        -1.5189955259001044e-8,
+        0.9776044125969735
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 9136,
+      "componentType": 5126,
+      "count": 2,
+      "max": [
+        -0.08011647313833237,
+        -0.060735460370778815,
+        -0.08011647313833237
+      ],
+      "min": [
+        -0.08011647313833238,
+        -0.06073546037077926,
+        -0.08011647313833238
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 9160,
+      "componentType": 5126,
+      "count": 2,
+      "max": [
+        3.3138703587753536e-17,
+        0.9238795325112868,
+        8.000390764101651e-17,
+        0.38268343236508967
+      ],
+      "min": [
+        -8.000390764101651e-17,
+        0.9238795325112868,
+        3.3138703587753536e-17,
+        0.3826834323650896
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 9192,
+      "componentType": 5126,
+      "count": 2,
+      "max": [
+        1.0000000000000004,
+        1,
+        1.0000000000000007
+      ],
+      "min": [
+        1,
+        1,
+        1
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 9216,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        -0.343444731652475,
+        -0.5558793258000364,
+        -0.3259296294201151,
+        0.617500033375863
+      ],
+      "min": [
+        -0.4900495809040575,
+        -0.6284700585352242,
+        -0.43653592424678644,
+        0.50871679025711
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 9680,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        -0.10349640764329562,
+        2.0664895611674537e-10,
+        -1.0346681992879392e-8,
+        0.9946298274257276
+      ],
+      "min": [
+        -0.21569876964838308,
+        -9.737863953382407e-10,
+        -1.0392400893684155e-8,
+        0.9764599534912702
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 10144,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        -0.050364139529761466,
+        1.640184618372893e-8,
+        -5.5468564316988404e-9,
+        0.9987309214445232
+      ],
+      "min": [
+        -0.20636874025867186,
+        1.5328995765290026e-8,
+        -8.050469704393677e-9,
+        0.9784742935019032
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 10608,
+      "componentType": 5126,
+      "count": 2,
+      "max": [
+        0.0801164731383324,
+        -0.06073546037077904,
+        -0.08011647313833235
+      ],
+      "min": [
+        0.08011647313833235,
+        -0.06073546037077904,
+        -0.08011647313833237
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 10632,
+      "componentType": 5126,
+      "count": 2,
+      "max": [
+        6.410702056087227e-18,
+        0.38268343236508967,
+        4.533050896025822e-18,
+        0.9238795325112868
+      ],
+      "min": [
+        -1.8776511600614058e-18,
+        0.38268343236508967,
+        -6.4107020560872245e-18,
+        0.9238795325112868
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 10664,
+      "componentType": 5126,
+      "count": 2,
+      "max": [
+        1.0000000000000002,
+        1,
+        1.0000000000000002
+      ],
+      "min": [
+        0.9999999999999999,
+        1,
+        0.9999999999999999
+      ],
+      "type": "VEC3"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 10688,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        -0.37352379378857636,
+        -0.5760199987620502,
+        -0.34708724295824445,
+        0.6006692983826076
+      ],
+      "min": [
+        -0.4128239883703936,
+        -0.618794036498964,
+        -0.4103461623942859,
+        0.5737047525262833
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 11152,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        -0.11039836731563783,
+        8.858239287575232e-10,
+        -1.0516184125613276e-8,
+        0.9938874184202361
+      ],
+      "min": [
+        -0.187059344927289,
+        6.851271898838119e-11,
+        -1.0553204128495013e-8,
+        0.9823486150422228
+      ],
+      "type": "VEC4"
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 11616,
+      "componentType": 5126,
+      "count": 29,
+      "max": [
+        -0.058074676265901945,
+        2.0036171020764496e-8,
+        -6.548683375121414e-9,
+        0.9983122417243065
+      ],
+      "min": [
+        -0.1636239282889574,
+        1.922864494882868e-8,
+        -8.63670163838025e-9,
+        0.9865227874161294
+      ],
+      "type": "VEC4"
+    }
+  ],
+  "animations": [
+    {
+      "channels": [
+        {
+          "sampler": 0,
+          "target": {
+            "node": 2,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 1,
+          "target": {
+            "node": 2,
+            "path": "scale"
+          }
+        },
+        {
+          "sampler": 2,
+          "target": {
+            "node": 2,
+            "path": "translation"
+          }
+        },
+        {
+          "sampler": 3,
+          "target": {
+            "node": 3,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 4,
+          "target": {
+            "node": 3,
+            "path": "scale"
+          }
+        },
+        {
+          "sampler": 5,
+          "target": {
+            "node": 3,
+            "path": "translation"
+          }
+        },
+        {
+          "sampler": 6,
+          "target": {
+            "node": 4,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 7,
+          "target": {
+            "node": 4,
+            "path": "scale"
+          }
+        },
+        {
+          "sampler": 8,
+          "target": {
+            "node": 4,
+            "path": "translation"
+          }
+        },
+        {
+          "sampler": 9,
+          "target": {
+            "node": 5,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 10,
+          "target": {
+            "node": 5,
+            "path": "scale"
+          }
+        },
+        {
+          "sampler": 11,
+          "target": {
+            "node": 5,
+            "path": "translation"
+          }
+        },
+        {
+          "sampler": 12,
+          "target": {
+            "node": 6,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 13,
+          "target": {
+            "node": 6,
+            "path": "scale"
+          }
+        },
+        {
+          "sampler": 14,
+          "target": {
+            "node": 6,
+            "path": "translation"
+          }
+        },
+        {
+          "sampler": 15,
+          "target": {
+            "node": 9,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 16,
+          "target": {
+            "node": 9,
+            "path": "scale"
+          }
+        },
+        {
+          "sampler": 17,
+          "target": {
+            "node": 9,
+            "path": "translation"
+          }
+        },
+        {
+          "sampler": 18,
+          "target": {
+            "node": 10,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 19,
+          "target": {
+            "node": 11,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 20,
+          "target": {
+            "node": 12,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 21,
+          "target": {
+            "node": 14,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 22,
+          "target": {
+            "node": 14,
+            "path": "scale"
+          }
+        },
+        {
+          "sampler": 23,
+          "target": {
+            "node": 14,
+            "path": "translation"
+          }
+        },
+        {
+          "sampler": 24,
+          "target": {
+            "node": 15,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 25,
+          "target": {
+            "node": 16,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 26,
+          "target": {
+            "node": 17,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 27,
+          "target": {
+            "node": 19,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 28,
+          "target": {
+            "node": 19,
+            "path": "scale"
+          }
+        },
+        {
+          "sampler": 29,
+          "target": {
+            "node": 19,
+            "path": "translation"
+          }
+        },
+        {
+          "sampler": 30,
+          "target": {
+            "node": 20,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 31,
+          "target": {
+            "node": 21,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 32,
+          "target": {
+            "node": 22,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 33,
+          "target": {
+            "node": 24,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 34,
+          "target": {
+            "node": 24,
+            "path": "scale"
+          }
+        },
+        {
+          "sampler": 35,
+          "target": {
+            "node": 24,
+            "path": "translation"
+          }
+        },
+        {
+          "sampler": 36,
+          "target": {
+            "node": 25,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 37,
+          "target": {
+            "node": 26,
+            "path": "rotation"
+          }
+        },
+        {
+          "sampler": 38,
+          "target": {
+            "node": 27,
+            "path": "rotation"
+          }
+        }
+      ],
+      "name": "Main",
+      "samplers": [
+        {
+          "input": 11,
+          "interpolation": "LINEAR",
+          "output": 13
+        },
+        {
+          "input": 11,
+          "interpolation": "LINEAR",
+          "output": 14
+        },
+        {
+          "input": 11,
+          "interpolation": "LINEAR",
+          "output": 12
+        },
+        {
+          "input": 15,
+          "interpolation": "LINEAR",
+          "output": 17
+        },
+        {
+          "input": 15,
+          "interpolation": "LINEAR",
+          "output": 18
+        },
+        {
+          "input": 15,
+          "interpolation": "LINEAR",
+          "output": 16
+        },
+        {
+          "input": 15,
+          "interpolation": "LINEAR",
+          "output": 20
+        },
+        {
+          "input": 15,
+          "interpolation": "LINEAR",
+          "output": 21
+        },
+        {
+          "input": 15,
+          "interpolation": "LINEAR",
+          "output": 19
+        },
+        {
+          "input": 15,
+          "interpolation": "LINEAR",
+          "output": 23
+        },
+        {
+          "input": 15,
+          "interpolation": "LINEAR",
+          "output": 24
+        },
+        {
+          "input": 15,
+          "interpolation": "LINEAR",
+          "output": 22
+        },
+        {
+          "input": 15,
+          "interpolation": "LINEAR",
+          "output": 26
+        },
+        {
+          "input": 15,
+          "interpolation": "LINEAR",
+          "output": 27
+        },
+        {
+          "input": 15,
+          "interpolation": "LINEAR",
+          "output": 25
+        },
+        {
+          "input": 28,
+          "interpolation": "LINEAR",
+          "output": 30
+        },
+        {
+          "input": 28,
+          "interpolation": "LINEAR",
+          "output": 31
+        },
+        {
+          "input": 28,
+          "interpolation": "LINEAR",
+          "output": 29
+        },
+        {
+          "input": 32,
+          "interpolation": "LINEAR",
+          "output": 33
+        },
+        {
+          "input": 34,
+          "interpolation": "LINEAR",
+          "output": 35
+        },
+        {
+          "input": 34,
+          "interpolation": "LINEAR",
+          "output": 36
+        },
+        {
+          "input": 28,
+          "interpolation": "LINEAR",
+          "output": 38
+        },
+        {
+          "input": 28,
+          "interpolation": "LINEAR",
+          "output": 39
+        },
+        {
+          "input": 28,
+          "interpolation": "LINEAR",
+          "output": 37
+        },
+        {
+          "input": 32,
+          "interpolation": "LINEAR",
+          "output": 40
+        },
+        {
+          "input": 32,
+          "interpolation": "LINEAR",
+          "output": 41
+        },
+        {
+          "input": 32,
+          "interpolation": "LINEAR",
+          "output": 42
+        },
+        {
+          "input": 28,
+          "interpolation": "LINEAR",
+          "output": 44
+        },
+        {
+          "input": 28,
+          "interpolation": "LINEAR",
+          "output": 45
+        },
+        {
+          "input": 28,
+          "interpolation": "LINEAR",
+          "output": 43
+        },
+        {
+          "input": 32,
+          "interpolation": "LINEAR",
+          "output": 46
+        },
+        {
+          "input": 32,
+          "interpolation": "LINEAR",
+          "output": 47
+        },
+        {
+          "input": 32,
+          "interpolation": "LINEAR",
+          "output": 48
+        },
+        {
+          "input": 28,
+          "interpolation": "LINEAR",
+          "output": 50
+        },
+        {
+          "input": 28,
+          "interpolation": "LINEAR",
+          "output": 51
+        },
+        {
+          "input": 28,
+          "interpolation": "LINEAR",
+          "output": 49
+        },
+        {
+          "input": 32,
+          "interpolation": "LINEAR",
+          "output": 52
+        },
+        {
+          "input": 32,
+          "interpolation": "LINEAR",
+          "output": 53
+        },
+        {
+          "input": 32,
+          "interpolation": "LINEAR",
+          "output": 54
+        }
+      ]
+    }
+  ],
+  "asset": {
+    "generator": "qtek fbx2gltf",
+    "version": "2.0"
+  },
+  "bufferViews": [
+    {
+      "buffer": 0,
+      "byteLength": 44660,
+      "byteOffset": 0,
+      "target": 34962
+    },
+    {
+      "buffer": 0,
+      "byteLength": 832,
+      "byteOffset": 44660,
+      "target": 34962
+    },
+    {
+      "buffer": 0,
+      "byteLength": 12080,
+      "byteOffset": 45492,
+      "target": 34962
+    },
+    {
+      "buffer": 0,
+      "byteLength": 9264,
+      "byteOffset": 57572,
+      "target": 34963
+    }
+  ],
+  "buffers": [
+    {
+      "byteLength": 66836,
+      "uri": "walk.bin"
+    }
+  ],
+  "materials": [
+    {
+      "name": "Wire",
+      "emissiveFactor": [
+        0.9019607843137255,
+        0,
+        0
+      ],
+      "extensions": {
+        "KHR_materials_pbrSpecularGlossiness": {
+          "diffuseFactor": [
+            0.9019607843137255,
+            0,
+            0,
+            1
+          ],
+          "specularFactor": [
+            0.10196078431372549,
+            0.10196078431372549,
+            0.10196078431372549
+          ],
+          "glossinessFactor": 0.5
+        }
+      }
+    },
+    {
+      "name": "Base",
+      "emissiveFactor": [
+        0,
+        0,
+        0
+      ],
+      "extensions": {
+        "KHR_materials_pbrSpecularGlossiness": {
+          "diffuseFactor": [
+            0,
+            0,
+            0,
+            1
+          ],
+          "specularFactor": [
+            0.10196078431372549,
+            0.10196078431372549,
+            0.10196078431372549
+          ],
+          "glossinessFactor": 0.5
+        }
+      }
+    }
+  ],
+  "meshes": [
+    {
+      "name": "Mesh_Material0",
+      "primitives": [
+        {
+          "attributes": {
+            "JOINTS_0": 2,
+            "POSITION": 0,
+            "TEXCOORD_0": 1,
+            "WEIGHTS_0": 3
+          },
+          "indices": 4,
+          "material": 0
+        },
+        {
+          "attributes": {
+            "JOINTS_0": 7,
+            "POSITION": 5,
+            "TEXCOORD_0": 6,
+            "WEIGHTS_0": 8
+          },
+          "indices": 9,
+          "material": 1
+        }
+      ]
+    }
+  ],
+  "nodes": [
+    {
+      "matrix": [
+        1,
+        0,
+        0,
+        0,
+        0,
+        1,
+        0,
+        0,
+        0,
+        0,
+        1,
+        0,
+        0,
+        0,
+        0,
+        1
+      ],
+      "mesh": 0,
+      "name": "Mesh",
+      "skin": 0
+    },
+    {
+      "children": [
+        2,
+        3,
+        4,
+        5,
+        6
+      ],
+      "matrix": [
+        1,
+        0,
+        0,
+        0,
+        0,
+        1,
+        0,
+        0,
+        0,
+        0,
+        1,
+        0,
+        0,
+        0,
+        0,
+        1
+      ],
+      "name": "layout"
+    },
+    {
+      "matrix": [
+        1,
+        0,
+        0,
+        0,
+        0,
+        1,
+        0,
+        0,
+        0,
+        0,
+        1,
+        0,
+        0.10908540338277817,
+        0.007917225360870361,
+        -0.19540420174598694,
+        1
+      ],
+      "name": "arm_L_goal1"
+    },
+    {
+      "matrix": [
+        1,
+        0,
+        0,
+        0,
+        0,
+        1,
+        0,
+        0,
+        0,
+        0,
+        1,
+        0,
+        -0.041061561554670334,
+        1.066356958290271e-8,
+        -0.19540409743785858,
+        1
+      ],
+      "name": "leg_L_goal1"
+    },
+    {
+      "matrix": [
+        1,
+        0,
+        0,
+        0,
+        0,
+        1,
+        0,
+        0,
+        0,
+        0,
+        1,
+        0,
+        -0.08989156037569046,
+        0.05766163021326065,
+        0.08743858337402344,
+        1
+      ],
+      "name": "leg_R_goal1"
+    },
+    {
+      "matrix": [
+        1,
+        0,
+        0,
+        0,
+        0,
+        1,
+        0,
+        0,
+        0,
+        0,
+        1,
+        0,
+        0.3882029056549072,
+        1.066356958290271e-8,
+        0.08743848651647568,
+        1
+      ],
+      "name": "arm_R_goal1"
+    },
+    {
+      "children": [
+        7,
+        9,
+        14,
+        19,
+        24
+      ],
+      "matrix": [
+        0.9999990460150695,
+        0.0009767210557009947,
+        -0.0009767215215899124,
+        0,
+        -0.0009418413642818668,
+        0.9993835218089064,
+        0.03509543092300879,
+        0,
+        0.001010397840414483,
+        -0.03509447752576613,
+        0.9993834883283782,
+        0,
+        0,
+        1.71110200881958,
+        0.03374987840652466,
+        1
+      ],
+      "name": "root"
+    },
+    {
+      "children": [
+        8
+      ],
+      "matrix": [
+        1.331580544223403e-7,
+        1.3877787807814457e-17,
+        0.9999999999999912,
+        0,
+        -0.9999999999999915,
+        6.125064648185297e-17,
+        1.3315805442190663e-7,
+        0,
+        -6.125064648185297e-17,
+        -1,
+        0,
+        0,
+        0,
+        0,
+        0,
+        1
+      ],
+      "name": "head_bone"
+    },
+    {
+      "matrix": [
+        0.9999999999999999,
+        -2.168404344971009e-19,
+        -1.3877787807814457e-17,
+        0,
+        -1.0842021724855044e-19,
+        1,
+        -8.809142651444724e-20,
+        0,
+        0,
+        1.3552527156068805e-19,
+        0.9999999999999999,
+        0,
+        0,
+        -2.168404344971009e-19,
+        -0.20000004768371582,
+        1
+      ],
+      "name": "head_bone_end_effector"
+    },
+    {
+      "children": [
+        10
+      ],
+      "matrix": [
+        0.7071067811865477,
+        1.3877787807814457e-17,
+        0.7071067811865476,
+        0,
+        -5.969888212248309e-18,
+        1,
+        0,
+        0,
+        -0.7071067811865477,
+        3.469446951953614e-18,
+        0.7071067811865477,
+        0,
+        0.08011647313833238,
+        -0.06073546037077904,
+        0.08011647313833237,
+        1
+      ],
+      "name": "arm_R_root"
+    },
+    {
+      "children": [
+        11
+      ],
+      "matrix": [
+        0.011257608597905144,
+        -0.11446542204609372,
+        0.9933634447695698,
+        0,
+        0.917545554229092,
+        0.3960659950484638,
+        0.03524036720436163,
+        0,
+        -0.3974712847025359,
+        0.9110594902211819,
+        0.10948599506254547,
+        0,
+        2.7755575615628914e-17,
+        -2.220446049250313e-16,
+        1.3877787807814457e-17,
+        1
+      ],
+      "name": "arm_R_bone1"
+    },
+    {
+      "children": [
+        12
+      ],
+      "matrix": [
+        1,
+        -1.904130303431728e-8,
+        8.301912282626667e-9,
+        0,
+        2.0545667389626487e-8,
+        0.9655471563735203,
+        -0.26022814763012936,
+        0,
+        -3.060804772830883e-9,
+        0.26022814763012964,
+        0.9655471563735208,
+        0,
+        4.5585011293480804e-8,
+        3.086052191569877e-9,
+        -0.5214316844940188,
+        1
+      ],
+      "name": "arm_R_bone2"
+    },
+    {
+      "children": [
+        13
+      ],
+      "matrix": [
+        0.9999999999999993,
+        -1.3284179367634152e-8,
+        -1.1297157027564952e-8,
+        0,
+        1.1143629130572208e-8,
+        0.9851037177266527,
+        -0.17196053879469503,
+        0,
+        1.3413226029879155e-8,
+        0.17196053879469492,
+        0.9851037177266524,
+        0,
+        3.3320748799026134e-8,
+        4.767711780928607e-10,
+        -0.3811449110507964,
+        1
+      ],
+      "name": "arm_R_bone3"
+    },
+    {
+      "matrix": [
+        1,
+        -1.6653345369377348e-16,
+        2.7755575615628914e-17,
+        0,
+        -1.1102230246251565e-16,
+        1,
+        0,
+        0,
+        2.7755575615628914e-17,
+        0,
+        0.9999999999999998,
+        0,
+        2.7755575615628914e-17,
+        0,
+        -0.8033324480056764,
+        1
+      ],
+      "name": "arm_R_bone3_end_effector"
+    },
+    {
+      "children": [
+        15
+      ],
+      "matrix": [
+        -0.7071067811865477,
+        6.938893903907228e-18,
+        0.7071067811865477,
+        0,
+        9.622294280808852e-19,
+        1,
+        6.938893903907228e-18,
+        0,
+        -0.7071067811865477,
+        0,
+        -0.7071067811865476,
+        0,
+        -0.08011647313833238,
+        -0.060735460370778815,
+        0.08011647313833237,
+        1
+      ],
+      "name": "leg_R_root"
+    },
+    {
+      "children": [
+        16
+      ],
+      "matrix": [
+        0.0003127647562193303,
+        -0.008904262574921468,
+        0.9999603073553489,
+        0,
+        0.8651231226820654,
+        0.5015420030419078,
+        0.004195448193306306,
+        0,
+        -0.5015594528857362,
+        0.865087471469046,
+        0.007860148106312193,
+        0,
+        -4.163336342344337e-17,
+        -2.220446049250313e-16,
+        0,
+        1
+      ],
+      "name": "leg_R_bone1"
+    },
+    {
+      "children": [
+        17
+      ],
+      "matrix": [
+        0.9999999999999997,
+        -2.576050761593507e-8,
+        -9.192067412788774e-9,
+        0,
+        1.94184820445642e-8,
+        0.9053448602044203,
+        -0.4246771527895501,
+        0,
+        1.926188996151379e-8,
+        0.42467715278955,
+        0.9053448602044204,
+        0,
+        4.5585011279603016e-8,
+        3.0860520805475744e-9,
+        -0.5214316844940187,
+        1
+      ],
+      "name": "leg_R_bone2"
+    },
+    {
+      "children": [
+        18
+      ],
+      "matrix": [
+        0.999999999999996,
+        -3.6092491495498535e-8,
+        -8.175138145952165e-8,
+        0,
+        -5.535752967134044e-10,
+        0.9122919994006253,
+        -0.4095400705804557,
+        0,
+        8.936245271540244e-8,
+        0.40954007058045433,
+        0.9122919994006214,
+        0,
+        3.3320748854537285e-8,
+        4.767711225817095e-10,
+        -0.3811449110507964,
+        1
+      ],
+      "name": "leg_R_bone3"
+    },
+    {
+      "matrix": [
+        1.0000000000000002,
+        -1.1102230246251565e-16,
+        -5.551115123125783e-17,
+        0,
+        -1.1102230246251565e-16,
+        1,
+        5.551115123125783e-17,
+        0,
+        -2.7755575615628914e-17,
+        -5.551115123125783e-17,
+        1,
+        0,
+        6.938893903907228e-18,
+        2.7755575615628914e-17,
+        -0.8033324480056764,
+        1
+      ],
+      "name": "leg_R_bone3_end_effector"
+    },
+    {
+      "children": [
+        20
+      ],
+      "matrix": [
+        -0.707106781186548,
+        -1.734723475976807e-17,
+        -0.7071067811865478,
+        0,
+        -5.969888212248309e-18,
+        1,
+        0,
+        0,
+        0.7071067811865479,
+        -1.0408340855860843e-17,
+        -0.707106781186548,
+        0,
+        -0.08011647313833238,
+        -0.06073546037077926,
+        -0.08011647313833237,
+        1
+      ],
+      "name": "leg_L_root"
+    },
+    {
+      "children": [
+        21
+      ],
+      "matrix": [
+        -0.001971520764457557,
+        0.053848580223104306,
+        0.9985471663941728,
+        0,
+        0.9406502904524712,
+        0.3389798200984154,
+        -0.016422930241824563,
+        0,
+        -0.3393716903007043,
+        0.9392513039511856,
+        -0.05132098838137236,
+        0,
+        0,
+        -2.220446049250313e-16,
+        0,
+        1
+      ],
+      "name": "leg_L_bone1"
+    },
+    {
+      "children": [
+        22
+      ],
+      "matrix": [
+        1.0000000000000004,
+        -2.0380670373576493e-8,
+        4.078802823270777e-9,
+        0,
+        2.0657071497787172e-8,
+        0.9528065559440093,
+        -0.3035781068359762,
+        0,
+        2.300815246658594e-9,
+        0.30357810683597636,
+        0.9528065559440095,
+        0,
+        4.5585011293480804e-8,
+        3.0860520805475744e-9,
+        -0.5214316844940187,
+        1
+      ],
+      "name": "leg_L_bone2"
+    },
+    {
+      "children": [
+        23
+      ],
+      "matrix": [
+        0.9999999999999993,
+        -1.7624569248475552e-8,
+        -2.980817417896908e-8,
+        0,
+        1.0295256203107783e-8,
+        0.973184055245276,
+        -0.23002729446262357,
+        0,
+        3.306297183952589e-8,
+        0.23002729446262302,
+        0.9731840552452757,
+        0,
+        3.33207488406595e-8,
+        4.767710670705583e-10,
+        -0.3811449110507963,
+        1
+      ],
+      "name": "leg_L_bone3"
+    },
+    {
+      "matrix": [
+        1.0000000000000002,
+        5.551115123125783e-17,
+        5.551115123125783e-17,
+        0,
+        5.551115123125783e-17,
+        1.0000000000000002,
+        -4.163336342344337e-17,
+        0,
+        -1.3877787807814457e-17,
+        -6.938893903907228e-18,
+        1.0000000000000004,
+        0,
+        5.551115123125783e-17,
+        0,
+        -0.8033324480056764,
+        1
+      ],
+      "name": "leg_L_bone3_end_effector"
+    },
+    {
+      "children": [
+        25
+      ],
+      "matrix": [
+        0.7071067811865478,
+        -6.938893903907228e-18,
+        -0.7071067811865476,
+        0,
+        9.622294280808852e-19,
+        1,
+        6.938893903907228e-18,
+        0,
+        0.7071067811865476,
+        0,
+        0.7071067811865477,
+        0,
+        0.0801164731383324,
+        -0.06073546037077904,
+        -0.08011647313833237,
+        1
+      ],
+      "name": "arm_L_root"
+    },
+    {
+      "children": [
+        26
+      ],
+      "matrix": [
+        0.0013311372808006983,
+        -0.02628076331575018,
+        0.999653714819827,
+        0,
+        0.9212641414912593,
+        0.3888334608959349,
+        0.008995626162872972,
+        0,
+        -0.388935225552935,
+        0.9209331469586867,
+        0.024729115569640914,
+        0,
+        -2.7755575615628914e-17,
+        -2.220446049250313e-16,
+        -1.3877787807814457e-17,
+        1
+      ],
+      "name": "arm_L_bone1"
+    },
+    {
+      "children": [
+        27
+      ],
+      "matrix": [
+        1,
+        -2.099252122134132e-8,
+        2.1939252359270967e-9,
+        0,
+        2.0585442239706708e-8,
+        0.9470524752700538,
+        -0.3210788206731546,
+        0,
+        4.66249158725951e-9,
+        0.3210788206731545,
+        0.9470524752700542,
+        0,
+        4.5585011314297486e-8,
+        3.0860520805475744e-9,
+        -0.5214316844940183,
+        1
+      ],
+      "name": "arm_L_bone2"
+    },
+    {
+      "children": [
+        28
+      ],
+      "matrix": [
+        0.9999999999999993,
+        -1.9477644552789997e-8,
+        -3.7389235998941395e-8,
+        0,
+        9.294988445596175e-9,
+        0.9669130921291693,
+        -0.2551055347314979,
+        0,
+        4.112099672576308e-8,
+        0.2551055347314972,
+        0.9669130921291688,
+        0,
+        3.3320748854537285e-8,
+        4.767711780928607e-10,
+        -0.3811449110507964,
+        1
+      ],
+      "name": "arm_L_bone3"
+    },
+    {
+      "matrix": [
+        1,
+        0,
+        0,
+        0,
+        1.6653345369377348e-16,
+        1,
+        0,
+        0,
+        0,
+        0,
+        1.0000000000000002,
+        0,
+        0,
+        -2.7755575615628914e-17,
+        -0.8033324480056765,
+        1
+      ],
+      "name": "arm_L_bone3_end_effector"
+    }
+  ],
+  "scene": 0,
+  "scenes": [
+    {
+      "nodes": [
+        0,
+        1
+      ]
+    }
+  ],
+  "skins": [
+    {
+      "inverseBindMatrices": 10,
+      "joints": [
+        7,
+        10,
+        11,
+        12,
+        15,
+        16,
+        17,
+        20,
+        21,
+        22,
+        25,
+        26,
+        27
+      ]
+    }
+  ],
+  "extras": {
+    "qtekModelViewerConfig": {
+      "preZ": true,
+      "materials": [
+        {
+          "name": "Wire",
+          "color": "#e60000",
+          "emission": "#e60000",
+          "alpha": 1,
+          "alphaCutoff": 0,
+          "emissionIntensity": 1,
+          "uvRepeat": {
+            "0": 1,
+            "1": 1
+          },
+          "parallaxOcclusionScale": 0.02,
+          "diffuseMap": "",
+          "normalMap": "",
+          "parallaxOcclusionMap": "",
+          "emissiveMap": "",
+          "specularColor": "#1a1a1a",
+          "glossiness": 0.5,
+          "specularMap": "",
+          "glossinessMap": "",
+          "type": "pbrSpecularGlossiness",
+          "targetMeshes": [
+            "Mesh_Material0"
+          ]
+        },
+        {
+          "name": "Base",
+          "color": "#000000",
+          "emission": "#000000",
+          "alpha": 1,
+          "alphaCutoff": 0,
+          "emissionIntensity": 1,
+          "uvRepeat": {
+            "0": 1,
+            "1": 1
+          },
+          "parallaxOcclusionScale": 0.02,
+          "diffuseMap": "",
+          "normalMap": "",
+          "parallaxOcclusionMap": "",
+          "emissiveMap": "",
+          "specularColor": "#1a1a1a",
+          "glossiness": 0.5,
+          "specularMap": "",
+          "glossinessMap": "",
+          "type": "pbrSpecularGlossiness",
+          "targetMeshes": [
+            "Mesh_Material0$1"
+          ]
+        }
+      ],
+      "takes": [],
+      "textureFlipY": false,
+      "zUpToYUp": false,
+      "shadow": true,
+      "environment": "auto",
+      "viewControl": {
+        "center": [
+          1.0690909624099731,
+          -1.1321967840194702,
+          0.9232351183891296
+        ],
+        "alpha": 10.229259828771838,
+        "beta": 33.79452028477864,
+        "distance": 19.502084087073698
+      },
+      "ground": {
+        "show": true
+      },
+      "mainLight": {
+        "shadow": true,
+        "shadowQuality": "medium",
+        "intensity": 0.8,
+        "color": "#fff",
+        "alpha": 45,
+        "beta": 45,
+        "$padAngle": [
+          0.25,
+          0.5
+        ]
+      },
+      "secondaryLight": {
+        "shadow": false,
+        "shadowQuality": "medium",
+        "intensity": 0,
+        "color": "#fff",
+        "alpha": 60,
+        "beta": -50,
+        "$padAngle": [
+          -0.2777777777777778,
+          0.6666666666666666
+        ]
+      },
+      "tertiaryLight": {
+        "shadow": false,
+        "shadowQuality": "medium",
+        "intensity": 0,
+        "color": "#fff",
+        "alpha": 89,
+        "beta": 0,
+        "$padAngle": [
+          0,
+          0.9888888888888889
+        ]
+      },
+      "ambientLight": {
+        "intensity": 0,
+        "color": "#fff"
+      },
+      "ambientCubemapLight": {
+        "texture": "./asset/texture/Barce_Rooftop_C.hdr",
+        "$texture": "Barce_Rooftop_C",
+        "$textureOptions": [
+          "pisa",
+          "Barce_Rooftop_C",
+          "Factory_Catwalk",
+          "Grand_Canyon_C",
+          "Ice_Lake",
+          "Hall",
+          "Old_Industrial_Hall"
+        ],
+        "exposure": 3,
+        "diffuseIntensity": 0.2,
+        "specularIntensity": 0.2,
+        "$intensity": 0.2
+      },
+      "postEffect": {
+        "enable": true,
+        "bloom": {
+          "enable": true,
+          "intensity": 0.1
+        },
+        "depthOfField": {
+          "enable": false,
+          "focalDistance": 4,
+          "focalRange": 1,
+          "blurRadius": 5,
+          "fstop": 10,
+          "quality": "medium",
+          "$qualityOptions": [
+            "low",
+            "medium",
+            "high",
+            "ultra"
+          ]
+        },
+        "screenSpaceAmbientOcclusion": {
+          "enable": false,
+          "radius": 1.5,
+          "quality": "medium",
+          "intensity": 1,
+          "$qualityOptions": [
+            "low",
+            "medium",
+            "high",
+            "ultra"
+          ]
+        },
+        "screenSpaceReflection": {
+          "enable": false,
+          "quality": "medium",
+          "maxRoughness": 0.8,
+          "$qualityOptions": [
+            "low",
+            "medium",
+            "high",
+            "ultra"
+          ]
+        },
+        "colorCorrection": {
+          "enable": true,
+          "exposure": 0,
+          "brightness": 0,
+          "contrast": 1,
+          "saturation": 1,
+          "lookupTexture": ""
+        },
+        "FXAA": {
+          "enable": false
+        }
+      }
+    }
+  },
+  "extensionsUsed": [
+    "KHR_materials_pbrSpecularGlossiness"
+  ]
+}

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

@@ -0,0 +1,78 @@
+Namespace myapp
+
+#Import "<std>"
+#Import "<mojo>"
+#Import "<mojo3d>"
+
+#Import "assets/bouncyball/"
+
+Using std..
+Using mojo..
+Using mojo3d..
+
+Class MyWindow Extends Window
+	
+	Field _scene:Scene
+	
+	Field _camera:Camera
+	
+	Field _light:Light
+	
+	Field _model: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,-5 )
+		
+		New FlyBehaviour( _camera )
+		
+		'create light
+		'
+		_light=New Light
+		_light.RotateX( 90 )
+		
+		'create donut - metallic silver...
+		
+		Local material:=New PbrMaterial( Color.Silver,1,0.5 )
+		
+		_model=Model.LoadBoned( "asset::bouncyball.gltf" )
+		
+		_model.Animator.Animate( 0 )
+		
+		_model.Move( 0,10,0 )
+		
+'		_model.Mesh.FitVertices( New Boxf( -1,1 ),False )
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		RequestRender()
+		
+		_scene.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

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

@@ -0,0 +1,78 @@
+Namespace myapp
+
+#Import "<std>"
+#Import "<mojo>"
+#Import "<mojo3d>"
+
+#Import "assets/walker/"
+
+Using std..
+Using mojo..
+Using mojo3d..
+
+Class MyWindow Extends Window
+	
+	Field _scene:Scene
+	
+	Field _camera:Camera
+	
+	Field _light:Light
+	
+	Field _model: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,-5 )
+		
+		New FlyBehaviour( _camera )
+		
+		'create light
+		'
+		_light=New Light
+		_light.RotateX( 90 )
+		
+		'create donut - metallic silver...
+		
+		Local material:=New PbrMaterial( Color.Silver,1,0.5 )
+		
+		_model=Model.LoadBoned( "asset::walk.gltf" )
+		
+		_model.Animator.Animate( 0 )
+		
+		_model.Move( 0,10,0 )
+		
+'		_model.Mesh.FitVertices( New Boxf( -1,1 ),False )
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		RequestRender()
+		
+		_scene.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