Explorar o código

improved morph animation

Daniel Salvadori %!s(int64=7) %!d(string=hai) anos
pai
achega
36c1184461
Modificáronse 3 ficheiros con 113 adicións e 74 borrados
  1. 2 2
      animation/channel.go
  2. 19 9
      geometry/morph.go
  3. 92 63
      loader/gltf/loader.go

+ 2 - 2
animation/channel.go

@@ -271,7 +271,7 @@ func NewMorphChannel(mg *geometry.MorphGeometry) *MorphChannel {
 				weights1 := mc.values[start1:start1+numWeights]
 				weights1 := mc.values[start1:start1+numWeights]
 				weights2 := mc.values[start2:start2+numWeights]
 				weights2 := mc.values[start2:start2+numWeights]
 				for i := range weights1 {
 				for i := range weights1 {
-					weights1[i] = weights1[i] + (weights2[i]-weights1[i])*k
+					weights1[i] += (weights2[i]-weights1[i])*k
 				}
 				}
 				mg.SetWeights(weights1)
 				mg.SetWeights(weights1)
 			}
 			}
@@ -282,7 +282,7 @@ func NewMorphChannel(mg *geometry.MorphGeometry) *MorphChannel {
 				weights1 := mc.values[start1:start1+numWeights]
 				weights1 := mc.values[start1:start1+numWeights]
 				weights2 := mc.values[start2:start2+numWeights]
 				weights2 := mc.values[start2:start2+numWeights]
 				for i := range weights1 {
 				for i := range weights1 {
-					weights1[i] = weights1[i] + (weights2[i]-weights1[i])*k
+					weights1[i] += (weights2[i]-weights1[i])*k
 				}
 				}
 				mg.SetWeights(weights1)
 				mg.SetWeights(weights1)
 			}
 			}

+ 19 - 9
geometry/morph.go

@@ -15,7 +15,6 @@ import (
 type MorphGeometry struct {
 type MorphGeometry struct {
 	baseGeometry  *Geometry   // The base geometry
 	baseGeometry  *Geometry   // The base geometry
 	targets       []*Geometry // The morph target geometries
 	targets       []*Geometry // The morph target geometries
-	activeTargets []*Geometry // The morph target geometries
 	weights       []float32   // The weights for each morph target
 	weights       []float32   // The weights for each morph target
 	uniWeights    gls.Uniform // Texture unit uniform location cache
 	uniWeights    gls.Uniform // Texture unit uniform location cache
 	morphGeom     *Geometry   // Cache of the last CPU-morphed geometry
 	morphGeom     *Geometry   // Cache of the last CPU-morphed geometry
@@ -31,8 +30,7 @@ func NewMorphGeometry(baseGeometry *Geometry) *MorphGeometry {
 	mg.baseGeometry = baseGeometry
 	mg.baseGeometry = baseGeometry
 
 
 	mg.targets = make([]*Geometry, 0)
 	mg.targets = make([]*Geometry, 0)
-	mg.activeTargets = make([]*Geometry, 0)
-	mg.weights = make([]float32, NumMorphTargets)
+	mg.weights = make([]float32, 0)
 
 
 	mg.baseGeometry.ShaderDefines.Set("MORPHTARGETS", strconv.Itoa(NumMorphTargets))
 	mg.baseGeometry.ShaderDefines.Set("MORPHTARGETS", strconv.Itoa(NumMorphTargets))
 	mg.uniWeights.Init("morphTargetInfluences")
 	mg.uniWeights.Init("morphTargetInfluences")
@@ -48,6 +46,9 @@ func (mg *MorphGeometry) GetGeometry() *Geometry {
 // SetWeights sets the morph target weights.
 // SetWeights sets the morph target weights.
 func (mg *MorphGeometry) SetWeights(weights []float32) {
 func (mg *MorphGeometry) SetWeights(weights []float32) {
 
 
+	if len(weights) != len(mg.weights) {
+		panic("weights have invalid length")
+	}
 	mg.weights = weights
 	mg.weights = weights
 }
 }
 
 
@@ -60,16 +61,18 @@ func (mg *MorphGeometry) Weights() []float32 {
 // Weights returns the morph target weights.
 // Weights returns the morph target weights.
 func (mg *MorphGeometry) AddMorphTargets(morphTargets ...*Geometry) {
 func (mg *MorphGeometry) AddMorphTargets(morphTargets ...*Geometry) {
 
 
-	log.Error("ADD morph targets")
 	mg.targets = append(mg.targets, morphTargets...)
 	mg.targets = append(mg.targets, morphTargets...)
+	for range morphTargets {
+		mg.weights = append(mg.weights, 0)
+	}
 }
 }
 
 
 // ActiveMorphTargets sorts the morph targets by weight and returns the top n morph targets with largest weight.
 // ActiveMorphTargets sorts the morph targets by weight and returns the top n morph targets with largest weight.
-func (mg *MorphGeometry) ActiveMorphTargets() []*Geometry {
+func (mg *MorphGeometry) ActiveMorphTargets() ([]*Geometry, []float32) {
 
 
 	numTargets := len(mg.targets)
 	numTargets := len(mg.targets)
 	if numTargets == 0 {
 	if numTargets == 0 {
-		return nil
+		return nil, nil
 	}
 	}
 
 
 	sortedMorphTargets := make([]*Geometry, numTargets)
 	sortedMorphTargets := make([]*Geometry, numTargets)
@@ -78,10 +81,17 @@ func (mg *MorphGeometry) ActiveMorphTargets() []*Geometry {
 		return mg.weights[i] > mg.weights[j]
 		return mg.weights[i] > mg.weights[j]
 	})
 	})
 
 
+	sortedWeights := make([]float32, numTargets)
+	copy(sortedWeights, mg.weights)
+	sort.Slice(sortedWeights, func(i, j int) bool {
+		return mg.weights[i] > mg.weights[j]
+	})
+
+
 	// TODO check current 0 weights
 	// TODO check current 0 weights
 
 
 	//if len(mg.targets) < NumMorphTargets-1 {
 	//if len(mg.targets) < NumMorphTargets-1 {
-		return sortedMorphTargets
+		return sortedMorphTargets, sortedWeights
 	//} else {
 	//} else {
 	//	return sortedMorphTargets[:NumMorphTargets-1]
 	//	return sortedMorphTargets[:NumMorphTargets-1]
 	//}
 	//}
@@ -124,7 +134,7 @@ func (mg *MorphGeometry) RenderSetup(gs *gls.GLS) {
 	mg.baseGeometry.RenderSetup(gs)
 	mg.baseGeometry.RenderSetup(gs)
 
 
 	// Sort weights and find top 8 morph targets with largest current weight (8 is the max sent to shader)
 	// Sort weights and find top 8 morph targets with largest current weight (8 is the max sent to shader)
-	activeMorphTargets := mg.ActiveMorphTargets()
+	activeMorphTargets, activeWeights := mg.ActiveMorphTargets()
 
 
 	for i, mt := range activeMorphTargets {
 	for i, mt := range activeMorphTargets {
 
 
@@ -140,5 +150,5 @@ func (mg *MorphGeometry) RenderSetup(gs *gls.GLS) {
 
 
 	// Transfer texture info combined uniform
 	// Transfer texture info combined uniform
 	location := mg.uniWeights.Location(gs)
 	location := mg.uniWeights.Location(gs)
-	gs.Uniform1fv(location, int32(len(activeMorphTargets)), mg.weights)
+	gs.Uniform1fv(location, int32(len(activeWeights)), activeWeights)
 }
 }

+ 92 - 63
loader/gltf/loader.go

@@ -263,29 +263,26 @@ func (g *GLTF) NewAnimation(i int) (*animation.Animation, error) {
 
 
 		var ch animation.IChannel
 		var ch animation.IChannel
 		if target.Path == "translation" {
 		if target.Path == "translation" {
-			ch = animation.NewPositionChannel(node)
 			validTypes = []string{VEC3}
 			validTypes = []string{VEC3}
 			validComponentTypes = []int{FLOAT}
 			validComponentTypes = []int{FLOAT}
+			ch = animation.NewPositionChannel(node)
 		} else if target.Path == "rotation" {
 		} else if target.Path == "rotation" {
-			ch = animation.NewRotationChannel(node)
 			validTypes = []string{VEC4}
 			validTypes = []string{VEC4}
 			validComponentTypes = []int{FLOAT, BYTE, UNSIGNED_BYTE, SHORT, UNSIGNED_SHORT}
 			validComponentTypes = []int{FLOAT, BYTE, UNSIGNED_BYTE, SHORT, UNSIGNED_SHORT}
+			ch = animation.NewRotationChannel(node)
 		} else if target.Path == "scale" {
 		} else if target.Path == "scale" {
-			ch = animation.NewScaleChannel(node)
 			validTypes = []string{VEC3}
 			validTypes = []string{VEC3}
 			validComponentTypes = []int{FLOAT}
 			validComponentTypes = []int{FLOAT}
+			ch = animation.NewScaleChannel(node)
 		} else if target.Path == "weights" {
 		} else if target.Path == "weights" {
 			validTypes = []string{SCALAR}
 			validTypes = []string{SCALAR}
 			validComponentTypes = []int{FLOAT, BYTE, UNSIGNED_BYTE, SHORT, UNSIGNED_SHORT}
 			validComponentTypes = []int{FLOAT, BYTE, UNSIGNED_BYTE, SHORT, UNSIGNED_SHORT}
-			return nil, fmt.Errorf("morph animation (with 'weights' path) not supported yet")
-			// TODO
-			//	for _, child := range node.GetNode().Children() {
-			//		gr, ok := child.(graphic.Graphic)
-			//		if ok {
-			//			gr.geom
-			//		}
-			//	}
-			//	ch = animation.NewMorphChannel(TODO)
+			children := node.GetNode().Children()
+			if len(children) > 1 {
+				return nil, fmt.Errorf("animating meshes with more than a single primitive is not supported")
+			}
+			morphGeom := children[0].(graphic.IGraphic).IGeometry().(*geometry.MorphGeometry)
+			ch = animation.NewMorphChannel(morphGeom)
 		}
 		}
 
 
 		// TODO what if Input and Output accessors are interleaved? probably de-interleave in these 2 cases
 		// TODO what if Input and Output accessors are interleaved? probably de-interleave in these 2 cases
@@ -379,60 +376,31 @@ func (g *GLTF) NewMesh(mi int) (core.INode, error) {
 		}
 		}
 
 
 		// Create geometry
 		// Create geometry
-		geom := geometry.NewGeometry()
-
-		// Indices of buffer views
-		interleavedVBOs := make(map[int]*gls.VBO, 0)
+		var igeom geometry.IGeometry
+		igeom = geometry.NewGeometry()
+		geom := igeom.GetGeometry()
 
 
-		// Load primitive attributes
-		for name, aci := range p.Attributes {
-			accessor := g.Accessors[aci]
+		err = g.loadAttributes(geom, p.Attributes, indices)
+		if err != nil {
+			return nil, err
+		}
 
 
-			// Validate that accessor is compatible with attribute
-			err = g.validateAccessorAttribute(accessor, name)
-			if err != nil {
-				return nil, err
-			}
+		// If primitive has targets then the geometry should be a morph geometry
+		if len(p.Targets) > 0 {
+			morphGeom := geometry.NewMorphGeometry(geom)
 
 
-			// Load data and add it to geometry's VBO
-			if g.isInterleaved(accessor) {
-				bvIdx := *accessor.BufferView
-				// Check if we already loaded this buffer view
-				vbo, ok := interleavedVBOs[bvIdx]
-				if ok {
-					// Already created VBO for this buffer view
-					// Add attribute with correct byteOffset
-					g.addAttributeToVBO(vbo, name, uint32(*accessor.ByteOffset))
-				} else {
-					// Load data and create vbo
-					buf, err := g.loadBufferView(g.BufferViews[bvIdx])
-					if err != nil {
-						return nil, err
-					}
-					data := g.bytesToArrayF32(buf, g.BufferViews[bvIdx].ByteLength)
-					vbo := gls.NewVBO(data)
-					g.addAttributeToVBO(vbo, name, 0)
-					// Save reference to VBO keyed by index of the buffer view
-					interleavedVBOs[bvIdx] = vbo
-					// Add VBO to geometry
-					geom.AddVBO(vbo)
-				}
-			} else {
-				buf, err := g.loadAccessorBytes(accessor)
+			// Load targets
+			for i := range p.Targets {
+				tGeom := geometry.NewGeometry()
+				attributes := p.Targets[i]
+				err = g.loadAttributes(tGeom, attributes, indices)
 				if err != nil {
 				if err != nil {
 					return nil, err
 					return nil, err
 				}
 				}
-				data := g.bytesToArrayF32(buf, accessor.Count*TypeSizes[accessor.Type])
-				vbo := gls.NewVBO(data)
-				g.addAttributeToVBO(vbo, name, 0)
-				// Add VBO to geometry
-				geom.AddVBO(vbo)
+				morphGeom.AddMorphTargets(tGeom)
 			}
 			}
-		}
 
 
-		// Creates Geometry and add attribute VBO
-		if len(indices) > 0 {
-			geom.SetIndices(indices)
+			igeom = morphGeom
 		}
 		}
 
 
 		// Default mode is 4 (TRIANGLES)
 		// Default mode is 4 (TRIANGLES)
@@ -443,15 +411,15 @@ func (g *GLTF) NewMesh(mi int) (core.INode, error) {
 
 
 		// Create Mesh
 		// Create Mesh
 		if mode == TRIANGLES {
 		if mode == TRIANGLES {
-			primitiveMesh := graphic.NewMesh(geom, nil)
+			primitiveMesh := graphic.NewMesh(igeom, nil)
 			primitiveMesh.AddMaterial(grMat, 0, 0)
 			primitiveMesh.AddMaterial(grMat, 0, 0)
 			meshNode.Add(primitiveMesh)
 			meshNode.Add(primitiveMesh)
 		} else if mode == LINES {
 		} else if mode == LINES {
-			meshNode.Add(graphic.NewLines(geom, grMat))
+			meshNode.Add(graphic.NewLines(igeom, grMat))
 		} else if mode == LINE_STRIP {
 		} else if mode == LINE_STRIP {
-			meshNode.Add(graphic.NewLineStrip(geom, grMat))
+			meshNode.Add(graphic.NewLineStrip(igeom, grMat))
 		} else if mode == POINTS {
 		} else if mode == POINTS {
-			meshNode.Add(graphic.NewPoints(geom, grMat))
+			meshNode.Add(graphic.NewPoints(igeom, grMat))
 		} else {
 		} else {
 			return nil, fmt.Errorf("Unsupported primitive:%v", mode)
 			return nil, fmt.Errorf("Unsupported primitive:%v", mode)
 		}
 		}
@@ -460,6 +428,66 @@ func (g *GLTF) NewMesh(mi int) (core.INode, error) {
 	return meshNode, nil
 	return meshNode, nil
 }
 }
 
 
+// loadAttributes loads the provided list of vertex attributes as VBO(s) into the specified geometry.
+func (g *GLTF) loadAttributes(geom *geometry.Geometry, attributes map[string]int, indices math32.ArrayU32) error {
+
+	// Indices of buffer views
+	interleavedVBOs := make(map[int]*gls.VBO, 0)
+
+	// Load primitive attributes
+	for name, aci := range attributes {
+		accessor := g.Accessors[aci]
+
+		// Validate that accessor is compatible with attribute
+		err := g.validateAccessorAttribute(accessor, name)
+		if err != nil {
+			return err
+		}
+
+		// Load data and add it to geometry's VBO
+		if g.isInterleaved(accessor) {
+			bvIdx := *accessor.BufferView
+			// Check if we already loaded this buffer view
+			vbo, ok := interleavedVBOs[bvIdx]
+			if ok {
+				// Already created VBO for this buffer view
+				// Add attribute with correct byteOffset
+				g.addAttributeToVBO(vbo, name, uint32(*accessor.ByteOffset))
+			} else {
+				// Load data and create vbo
+				buf, err := g.loadBufferView(g.BufferViews[bvIdx])
+				if err != nil {
+					return err
+				}
+				data := g.bytesToArrayF32(buf, g.BufferViews[bvIdx].ByteLength)
+				vbo := gls.NewVBO(data)
+				g.addAttributeToVBO(vbo, name, 0)
+				// Save reference to VBO keyed by index of the buffer view
+				interleavedVBOs[bvIdx] = vbo
+				// Add VBO to geometry
+				geom.AddVBO(vbo)
+			}
+		} else {
+			buf, err := g.loadAccessorBytes(accessor)
+			if err != nil {
+				return err
+			}
+			data := g.bytesToArrayF32(buf, accessor.Count*TypeSizes[accessor.Type])
+			vbo := gls.NewVBO(data)
+			g.addAttributeToVBO(vbo, name, 0)
+			// Add VBO to geometry
+			geom.AddVBO(vbo)
+		}
+	}
+
+	// Set indices
+	if len(indices) > 0 {
+		geom.SetIndices(indices)
+	}
+
+	return nil
+}
+
 // loadIndices loads the indices stored in the specified accessor.
 // loadIndices loads the indices stored in the specified accessor.
 func (g *GLTF) loadIndices(ai int) (math32.ArrayU32, error) {
 func (g *GLTF) loadIndices(ai int) (math32.ArrayU32, error) {
 
 
@@ -501,7 +529,8 @@ func (g *GLTF) validateAccessorAttribute(ac Accessor, attribName string) error {
 	} else if attribName == "NORMAL" {
 	} else if attribName == "NORMAL" {
 		return g.validateAccessor(ac, usage, []string{VEC3}, []int{FLOAT})
 		return g.validateAccessor(ac, usage, []string{VEC3}, []int{FLOAT})
 	} else if attribName == "TANGENT" {
 	} else if attribName == "TANGENT" {
-		return g.validateAccessor(ac, usage, []string{VEC4}, []int{FLOAT})
+		// Note that morph targets only support VEC3 whereas normal attributes only support VEC4.
+		return g.validateAccessor(ac, usage, []string{VEC3, VEC4}, []int{FLOAT})
 	} else if semantic == "TEXCOORD" {
 	} else if semantic == "TEXCOORD" {
 		return g.validateAccessor(ac, usage, []string{VEC2}, []int{FLOAT, UNSIGNED_BYTE, UNSIGNED_SHORT})
 		return g.validateAccessor(ac, usage, []string{VEC2}, []int{FLOAT, UNSIGNED_BYTE, UNSIGNED_SHORT})
 	} else if semantic == "COLOR" {
 	} else if semantic == "COLOR" {