From 6b9d99392ef11a92a4c96a23468bd36256a12b96 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Tue, 13 Oct 2020 17:58:59 +0200 Subject: [PATCH] refactor writter --- README.md | 16 +- binary/bench_test.go | 8 +- binary/binary.go | 4 +- binary/encode.go | 51 +++--- binary/encode_test.go | 2 +- binary/example_test.go | 11 +- binary/size.go | 37 +---- binary/unsafe.go | 28 ++-- const.go | 25 +++ decoder.go | 6 +- modeler/compression.go | 52 ------ modeler/compression_test.go | 55 ------- modeler/example_test.go | 16 +- modeler/modeler.go | 269 ++++++++++++------------------- modeler/modeler_test.go | 313 ++++++++++++++++-------------------- struct.go | 12 ++ struct_test.go | 23 +++ 17 files changed, 378 insertions(+), 550 deletions(-) delete mode 100644 modeler/compression.go delete mode 100644 modeler/compression_test.go diff --git a/README.md b/README.md index 6ddf213..54be919 100644 --- a/README.md +++ b/README.md @@ -94,11 +94,11 @@ import ( ) func main() { - m := modeler.NewModeler() - positionAccessor := m.AddPosition(0, [][3]float32{{43, 43, 0}, {83, 43, 0}, {63, 63, 40}, {43, 83, 0}, {83, 83, 0}}) - indicesAccessor := m.AddIndices(0, []uint8{0, 1, 2, 3, 1, 0, 0, 2, 3, 1, 4, 2, 4, 3, 2, 4, 1, 3}) - colorIndices := m.AddColor(0, [][3]uint8{{50, 155, 255}, {0, 100, 200}, {255, 155, 50}, {155, 155, 155}, {25, 25, 25}}) - m.Document.Meshes = []*gltf.Mesh{{ + doc := gltf.NewDocument() + positionAccessor := modeler.WritePosition(doc, [][3]float32{{43, 43, 0}, {83, 43, 0}, {63, 63, 40}, {43, 83, 0}, {83, 83, 0}}) + indicesAccessor := modeler.WriteIndices(doc, []uint8{0, 1, 2, 3, 1, 0, 0, 2, 3, 1, 4, 2, 4, 3, 2, 4, 1, 3}) + colorIndices := modeler.WriteColor(doc, [][3]uint8{{50, 155, 255}, {0, 100, 200}, {255, 155, 50}, {155, 155, 155}, {25, 25, 25}}) + doc.Meshes = []*gltf.Mesh{{ Name: "Pyramid", Primitives: []*gltf.Primitive{{ Indices: gltf.Index(indicesAccessor), @@ -108,9 +108,9 @@ func main() { }, }}, }} - m.Nodes = []*gltf.Node{{Name: "Root", Mesh: gltf.Index(0)}} - m.Scenes[0].Nodes = append(m.Scenes[0].Nodes, 0) - if err := gltf.SaveBinary(m.Document, "./example.glb"); err != nil { + doc.Nodes = []*gltf.Node{{Name: "Root", Mesh: gltf.Index(0)}} + doc.Scenes[0].Nodes = append(doc.Scenes[0].Nodes, 0) + if err := gltf.SaveBinary(doc, "./example.glb"); err != nil { panic(err) } } diff --git a/binary/bench_test.go b/binary/bench_test.go index 1a308c4..598cf56 100644 --- a/binary/bench_test.go +++ b/binary/bench_test.go @@ -9,7 +9,7 @@ import ( ) func BenchmarkNative(b *testing.B) { - s := 1000 + var s uint32 = 1000 bs := make([]byte, s*SizeOfElement(gltf.ComponentFloat, gltf.AccessorVec3)) data := make([][3]float32, s) b.ResetTimer() @@ -21,17 +21,17 @@ func BenchmarkNative(b *testing.B) { } func BenchmarkWrite(b *testing.B) { - s := 1000 + var s uint32 = 1000 bs := make([]byte, s*SizeOfElement(gltf.ComponentFloat, gltf.AccessorVec3)) data := make([][3]float32, s) b.ResetTimer() for n := 0; n < b.N; n++ { - Write(bs, data) + Write(bs, 0, data) } } func BenchmarkWrite_builtint(b *testing.B) { - s := 1000 + var s uint32 = 1000 bs := bytes.NewBuffer(make([]byte, s*SizeOfElement(gltf.ComponentFloat, gltf.AccessorVec3))) data := make([][3]float32, s) b.ResetTimer() diff --git a/binary/binary.go b/binary/binary.go index 7833f26..821a541 100644 --- a/binary/binary.go +++ b/binary/binary.go @@ -45,8 +45,8 @@ var Short shortComponent // Ushort is the usnigned short implementation of Component. var Ushort ushortComponent -// UInt is the unsigned int implementation of Component. -var UInt uintComponent +// Uint is the unsigned int implementation of Component. +var Uint uintComponent // Float is the float implementation of Component. var Float floatComponent diff --git a/binary/encode.go b/binary/encode.go index a5b883c..2c348e2 100644 --- a/binary/encode.go +++ b/binary/encode.go @@ -9,18 +9,18 @@ import ( "github.com/qmuntal/gltf" ) -// Read reads structured binary data from r into data. +// Read reads structured binary data from b into data. // Data should be a slice of glTF predefined fixed-size types, // else it fallbacks to `encoding/binary.Read`. // // If data length is greater than the length of b, Read returns io.ErrShortBuffer. func Read(b []byte, data interface{}) error { c, t, n := Type(data) - if n <= 0 { + if n == 0 { return binary.Read(bytes.NewReader(b), binary.LittleEndian, data) } - e := SizeOfElement(c, t) - if len(b) < n*e { + e := int(SizeOfElement(c, t)) + if len(b) < int(n)*e { return io.ErrShortBuffer } switch data := data.(type) { @@ -184,46 +184,51 @@ func Read(b []byte, data interface{}) error { } case []uint32: for i := range data { - data[i] = UInt.Scalar(b[e*i:]) + data[i] = Uint.Scalar(b[e*i:]) } case [][2]uint32: for i := range data { - data[i] = UInt.Vec2(b[e*i:]) + data[i] = Uint.Vec2(b[e*i:]) } case [][3]uint32: for i := range data { - data[i] = UInt.Vec3(b[e*i:]) + data[i] = Uint.Vec3(b[e*i:]) } case [][4]uint32: for i := range data { - data[i] = UInt.Vec4(b[e*i:]) + data[i] = Uint.Vec4(b[e*i:]) } case [][2][2]uint32: for i := range data { - data[i] = UInt.Mat2(b[e*i:]) + data[i] = Uint.Mat2(b[e*i:]) } case [][3][3]uint32: for i := range data { - data[i] = UInt.Mat3(b[e*i:]) + data[i] = Uint.Mat3(b[e*i:]) } case [][4][4]uint32: for i := range data { - data[i] = UInt.Mat4(b[e*i:]) + data[i] = Uint.Mat4(b[e*i:]) } } return nil } // Write writes the binary representation of data into b. +// If stride is diferent than zero data will be interleaved. +// // Data must be a slice of glTF predefined fixed-size types, // else it fallbacks to `encoding/binary.Write`. -func Write(b []byte, data interface{}) error { +func Write(b []byte, stride uint32, data interface{}) error { c, t, n := Type(data) - if n <= 0 { + if n == 0 { return binary.Write(bytes.NewBuffer(b), binary.LittleEndian, data) } - e := SizeOfElement(c, t) - if len(b) < e*n { + e := int(stride) + if stride == 0 { + e = int(SizeOfElement(c, t)) + } + if len(b) < e*int(n) { return io.ErrShortBuffer } switch data := data.(type) { @@ -245,7 +250,7 @@ func Write(b []byte, data interface{}) error { } case []int8: for i, x := range data { - b[i] = byte(x) + b[e*i] = byte(x) } case [][2]int8: for i := range data { @@ -383,31 +388,31 @@ func Write(b []byte, data interface{}) error { } case []uint32: for i := range data { - UInt.PutScalar(b[e*i:], data[i]) + Uint.PutScalar(b[e*i:], data[i]) } case [][2]uint32: for i := range data { - UInt.PutVec2(b[e*i:], data[i]) + Uint.PutVec2(b[e*i:], data[i]) } case [][3]uint32: for i := range data { - UInt.PutVec3(b[e*i:], data[i]) + Uint.PutVec3(b[e*i:], data[i]) } case [][4]uint32: for i := range data { - UInt.PutVec4(b[e*i:], data[i]) + Uint.PutVec4(b[e*i:], data[i]) } case [][2][2]uint32: for i := range data { - UInt.PutMat2(b[e*i:], data[i]) + Uint.PutMat2(b[e*i:], data[i]) } case [][3][3]uint32: for i := range data { - UInt.PutMat3(b[e*i:], data[i]) + Uint.PutMat3(b[e*i:], data[i]) } case [][4][4]uint32: for i := range data { - UInt.PutMat4(b[e*i:], data[i]) + Uint.PutMat4(b[e*i:], data[i]) } } return nil diff --git a/binary/encode_test.go b/binary/encode_test.go index b1aee06..e14d44f 100644 --- a/binary/encode_test.go +++ b/binary/encode_test.go @@ -305,7 +305,7 @@ func TestWrite(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := make([]byte, tt.args.n) - if err := Write(b, tt.args.data); (err != nil) != tt.wantErr { + if err := Write(b, 0, tt.args.data); (err != nil) != tt.wantErr { t.Errorf("Write() error = %v, wantErr %v", err, tt.wantErr) } if !tt.wantErr && !reflect.DeepEqual(b, tt.want) { diff --git a/binary/example_test.go b/binary/example_test.go index 3e9bc73..151952d 100644 --- a/binary/example_test.go +++ b/binary/example_test.go @@ -2,6 +2,7 @@ package binary_test import ( "fmt" + "github.com/qmuntal/gltf" "github.com/qmuntal/gltf/binary" ) @@ -12,13 +13,13 @@ func ExampleWrite() { vertices := [][3]float32{{43, 43, 0}, {83, 43, 0}, {63, 63, 40}, {43, 83, 0}, {83, 83, 0}} // Allocate buffer - sizeIndices := len(indices) * binary.SizeOfElement(gltf.ComponentUbyte, gltf.AccessorScalar) - sizeVertices := len(vertices) * binary.SizeOfElement(gltf.ComponentFloat, gltf.AccessorVec3) + sizeIndices := uint32(len(indices)) * binary.SizeOfElement(gltf.ComponentUbyte, gltf.AccessorScalar) + sizeVertices := uint32(len(vertices)) * binary.SizeOfElement(gltf.ComponentFloat, gltf.AccessorVec3) b := make([]byte, sizeIndices+sizeVertices) // Write - binary.Write(b, indices) - binary.Write(b[sizeIndices:], vertices) + binary.Write(b, 0, indices) + binary.Write(b[sizeIndices:], 0, vertices) fmt.Print(b) // Output: @@ -32,7 +33,7 @@ func ExampleRead() { // Define buffer b := []byte{0, 1, 2, 3, 1, 0, 0, 2, 3, 1, 4, 2, 4, 3, 2, 4, 1, 3, 0, 0, 44, 66, 0, 0, 44, 66, 0, 0, 0, 0, 0, 0, 166, 66, 0, 0, 44, 66, 0, 0, 0, 0, 0, 0, 124, 66, 0, 0, 124, 66, 0, 0, 32, 66, 0, 0, 44, 66, 0, 0, 166, 66, 0, 0, 0, 0, 0, 0, 166, 66, 0, 0, 166, 66, 0, 0, 0, 0} - sizeIndices := len(indices) * binary.SizeOfElement(gltf.ComponentUbyte, gltf.AccessorScalar) + sizeIndices := uint32(len(indices)) * binary.SizeOfElement(gltf.ComponentUbyte, gltf.AccessorScalar) // Write binary.Read(b, indices) diff --git a/binary/size.go b/binary/size.go index 62066a1..59d3be2 100644 --- a/binary/size.go +++ b/binary/size.go @@ -7,36 +7,11 @@ import ( "github.com/qmuntal/gltf" ) -// SizeOf returns the size, in bytes, of a component type. -func SizeOf(c gltf.ComponentType) int { - return map[gltf.ComponentType]int{ - gltf.ComponentByte: 1, - gltf.ComponentUbyte: 1, - gltf.ComponentShort: 2, - gltf.ComponentUshort: 2, - gltf.ComponentUint: 4, - gltf.ComponentFloat: 4, - }[c] -} - -// ComponentsOf returns the number of components of an accessor type. -func ComponentsOf(t gltf.AccessorType) int { - return map[gltf.AccessorType]int{ - gltf.AccessorScalar: 1, - gltf.AccessorVec2: 2, - gltf.AccessorVec3: 3, - gltf.AccessorVec4: 4, - gltf.AccessorMat2: 4, - gltf.AccessorMat3: 9, - gltf.AccessorMat4: 16, - }[t] -} - // SizeOfElement returns the size, in bytes, of an element. // The element size may not be (component size) * (number of components), // as some of the elements are tightly packed in order to ensure // that they are aligned to 4-byte boundaries. -func SizeOfElement(c gltf.ComponentType, t gltf.AccessorType) int { +func SizeOfElement(c gltf.ComponentType, t gltf.AccessorType) uint32 { // special cases switch { case (t == gltf.AccessorVec3 || t == gltf.AccessorVec2) && (c == gltf.ComponentByte || c == gltf.ComponentUbyte): @@ -50,17 +25,17 @@ func SizeOfElement(c gltf.ComponentType, t gltf.AccessorType) int { case t == gltf.AccessorMat3 && (c == gltf.ComponentShort || c == gltf.ComponentUshort): return 24 } - return SizeOf(c) * ComponentsOf(t) + return c.ByteSize() * t.Components() } // Type returns the associated glTF type data. // If data is an slice, it also returns the length of the slice. -// If data does not have an associated glTF type, length will be -1. -func Type(data interface{}) (c gltf.ComponentType, t gltf.AccessorType, length int) { +// If data does not have an associated glTF type length will be 0. +func Type(data interface{}) (c gltf.ComponentType, t gltf.AccessorType, length uint32) { v := reflect.ValueOf(data) switch v.Kind() { case reflect.Slice: - length = v.Len() + length = uint32(v.Len()) } switch data.(type) { case []int8, int8: @@ -148,7 +123,7 @@ func Type(data interface{}) (c gltf.ComponentType, t gltf.AccessorType, length i case [][4][4]float32, [4][4]float32: c, t = gltf.ComponentFloat, gltf.AccessorMat4 default: - length = -1 + length = 0 } return } diff --git a/binary/unsafe.go b/binary/unsafe.go index 910d86c..e4be5a1 100644 --- a/binary/unsafe.go +++ b/binary/unsafe.go @@ -135,64 +135,64 @@ func (shortComponent) PutMat4(b []byte, v [4][4]int16) { } func (floatComponent) Scalar(b []byte) float32 { - v := UInt.Scalar(b) + v := Uint.Scalar(b) return *(*float32)(unsafe.Pointer(&v)) } func (floatComponent) PutScalar(b []byte, v float32) { - UInt.PutScalar(b, *(*uint32)(unsafe.Pointer(&v))) + Uint.PutScalar(b, *(*uint32)(unsafe.Pointer(&v))) } func (floatComponent) Vec2(b []byte) [2]float32 { - v := UInt.Vec2(b) + v := Uint.Vec2(b) return *(*[2]float32)(unsafe.Pointer(&v)) } func (floatComponent) PutVec2(b []byte, v [2]float32) { - UInt.PutVec2(b, *(*[2]uint32)(unsafe.Pointer(&v))) + Uint.PutVec2(b, *(*[2]uint32)(unsafe.Pointer(&v))) } func (floatComponent) Vec3(b []byte) [3]float32 { - v := UInt.Vec3(b) + v := Uint.Vec3(b) return *(*[3]float32)(unsafe.Pointer(&v)) } func (floatComponent) PutVec3(b []byte, v [3]float32) { - UInt.PutVec3(b, *(*[3]uint32)(unsafe.Pointer(&v))) + Uint.PutVec3(b, *(*[3]uint32)(unsafe.Pointer(&v))) } func (floatComponent) Vec4(b []byte) [4]float32 { - v := UInt.Vec4(b) + v := Uint.Vec4(b) return *(*[4]float32)(unsafe.Pointer(&v)) } func (floatComponent) PutVec4(b []byte, v [4]float32) { - UInt.PutVec4(b, *(*[4]uint32)(unsafe.Pointer(&v))) + Uint.PutVec4(b, *(*[4]uint32)(unsafe.Pointer(&v))) } func (floatComponent) Mat2(b []byte) [2][2]float32 { - v := UInt.Mat2(b) + v := Uint.Mat2(b) return *(*[2][2]float32)(unsafe.Pointer(&v)) } func (floatComponent) PutMat2(b []byte, v [2][2]float32) { - UInt.PutMat2(b, *(*[2][2]uint32)(unsafe.Pointer(&v))) + Uint.PutMat2(b, *(*[2][2]uint32)(unsafe.Pointer(&v))) } func (floatComponent) Mat3(b []byte) [3][3]float32 { - v := UInt.Mat3(b) + v := Uint.Mat3(b) return *(*[3][3]float32)(unsafe.Pointer(&v)) } func (floatComponent) PutMat3(b []byte, v [3][3]float32) { - UInt.PutMat3(b, *(*[3][3]uint32)(unsafe.Pointer(&v))) + Uint.PutMat3(b, *(*[3][3]uint32)(unsafe.Pointer(&v))) } func (floatComponent) Mat4(b []byte) [4][4]float32 { - v := UInt.Mat4(b) + v := Uint.Mat4(b) return *(*[4][4]float32)(unsafe.Pointer(&v)) } func (floatComponent) PutMat4(b []byte, v [4][4]float32) { - UInt.PutMat4(b, *(*[4][4]uint32)(unsafe.Pointer(&v))) + Uint.PutMat4(b, *(*[4][4]uint32)(unsafe.Pointer(&v))) } diff --git a/const.go b/const.go index 7c408ce..a2ee956 100644 --- a/const.go +++ b/const.go @@ -38,6 +38,18 @@ const ( ComponentUint ) +// ByteSize returns the size of a component in bytes. +func (c ComponentType) ByteSize() uint32 { + return map[ComponentType]uint32{ + ComponentByte: 1, + ComponentUbyte: 1, + ComponentShort: 2, + ComponentUshort: 2, + ComponentUint: 4, + ComponentFloat: 4, + }[c] +} + // UnmarshalJSON unmarshal the component type with the correct default values. func (c *ComponentType) UnmarshalJSON(data []byte) error { var tmp uint16 @@ -87,6 +99,19 @@ const ( AccessorMat4 ) +// Components returns the number of components of an accessor type. +func (a AccessorType) Components() uint32 { + return map[AccessorType]uint32{ + AccessorScalar: 1, + AccessorVec2: 2, + AccessorVec3: 3, + AccessorVec4: 4, + AccessorMat2: 4, + AccessorMat3: 9, + AccessorMat4: 16, + }[a] +} + // UnmarshalJSON unmarshal the accessor type with the correct default values. func (a *AccessorType) UnmarshalJSON(data []byte) error { var tmp string diff --git a/decoder.go b/decoder.go index b7fd099..d79949f 100644 --- a/decoder.go +++ b/decoder.go @@ -1,7 +1,6 @@ package gltf import ( - "math" "bufio" "bytes" "encoding/binary" @@ -9,6 +8,7 @@ import ( "errors" "fmt" "io" + "math" "os" "path/filepath" "strings" @@ -16,8 +16,8 @@ import ( ) const ( - defaultMaxExternalBufferCount = 10 - defaultMaxMemoryAllocation = math.MaxUint32 // 4GB + defaultMaxExternalBufferCount = 10 + defaultMaxMemoryAllocation = math.MaxUint32 // 4GB ) // ReadHandler is the interface that wraps the ReadFullResource method. diff --git a/modeler/compression.go b/modeler/compression.go deleted file mode 100644 index a8a6111..0000000 --- a/modeler/compression.go +++ /dev/null @@ -1,52 +0,0 @@ -package modeler - -import "math" - -func compressUint32(data []uint32) interface{} { - u8, u16 := true, true - // Start at the end, as there are more possibilities to have a higher numbers. - for i := len(data) - 1; i >= 0; i-- { - x := data[i] - if u16 && x >= math.MaxUint16 { - u8, u16 = false, false - break - } else if u8 && x >= math.MaxUint8 { - u8 = false - } - } - if u8 { - optData := make([]uint8, len(data)) - for i, x := range data { - optData[i] = uint8(x) - } - return optData - } - if u16 { - optData := make([]uint16, len(data)) - for i, x := range data { - optData[i] = uint16(x) - } - return optData - } - return data -} - -func compressUint16(data []uint16) interface{} { - u8 := true - // Start at the end, as there are more possibilities to have a higher numbers. - for i := len(data) - 1; i >= 0; i-- { - x := data[i] - if u8 && x >= math.MaxUint8 { - u8 = false - break - } - } - if u8 { - optData := make([]uint8, len(data)) - for i, x := range data { - optData[i] = uint8(x) - } - return optData - } - return data -} diff --git a/modeler/compression_test.go b/modeler/compression_test.go deleted file mode 100644 index e029ba3..0000000 --- a/modeler/compression_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package modeler - -import ( - "math" - "reflect" - "testing" -) - -func Test_compressUint32(t *testing.T) { - type args struct { - data []uint32 - } - tests := []struct { - name string - args args - want interface{} - }{ - {"empty", args{[]uint32{}}, []uint8{}}, - {"32", args{[]uint32{1, math.MaxUint16}}, []uint32{1, math.MaxUint16}}, - {"32-2", args{[]uint32{1, math.MaxUint8, math.MaxUint16}}, []uint32{1, math.MaxUint8, math.MaxUint16}}, - {"32-3", args{[]uint32{1, math.MaxUint16, math.MaxUint8}}, []uint32{1, math.MaxUint16, math.MaxUint8}}, - {"32-4", args{[]uint32{1, math.MaxUint8}}, []uint16{1, math.MaxUint8}}, - {"16", args{[]uint32{1, math.MaxUint16 - 1}}, []uint16{1, math.MaxUint16 - 1}}, - {"8", args{[]uint32{1, math.MaxUint8 - 1}}, []uint8{1, math.MaxUint8 - 1}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := compressUint32(tt.args.data); !reflect.DeepEqual(got, tt.want) { - t.Errorf("compressUint32() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_compressUint16(t *testing.T) { - type args struct { - data []uint16 - } - tests := []struct { - name string - args args - want interface{} - }{ - {"empty", args{[]uint16{}}, []uint8{}}, - {"16", args{[]uint16{1, math.MaxUint8}}, []uint16{1, math.MaxUint8}}, - {"8", args{[]uint16{1, math.MaxUint8 - 1}}, []uint8{1, math.MaxUint8 - 1}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := compressUint16(tt.args.data); !reflect.DeepEqual(got, tt.want) { - t.Errorf("compressUint16() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/modeler/example_test.go b/modeler/example_test.go index 4e2e99f..ad67b04 100644 --- a/modeler/example_test.go +++ b/modeler/example_test.go @@ -6,11 +6,11 @@ import ( ) func Example() { - m := modeler.NewModeler() - positionAccessor := m.AddPosition(0, [][3]float32{{43, 43, 0}, {83, 43, 0}, {63, 63, 40}, {43, 83, 0}, {83, 83, 0}}) - indicesAccessor := m.AddIndices(0, []uint8{0, 1, 2, 3, 1, 0, 0, 2, 3, 1, 4, 2, 4, 3, 2, 4, 1, 3}) - colorIndices := m.AddColor(0, [][3]uint8{{50, 155, 255}, {0, 100, 200}, {255, 155, 50}, {155, 155, 155}, {25, 25, 25}}) - m.Document.Meshes = []*gltf.Mesh{{ + doc := gltf.NewDocument() + positionAccessor := modeler.WritePosition(doc, [][3]float32{{43, 43, 0}, {83, 43, 0}, {63, 63, 40}, {43, 83, 0}, {83, 83, 0}}) + indicesAccessor := modeler.WriteIndices(doc, []uint8{0, 1, 2, 3, 1, 0, 0, 2, 3, 1, 4, 2, 4, 3, 2, 4, 1, 3}) + colorIndices := modeler.WriteColor(doc, [][3]uint8{{50, 155, 255}, {0, 100, 200}, {255, 155, 50}, {155, 155, 155}, {25, 25, 25}}) + doc.Meshes = []*gltf.Mesh{{ Name: "Pyramid", Primitives: []*gltf.Primitive{ { @@ -22,9 +22,9 @@ func Example() { }, }, }} - m.Nodes = []*gltf.Node{{Name: "Root", Mesh: gltf.Index(0)}} - m.Scenes[0].Nodes = append(m.Scenes[0].Nodes, 0) - if err := gltf.SaveBinary(m.Document, "./example.glb"); err != nil { + doc.Nodes = []*gltf.Node{{Name: "Root", Mesh: gltf.Index(0)}} + doc.Scenes[0].Nodes = append(doc.Scenes[0].Nodes, 0) + if err := gltf.SaveBinary(doc, "./example.glb"); err != nil { panic(err) } } diff --git a/modeler/modeler.go b/modeler/modeler.go index 7f280ac..8a46bd4 100644 --- a/modeler/modeler.go +++ b/modeler/modeler.go @@ -14,143 +14,82 @@ import ( "github.com/qmuntal/gltf/binary" ) -// CompressionLevel defines the different levels of compression. -type CompressionLevel uint8 - -const ( - // CompressionNone will not apply any compression. - CompressionNone CompressionLevel = iota - // CompressionLossless will reduce the byte size without sacrificing quality. - CompressionLossless -) - -// Modeler wraps a Document and add useful methods to build it. -// If Compress is true, all the data added to accessors that support different component types -// will be evaluated to see if it fits in a smaller component type. -type Modeler struct { - *gltf.Document - Compression CompressionLevel -} - -// NewModeler returns a new Modeler instance. -func NewModeler() *Modeler { - return &Modeler{ - Document: &gltf.Document{ - Scene: gltf.Index(0), - Scenes: []*gltf.Scene{{Name: "Root Scene"}}, - }, - Compression: CompressionLossless, - } -} - -// AddIndices adds a new INDICES accessor to the Document -// and fills the buffer with the indices data. +// WriteIndices adds a new INDICES accessor to doc +// and fills the last buffer with the indices data. // If success it returns the index of the new accessor. -func (m *Modeler) AddIndices(bufferIndex uint32, data interface{}) uint32 { - var ok bool +func WriteIndices(doc *gltf.Document, data interface{}) uint32 { switch data.(type) { - case []uint8: - ok = true - case []uint16: - ok = true - if m.Compression >= CompressionLossless { - data = compressUint16(data.([]uint16)) - } - case []uint32: - ok = true - if m.Compression >= CompressionLossless { - data = compressUint32(data.([]uint32)) - } - } - componentType, accessorType, length := binary.Type(data) - if !ok || length <= 0 { - panic(fmt.Sprintf("modeler.AddIndices: invalid type %T", data)) + case []uint8, []uint16, []uint32: + default: + panic(fmt.Sprintf("modeler.WriteIndices: invalid type %T", data)) } - index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, true) - return uint32(index) + return WriteAccessor(doc, gltf.TargetElementArrayBuffer, data) } -// AddNormal adds a new NORMAL accessor to the Document -// and fills the buffer with the indices data. +// WriteNormal adds a new NORMAL accessor to doc +// and fills the last buffer with the indices data. // If success it returns the index of the new accessor. -func (m *Modeler) AddNormal(bufferIndex uint32, data [][3]float32) uint32 { - componentType, accessorType, length := binary.Type(data) - index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, false) - return uint32(index) +func WriteNormal(doc *gltf.Document, data [][3]float32) uint32 { + return WriteAccessor(doc, gltf.TargetArrayBuffer, data) } -// AddTangent adds a new TANGENT accessor to the Document -// and fills the buffer with the indices data. +// WriteTangent adds a new TANGENT accessor to doc +// and fills the last buffer with the indices data. // If success it returns the index of the new accessor. -func (m *Modeler) AddTangent(bufferIndex uint32, data [][4]float32) uint32 { - componentType, accessorType, length := binary.Type(data) - index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, false) - return uint32(index) +func WriteTangent(doc *gltf.Document, data [][4]float32) uint32 { + return WriteAccessor(doc, gltf.TargetArrayBuffer, data) } -// AddTextureCoord adds a new TEXTURECOORD accessor to the Document -// and fills the buffer with the texturecoord data. +// WriteTextureCoord adds a new TEXTURECOORD accessor to doc +// and fills the last buffer with the texturecoord data. // If success it returns the index of the new accessor. -func (m *Modeler) AddTextureCoord(bufferIndex uint32, data interface{}) uint32 { - var normalized, ok bool +func WriteTextureCoord(doc *gltf.Document, data interface{}) uint32 { + var normalized bool switch data.(type) { case [][2]uint8, [][2]uint16: - ok = true normalized = true case [][2]float32: - ok = true - } - componentType, accessorType, length := binary.Type(data) - if !ok || length <= 0 { - panic(fmt.Sprintf("modeler.AddTextureCoord: invalid type %T", data)) + default: + panic(fmt.Sprintf("modeler.WriteTextureCoord: invalid type %T", data)) } - index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, false) - m.Document.Accessors[index].Normalized = normalized - return uint32(index) + index := WriteAccessor(doc, gltf.TargetArrayBuffer, data) + doc.Accessors[index].Normalized = normalized + return index } -// AddWeights adds a new WEIGHTS accessor to the Document -// and fills the buffer with the weights data. +// WriteWeights adds a new WEIGHTS accessor to doc +// and fills the last buffer with the weights data. // If success it returns the index of the new accessor. -func (m *Modeler) AddWeights(bufferIndex uint32, data interface{}) uint32 { - var normalized, ok bool +func WriteWeights(doc *gltf.Document, data interface{}) uint32 { + var normalized bool switch data.(type) { case [][4]uint8, [][4]uint16: - ok = true normalized = true case [][4]float32: - ok = true - } - componentType, accessorType, length := binary.Type(data) - if !ok || length <= 0 { - panic(fmt.Sprintf("modeler.AddWeights: invalid type %T", data)) + default: + panic(fmt.Sprintf("modeler.WriteWeights: invalid type %T", data)) } - index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, false) - m.Document.Accessors[index].Normalized = normalized - return uint32(index) + index := WriteAccessor(doc, gltf.TargetArrayBuffer, data) + doc.Accessors[index].Normalized = normalized + return index } -// AddJoints adds a new JOINTS accessor to the Document -// and fills the buffer with the joints data. +// WriteJoints adds a new JOINTS accessor to doc +// and fills the last buffer with the joints data. // If success it returns the index of the new accessor. -func (m *Modeler) AddJoints(bufferIndex uint32, data interface{}) uint32 { - var ok bool +func WriteJoints(doc *gltf.Document, data interface{}) uint32 { switch data.(type) { case [][4]uint8, [][4]uint16: - ok = true - } - componentType, accessorType, length := binary.Type(data) - if !ok || length <= 0 { - panic(fmt.Sprintf("modeler.AddJoints: invalid type %T", data)) + default: + panic(fmt.Sprintf("modeler.WriteJoints: invalid type %T", data)) } - index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, false) - return uint32(index) + return WriteAccessor(doc, gltf.TargetArrayBuffer, data) } -// AddPosition adds a new POSITION accessor to the Document -// and fills the buffer with the vertices data. +// WritePosition adds a new POSITION accessor to doc +// and fills the last buffer with the vertices data. // If success it returns the index of the new accessor. -func (m *Modeler) AddPosition(bufferIndex uint32, data [][3]float32) uint32 { +func WritePosition(doc *gltf.Document, data [][3]float32) uint32 { min := [3]float64{math.MaxFloat64, math.MaxFloat64, math.MaxFloat64} max := [3]float64{-math.MaxFloat64, -math.MaxFloat64, -math.MaxFloat64} for _, v := range data { @@ -159,105 +98,103 @@ func (m *Modeler) AddPosition(bufferIndex uint32, data [][3]float32) uint32 { max[i] = math.Max(max[i], float64(x)) } } - componentType, accessorType, length := binary.Type(data) - index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, false) - m.Accessors[index].Min = min[:] - m.Accessors[index].Max = max[:] - return uint32(index) + index := WriteAccessor(doc, gltf.TargetArrayBuffer, data) + doc.Accessors[index].Min = min[:] + doc.Accessors[index].Max = max[:] + return index } -// AddColor adds a new COLOR accessor to the Document +// WriteColor adds a new COLOR accessor to doc // and fills the buffer with the color data. // If success it returns the index of the new accessor. -func (m *Modeler) AddColor(bufferIndex uint32, data interface{}) uint32 { - var normalized, ok bool +func WriteColor(doc *gltf.Document, data interface{}) uint32 { + var normalized bool switch data.(type) { case []color.RGBA, []color.RGBA64, [][4]uint8, [][3]uint8, [][4]uint16, [][3]uint16: normalized = true - ok = true - case []gltf.RGBA, []gltf.RGB, [][3]float32, [][4]float32: - ok = true - } - componentType, accessorType, length := binary.Type(data) - if !ok || length <= 0 { - panic(fmt.Sprintf("modeler.AddColor: invalid type %T", data)) + case []gltf.RGB, []gltf.RGBA, [][3]float32, [][4]float32: + default: + panic(fmt.Sprintf("modeler.WriteColor: invalid type %T", data)) } - index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, false) - m.Document.Accessors[index].Normalized = normalized - return uint32(index) + index := WriteAccessor(doc, gltf.TargetArrayBuffer, data) + doc.Accessors[index].Normalized = normalized + return index } -// AddImage adds a new image to the Document +// WriteImage adds a new image to doc // and fills the buffer with the image data. // If success it returns the index of the new image. -func (m *Modeler) AddImage(bufferIndex uint32, name, mimeType string, r io.Reader) (uint32, error) { - buffer := m.buffer(bufferIndex) - offset := uint32(len(buffer.Data)) +func WriteImage(doc *gltf.Document, name string, mimeType string, r io.Reader) (uint32, error) { + var data []byte switch r := r.(type) { case *bytes.Buffer: - buffer.Data = append(buffer.Data, r.Bytes()...) + data = r.Bytes() default: - data, err := ioutil.ReadAll(r) + var err error + data, err = ioutil.ReadAll(r) if err != nil { return 0, err } - buffer.Data = append(buffer.Data, data...) } - index := m.addBufferView(bufferIndex, uint32(len(buffer.Data))-offset, offset, 0, false) - buffer.ByteLength = uint32(len(buffer.Data)) - m.BufferViews[index].Target = gltf.TargetNone - m.Images = append(m.Images, &gltf.Image{ + index := WriteBufferView(doc, gltf.TargetNone, data) + doc.Images = append(doc.Images, &gltf.Image{ Name: name, MimeType: mimeType, BufferView: gltf.Index(index), }) - return uint32(len(m.Images) - 1), nil + return uint32(len(doc.Images) - 1), nil } -func (m *Modeler) addAccessor(bufferIndex uint32, count int, data interface{}, componentType gltf.ComponentType, accessorType gltf.AccessorType, isIndex bool) uint32 { - buffer := m.buffer(bufferIndex) - offset := uint32(len(buffer.Data)) - padding := ((offset+3)/4)*4 - offset - elementSize := binary.SizeOfElement(componentType, accessorType) - size := uint32(count * elementSize) - buffer.ByteLength += uint32(size + padding) - buffer.Data = append(buffer.Data, make([]byte, size+padding)...) - // Cannot return error as the buffer has enough size and the data type is controlled. - _ = binary.Write(buffer.Data[offset+padding:], data) - index := m.addBufferView(bufferIndex, size, offset+padding, uint32(elementSize), isIndex) - m.Accessors = append(m.Accessors, &gltf.Accessor{ +// WriteAccessor adds a new Accessor to doc +// and fills the buffer with data. +// If success it returns the index of the new accessor. +func WriteAccessor(doc *gltf.Document, target gltf.Target, data interface{}) uint32 { + c, a, l := binary.Type(data) + index := WriteBufferView(doc, target, data) + doc.Accessors = append(doc.Accessors, &gltf.Accessor{ BufferView: gltf.Index(index), ByteOffset: 0, - ComponentType: componentType, - Type: accessorType, - Count: uint32(count), + ComponentType: c, + Type: a, + Count: l, }) - return uint32(len(m.Accessors) - 1) + return uint32(len(doc.Accessors) - 1) } -func (m *Modeler) addBufferView(buffer, size, offset, stride uint32, isIndices bool) uint32 { +// WriteBufferView adds a new BufferView to doc +// and fills the buffer with the data. +// If success it returns the index of the new buffer view. +func WriteBufferView(doc *gltf.Document, target gltf.Target, data interface{}) uint32 { + c, a, l := binary.Type(data) + size := l * binary.SizeOfElement(c, a) + buffer := lastBuffer(doc) + offset := uint32(len(buffer.Data)) + padding := getPadding(offset, c.ByteSize()) + buffer.ByteLength += size + padding + buffer.Data = append(buffer.Data, make([]byte, size+padding)...) + // Cannot return error as the buffer has enough size and the data type is controlled. + _ = binary.Write(buffer.Data[offset+padding:], 0, data) bufferView := &gltf.BufferView{ - Buffer: buffer, + Buffer: uint32(len(doc.Buffers)) - 1, ByteLength: size, ByteOffset: offset, + Target: target, } - if isIndices { - bufferView.Target = gltf.TargetElementArrayBuffer - } else { - bufferView.Target = gltf.TargetArrayBuffer - bufferView.ByteStride = stride + doc.BufferViews = append(doc.BufferViews, bufferView) + return uint32(len(doc.BufferViews)) - 1 +} + +func lastBuffer(doc *gltf.Document) *gltf.Buffer { + if len(doc.Buffers) == 0 { + doc.Buffers = append(doc.Buffers, new(gltf.Buffer)) } - m.BufferViews = append(m.BufferViews, bufferView) - return uint32(len(m.BufferViews)) - 1 + return doc.Buffers[len(doc.Buffers)-1] } -func (m *Modeler) buffer(bufferIndex uint32) *gltf.Buffer { - if int(bufferIndex) >= len(m.Buffers) { - l := len(m.Buffers) - m.Buffers = append(m.Buffers, make([]*gltf.Buffer, int(bufferIndex)-l+1)...) - for i := l; i < len(m.Buffers); i++ { - m.Buffers[i] = new(gltf.Buffer) - } +func getPadding(offset uint32, alignment uint32) uint32 { + padAlign := offset % alignment + if padAlign == 0 { + return 0 } - return m.Buffers[bufferIndex] + return alignment - padAlign } diff --git a/modeler/modeler_test.go b/modeler/modeler_test.go index 134b85f..0a1930b 100644 --- a/modeler/modeler_test.go +++ b/modeler/modeler_test.go @@ -4,54 +4,51 @@ import ( "bytes" "errors" "io" - "reflect" "testing" "github.com/go-test/deep" "github.com/qmuntal/gltf" ) -func TestNewModeler(t *testing.T) { - tests := []struct { - name string - want *Modeler - }{ - {"base", &Modeler{Document: &gltf.Document{ - Scene: gltf.Index(0), - Scenes: []*gltf.Scene{{Name: "Root Scene"}}, - }, Compression: CompressionLossless}}, +func TestAlignment(t *testing.T) { + doc := gltf.NewDocument() + WriteIndices(doc, []uint16{0, 1, 2}) + WritePosition(doc, [][3]float32{{0, 0, 0}, {1, 0, 0}, {0, 1, 0}}) + if len(doc.Buffers) != 1 { + t.Errorf("Testalignment() buffer size = %v, want 1", len(doc.Buffers)) } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := NewModeler(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewModeler() = %v, want %v", got, tt.want) - } - }) + buffer := doc.Buffers[0] + want := make([]byte, 44) + want[2], want[4] = 1, 2 + want[22], want[23] = 0x80, 0x3f + want[38], want[39] = 0x80, 0x3f + if diff := deep.Equal(buffer.Data, want); diff != nil { + t.Errorf("Testalignment() = %v", diff) + return } } -func TestModeler_AddNormal(t *testing.T) { +func TestWriteNormal(t *testing.T) { type args struct { - bufferIndex uint32 - data [][3]float32 + data [][3]float32 } tests := []struct { name string - m *Modeler + m *gltf.Document args args want uint32 wantDoc *gltf.Document }{ - {"base", &Modeler{Document: &gltf.Document{ + {"base", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, [][3]float32{{1, 2, 3}}}, 1, &gltf.Document{ + }, args{[][3]float32{{1, 2, 3}}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec3, ComponentType: gltf.ComponentFloat}, }, BufferViews: []*gltf.BufferView{ - {ByteLength: 12, Target: gltf.TargetArrayBuffer, ByteStride: 12}, + {ByteLength: 12, Target: gltf.TargetArrayBuffer}, }, Buffers: []*gltf.Buffer{ {ByteLength: 22, Data: []byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64}}, @@ -60,41 +57,40 @@ func TestModeler_AddNormal(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tt.m.AddNormal(tt.args.bufferIndex, tt.args.data) + got := WriteNormal(tt.m, tt.args.data) if tt.want != got { - t.Errorf("Modeler.AddNormal() = %v, want %v", got, tt.want) + t.Errorf("WriteNormal() = %v, want %v", got, tt.want) return } - if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil { - t.Errorf("Modeler.AddNormal() = %v", diff) + if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil { + t.Errorf("WriteNormal() = %v", diff) return } }) } } -func TestModeler_AddTangent(t *testing.T) { +func TestWriteTangent(t *testing.T) { type args struct { - bufferIndex uint32 - data [][4]float32 + data [][4]float32 } tests := []struct { name string - m *Modeler + m *gltf.Document args args want uint32 wantDoc *gltf.Document }{ - {"base", &Modeler{Document: &gltf.Document{ + {"base", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, [][4]float32{{1, 2, 3, 4}, {}}}, 1, &gltf.Document{ + }, args{[][4]float32{{1, 2, 3, 4}, {}}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentFloat}, }, BufferViews: []*gltf.BufferView{ - {ByteLength: 32, Target: gltf.TargetArrayBuffer, ByteStride: 16}, + {ByteLength: 32, Target: gltf.TargetArrayBuffer}, }, Buffers: []*gltf.Buffer{ {ByteLength: 42, Data: []byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, @@ -103,41 +99,40 @@ func TestModeler_AddTangent(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tt.m.AddTangent(tt.args.bufferIndex, tt.args.data) + got := WriteTangent(tt.m, tt.args.data) if tt.want != got { - t.Errorf("Modeler.AddTangent() = %v, want %v", got, tt.want) + t.Errorf("WriteTangent() = %v, want %v", got, tt.want) return } - if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil { - t.Errorf("Modeler.AddTangent() = %v", diff) + if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil { + t.Errorf("WriteTangent() = %v", diff) return } }) } } -func TestModeler_AddPosition(t *testing.T) { +func TestWritePosition(t *testing.T) { type args struct { - bufferIndex uint32 - data [][3]float32 + data [][3]float32 } tests := []struct { name string - m *Modeler + m *gltf.Document args args want uint32 wantDoc *gltf.Document }{ - {"base", &Modeler{Document: &gltf.Document{ + {"base", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, [][3]float32{{1, 2, 3}, {0, 0, -1}}}, 1, &gltf.Document{ + }, args{[][3]float32{{1, 2, 3}, {0, 0, -1}}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorVec3, ComponentType: gltf.ComponentFloat, Max: []float64{1, 2, 3}, Min: []float64{0, 0, -1}}, }, BufferViews: []*gltf.BufferView{ - {ByteLength: 24, Target: gltf.TargetArrayBuffer, ByteStride: 12}, + {ByteLength: 24, Target: gltf.TargetArrayBuffer}, }, Buffers: []*gltf.Buffer{ {ByteLength: 34, Data: []byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 191}}, @@ -146,56 +141,55 @@ func TestModeler_AddPosition(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tt.m.AddPosition(tt.args.bufferIndex, tt.args.data) + got := WritePosition(tt.m, tt.args.data) if tt.want != got { - t.Errorf("Modeler.AddPosition() = %v, want %v", got, tt.want) + t.Errorf("WritePosition() = %v, want %v", got, tt.want) return } - if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil { - t.Errorf("Modeler.AddPosition() = %v", diff) + if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil { + t.Errorf("WritePosition() = %v", diff) return } }) } } -func TestModeler_AddJoints(t *testing.T) { +func TestWriteJoints(t *testing.T) { type args struct { - bufferIndex uint32 - data interface{} + data interface{} } tests := []struct { name string - m *Modeler + m *gltf.Document args args want uint32 wantDoc *gltf.Document }{ - {"uint8", &Modeler{Document: &gltf.Document{ + {"uint8", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, [][4]uint8{{1, 2, 3, 4}}}, 1, &gltf.Document{ + }, args{[][4]uint8{{1, 2, 3, 4}}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUbyte}, }, BufferViews: []*gltf.BufferView{ - {ByteLength: 4, Target: gltf.TargetArrayBuffer, ByteStride: 4}, + {ByteLength: 4, Target: gltf.TargetArrayBuffer}, }, Buffers: []*gltf.Buffer{ {ByteLength: 14, Data: []byte{1, 2, 3, 4}}, }, }}, - {"uint16", &Modeler{Document: &gltf.Document{ + {"uint16", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, [][4]uint16{{1, 2, 3, 4}}}, 1, &gltf.Document{ + }, args{[][4]uint16{{1, 2, 3, 4}}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUshort}, }, BufferViews: []*gltf.BufferView{ - {ByteLength: 8, Target: gltf.TargetArrayBuffer, ByteStride: 8}, + {ByteLength: 8, Target: gltf.TargetArrayBuffer}, }, Buffers: []*gltf.Buffer{ {ByteLength: 18, Data: []byte{1, 0, 2, 0, 3, 0, 4, 0}}, @@ -204,71 +198,70 @@ func TestModeler_AddJoints(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tt.m.AddJoints(tt.args.bufferIndex, tt.args.data) + got := WriteJoints(tt.m, tt.args.data) if tt.want != got { - t.Errorf("Modeler.AddJoints() = %v, want %v", got, tt.want) + t.Errorf("WriteJoints() = %v, want %v", got, tt.want) return } - if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil { - t.Errorf("Modeler.AddJoints() = %v", diff) + if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil { + t.Errorf("WriteJoints() = %v", diff) return } }) } } -func TestModeler_AddWeights(t *testing.T) { +func TestWriteWeights(t *testing.T) { type args struct { - bufferIndex uint32 - data interface{} + data interface{} } tests := []struct { name string - m *Modeler + m *gltf.Document args args want uint32 wantDoc *gltf.Document }{ - {"uint8", &Modeler{Document: &gltf.Document{ + {"uint8", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, [][4]uint8{{1, 2, 3, 4}}}, 1, &gltf.Document{ + }, args{[][4]uint8{{1, 2, 3, 4}}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUbyte, Normalized: true}, }, BufferViews: []*gltf.BufferView{ - {ByteLength: 4, Target: gltf.TargetArrayBuffer, ByteStride: 4}, + {ByteLength: 4, Target: gltf.TargetArrayBuffer}, }, Buffers: []*gltf.Buffer{ {ByteLength: 14, Data: []byte{1, 2, 3, 4}}, }, }}, - {"uint16", &Modeler{Document: &gltf.Document{ + {"uint16", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, [][4]uint16{{1, 2, 3, 4}}}, 1, &gltf.Document{ + }, args{[][4]uint16{{1, 2, 3, 4}}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUshort, Normalized: true}, }, BufferViews: []*gltf.BufferView{ - {ByteLength: 8, Target: gltf.TargetArrayBuffer, ByteStride: 8}, + {ByteLength: 8, Target: gltf.TargetArrayBuffer}, }, Buffers: []*gltf.Buffer{ {ByteLength: 18, Data: []byte{1, 0, 2, 0, 3, 0, 4, 0}}, }, }}, - {"float", &Modeler{Document: &gltf.Document{ + {"float", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, [][4]float32{{1, 2, 3, 4}, {}}}, 1, &gltf.Document{ + }, args{[][4]float32{{1, 2, 3, 4}, {}}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentFloat}, }, BufferViews: []*gltf.BufferView{ - {ByteLength: 32, Target: gltf.TargetArrayBuffer, ByteStride: 16}, + {ByteLength: 32, Target: gltf.TargetArrayBuffer}, }, Buffers: []*gltf.Buffer{ {ByteLength: 42, Data: []byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, @@ -277,110 +270,106 @@ func TestModeler_AddWeights(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tt.m.AddWeights(tt.args.bufferIndex, tt.args.data) + got := WriteWeights(tt.m, tt.args.data) if tt.want != got { - t.Errorf("Modeler.AddWeights() = %v, want %v", got, tt.want) + t.Errorf("WriteWeights() = %v, want %v", got, tt.want) return } - if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil { - t.Errorf("Modeler.AddWeights() = %v", diff) + if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil { + t.Errorf("WriteWeights() = %v", diff) return } }) } } -func TestModeler_AddTextureCoord(t *testing.T) { +func TestWriteTextureCoord(t *testing.T) { type args struct { - bufferIndex uint32 - data interface{} + data interface{} } tests := []struct { name string - m *Modeler + m *gltf.Document args args want uint32 wantDoc *gltf.Document }{ - {"uint8", &Modeler{Document: &gltf.Document{ + {"uint8", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, [][2]uint8{{1, 2}}}, 1, &gltf.Document{ + }, args{[][2]uint8{{1, 2}}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec2, ComponentType: gltf.ComponentUbyte, Normalized: true}, }, BufferViews: []*gltf.BufferView{ - {ByteLength: 4, Target: gltf.TargetArrayBuffer, ByteStride: 4}, + {ByteLength: 4, Target: gltf.TargetArrayBuffer}, }, Buffers: []*gltf.Buffer{ {ByteLength: 14, Data: []byte{1, 2, 0, 0}}, }, }}, - {"uint16", &Modeler{Document: &gltf.Document{ + {"uint16", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, [][2]uint16{{1, 2}}}, 1, &gltf.Document{ + }, args{[][2]uint16{{1, 2}}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec2, ComponentType: gltf.ComponentUshort, Normalized: true}, }, BufferViews: []*gltf.BufferView{ - {ByteLength: 4, Target: gltf.TargetArrayBuffer, ByteStride: 4}, + {ByteLength: 4, Target: gltf.TargetArrayBuffer}, }, Buffers: []*gltf.Buffer{ {ByteLength: 14, Data: []byte{1, 0, 2, 0}}, }, }}, - {"float", &Modeler{Document: &gltf.Document{ + {"float", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{2, [][2]float32{{1, 2}, {}}}, 1, &gltf.Document{ + }, args{[][2]float32{{1, 2}, {}}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorVec2, ComponentType: gltf.ComponentFloat}, }, BufferViews: []*gltf.BufferView{ - {ByteLength: 16, Target: gltf.TargetArrayBuffer, Buffer: 2, ByteStride: 8}, + {ByteLength: 16, Target: gltf.TargetArrayBuffer, Buffer: 0}, }, Buffers: []*gltf.Buffer{ - {ByteLength: 10}, - {}, - {ByteLength: 16, Data: []byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0}}, + {ByteLength: 26, Data: []byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0}}, }, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tt.m.AddTextureCoord(tt.args.bufferIndex, tt.args.data) + got := WriteTextureCoord(tt.m, tt.args.data) if tt.want != got { - t.Errorf("Modeler.AddTextureCoord() = %v, want %v", got, tt.want) + t.Errorf("WriteTextureCoord() = %v, want %v", got, tt.want) return } - if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil { - t.Errorf("Modeler.AddTextureCoord() = %v", diff) + if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil { + t.Errorf("WriteTextureCoord() = %v", diff) return } }) } } -func TestModeler_AddIndices(t *testing.T) { +func TestWriteIndices(t *testing.T) { type args struct { - bufferIndex uint32 - data interface{} + data interface{} } tests := []struct { name string - m *Modeler + m *gltf.Document args args want uint32 wantDoc *gltf.Document }{ - {"uint8", &Modeler{Document: &gltf.Document{ + {"uint8", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, []uint8{1, 2}}, 1, &gltf.Document{ + }, args{[]uint8{1, 2}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorScalar, ComponentType: gltf.ComponentUbyte}, @@ -392,10 +381,10 @@ func TestModeler_AddIndices(t *testing.T) { {ByteLength: 12, Data: []byte{1, 2}}, }, }}, - {"uint16", &Modeler{Document: &gltf.Document{ + {"uint16", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, []uint16{1, 2}}, 1, &gltf.Document{ + }, args{[]uint16{1, 2}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorScalar, ComponentType: gltf.ComponentUshort}, @@ -407,25 +396,10 @@ func TestModeler_AddIndices(t *testing.T) { {ByteLength: 14, Data: []byte{1, 0, 2, 0}}, }, }}, - {"uint16-compress", &Modeler{Document: &gltf.Document{ - Accessors: []*gltf.Accessor{{}}, - Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }, Compression: CompressionLossless}, args{0, []uint16{1, 2}}, 1, &gltf.Document{ - Accessors: []*gltf.Accessor{ - {}, - {BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorScalar, ComponentType: gltf.ComponentUbyte}, - }, - BufferViews: []*gltf.BufferView{ - {ByteLength: 2, Target: gltf.TargetElementArrayBuffer}, - }, - Buffers: []*gltf.Buffer{ - {ByteLength: 12, Data: []byte{1, 2}}, - }, - }}, - {"uint32", &Modeler{Document: &gltf.Document{ + {"uint32", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, []uint32{1, 2}}, 1, &gltf.Document{ + }, args{[]uint32{1, 2}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorScalar, ComponentType: gltf.ComponentUint}, @@ -437,88 +411,72 @@ func TestModeler_AddIndices(t *testing.T) { {ByteLength: 18, Data: []byte{1, 0, 0, 0, 2, 0, 0, 0}}, }, }}, - {"uint32-compress", &Modeler{Document: &gltf.Document{ - Accessors: []*gltf.Accessor{{}}, - Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }, Compression: CompressionLossless}, args{0, []uint32{1, 2}}, 1, &gltf.Document{ - Accessors: []*gltf.Accessor{ - {}, - {BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorScalar, ComponentType: gltf.ComponentUbyte}, - }, - BufferViews: []*gltf.BufferView{ - {ByteLength: 2, Target: gltf.TargetElementArrayBuffer}, - }, - Buffers: []*gltf.Buffer{ - {ByteLength: 12, Data: []byte{1, 2}}, - }, - }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tt.m.AddIndices(tt.args.bufferIndex, tt.args.data) + got := WriteIndices(tt.m, tt.args.data) if tt.want != got { - t.Errorf("Modeler.AddIndices() = %v, want %v", got, tt.want) + t.Errorf("WriteIndices() = %v, want %v", got, tt.want) return } - if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil { - t.Errorf("Modeler.AddIndices() = %v", diff) + if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil { + t.Errorf("WriteIndices() = %v", diff) return } }) } } -func TestModeler_AddColor(t *testing.T) { +func TestWriteColor(t *testing.T) { type args struct { - bufferIndex uint32 - data interface{} + data interface{} } tests := []struct { name string - m *Modeler + m *gltf.Document args args want uint32 wantDoc *gltf.Document }{ - {"uint8", &Modeler{Document: &gltf.Document{ + {"uint8", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, [][4]uint8{{1, 2, 3, 4}}}, 1, &gltf.Document{ + }, args{[][4]uint8{{1, 2, 3, 4}}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUbyte, Normalized: true}, }, BufferViews: []*gltf.BufferView{ - {ByteLength: 4, Target: gltf.TargetArrayBuffer, ByteStride: 4}, + {ByteLength: 4, Target: gltf.TargetArrayBuffer}, }, Buffers: []*gltf.Buffer{ {ByteLength: 14, Data: []byte{1, 2, 3, 4}}, }, }}, - {"uint16", &Modeler{Document: &gltf.Document{ + {"uint16", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, [][4]uint16{{1, 2, 3, 4}}}, 1, &gltf.Document{ + }, args{[][4]uint16{{1, 2, 3, 4}}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUshort, Normalized: true}, }, BufferViews: []*gltf.BufferView{ - {ByteLength: 8, Target: gltf.TargetArrayBuffer, ByteStride: 8}, + {ByteLength: 8, Target: gltf.TargetArrayBuffer}, }, Buffers: []*gltf.Buffer{ {ByteLength: 18, Data: []byte{1, 0, 2, 0, 3, 0, 4, 0}}, }, }}, - {"float", &Modeler{Document: &gltf.Document{ + {"float", &gltf.Document{ Accessors: []*gltf.Accessor{{}}, - }}, args{0, [][4]float32{{1, 2, 3, 4}, {}}}, 1, &gltf.Document{ + }, args{[][4]float32{{1, 2, 3, 4}, {}}}, 1, &gltf.Document{ Accessors: []*gltf.Accessor{ {}, {BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentFloat}, }, BufferViews: []*gltf.BufferView{ - {ByteLength: 32, Target: gltf.TargetArrayBuffer, ByteStride: 16}, + {ByteLength: 32, Target: gltf.TargetArrayBuffer}, }, Buffers: []*gltf.Buffer{ {ByteLength: 32, Data: []byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, @@ -527,38 +485,37 @@ func TestModeler_AddColor(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tt.m.AddColor(tt.args.bufferIndex, tt.args.data) + got := WriteColor(tt.m, tt.args.data) if tt.want != got { - t.Errorf("Modeler.AddColor() = %v, want %v", got, tt.want) + t.Errorf("WriteColor() = %v, want %v", got, tt.want) return } - if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil { - t.Errorf("Modeler.AddColor() = %v", diff) + if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil { + t.Errorf("WriteColor() = %v", diff) return } }) } } -func TestModeler_AddImage(t *testing.T) { +func TestWriteImage(t *testing.T) { type args struct { - bufferIndex uint32 - name string - mimeType string - r io.Reader + name string + mimeType string + r io.Reader } tests := []struct { name string - m *Modeler + m *gltf.Document args args want uint32 wantDoc *gltf.Document wantErr bool }{ - {"base", &Modeler{Document: &gltf.Document{ + {"base", &gltf.Document{ Images: []*gltf.Image{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10, Data: make([]byte, 10)}}, - }}, args{0, "fake", "fake/type", bytes.NewReader([]byte{1, 2})}, 1, &gltf.Document{ + }, args{"fake", "fake/type", bytes.NewReader([]byte{1, 2})}, 1, &gltf.Document{ Images: []*gltf.Image{ {}, {BufferView: gltf.Index(0), Name: "fake", MimeType: "fake/type"}, @@ -570,10 +527,10 @@ func TestModeler_AddImage(t *testing.T) { {ByteLength: 12, Data: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2}}, }, }, false}, - {"buffer", &Modeler{Document: &gltf.Document{ + {"buffer", &gltf.Document{ Images: []*gltf.Image{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10, Data: make([]byte, 10)}}, - }}, args{0, "fake", "fake/type", bytes.NewBuffer([]byte{1, 2})}, 1, &gltf.Document{ + }, args{"fake", "fake/type", bytes.NewBuffer([]byte{1, 2})}, 1, &gltf.Document{ Images: []*gltf.Image{ {}, {BufferView: gltf.Index(0), Name: "fake", MimeType: "fake/type"}, @@ -585,10 +542,10 @@ func TestModeler_AddImage(t *testing.T) { {ByteLength: 12, Data: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2}}, }, }, false}, - {"err", &Modeler{Document: &gltf.Document{ + {"err", &gltf.Document{ Images: []*gltf.Image{{}}, Buffers: []*gltf.Buffer{{ByteLength: 10}}, - }}, args{0, "fake", "fake/type", &errReader{}}, 0, &gltf.Document{ + }, args{"fake", "fake/type", &errReader{}}, 0, &gltf.Document{ Images: []*gltf.Image{ {}, }, @@ -599,17 +556,17 @@ func TestModeler_AddImage(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := tt.m.AddImage(tt.args.bufferIndex, tt.args.name, tt.args.mimeType, tt.args.r) + got, err := WriteImage(tt.m, tt.args.name, tt.args.mimeType, tt.args.r) if (err != nil) != tt.wantErr { - t.Errorf("Modeler.AddImage() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("WriteImage() error = %v, wantErr %v", err, tt.wantErr) return } if tt.want != got { - t.Errorf("Modeler.AddImage() = %v, want %v", got, tt.want) + t.Errorf("WriteImage() = %v, want %v", got, tt.want) return } - if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil { - t.Errorf("Modeler.AddImage() = %v", diff) + if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil { + t.Errorf("WriteImage() = %v", diff) return } }) diff --git a/struct.go b/struct.go index 36a214d..6fa271c 100644 --- a/struct.go +++ b/struct.go @@ -51,6 +51,18 @@ type Document struct { Textures []*Texture `json:"textures,omitempty" validate:"dive"` } +// NewDocument returns a new Document with sane defaults. +func NewDocument() *Document { + return &Document{ + Scene: Index(0), + Scenes: []*Scene{{Name: "Root Scene"}}, + Asset: Asset{ + Generator: "qmuntal/gltf", + Version: "2.0", + }, + } +} + // An Accessor is a typed view into a bufferView. // An accessor provides a typed view into a bufferView or a subset of a bufferView // similar to how WebGL's vertexAttribPointer() defines an attribute in a buffer. diff --git a/struct_test.go b/struct_test.go index 95299df..6f935d7 100644 --- a/struct_test.go +++ b/struct_test.go @@ -8,6 +8,29 @@ import ( "github.com/go-test/deep" ) +func TestDocument(t *testing.T) { + tests := []struct { + name string + want *Document + }{ + {"base", &Document{ + Scene: Index(0), + Scenes: []*Scene{{Name: "Root Scene"}}, + Asset: Asset{ + Generator: "qmuntal/gltf", + Version: "2.0", + }, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewDocument(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewDocument() = %v, want %v", got, tt.want) + } + }) + } +} + func TestBuffer_IsEmbeddedResource(t *testing.T) { tests := []struct { name string