Skip to content

Commit

Permalink
Merge pull request #4 from qmuntal/gltf
Browse files Browse the repository at this point in the history
gltf/draco: implement mesh decoding
  • Loading branch information
qmuntal committed Mar 3, 2021
2 parents 17f735f + 587a097 commit 071dd6f
Show file tree
Hide file tree
Showing 6 changed files with 404 additions and 0 deletions.
136 changes: 136 additions & 0 deletions gltf/draco/gltf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package draco

import (
"encoding/json"
"fmt"
"reflect"
"unsafe"

"github.com/qmuntal/draco-go/draco"
"github.com/qmuntal/gltf"
"github.com/qmuntal/gltf/binary"
"github.com/qmuntal/gltf/modeler"
)

const (
// ExtensionName defines the KHR_draco_mesh_compression unique key.
ExtensionName = "KHR_draco_mesh_compression"
)

func init() {
gltf.RegisterExtension(ExtensionName, Unmarshal)
}

// Unmarshal decodes the json data into the correct type.
func Unmarshal(data []byte) (interface{}, error) {
drc := new(PrimitiveExt)
err := json.Unmarshal(data, drc)
return drc, err
}

// PrimitiveExt extends the gltf.Primtive info to handle draco compressed meshes.
type PrimitiveExt struct {
Extensions gltf.Extensions `json:"extensions,omitempty"`
Extras interface{} `json:"extras,omitempty"`
BufferView uint32 `json:"bufferView"`
Attributes gltf.Attribute `json:"attributes"`
}

// GetPrimitiveExt retrieve a PrimitiveExt from p.
// If p does not contain the draco extensions it returns nil.
func GetPrimitiveExt(p *gltf.Primitive) *PrimitiveExt {
var pe *PrimitiveExt
if ext, ok := p.Extensions[ExtensionName]; ok {
if pe, ok = ext.(*PrimitiveExt); !ok {
return nil
}
} else {
return nil
}
return pe
}

// Mesh contains the necessary information to process a draco-encoded
// in a gltf context.
type Mesh struct {
doc *gltf.Document
m *draco.Mesh
}

// UnmarshalMesh unmarshal the draco-encoded mesh from a gltf.BufferView
func UnmarshalMesh(doc *gltf.Document, bv *gltf.BufferView) (*Mesh, error) {
data, err := modeler.ReadBufferView(doc, bv)
if err != nil {
return nil, err
}
if tp := draco.GetEncodedGeometryType(data); tp != draco.EGT_TRIANGULAR_MESH {
return nil, fmt.Errorf("draco-go: unsupported geometry type %v", tp)
}
m := draco.NewMesh()
d := draco.NewDecoder()
if err := d.DecodeMesh(m, data); err != nil {
return nil, err
}
return &Mesh{
doc: doc,
m: m,
}, nil
}

// ReadIndices reads the faces of the Mesh.
// buffer can be nil.
func (m Mesh) ReadIndices(buffer []uint32) []uint32 {
return m.m.Faces(buffer)
}

// ReadAttr reads the named attribute of a gltf.Primitive.
// If the attribute is defined in the primitive but not in the mesh
// it fallbacks to modeler.ReadAccessor.
// buffer can be nil.
func (m Mesh) ReadAttr(p *gltf.Primitive, name string, buffer interface{}) (interface{}, error) {
var (
gltfIndex, dracoID uint32
ok bool
)
if gltfIndex, ok = p.Attributes[name]; !ok {
return nil, nil
}
pe := GetPrimitiveExt(p)
ok = false
if pe != nil {
dracoID, ok = pe.Attributes[name]
}
acr := m.doc.Accessors[gltfIndex]
if !ok {
return modeler.ReadAccessor(m.doc, acr, buffer)
}
attr := m.m.AttrByUniqueID(dracoID)
if attr == nil {
return nil, fmt.Errorf("draco: mesh does not contain attribute %v", dracoID)
}
buffer = binary.MakeSliceBuffer(acr.ComponentType, acr.Type, acr.Count, buffer)
sh := new(reflect.SliceHeader)
sh.Data = reflect.ValueOf(buffer).Pointer()
sh.Len = int(acr.Type.Components() * acr.Count)
sh.Cap = sh.Len
var data interface{}
switch acr.ComponentType {
case gltf.ComponentByte:
data = *(*[]int8)(unsafe.Pointer(sh))
case gltf.ComponentUbyte:
data = *(*[]uint8)(unsafe.Pointer(sh))
case gltf.ComponentShort:
data = *(*[]int16)(unsafe.Pointer(sh))
case gltf.ComponentUshort:
data = *(*[]uint16)(unsafe.Pointer(sh))
case gltf.ComponentUint:
data = *(*[]uint32)(unsafe.Pointer(sh))
case gltf.ComponentFloat:
data = *(*[]float32)(unsafe.Pointer(sh))
default:
panic("draco-go: unsupported data type")
}

_, _ = m.m.AttrData(attr, data)
return buffer, nil
}
102 changes: 102 additions & 0 deletions gltf/draco/gltf_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package draco

import (
"reflect"
"testing"

"github.com/qmuntal/gltf"
)

func TestUnmarshal(t *testing.T) {
type args struct {
data []byte
}
tests := []struct {
name string
args args
want interface{}
wantErr bool
}{
{"base", args{[]byte(`{
"bufferView" : 5,
"attributes" : {
"POSITION" : 0,
"NORMAL" : 1,
"TEXCOORD_0" : 2,
"WEIGHTS_0" : 3,
"JOINTS_0" : 4
}
}`)}, &PrimitiveExt{BufferView: 5, Attributes: gltf.Attribute{
"JOINTS_0": 4,
"NORMAL": 1,
"POSITION": 0,
"TEXCOORD_0": 2,
"WEIGHTS_0": 3,
}}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Unmarshal(tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("Unmarshal() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Unmarshal() = %v, want %v", got, tt.want)
}
})
}
}

func TestUnmarshalMesh(t *testing.T) {
doc, err := gltf.Open("testdata/box/Box.gltf")
if err != nil {
t.Fatal(err)
}
pd, err := UnmarshalMesh(doc, doc.BufferViews[0])
if err != nil {
t.Fatal(err)
}
p := doc.Meshes[0].Primitives[0]
indWant := []uint32{2, 5, 6, 3, 11, 8, 8, 11, 12, 14, 9, 17}
if got := pd.ReadIndices(nil); !reflect.DeepEqual(indWant, got) {
t.Errorf("ReadIndices want %v, got %v", indWant, got)
}
_, err = pd.ReadAttr(p, "POSITION", nil)
if err != nil {
t.Error(err)
}
_, err = pd.ReadAttr(p, "NORMAL", [][3]float32{{1, 2, 3}})
if err != nil {
t.Error(err)
}
_, err = pd.ReadAttr(p, "OTHER", nil)
if err != nil {
t.Error(err)
}
}

func TestGetPrimitiveExt(t *testing.T) {
type args struct {
p *gltf.Primitive
}
tests := []struct {
name string
args args
want *PrimitiveExt
}{
{"no extension", args{&gltf.Primitive{}}, nil},
{"other extension", args{&gltf.Primitive{Extensions: gltf.Extensions{"other": nil}}}, nil},
{"draco other extension", args{&gltf.Primitive{Extensions: gltf.Extensions{ExtensionName: nil}}}, nil},
{"draco extension", args{&gltf.Primitive{Extensions: gltf.Extensions{ExtensionName: &PrimitiveExt{
BufferView: 1,
}}}}, &PrimitiveExt{BufferView: 1}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetPrimitiveExt(tt.args.p); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetPrimitiveExt() = %v, want %v", got, tt.want)
}
})
}
}
10 changes: 10 additions & 0 deletions gltf/draco/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/qmuntal/draco-go/gltf/draco

go 1.16

require (
github.com/qmuntal/draco-go v0.4.0
github.com/qmuntal/gltf v0.19.0
)

replace github.com/qmuntal/draco-go => ../..
4 changes: 4 additions & 0 deletions gltf/draco/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg=
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/qmuntal/gltf v0.19.0 h1:FFPoHZBNIHzlPPEUudQvQgV32VMohGE2pAqIBzdu0RQ=
github.com/qmuntal/gltf v0.19.0/go.mod h1:ENqYfECmeaqs2BWXWe6OKtMC8ucZII6s9OHr6F5oZ94=
Binary file added gltf/draco/testdata/box/Box.bin
Binary file not shown.
Loading

0 comments on commit 071dd6f

Please sign in to comment.