Browse Source

implemented gltf animation

Daniel Salvadori 7 years ago
parent
commit
8ff00c4108
6 changed files with 384 additions and 165 deletions
  1. 14 13
      animation/animation.go
  2. 37 6
      animation/channel.go
  3. 1 1
      core/node.go
  4. 8 4
      loader/gltf/gltf.go
  5. 274 135
      loader/gltf/loader.go
  6. 50 6
      loader/gltf/material_pbr.go

+ 14 - 13
animation/animation.go

@@ -42,6 +42,8 @@ func (anim *Animation) Name() string {
 func (anim *Animation) Reset() {
 
 	anim.time = anim.start
+	anim.paused = false
+	// TODO reset channels?
 }
 
 // SetPaused sets whether the animation is paused.
@@ -77,25 +79,26 @@ func (anim *Animation) SetStart(v float32) {
 // Update interpolates and updates the target values for each channel.
 // If the animation is paused, returns false. If the animation is not paused,
 // returns true if the input value is inside the key frames ranges or false otherwise.
-func (anim *Animation) Update(delta float32) bool {
+func (anim *Animation) Update(delta float32) {
 
 	// Check if paused
 	if anim.paused {
-		return false
+		return
 	}
 
 	// Check if input is less than minimum
 	anim.time = anim.time + delta
 	if anim.time < anim.minTime {
-		return false
+		return
 	}
 
 	// Check if input is greater than maximum
 	if anim.time > anim.maxTime {
 		if anim.loop {
-			anim.Reset()
+			anim.time = anim.time - anim.maxTime
 		} else {
-			return false
+			anim.time = anim.maxTime - 0.000001
+			anim.SetPaused(true)
 		}
 	}
 
@@ -104,8 +107,6 @@ func (anim *Animation) Update(delta float32) bool {
 		ch := anim.channels[i]
 		ch.Update(anim.time)
 	}
-
-	return true
 }
 
 // AddChannel adds a channel to the animation.
@@ -118,12 +119,12 @@ func (anim *Animation) AddChannel(ch IChannel) {
 
 	// Update maxTime and minTime values
 	kf := ch.Keyframes()
-	firstKf := kf[0]
-	if anim.minTime > firstKf {
-		anim.minTime = firstKf
+	firstTime := kf[0]
+	if anim.minTime > firstTime {
+		anim.minTime = firstTime
 	}
-	lastKf := kf[len(kf)-1]
-	if anim.maxTime < lastKf {
-		anim.maxTime = lastKf
+	lastTime := kf[len(kf)-1]
+	if anim.maxTime < lastTime {
+		anim.maxTime = lastTime
 	}
 }

+ 37 - 6
animation/channel.go

@@ -103,6 +103,7 @@ func (c *Channel) Update(time float32) {
 // IChannel is the interface for all channel types.
 type IChannel interface {
 	Update(time float32)
+	SetBuffers(keyframes, values math32.ArrayF32)
 	Keyframes() math32.ArrayF32
 	Values() math32.ArrayF32
 	SetInterpolationType(it InterpolationType)
@@ -140,6 +141,14 @@ func NewPositionChannel(node core.INode) *PositionChannel {
 				v1.Lerp(&v2, k)
 				node.SetPositionVec(&v1)
 			}
+		case CUBICSPLINE: // TODO
+			pc.interpAction = func(idx int, k float32) {
+				var v1, v2 math32.Vector3
+				pc.values.GetVector3(idx*3, &v1)
+				pc.values.GetVector3((idx+1)*3, &v2)
+				v1.Lerp(&v2, k)
+				node.SetPositionVec(&v1)
+			}
 		}
 	}
 	pc.SetInterpolationType(LINEAR)
@@ -169,9 +178,23 @@ func NewRotationChannel(node core.INode) *RotationChannel {
 				var q1, q2 math32.Vector4
 				rc.values.GetVector4(idx*4, &q1)
 				rc.values.GetVector4((idx+1)*4, &q2)
+				q1.Lerp(&q2, k)
 				quat1 := math32.NewQuaternion(q1.X, q1.Y, q1.Z, q1.W)
-				quat2 := math32.NewQuaternion(q2.X, q2.Y, q2.Z, q2.W)
-				quat1.Slerp(quat2, k)
+				// TODO spherical linear interpolation (slerp) not working as expected for some reason (doing a lerp for now)
+				//quat2 := math32.NewQuaternion(q2.X, q2.Y, q2.Z, q2.W)
+				//quat1.Slerp(quat2, k)
+				node.SetQuaternionQuat(quat1)
+			}
+		case CUBICSPLINE: // TODO
+			rc.interpAction = func(idx int, k float32) {
+				var q1, q2 math32.Vector4
+				rc.values.GetVector4(idx*4, &q1)
+				rc.values.GetVector4((idx+1)*4, &q2)
+				q1.Lerp(&q2, k)
+				quat1 := math32.NewQuaternion(q1.X, q1.Y, q1.Z, q1.W)
+				// TODO spherical linear interpolation (slerp) not working for some reason
+				//quat2 := math32.NewQuaternion(q2.X, q2.Y, q2.Z, q2.W)
+				//quat1.Slerp(quat2, k)
 				node.SetQuaternionQuat(quat1)
 			}
 		}
@@ -206,6 +229,14 @@ func NewScaleChannel(node core.INode) *ScaleChannel {
 				v1.Lerp(&v2, k)
 				node.SetScaleVec(&v1)
 			}
+		case CUBICSPLINE: // TODO
+			sc.interpAction = func(idx int, k float32) {
+				var v1, v2 math32.Vector3
+				sc.values.GetVector3(idx*3, &v1)
+				sc.values.GetVector3((idx+1)*3, &v2)
+				v1.Lerp(&v2, k)
+				node.SetScaleVec(&v1)
+			}
 		}
 	}
 	sc.SetInterpolationType(LINEAR)
@@ -213,11 +244,11 @@ func NewScaleChannel(node core.INode) *ScaleChannel {
 }
 
 // InterpolationType specifies the interpolation type.
-type InterpolationType int
+type InterpolationType string
 
 // The various interpolation types.
 const (
-	STEP        = InterpolationType(iota) // The animated values remain constant to the output of the first keyframe, until the next keyframe.
-	LINEAR                                // The animated values are linearly interpolated between keyframes. Spherical linear interpolation (slerp) is used to interpolate quaternions.
-	CUBICSPLINE                           // TODO
+	STEP        = InterpolationType("STEP")          // The animated values remain constant to the output of the first keyframe, until the next keyframe.
+	LINEAR      = InterpolationType("LINEAR")        // The animated values are linearly interpolated between keyframes. Spherical linear interpolation (slerp) is used to interpolate quaternions.
+	CUBICSPLINE = InterpolationType("CUBICSPLINE")   // TODO
 )

+ 1 - 1
core/node.go

@@ -573,7 +573,7 @@ func (n *Node) Direction() math32.Vector3 {
 func (n *Node) SetMatrix(m *math32.Matrix4) {
 
 	n.matrix = *m
-	n.changed = true
+	n.matrix.Decompose(&n.position, &n.quaternion, &n.scale)
 }
 
 // Matrix returns a copy of the local transformation matrix.

+ 8 - 4
loader/gltf/gltf.go

@@ -5,6 +5,8 @@
 // Package gltf
 package gltf
 
+import "github.com/g3n/engine/core"
+
 // GLTF is the root object for a glTF asset.
 type GLTF struct {
 	ExtensionsUsed     []string               // Names of glTF extensions used somewhere in this asset. Not required.
@@ -143,16 +145,16 @@ type Indices struct {
 // Material describes the material appearance of a primitive.
 type Material struct {
 	Name                 string                 // The user-defined name of this object. Not required.
-	Extensions           map[string]interface{} // Dictionary object with extension-specific objects. Not required.
-	Extras               interface{}            // Application-specific data. Not required.
 	PbrMetallicRoughness *PbrMetallicRoughness  // A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology. When not specified, all the default values of pbrMetallicRoughness apply. Not required.
 	NormalTexture        *NormalTextureInfo     // The normal map texture. Not required.
 	OcclusionTexture     *OcclusionTextureInfo  // The occlusion map texture. Not required.
 	EmissiveTexture      *TextureInfo           // The emissive map texture. Not required.
-	EmissiveFactor       [3]float32             // The emissive color of the material. Not required. Default is [0,0,0]
+	EmissiveFactor       *[3]float32            // The emissive color of the material. Not required. Default is [0,0,0]
 	AlphaMode            string                 // The alpha rendering mode of the material. Not required. Default is OPAQUE.
 	AlphaCutoff          float32                // The alpha cutoff value of the material. Not required. Default is 0.5.
 	DoubleSided          bool                   // Specifies whether the material is double sided. Not required. Default is false.
+	Extensions           map[string]interface{} // Dictionary object with extension-specific objects. Not required.
+	Extras               interface{}            // Application-specific data. Not required.
 }
 
 // Mesh is a set of primitives to be rendered.
@@ -184,6 +186,8 @@ type Node struct {
 	Name        string                 // The user-defined name of this object. Not required.
 	Extensions  map[string]interface{} // Dictionary object with extension-specific objects. Not required.
 	Extras      interface{}            // Application-specific data. Not required.
+
+	node        core.INode             // Cached node
 }
 
 // TODO Why not combine NormalTextureInfo and OcclusionTextureInfo ? Or simply add Scale to TextureInfo and use only TextureInfo?
@@ -300,7 +304,7 @@ type Target struct {
 
 // Texture represents a texture and its sampler.
 type Texture struct {
-	Sampler    int                    // The index of the sampler used by this texture. When undefined, a sampler with REPEAT wrapping and AUTO filtering should be used. Not required.
+	Sampler    *int                   // The index of the sampler used by this texture. When undefined, a sampler with REPEAT wrapping and AUTO filtering should be used. Not required.
 	Source     int                    // The index of the image used by this texture. Not required.
 	Name       string                 // The user-defined name of this object. Not required.
 	Extensions map[string]interface{} // Dictionary object with extension-specific objects. Not required. Not required.

+ 274 - 135
loader/gltf/loader.go

@@ -27,6 +27,7 @@ import (
 	"github.com/g3n/engine/material"
 	"github.com/g3n/engine/math32"
 	"github.com/g3n/engine/texture"
+	"github.com/g3n/engine/animation"
 )
 
 // ParseJSON parses the glTF data from the specified JSON file
@@ -78,14 +79,14 @@ func ParseBin(filename string) (*GLTF, error) {
 // and returns a pointer to the parsed structure
 func ParseBinReader(r io.Reader, path string) (*GLTF, error) {
 
-	// Reads header
+	// Read header
 	var header GLBHeader
 	err := binary.Read(r, binary.LittleEndian, &header)
 	if err != nil {
 		return nil, err
 	}
 
-	// Checks magic and version
+	// Check magic and version
 	if header.Magic != GLBMagic {
 		return nil, fmt.Errorf("Invalid GLB Magic field")
 	}
@@ -149,6 +150,8 @@ func readChunk(r io.Reader, chunkType uint32) ([]byte, error) {
 // the specified scene index from the GLTF Scenes array
 func (g *GLTF) NewScene(si int) (core.INode, error) {
 
+	log.Debug("Creating Scene %d", si)
+
 	// Check if provided scene index is valid
 	if si < 0 || si >= len(g.Scenes) {
 		return nil, fmt.Errorf("invalid scene index")
@@ -157,8 +160,8 @@ func (g *GLTF) NewScene(si int) (core.INode, error) {
 
 	scene := core.NewNode()
 	scene.SetName(s.Name)
-	for i := 0; i < len(s.Nodes); i++ {
-		child, err := g.NewNode(i)
+	for _, ni := range s.Nodes {
+		child, err := g.NewNode(ni)
 		if err != nil {
 			return nil, err
 		}
@@ -171,69 +174,121 @@ func (g *GLTF) NewScene(si int) (core.INode, error) {
 // in the decoded GLTF Nodes array.
 func (g *GLTF) NewNode(i int) (core.INode, error) {
 
+	log.Debug("Creating Node %d", i)
+
 	var in core.INode
 	var err error
-	node := g.Nodes[i]
+	nodeData := g.Nodes[i]
 
 	// Check if the node is a Mesh (triangles, lines, etc...)
-	if node.Mesh != nil {
-		in, err = g.loadMesh(*node.Mesh)
+	if nodeData.Mesh != nil {
+		in, err = g.loadMesh(*nodeData.Mesh)
 		if err != nil {
 			return nil, err
 		}
 		// Check if the node is Camera
-	} else if node.Camera != nil {
-		in, err = g.loadCamera(*node.Camera)
+	} else if nodeData.Camera != nil {
+		in, err = g.loadCamera(*nodeData.Camera)
 		if err != nil {
 			return nil, err
 		}
 		// Other cases, return empty node
 	} else {
+		log.Debug("Empty Node")
 		in = core.NewNode()
 	}
 
+	// Cache inode in nodeData
+	g.Nodes[i].node = in
+
 	// Get *core.Node from core.INode
-	n := in.GetNode()
-	n.SetName(node.Name)
+	node := in.GetNode()
+	node.SetName(nodeData.Name)
 
 	// If defined, set node local transformation matrix
-	if node.Matrix != nil {
-		n.SetMatrix((*math32.Matrix4)(node.Matrix))
+	if nodeData.Matrix != nil {
+		node.SetMatrix((*math32.Matrix4)(nodeData.Matrix))
 		// Otherwise, check rotation, scale and translation fields
 	} else {
 		// Rotation quaternion
-		if node.Rotation != nil {
-			log.Error("Rotation:%v", node.Translation)
-			n.SetQuaternion(node.Rotation[0], node.Rotation[1], node.Rotation[2], node.Rotation[3])
+		if nodeData.Rotation != nil {
+			node.SetQuaternion(nodeData.Rotation[0], nodeData.Rotation[1], nodeData.Rotation[2], nodeData.Rotation[3])
 		}
 		// Scale
-		if node.Scale != nil {
-			log.Error("Scale:%v", node.Translation)
-			n.SetScale(node.Scale[0], node.Scale[1], node.Scale[2])
+		if nodeData.Scale != nil {
+			node.SetScale(nodeData.Scale[0], nodeData.Scale[1], nodeData.Scale[2])
 		}
 		// Translation
-		if node.Translation != nil {
-			log.Error("Translation:%v", node.Translation)
-			n.SetPosition(node.Translation[0], node.Translation[1], node.Translation[2])
+		if nodeData.Translation != nil {
+			node.SetPosition(nodeData.Translation[0], nodeData.Translation[1], nodeData.Translation[2])
 		}
 	}
 
-	// Recursively load node children  and add them to the parent
-	for _, ci := range node.Children {
+	// Recursively load node children and add them to the parent
+	for _, ci := range nodeData.Children {
 		child, err := g.NewNode(ci)
 		if err != nil {
 			return nil, err
 		}
-		n.Add(child)
+		node.Add(child)
 	}
 
 	return in, nil
 }
 
+// NewAnimation creates a parent Node which contains all nodes contained by
+// the specified scene index from the GLTF Scenes array
+func (g *GLTF) NewAnimation(i int) (*animation.Animation, error) {
+
+	log.Debug("Creating Animation %d", i)
+
+	// Check if provided scene index is valid
+	if i < 0 || i >= len(g.Animations) {
+		return nil, fmt.Errorf("invalid animation index")
+	}
+	a := g.Animations[i]
+
+	anim := animation.NewAnimation()
+	anim.SetName(a.Name)
+	for i := 0; i < len(a.Channels); i++ {
+
+		chData := a.Channels[i]
+		target := chData.Target
+		sampler := a.Samplers[chData.Sampler]
+		node := g.Nodes[target.Node].node
+		// TODO Instantiate node if not exists ?
+
+		var ch animation.IChannel
+		if target.Path == "translation" {
+			ch = animation.NewPositionChannel(node)
+		} else if target.Path == "rotation" {
+			ch = animation.NewRotationChannel(node)
+		} else if target.Path == "scale" {
+			ch = animation.NewScaleChannel(node)
+		}
+
+		keyframes, err := g.loadAccessorF32(sampler.Input, []string{}, []int{})
+		if err != nil {
+			return nil, err
+		}
+		values, err := g.loadAccessorF32(sampler.Output, []string{}, []int{})
+		if err != nil {
+			return nil, err
+		}
+		ch.SetBuffers(keyframes, values)
+		ch.SetInterpolationType(animation.InterpolationType(sampler.Interpolation))
+		//ch.SetInterpolationType(animation.STEP)//animation.InterpolationType(sampler.Interpolation))
+		anim.AddChannel(ch)
+	}
+	return anim, nil
+}
+
 // loadCamera creates and returns a Camera Node
 // from the specified GLTF.Cameras index
 func (g *GLTF) loadCamera(ci int) (core.INode, error) {
 
+	log.Debug("Loading Camera %d", ci)
+
 	camDesc := g.Cameras[ci]
 	if camDesc.Type == "perspective" {
 		desc := camDesc.Perspective
@@ -264,6 +319,8 @@ func (g *GLTF) loadCamera(ci int) (core.INode, error) {
 // from the specified GLTF Mesh index
 func (g *GLTF) loadMesh(mi int) (core.INode, error) {
 
+	log.Debug("Loading Mesh %d", mi)
+
 	var err error
 	m := g.Meshes[mi]
 
@@ -313,38 +370,51 @@ func (g *GLTF) loadMesh(mi int) (core.INode, error) {
 			//	}
 			//}
 			if name == "POSITION" {
-				ppos, err := g.loadVec3(aci)
+				ppos, err := g.loadPositions(aci)
 				if err != nil {
 					return nil, err
 				}
-				vbo := gls.NewVBO()
-				vbo.AddAttrib("VertexPosition", 3)
-				vbo.SetBuffer(ppos)
+				vbo := gls.NewVBO().AddAttrib("VertexPosition", 3).SetBuffer(ppos)
 				geom.AddVBO(vbo)
 				continue
 			}
 			if name == "NORMAL" {
-				pnorms, err := g.loadVec3(aci)
+				pnorms, err := g.loadNormals(aci)
 				if err != nil {
 					return nil, err
 				}
-				vbo := gls.NewVBO()
-				vbo.AddAttrib("VertexNormal", 3)
-				vbo.SetBuffer(pnorms)
+				vbo := gls.NewVBO().AddAttrib("VertexNormal", 3).SetBuffer(pnorms)
 				geom.AddVBO(vbo)
 				continue
 			}
-			if name == "TEXCOORD_0" {
-				puvs, err := g.loadVec2(aci)
+			if name == "TANGENT" {
+				// TODO
+				log.Error("TANGENT attribute not supported yet.")
+				continue
+			}
+
+			attrib := strings.Split(name, "_")
+			semantic := attrib[0]
+			//set := attrib[1] TODO
+
+			if semantic == "TEXCOORD" {
+				puvs, err := g.loadTexcoords(aci)
 				if err != nil {
 					return nil, err
 				}
-				vbo := gls.NewVBO()
-				vbo.AddAttrib("VertexTexcoord", 2)
-				vbo.SetBuffer(puvs)
+				vbo := gls.NewVBO().AddAttrib("VertexTexcoord", 2).SetBuffer(puvs)
 				geom.AddVBO(vbo)
 				continue
 			}
+			if semantic == "COLOR" {
+				// TODO
+			}
+			if semantic == "JOINTS" {
+				// TODO
+			}
+			if semantic == "WEIGHTS" {
+				// TODO
+			}
 		}
 
 		// Creates Geometry and add attribute VBO
@@ -352,11 +422,11 @@ func (g *GLTF) loadMesh(mi int) (core.INode, error) {
 			geom.SetIndices(indices)
 		}
 
-		//log.Error("positions:%v", positions)
-		//log.Error("indices..:%v", indices)
-		//log.Error("normals..:%v", normals)
-		//log.Error("uvs0.....:%v", uvs0)
-		//log.Error("VBUF size in number of floats:%v", len(vbuf))
+		//log.Debug("positions:%v", positions)
+		//log.Debug("indices..:%v", indices)
+		//log.Debug("normals..:%v", normals)
+		//log.Debug("uvs0.....:%v", uvs0)
+		//log.Debug("VBUF size in number of floats:%v", len(vbuf))
 
 		// Default mode is 4 (TRIANGLES)
 		mode := TRIANGLES
@@ -400,10 +470,10 @@ func (g *GLTF) newDefaultMaterial() material.IMaterial {
 // loadMaterials loads the material specified by the material index
 func (g *GLTF) loadMaterial(mi int) (material.IMaterial, error) {
 
-	mat := g.Materials[mi]
+	matData := g.Materials[mi]
 	// Checks for material extensions
-	if mat.Extensions != nil {
-		for ext, v := range mat.Extensions {
+	if matData.Extensions != nil {
+		for ext, v := range matData.Extensions {
 			if ext == "KHR_materials_common" {
 				return g.loadMaterialCommon(v)
 			} else {
@@ -413,7 +483,7 @@ func (g *GLTF) loadMaterial(mi int) (material.IMaterial, error) {
 		return nil, fmt.Errorf("Empty material extensions")
 		// Material should be PBR
 	} else {
-		return g.loadMaterialPBR(&mat)
+		return g.loadMaterialPBR(&matData)
 	}
 }
 
@@ -435,37 +505,43 @@ func (g *GLTF) loadTexture(texi int) (*texture.Texture2D, error) {
 	tex := texture.NewTexture2DFromRGBA(img)
 
 	// Get sampler and apply texture parameters
-	samp := g.Samplers[texDesc.Sampler]
+	if texDesc.Sampler != nil {
+		sampler := g.Samplers[*texDesc.Sampler]
+		g.applySampler(sampler, tex)
+	}
+
+	return tex, nil
+}
+
+func (g *GLTF) applySampler(sampler Sampler, tex *texture.Texture2D) {
 
 	// Magnification filter
 	magFilter := gls.NEAREST
-	if samp.MagFilter != nil {
-		magFilter = *samp.MagFilter
+	if sampler.MagFilter != nil {
+		magFilter = *sampler.MagFilter
 	}
 	tex.SetMagFilter(uint32(magFilter))
 
 	// Minification filter
 	minFilter := gls.NEAREST
-	if samp.MinFilter != nil {
-		minFilter = *samp.MinFilter
+	if sampler.MinFilter != nil {
+		minFilter = *sampler.MinFilter
 	}
 	tex.SetMinFilter(uint32(minFilter))
 
 	// S coordinate wrapping mode
 	wrapS := gls.REPEAT
-	if samp.WrapS != nil {
-		wrapS = *samp.WrapS
+	if sampler.WrapS != nil {
+		wrapS = *sampler.WrapS
 	}
 	tex.SetWrapS(uint32(wrapS))
 
 	// T coordinate wrapping mode
 	wrapT := gls.REPEAT
-	if samp.WrapT != nil {
-		wrapT = *samp.WrapT
+	if sampler.WrapT != nil {
+		wrapT = *sampler.WrapT
 	}
 	tex.SetWrapT(uint32(wrapT))
-
-	return tex, nil
 }
 
 // loadImage loads the image specified by the index of GLTF.Images
@@ -524,45 +600,57 @@ func (g *GLTF) loadImage(ii int) (*image.RGBA, error) {
 	return rgba, nil
 }
 
-// loadVec3 load array of float32 values from the specified accessor index.
-// The acesssor must have type of VEC3 and component type of FLOAT
-func (g *GLTF) loadVec3(ai int) (math32.ArrayF32, error) {
+
+// loadAccessorData loads the indices array specified by the Accessor index.
+func (g *GLTF) loadAccessorU32(ai int, validTypes []string, validComponentTypes []int) (math32.ArrayU32, error) {
 
 	// Get Accessor for the specified index
 	ac := g.Accessors[ai]
 	if ac.BufferView == nil {
-		return nil, fmt.Errorf("Accessor.BufferView == nil NOT SUPPORTED")
+		return nil, fmt.Errorf("Accessor.BufferView == nil NOT SUPPORTED YET") // TODO
 	}
 
-	// Checks acessor ComponentType
-	if ac.ComponentType != FLOAT {
-		return nil, fmt.Errorf("Accessor.ComponentType != FLOAT NOT SUPPORTED")
-	}
-
-	// Checks acessor Type
-	if ac.Type != VEC3 {
-		return nil, fmt.Errorf("Accessor.ComponentType != VEC3 NOT SUPPORTED")
+	// Validate type and component type
+	err := g.validateAccessor(ac, validTypes, validComponentTypes)
+	if err != nil {
+		return nil, err
 	}
 
-	// Loads data from associated BufferView
-	data, err := g.loadBufferView(*ac.BufferView)
+	// Load bytes
+	data, err := g.loadAccessorBytes(ac)
 	if err != nil {
 		return nil, err
 	}
 
-	// Accessor offset into BufferView
-	offset := 0
-	if ac.ByteOffset != nil {
-		offset = *ac.ByteOffset
+	// If component is UNSIGNED_INT nothing to do
+	if ac.ComponentType == UNSIGNED_INT {
+		arr := (*[1 << 30]uint32)(unsafe.Pointer(&data[0]))[:ac.Count]
+		return math32.ArrayU32(arr), nil
 	}
-	data = data[offset:]
 
-	arr := (*[1 << 30]float32)(unsafe.Pointer(&data[0]))[:ac.Count*3]
-	return math32.ArrayF32(arr), nil
+	// Converts UNSIGNED_SHORT to UNSIGNED_INT
+	if ac.ComponentType == UNSIGNED_SHORT {
+		out := math32.NewArrayU32(ac.Count, ac.Count)
+		for i := 0; i < ac.Count; i++ {
+			out[i] = uint32(data[i*2]) + uint32(data[i*2+1])*256
+		}
+		return out, nil
+	}
+
+	// Converts UNSIGNED_BYTE indices to UNSIGNED_INT
+	if ac.ComponentType == UNSIGNED_BYTE {
+		out := math32.NewArrayU32(ac.Count, ac.Count)
+		for i := 0; i < ac.Count; i++ {
+			out[i] = uint32(data[i])
+		}
+		return out, nil
+	}
+
+	return nil, fmt.Errorf("Unsupported Accessor ComponentType:%v", ac.ComponentType)
 }
 
-// loadVec2 load array of Vector2 from the specified accessor index
-func (g *GLTF) loadVec2(ai int) (math32.ArrayF32, error) {
+
+func (g *GLTF) loadAccessorF32(ai int, validTypes []string, validComponentTypes []int) (math32.ArrayF32, error) {
 
 	// Get Accessor for the specified index
 	ac := g.Accessors[ai]
@@ -570,44 +658,54 @@ func (g *GLTF) loadVec2(ai int) (math32.ArrayF32, error) {
 		return nil, fmt.Errorf("Accessor.BufferView == nil NOT SUPPORTED")
 	}
 
-	// Checks acessor ComponentType
-	if ac.ComponentType != FLOAT {
-		return nil, fmt.Errorf("Accessor.ComponentType != FLOAT NOT SUPPORTED")
-	}
-
-	// Checks acessor Type
-	if ac.Type != VEC2 {
-		return nil, fmt.Errorf("Accessor.ComponentType != VEC2 NOT SUPPORTED")
-	}
-
-	// Loads data from associated BufferView
-	data, err := g.loadBufferView(*ac.BufferView)
+	// Validate type and component type
+	err := g.validateAccessor(ac, validTypes, validComponentTypes)
 	if err != nil {
 		return nil, err
 	}
 
-	// Accessor offset into BufferView
-	offset := 0
-	if ac.ByteOffset != nil {
-		offset = *ac.ByteOffset
+	// Load bytes
+	data, err := g.loadAccessorBytes(ac)
+	if err != nil {
+		return nil, err
 	}
-	data = data[offset:]
 
-	arr := (*[1 << 30]float32)(unsafe.Pointer(&data[0]))[:ac.Count*2]
+	arr := (*[1 << 30]float32)(unsafe.Pointer(&data[0]))[:ac.Count*TypeSizes[ac.Type]]
 	return math32.ArrayF32(arr), nil
 }
 
-// loadIndices load the indices array specified by the Accessor index.
-func (g *GLTF) loadIndices(ai int) (math32.ArrayU32, error) {
+// TODO
+func (g *GLTF) validateAccessor(ac Accessor, validTypes []string, validComponentTypes []int) error {
+
+	// Check if points to a valid buffer view
+	//if ac.BufferView == nil {
+	//	return fmt.Errorf("Accessor.BufferView == nil NOT SUPPORTED")
+	//}
+	//
+	//// Check accessor ComponentType
+	//if ac.ComponentType != FLOAT {
+	//	return fmt.Errorf("Accessor.ComponentType != FLOAT NOT SUPPORTED")
+	//}
+	//
+	//// Check accessor Type
+	//if ac.Type != VEC3 {
+	//	return fmt.Errorf("Accessor.ComponentType != VEC3 NOT SUPPORTED")
+	//}
 
-	// Get Accessor for the specified index
-	ac := g.Accessors[ai]
+	return nil
+}
+
+// loadAccessorBytes
+func (g *GLTF) loadAccessorBytes(ac Accessor) ([]byte, error) {
+
+	// Get the Accessor's BufferView
 	if ac.BufferView == nil {
-		return nil, fmt.Errorf("Accessor.BufferView == nil NOT SUPPORTED YET")
+		return nil, fmt.Errorf("Accessor has nil BufferView") // TODO
 	}
+	bv := g.BufferViews[*ac.BufferView]
 
-	// Loads indices data from associated BufferView
-	data, err := g.loadBufferView(*ac.BufferView)
+	// Loads data from associated BufferView
+	data, err := g.loadBufferView(bv)
 	if err != nil {
 		return nil, err
 	}
@@ -619,30 +717,20 @@ func (g *GLTF) loadIndices(ai int) (math32.ArrayU32, error) {
 	}
 	data = data[offset:]
 
-	// If index component is UNSIGNED_INT nothing to do
-	if ac.ComponentType == UNSIGNED_INT {
-		arr := (*[1 << 30]uint32)(unsafe.Pointer(&data[0]))[:ac.Count]
-		return math32.ArrayU32(arr), nil
-	}
+	// Check if interleaved and de-interleave if necessary
 
-	// Converts UNSIGNED_SHORT indices to UNSIGNED_INT
-	if ac.ComponentType == UNSIGNED_SHORT {
-		indices := math32.NewArrayU32(ac.Count, ac.Count)
-		for i := 0; i < ac.Count; i++ {
-			indices[i] = uint32(data[i*2]) + uint32(data[i*2+1])*256
-		}
-		return indices, nil
-	}
+	// Calculate the size in bytes of a complete attribute
+	itemSize := TypeSizes[ac.Type]
+	itemBytes := int(gls.FloatSize) * itemSize
 
-	// Converts UNSIGNED_BYTE indices to UNSIGNED_INT
-	if ac.ComponentType == UNSIGNED_BYTE {
-		indices := math32.NewArrayU32(ac.Count, ac.Count)
-		for i := 0; i < ac.Count; i++ {
-			indices[i] = uint32(data[i])
-		}
-		return indices, nil
+	// If the BufferView stride is equal to the item size, the buffer is not interleaved
+	if (bv.ByteStride != nil) && (*bv.ByteStride != itemBytes) {
+		// BufferView data is interleaved, de-interleave
+		// TODO
+		log.Error("DATA IS INTERLEAVED - NOT SUPPORTED YET!")
 	}
-	return nil, fmt.Errorf("Unsupported Accessor ComponentType:%v", ac.ComponentType)
+
+	return data, nil
 }
 
 // isInterleaves checks if the BufferView used by the specified Accessor index is
@@ -670,20 +758,71 @@ func (g *GLTF) isInterleaved(aci int) bool {
 	return true
 }
 
-// loadBufferView loads and returns a byte slice with data from the specified
-// BufferView index
-func (g *GLTF) loadBufferView(bvi int) ([]byte, error) {
+// loadPosition load array of float32 values from the specified accessor index.
+// The acesssor must have type of VEC3 and component type of FLOAT
+func (g *GLTF) loadPositions(ai int) (math32.ArrayF32, error) {
+
+	return g.loadAccessorF32(ai, []string{VEC3}, []int{FLOAT})
+}
+
+//
+func (g *GLTF) loadNormals(ai int) (math32.ArrayF32, error) {
+
+	return g.loadAccessorF32(ai, []string{VEC3}, []int{FLOAT})
+}
+
+//
+func (g *GLTF) loadTangents(ai int) (math32.ArrayF32, error) {
+
+	return g.loadAccessorF32(ai, []string{VEC4}, []int{FLOAT})
+}
+
+//
+func (g *GLTF) loadTexcoords(ai int) (math32.ArrayF32, error) {
+
+	return g.loadAccessorF32(ai, []string{VEC3}, []int{FLOAT, UNSIGNED_BYTE, UNSIGNED_SHORT})
+}
+
+//
+func (g *GLTF) loadColors(ai int) (math32.ArrayF32, error) {
+
+	return g.loadAccessorF32(ai, []string{VEC3, VEC4}, []int{FLOAT, UNSIGNED_BYTE, UNSIGNED_SHORT})
+}
+
+//
+func (g *GLTF) loadJoints(ai int) (math32.ArrayF32, error) {
+
+	return g.loadAccessorF32(ai, []string{VEC4}, []int{UNSIGNED_BYTE, UNSIGNED_SHORT})
+}
+
+//
+func (g *GLTF) loadWeights(ai int) (math32.ArrayF32, error) {
 
-	bv := g.BufferViews[bvi]
+	return g.loadAccessorF32(ai, []string{VEC4}, []int{FLOAT, UNSIGNED_BYTE, UNSIGNED_SHORT})
+}
+
+//
+func (g *GLTF) loadIndices(ai int) (math32.ArrayU32, error) {
+
+	return g.loadAccessorU32(ai, []string{SCALAR}, []int{UNSIGNED_BYTE, UNSIGNED_SHORT, UNSIGNED_INT}) // TODO check that it's ELEMENT_ARRAY_BUFFER
+}
+
+// loadBufferView loads and returns a byte slice with data from the specified BufferView index
+func (g *GLTF) loadBufferView(bv BufferView) ([]byte, error) {
+
+	// Load buffer view buffer
 	buf, err := g.loadBuffer(bv.Buffer)
 	if err != nil {
 		return nil, err
 	}
 
+	// Establish offset
 	offset := 0
 	if bv.ByteOffset != nil {
 		offset = *bv.ByteOffset
 	}
+
+	// Compute and return offset slice
 	return buf[offset : offset+bv.ByteLength], nil
 }
 
@@ -691,13 +830,13 @@ func (g *GLTF) loadBufferView(bvi int) ([]byte, error) {
 func (g *GLTF) loadBuffer(bi int) ([]byte, error) {
 
 	buf := &g.Buffers[bi]
-	// If Buffer URI uses the chunk data field
+	// If Buffer URI use the chunk data field
 	if buf.Uri == "" {
 		return g.data, nil
 	}
 
 	// If buffer already loaded:
-	log.Error("loadBuffer cache:%v", len(buf.data))
+	log.Debug("loadBuffer cache:%v", len(buf.data))
 	if len(buf.data) > 0 {
 		return buf.data, nil
 	}
@@ -712,7 +851,7 @@ func (g *GLTF) loadBuffer(bi int) ([]byte, error) {
 		}
 		// Loads external buffer file
 	} else {
-		log.Error("loadBuffer: loading file")
+		log.Debug("loadBuffer: loading file")
 		// Try to load buffer from file
 		fpath := filepath.Join(g.path, buf.Uri)
 		f, err := os.Open(fpath)
@@ -731,7 +870,7 @@ func (g *GLTF) loadBuffer(bi int) ([]byte, error) {
 	}
 	// Cache buffer data
 	buf.data = data
-	log.Error("cache data:%v", len(buf.data))
+	log.Debug("cache data:%v", len(buf.data))
 	return data, nil
 }
 

+ 50 - 6
loader/gltf/material_pbr.go

@@ -5,7 +5,6 @@ import (
 
 	"github.com/g3n/engine/material"
 	"github.com/g3n/engine/math32"
-	"github.com/g3n/engine/texture"
 )
 
 func (g *GLTF) loadMaterialPBR(m *Material) (material.IMaterial, error) {
@@ -19,8 +18,6 @@ func (g *GLTF) loadMaterialPBR(m *Material) (material.IMaterial, error) {
 	// Create new physically based material
 	pm := material.NewPhysical()
 
-	// TODO emmisive factor, emmissive map, occlusion, etc...
-
 	// BaseColorFactor
 	var baseColorFactor math32.Color4
 	if pbr.BaseColorFactor != nil {
@@ -48,16 +45,63 @@ func (g *GLTF) loadMaterialPBR(m *Material) (material.IMaterial, error) {
 	}
 	pm.SetRoughnessFactor(roughnessFactor)
 
+	// EmissiveFactor
+	var emissiveFactor math32.Color
+	if m.EmissiveFactor != nil {
+		emissiveFactor = math32.Color{m.EmissiveFactor[0], m.EmissiveFactor[1], m.EmissiveFactor[2]}
+	} else {
+		if m.EmissiveTexture != nil {
+			emissiveFactor = math32.Color{1, 1, 1}
+		} else {
+			emissiveFactor = math32.Color{0,0,0}
+		}
+	}
+	pm.SetEmissiveFactor(&emissiveFactor)
+
 	// BaseColorTexture
-	var tex *texture.Texture2D
-	var err error
 	if pbr.BaseColorTexture != nil {
-		tex, err = g.loadTextureInfo(pbr.BaseColorTexture)
+		tex, err := g.loadTextureInfo(pbr.BaseColorTexture)
 		if err != nil {
 			return nil, err
 		}
 		pm.SetBaseColorMap(tex)
 	}
 
+	// MetallicRoughnessTexture
+	if pbr.MetallicRoughnessTexture != nil {
+		tex, err := g.loadTextureInfo(pbr.MetallicRoughnessTexture)
+		if err != nil {
+			return nil, err
+		}
+		pm.SetMetallicRoughnessMap(tex)
+	}
+
+	// NormalTexture
+	if m.NormalTexture != nil {
+		tex, err := g.loadTexture(m.NormalTexture.Index)
+		if err != nil {
+			return nil, err
+		}
+		pm.SetNormalMap(tex)
+	}
+
+	// OcclusionTexture
+	if m.OcclusionTexture != nil {
+		tex, err := g.loadTexture(m.OcclusionTexture.Index)
+		if err != nil {
+			return nil, err
+		}
+		pm.SetOcclusionMap(tex)
+	}
+
+	// EmissiveTexture
+	if m.EmissiveTexture != nil {
+		tex, err := g.loadTexture(m.EmissiveTexture.Index)
+		if err != nil {
+			return nil, err
+		}
+		pm.SetEmissiveMap(tex)
+	}
+
 	return pm, nil
 }