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()
 		
 		Return _time
+		
+	Setter( time:Float )
+			
+		_time=time
 	End
 	
 	Property Value:T()
 		
 		Return _value
+	
+	Setter( value:T )
+		
+		_value=value
 	End
 
 	Private

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

@@ -92,6 +92,11 @@ Class Animator Extends Component
 		Return Null
 	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 )
 		
 		Animate( FindAnimation( name ),transition,finished )
@@ -209,6 +214,32 @@ Class Animator Extends Component
 			Local chan0:=playing0 ? playing0.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
 				
 				Local pos0:=chan0 ? chan0.GetPosition( time0 ) Else New Vec3f
@@ -244,6 +275,8 @@ Class Animator Extends Component
 				_skeleton[i].LocalScale=scl1
 			
 			Endif
+			
+			#end
 		
 		Next
 	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()
 	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.
 	#end
@@ -303,6 +284,35 @@ Class Mesh Extends Resource
 		Return first
 	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.
 	#end
 	Method TransformVertices( matrix:AffineMat4f )

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

@@ -1,6 +1,14 @@
 
 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
 #end
 Class Gltf2Buffer
@@ -75,6 +83,8 @@ Class Gltf2Primitive
 	Field NORMAL:Gltf2Accessor
 	Field TANGENT:Gltf2Accessor
 	Field TEXCOORD_0:Gltf2Accessor
+	Field JOINTS_0:Gltf2Accessor
+	Field WEIGHTS_0:Gltf2Accessor
 	Field indices:Gltf2Accessor
 	Field material:Gltf2Material
 	Field mode:Int
@@ -101,6 +111,39 @@ Class Gltf2Node
 	Field hasMatrix:Bool
 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
 #end
 Class Gltf2Scene
@@ -121,7 +164,10 @@ Class Gltf2Asset
 	Field materials:Gltf2Material[]
 	Field meshes:Gltf2Mesh[]
 	Field nodes:Gltf2Node[]
+	Field animations:Gltf2Animation[]
+	Field skins:Gltf2Skin[]
 	Field scenes:Gltf2Scene[]
+	Field scene:Gltf2Scene
 	
 	Function Load:Gltf2Asset( path:String )
 		
@@ -156,10 +202,10 @@ Class Gltf2Asset
 	
 	Method GetMat4f:Mat4f( jval:JsonArray )
 		Return New Mat4f(
-			New Vec4f( jval.GetNumber(0),jval.GetNumber(1),jval.GetNumber(2),jval.GetNumber(3) ),
-			New Vec4f( jval.GetNumber(4),jval.GetNumber(5),jval.GetNumber(6),jval.GetNumber(7) ),
-			New Vec4f( jval.GetNumber(8),jval.GetNumber(9),jval.GetNumber(10),jval.GetNumber(11) ),
-			New Vec4f( jval.GetNumber(12),jval.GetNumber(13),jval.GetNumber(14),jval.GetNumber(15) ) )
+			New Vec4f( jval.GetNumber(0),  jval.GetNumber(1),  jval.GetNumber(2),  jval.GetNumber(3) ),
+			New Vec4f( jval.GetNumber(4),  jval.GetNumber(5),  jval.GetNumber(6),  jval.GetNumber(7) ),
+			New Vec4f( jval.GetNumber(8),  jval.GetNumber(9),  jval.GetNumber(10), jval.GetNumber(11) ),
+			New Vec4f( jval.GetNumber(12), jval.GetNumber(13), jval.GetNumber(14), jval.GetNumber(15) ) )
 	End
 	
 	Method LoadBuffers:Bool()
@@ -395,6 +441,12 @@ Class Gltf2Asset
 				If jattribs.Contains( "TEXCOORD_0" )
 					prim.TEXCOORD_0=accessors[jattribs.GetNumber( "TEXCOORD_0" )]
 				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" )
 					prim.indices=accessors[jprim.GetNumber( "indices" )]
 				Endif
@@ -413,7 +465,102 @@ Class Gltf2Asset
 		
 		Return True
 	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()
 
 		Local jnodes:=root.GetArray( "nodes" )
@@ -448,7 +595,6 @@ Class Gltf2Asset
 			
 			If jnode.Contains( "translation" )
 				node.translation=GetVec3f( jnode.GetArray( "translation" ) )
-				node.translation.z=-node.translation.z
 			Endif
 
 			If jnode.Contains( "rotation" )
@@ -461,7 +607,6 @@ Class Gltf2Asset
 				
 			If jnode.Contains( "matrix" )
 				node.matrix=GetMat4f( jnode.GetArray( "matrix" ) )
-				node.matrix.t.z=-node.matrix.t.z
 			Else
 				node.matrix=Mat4f.Translation( node.translation ) * Mat4f.Rotation( node.rotation ) * Mat4f.Scaling( node.scale )
 			Endif
@@ -500,6 +645,8 @@ Class Gltf2Asset
 		
 		Next
 		
+		scene=scenes[root.GetNumber( "scene" )]
+		
 		Return True
 	End
 	
@@ -520,6 +667,8 @@ Class Gltf2Asset
 		If Not LoadMaterials() Return False
 		If Not LoadMeshes() Return False
 		If Not LoadNodes() Return False
+		If Not LoadAnimations() Return False
+		If Not LoadSkins() Return False
 		If Not LoadScenes() Return False
 		
 		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/scene"
 
-#Import "geometry/loader"
-#Import "geometry/gltf2"
-#Import "geometry/gltf2loader"
+#Import "loader/loader"
+#Import "loader/gltf2"
+#Import "loader/gltf2loader"
+
 #Import "geometry/mesh"
 #Import "geometry/meshprims"
-#Import "geometry/util3d"
+'#Import "geometry/util3d"
 
 Using std..
 Using mojo..

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

@@ -180,6 +180,7 @@ Class Entity Extends DynamicObject
 	Setter( matrix:AffineMat4f )
 		
 		Local scale:=matrix.m.GetScaling()
+		
 		Basis=matrix.m.Scale( 1/scale.x,1/scale.y,1/scale.z )
 		Position=matrix.t
 		Scale=scale
@@ -256,6 +257,16 @@ Class Entity Extends DynamicObject
 		Endif
 		
 		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
 
 	#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