205 lines
5.2 KiB
Go
205 lines
5.2 KiB
Go
package gltf
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/qmuntal/gltf"
|
|
"strings"
|
|
"zworld/engine/object"
|
|
"zworld/engine/object/mesh"
|
|
"zworld/engine/renderapi/types"
|
|
"zworld/engine/renderapi/vertex"
|
|
"zworld/plugins/math/quat"
|
|
"zworld/plugins/math/vec3"
|
|
)
|
|
|
|
func Load(path string) object.Component {
|
|
assetPath := fmt.Sprintf("assets/%s", path)
|
|
doc, _ := gltf.Open(assetPath)
|
|
|
|
// load default scene
|
|
scene := doc.Scenes[*doc.Scene]
|
|
|
|
return loadScene(doc, scene)
|
|
}
|
|
|
|
func loadScene(doc *gltf.Document, scene *gltf.Scene) object.Component {
|
|
root := object.Empty(scene.Name)
|
|
|
|
for _, nodeId := range scene.Nodes {
|
|
node := loadNode(doc, doc.Nodes[nodeId])
|
|
object.Attach(root, node)
|
|
}
|
|
|
|
// rotate to get Y+ up
|
|
root.Transform().SetRotation(quat.Euler(90, 0, 0))
|
|
|
|
return root
|
|
}
|
|
|
|
func loadNode(doc *gltf.Document, node *gltf.Node) object.Component {
|
|
obj := object.Empty(node.Name)
|
|
|
|
// mesh components
|
|
if node.Mesh != nil {
|
|
msh := doc.Meshes[*node.Mesh]
|
|
for _, primitive := range msh.Primitives {
|
|
renderer := loadPrimitive(doc, msh.Name, primitive)
|
|
object.Attach(obj, renderer)
|
|
}
|
|
}
|
|
|
|
// object transform
|
|
obj.Transform().SetPosition(vec3.FromSlice(node.Translation[:3]))
|
|
obj.Transform().SetRotation(quat.T{W: node.Rotation[3], V: vec3.FromSlice(node.Rotation[:3])})
|
|
obj.Transform().SetScale(vec3.FromSlice(node.Scale[:3]))
|
|
|
|
// child objects
|
|
for _, child := range node.Children {
|
|
object.Attach(obj, loadNode(doc, doc.Nodes[child]))
|
|
}
|
|
|
|
return obj
|
|
}
|
|
|
|
func loadPrimitive(doc *gltf.Document, name string, primitive *gltf.Primitive) mesh.Mesh {
|
|
kind := mapPrimitiveType(primitive.Mode)
|
|
|
|
// create interleaved buffers
|
|
pointers, vertexData := createBuffer(doc, primitive)
|
|
indexElements, indexData := createIndexBuffer(doc, primitive)
|
|
|
|
// ensure vertex attribute names are in lowercase
|
|
for i, ptr := range pointers {
|
|
pointers[i].Name = strings.ToLower(ptr.Name)
|
|
}
|
|
|
|
// mesh data
|
|
gmesh := &gltfMesh{
|
|
key: name,
|
|
primitive: kind,
|
|
elements: indexElements,
|
|
pointers: pointers,
|
|
vertices: vertexData,
|
|
indices: indexData,
|
|
indexsize: len(indexData) / indexElements,
|
|
}
|
|
|
|
// create mesh component
|
|
mesh := mesh.NewPrimitiveMesh(kind, nil)
|
|
mesh.VertexData.Set(gmesh)
|
|
return mesh
|
|
}
|
|
|
|
func extractPointers(doc *gltf.Document, primitive *gltf.Primitive) []vertex.Pointer {
|
|
offset := 0
|
|
pointers := make(vertex.Pointers, 0, len(primitive.Attributes))
|
|
for name, index := range primitive.Attributes {
|
|
accessor := doc.Accessors[index]
|
|
|
|
pointers = append(pointers, vertex.Pointer{
|
|
Name: name,
|
|
Source: mapComponentType(accessor.ComponentType),
|
|
Offset: offset,
|
|
Elements: int(accessor.Type.Components()),
|
|
Normalize: accessor.Normalized,
|
|
Stride: 0, // filed in in next pass
|
|
})
|
|
|
|
size := int(accessor.ComponentType.ByteSize() * accessor.Type.Components())
|
|
offset += size
|
|
}
|
|
|
|
// at this point, offset equals the final stride value. fill it in
|
|
for index := range pointers {
|
|
pointers[index].Stride = offset
|
|
}
|
|
|
|
return pointers
|
|
}
|
|
|
|
func createBuffer(doc *gltf.Document, primitive *gltf.Primitive) (vertex.Pointers, []byte) {
|
|
pointers := extractPointers(doc, primitive)
|
|
|
|
count := int(doc.Accessors[primitive.Attributes[pointers[0].Name]].Count)
|
|
size := count * pointers[0].Stride
|
|
|
|
output := make([]byte, size)
|
|
|
|
for _, ptr := range pointers {
|
|
accessor := doc.Accessors[primitive.Attributes[ptr.Name]]
|
|
view := doc.BufferViews[*accessor.BufferView]
|
|
buffer := doc.Buffers[view.Buffer]
|
|
size := int(accessor.ComponentType.ByteSize() * accessor.Type.Components())
|
|
stride := size
|
|
if view.ByteStride != 0 {
|
|
stride = int(view.ByteStride)
|
|
}
|
|
|
|
for i := 0; i < count; i++ {
|
|
srcStart := int(view.ByteOffset) + i*stride + int(accessor.ByteOffset)
|
|
srcEnd := srcStart + size
|
|
dstStart := i*ptr.Stride + ptr.Offset
|
|
dstEnd := dstStart + size
|
|
|
|
copy(output[dstStart:dstEnd], buffer.Data[srcStart:srcEnd])
|
|
}
|
|
}
|
|
|
|
return pointers, output
|
|
}
|
|
|
|
func createIndexBuffer(doc *gltf.Document, primitive *gltf.Primitive) (int, []byte) {
|
|
accessor := doc.Accessors[*primitive.Indices]
|
|
view := doc.BufferViews[*accessor.BufferView]
|
|
buffer := doc.Buffers[view.Buffer]
|
|
|
|
count := int(accessor.Count)
|
|
size := int(accessor.ComponentType.ByteSize() * accessor.Type.Components())
|
|
stride := size
|
|
if view.ByteStride != 0 {
|
|
stride = int(view.ByteStride)
|
|
}
|
|
|
|
output := make([]byte, size*count)
|
|
for i := 0; i < count; i++ {
|
|
srcStart := int(view.ByteOffset) + i*stride + int(accessor.ByteOffset)
|
|
srcEnd := srcStart + size
|
|
dstStart := i * size
|
|
dstEnd := dstStart + size
|
|
|
|
copy(output[dstStart:dstEnd], buffer.Data[srcStart:srcEnd])
|
|
}
|
|
|
|
return count, output
|
|
}
|
|
|
|
func mapPrimitiveType(mode gltf.PrimitiveMode) vertex.Primitive {
|
|
switch mode {
|
|
case gltf.PrimitiveTriangles:
|
|
return vertex.Triangles
|
|
case gltf.PrimitiveLines:
|
|
return vertex.Lines
|
|
default:
|
|
panic("unsupported render primitive")
|
|
}
|
|
}
|
|
|
|
func mapComponentType(kind gltf.ComponentType) types.Type {
|
|
switch kind {
|
|
case gltf.ComponentFloat:
|
|
return types.Float
|
|
case gltf.ComponentByte:
|
|
return types.Int8
|
|
case gltf.ComponentUbyte:
|
|
return types.UInt8
|
|
case gltf.ComponentShort:
|
|
return types.Int16
|
|
case gltf.ComponentUshort:
|
|
return types.UInt16
|
|
case gltf.ComponentUint:
|
|
return types.UInt32
|
|
default:
|
|
panic(fmt.Sprintf("unmapped type %s (%d)", kind, kind))
|
|
}
|
|
}
|