From 378e76081aa8c4899dc6e57ba102c0810c363f2f Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Thu, 29 Oct 2020 22:38:22 +0100 Subject: [PATCH] add reader api --- README.md | 2 +- binary/encode.go | 49 +- binary/encode_test.go | 10 +- binary/example_test.go | 4 +- binary/size.go | 135 +++-- binary/size_test.go | 78 +++ decoder_test.go | 4 +- encode_test.go | 2 +- example_test.go | 2 +- ext/lightspuntual/lightspuntual.go | 18 +- ext/lightspuntual/lightspuntual_test.go | 14 +- ext/specular/specular.go | 10 +- ext/specular/specular_test.go | 10 +- marshal.go | 34 +- marshal_test.go | 8 +- math.go | 141 +++++ math_test.go | 63 ++ modeler/read.go | 438 ++++++++++++++ modeler/read_test.go | 643 +++++++++++++++++++++ modeler/{modeler.go => write.go} | 2 +- modeler/{modeler_test.go => write_test.go} | 0 struct.go | 50 +- struct_test.go | 75 +-- 23 files changed, 1517 insertions(+), 275 deletions(-) create mode 100644 binary/size_test.go create mode 100644 math.go create mode 100644 math_test.go create mode 100644 modeler/read.go create mode 100644 modeler/read_test.go rename modeler/{modeler.go => write.go} (99%) rename modeler/{modeler_test.go => write_test.go} (100%) diff --git a/README.md b/README.md index 54be919..68a0a7f 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ func main() { Buffers: []*gltf.Buffer{{ByteLength: 1224, URI: bufferData}}, Materials: []*gltf.Material{{ Name: "Default", AlphaMode: gltf.AlphaOpaque, AlphaCutoff: gltf.Float64(0.5), - PBRMetallicRoughness: &gltf.PBRMetallicRoughness{BaseColorFactor: &gltf.RGBA{R: 0.8, G: 0.8, B: 0.8, A: 1}, MetallicFactor: gltf.Float64(0.1), RoughnessFactor: gltf.Float64(0.99)}, + PBRMetallicRoughness: &gltf.PBRMetallicRoughness{BaseColorFactor: &[4]float32{0.8, 0.8, 0.8, 0.5}, MetallicFactor: gltf.Float64(0.1), RoughnessFactor: gltf.Float64(0.99)}, }}, Meshes: []*gltf.Mesh{{Name: "Cube", Primitives: []*gltf.Primitive{{Indices: gltf.Index(0), Material: gltf.Index(0), Mode: gltf.PrimitiveTriangles, Attributes: map[string]uint32{"POSITION": 1, "COLOR_0": 3, "NORMAL": 2, "TEXCOORD_0": 4}}}}}, Nodes: []*gltf.Node{ diff --git a/binary/encode.go b/binary/encode.go index 2c348e2..ab92cfa 100644 --- a/binary/encode.go +++ b/binary/encode.go @@ -5,22 +5,25 @@ import ( "encoding/binary" "image/color" "io" - - "github.com/qmuntal/gltf" ) // 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`. +// byteStride can be zero for non-interleaved buffer views. // +// Data should be a slice of glTF predefined fixed-size types. // If data length is greater than the length of b, Read returns io.ErrShortBuffer. -func Read(b []byte, data interface{}) error { +func Read(b []byte, byteStride uint32, data interface{}) error { c, t, n := Type(data) - if n == 0 { - return binary.Read(bytes.NewReader(b), binary.LittleEndian, data) + size := SizeOfElement(c, t) + if byteStride == 0 { + byteStride = size + } + e := int(byteStride) + high := int(n) * e + if byteStride != size { + high -= int(size) } - e := int(SizeOfElement(c, t)) - if len(b) < int(n)*e { + if len(b) < high { return io.ErrShortBuffer } switch data := data.(type) { @@ -34,16 +37,6 @@ func Read(b []byte, data interface{}) error { c := Ushort.Vec4(b[e*i:]) data[i] = color.RGBA64{R: c[0], G: c[1], B: c[2], A: c[3]} } - case []gltf.RGBA: - for i := range data { - c := Float.Vec4(b[e*i:]) - data[i] = gltf.RGBA{R: float64(c[0]), G: float64(c[1]), B: float64(c[2]), A: float64(c[3])} - } - case []gltf.RGB: - for i := range data { - c := Float.Vec3(b[e*i:]) - data[i] = gltf.RGB{R: float64(c[0]), G: float64(c[1]), B: float64(c[2])} - } case []int8: for i, x := range b { data[i] = int8(x) @@ -73,7 +66,13 @@ func Read(b []byte, data interface{}) error { data[i] = Byte.Mat4(b[e*i:]) } case []uint8: - copy(data, b) + if byteStride != 1 { + copy(data, b) + } else { + for i := range data { + data[i] = Ubyte.Scalar(b[e*i:]) + } + } case [][2]uint8: for i := range data { data[i] = Ubyte.Vec2(b[e*i:]) @@ -210,6 +209,8 @@ func Read(b []byte, data interface{}) error { for i := range data { data[i] = Uint.Mat4(b[e*i:]) } + default: + panic("unsupported type") } return nil } @@ -240,14 +241,6 @@ func Write(b []byte, stride uint32, data interface{}) error { for i, x := range data { Ushort.PutVec4(b[e*i:], [4]uint16{x.R, x.G, x.B, x.A}) } - case []gltf.RGBA: - for i, x := range data { - Float.PutVec4(b[e*i:], [4]float32{float32(x.R), float32(x.G), float32(x.B), float32(x.A)}) - } - case []gltf.RGB: - for i, x := range data { - Float.PutVec3(b[e*i:], [3]float32{float32(x.R), float32(x.G), float32(x.B)}) - } case []int8: for i, x := range data { b[e*i] = byte(x) diff --git a/binary/encode_test.go b/binary/encode_test.go index e14d44f..7e2046d 100644 --- a/binary/encode_test.go +++ b/binary/encode_test.go @@ -6,8 +6,6 @@ import ( "math" "reflect" "testing" - - "github.com/qmuntal/gltf" ) func buildBuffer1(n int, empty ...int) []byte { @@ -59,7 +57,6 @@ func TestRead(t *testing.T) { want interface{} wantErr bool }{ - {"not suported", args{make([]byte, 2), 1}, nil, true}, {"small", args{[]byte{0, 0}, []int8{1, 2, 3}}, []int8{0, 0}, true}, {"empty", args{make([]byte, 0), []int8{}}, []int8{}, false}, {"int8", args{buildBuffer1(4), make([]int8, 4)}, []int8{1, 2, 3, 4}, false}, @@ -166,12 +163,10 @@ func TestRead(t *testing.T) { }, false}, {"color.RGBA", args{buildBuffer1(2 * 4), make([]color.RGBA, 2)}, []color.RGBA{{1, 2, 3, 4}, {5, 6, 7, 8}}, false}, {"color.RGBA64", args{buildBuffer2(2 * 4), make([]color.RGBA64, 2)}, []color.RGBA64{{1, 2, 3, 4}, {5, 6, 7, 8}}, false}, - {"gltf.RGBA", args{buildBufferF(2 * 4), make([]gltf.RGBA, 2)}, []gltf.RGBA{{R: 1, G: 2, B: 3, A: 4}, {R: 5, G: 6, B: 7, A: 8}}, false}, - {"gltf.RGB", args{buildBufferF(2 * 3), make([]gltf.RGB, 2)}, []gltf.RGB{{R: 1, G: 2, B: 3}, {R: 4, G: 5, B: 6}}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := Read(tt.args.b, tt.args.data); (err != nil) != tt.wantErr { + if err := Read(tt.args.b, 0, tt.args.data); (err != nil) != tt.wantErr { t.Errorf("Read() error = %v, wantErr %v", err, tt.wantErr) } if !tt.wantErr && !reflect.DeepEqual(tt.args.data, tt.want) { @@ -192,7 +187,6 @@ func TestWrite(t *testing.T) { want []byte wantErr bool }{ - {"not suported", args{2, 1}, nil, true}, {"small", args{2, []int8{1, 2, 3}}, []byte{1, 2, 3}, true}, {"empty", args{0, []int8{}}, []byte{}, false}, {"int8", args{4, []int8{1, 2, 3, 4}}, buildBuffer1(4), false}, @@ -299,8 +293,6 @@ func TestWrite(t *testing.T) { }}, buildBufferF(32), false}, {"color.RGBA", args{8, []color.RGBA{{1, 2, 3, 4}, {5, 6, 7, 8}}}, buildBuffer1(2 * 4), false}, {"color.RGBA64", args{16, []color.RGBA64{{1, 2, 3, 4}, {5, 6, 7, 8}}}, buildBuffer2(2 * 4), false}, - {"gltf.RGBA", args{32, []gltf.RGBA{{R: 1, G: 2, B: 3, A: 4}, {R: 5, G: 6, B: 7, A: 8}}}, buildBufferF(2 * 4), false}, - {"gltf.RGB", args{24, []gltf.RGB{{R: 1, G: 2, B: 3}, {R: 4, G: 5, B: 6}}}, buildBufferF(2 * 3), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/binary/example_test.go b/binary/example_test.go index 151952d..45710b5 100644 --- a/binary/example_test.go +++ b/binary/example_test.go @@ -36,8 +36,8 @@ func ExampleRead() { sizeIndices := uint32(len(indices)) * binary.SizeOfElement(gltf.ComponentUbyte, gltf.AccessorScalar) // Write - binary.Read(b, indices) - binary.Read(b[sizeIndices:], vertices) + binary.Read(b, 0, indices) + binary.Read(b[sizeIndices:], 0, vertices) fmt.Println(indices) fmt.Println(vertices) diff --git a/binary/size.go b/binary/size.go index 59d3be2..98cddb2 100644 --- a/binary/size.go +++ b/binary/size.go @@ -1,6 +1,7 @@ package binary import ( + "fmt" "image/color" "reflect" @@ -28,102 +29,138 @@ func SizeOfElement(c gltf.ComponentType, t gltf.AccessorType) uint32 { return c.ByteSize() * t.Components() } +// MakeSlice returns the slice type associated with c and t and with the given element count. +// For example, if c is gltf.ComponentFloat and t is gltf.AccessorVec3 +// then MakeSlice(c, t, 5) is equivalent to make([][3]float32, 5). +func MakeSlice(c gltf.ComponentType, t gltf.AccessorType, count uint32) interface{} { + var tp reflect.Type + switch c { + case gltf.ComponentUbyte: + tp = reflect.TypeOf((*uint8)(nil)) + case gltf.ComponentByte: + tp = reflect.TypeOf((*int8)(nil)) + case gltf.ComponentUshort: + tp = reflect.TypeOf((*uint16)(nil)) + case gltf.ComponentShort: + tp = reflect.TypeOf((*int16)(nil)) + case gltf.ComponentUint: + tp = reflect.TypeOf((*uint32)(nil)) + case gltf.ComponentFloat: + tp = reflect.TypeOf((*float32)(nil)) + } + tp = tp.Elem() + switch t { + case gltf.AccessorVec2: + tp = reflect.ArrayOf(2, tp) + case gltf.AccessorVec3: + tp = reflect.ArrayOf(3, tp) + case gltf.AccessorVec4: + tp = reflect.ArrayOf(4, tp) + case gltf.AccessorMat2: + tp = reflect.ArrayOf(2, reflect.ArrayOf(2, tp)) + case gltf.AccessorMat3: + tp = reflect.ArrayOf(3, reflect.ArrayOf(3, tp)) + case gltf.AccessorMat4: + tp = reflect.ArrayOf(4, reflect.ArrayOf(4, tp)) + } + return reflect.MakeSlice(reflect.SliceOf(tp), int(count), int(count)).Interface() +} + // 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 0. -func Type(data interface{}) (c gltf.ComponentType, t gltf.AccessorType, length uint32) { +// It panics if data is not an slice. +func Type(data interface{}) (c gltf.ComponentType, t gltf.AccessorType, count uint32) { v := reflect.ValueOf(data) - switch v.Kind() { - case reflect.Slice: - length = uint32(v.Len()) + if v.Kind() != reflect.Slice { + panic(fmt.Sprintf("go3mf: binary.Type expecting a slice but got %s", v.Kind())) } + count = uint32(v.Len()) switch data.(type) { - case []int8, int8: + case []int8: c, t = gltf.ComponentByte, gltf.AccessorScalar - case [][2]int8, [2]int8: + case [][2]int8: c, t = gltf.ComponentByte, gltf.AccessorVec2 - case [][3]int8, [3]int8: + case [][3]int8: c, t = gltf.ComponentByte, gltf.AccessorVec3 - case [][4]int8, [4]int8: + case [][4]int8: c, t = gltf.ComponentByte, gltf.AccessorVec4 - case [][2][2]int8, [2][2]int8: + case [][2][2]int8: c, t = gltf.ComponentByte, gltf.AccessorMat2 - case [][3][3]int8, [3][3]int8: + case [][3][3]int8: c, t = gltf.ComponentByte, gltf.AccessorMat3 - case [][4][4]int8, [4][4]int8: + case [][4][4]int8: c, t = gltf.ComponentByte, gltf.AccessorMat4 - case []uint8, uint8: + case []uint8: c, t = gltf.ComponentUbyte, gltf.AccessorScalar - case [][2]uint8, [2]uint8: + case [][2]uint8: c, t = gltf.ComponentUbyte, gltf.AccessorVec2 - case [][3]uint8, [3]uint8: + case [][3]uint8: c, t = gltf.ComponentUbyte, gltf.AccessorVec3 - case []color.RGBA, color.RGBA, [][4]uint8, [4]uint8: + case []color.RGBA, [][4]uint8: c, t = gltf.ComponentUbyte, gltf.AccessorVec4 - case [][2][2]uint8, [2][2]uint8: + case [][2][2]uint8: c, t = gltf.ComponentUbyte, gltf.AccessorMat2 - case [][3][3]uint8, [3][3]uint8: + case [][3][3]uint8: c, t = gltf.ComponentUbyte, gltf.AccessorMat3 - case [][4][4]uint8, [4][4]uint8: + case [][4][4]uint8: c, t = gltf.ComponentUbyte, gltf.AccessorMat4 - case []int16, int16: + case []int16: c, t = gltf.ComponentShort, gltf.AccessorScalar - case [][2]int16, [2]int16: + case [][2]int16: c, t = gltf.ComponentShort, gltf.AccessorVec2 - case [][3]int16, [3]int16: + case [][3]int16: c, t = gltf.ComponentShort, gltf.AccessorVec3 - case [][4]int16, [4]int16: + case [][4]int16: c, t = gltf.ComponentShort, gltf.AccessorVec4 - case [][2][2]int16, [2][2]int16: + case [][2][2]int16: c, t = gltf.ComponentShort, gltf.AccessorMat2 - case [][3][3]int16, [3][3]int16: + case [][3][3]int16: c, t = gltf.ComponentShort, gltf.AccessorMat3 - case [][4][4]int16, [4][4]int16: + case [][4][4]int16: c, t = gltf.ComponentShort, gltf.AccessorMat4 - case []uint16, uint16: + case []uint16: c, t = gltf.ComponentUshort, gltf.AccessorScalar - case [][2]uint16, [2]uint16: + case [][2]uint16: c, t = gltf.ComponentUshort, gltf.AccessorVec2 - case [][3]uint16, [3]uint16: + case [][3]uint16: c, t = gltf.ComponentUshort, gltf.AccessorVec3 - case []color.RGBA64, color.RGBA64, [][4]uint16, [4]uint16: + case []color.RGBA64, [][4]uint16: c, t = gltf.ComponentUshort, gltf.AccessorVec4 - case [][2][2]uint16, [2][2]uint16: + case [][2][2]uint16: c, t = gltf.ComponentUshort, gltf.AccessorMat2 - case [][3][3]uint16, [3][3]uint16: + case [][3][3]uint16: c, t = gltf.ComponentUshort, gltf.AccessorMat3 - case [][4][4]uint16, [4][4]uint16: + case [][4][4]uint16: c, t = gltf.ComponentUshort, gltf.AccessorMat4 - case []uint32, uint32: + case []uint32: c, t = gltf.ComponentUint, gltf.AccessorScalar - case [][2]uint32, [2]uint32: + case [][2]uint32: c, t = gltf.ComponentUint, gltf.AccessorVec2 - case [][3]uint32, [3]uint32: + case [][3]uint32: c, t = gltf.ComponentUint, gltf.AccessorVec3 - case [][4]uint32, [4]uint32: + case [][4]uint32: c, t = gltf.ComponentUint, gltf.AccessorVec4 - case [][2][2]uint32, [2][2]uint32: + case [][2][2]uint32: c, t = gltf.ComponentUint, gltf.AccessorMat2 - case [][3][3]uint32, [3][3]uint32: + case [][3][3]uint32: c, t = gltf.ComponentUint, gltf.AccessorMat3 - case [][4][4]uint32, [4][4]uint32: + case [][4][4]uint32: c, t = gltf.ComponentUint, gltf.AccessorMat4 - case []float32, float32: + case []float32: c, t = gltf.ComponentFloat, gltf.AccessorScalar - case [][2]float32, [2]float32: + case [][2]float32: c, t = gltf.ComponentFloat, gltf.AccessorVec2 - case []gltf.RGB, gltf.RGB, [][3]float32, [3]float32: + case [][3]float32: c, t = gltf.ComponentFloat, gltf.AccessorVec3 - case []gltf.RGBA, gltf.RGBA, [][4]float32, [4]float32: + case [][4]float32: c, t = gltf.ComponentFloat, gltf.AccessorVec4 - case [][2][2]float32, [2][2]float32: + case [][2][2]float32: c, t = gltf.ComponentFloat, gltf.AccessorMat2 - case [][3][3]float32, [3][3]float32: + case [][3][3]float32: c, t = gltf.ComponentFloat, gltf.AccessorMat3 - case [][4][4]float32, [4][4]float32: + case [][4][4]float32: c, t = gltf.ComponentFloat, gltf.AccessorMat4 default: - length = 0 + panic(fmt.Sprintf("go3mf: binary.Type expecting a glTF supported type but got %s", v.Kind())) } return } diff --git a/binary/size_test.go b/binary/size_test.go new file mode 100644 index 0000000..3ab1d04 --- /dev/null +++ b/binary/size_test.go @@ -0,0 +1,78 @@ +package binary + +import ( + "reflect" + "testing" + + "github.com/qmuntal/gltf" +) + +func TestMakeSlice(t *testing.T) { + type args struct { + c gltf.ComponentType + t gltf.AccessorType + count uint32 + } + tests := []struct { + name string + args args + want interface{} + }{ + // Scalar + {"[]uint8", args{gltf.ComponentUbyte, gltf.AccessorScalar, 5}, make([]uint8, 5)}, + {"[]int8", args{gltf.ComponentByte, gltf.AccessorScalar, 5}, make([]int8, 5)}, + {"[]uint16", args{gltf.ComponentUshort, gltf.AccessorScalar, 5}, make([]uint16, 5)}, + {"[]int16", args{gltf.ComponentShort, gltf.AccessorScalar, 5}, make([]int16, 5)}, + {"[]uint32", args{gltf.ComponentUint, gltf.AccessorScalar, 5}, make([]uint32, 5)}, + {"[]float32", args{gltf.ComponentFloat, gltf.AccessorScalar, 5}, make([]float32, 5)}, + // Vec2 + {"[][2]uint8", args{gltf.ComponentUbyte, gltf.AccessorVec2, 5}, make([][2]uint8, 5)}, + {"[][2]int8", args{gltf.ComponentByte, gltf.AccessorVec2, 5}, make([][2]int8, 5)}, + {"[][2]uint16", args{gltf.ComponentUshort, gltf.AccessorVec2, 5}, make([][2]uint16, 5)}, + {"[][2]int16", args{gltf.ComponentShort, gltf.AccessorVec2, 5}, make([][2]int16, 5)}, + {"[][2]uint32", args{gltf.ComponentUint, gltf.AccessorVec2, 5}, make([][2]uint32, 5)}, + {"[][2]float32", args{gltf.ComponentFloat, gltf.AccessorVec2, 5}, make([][2]float32, 5)}, + // Vec3 + {"[][3]uint8", args{gltf.ComponentUbyte, gltf.AccessorVec3, 5}, make([][3]uint8, 5)}, + {"[][3]int8", args{gltf.ComponentByte, gltf.AccessorVec3, 5}, make([][3]int8, 5)}, + {"[][3]uint16", args{gltf.ComponentUshort, gltf.AccessorVec3, 5}, make([][3]uint16, 5)}, + {"[][3]int16", args{gltf.ComponentShort, gltf.AccessorVec3, 5}, make([][3]int16, 5)}, + {"[][3]uint32", args{gltf.ComponentUint, gltf.AccessorVec3, 5}, make([][3]uint32, 5)}, + {"[][3]float32", args{gltf.ComponentFloat, gltf.AccessorVec3, 5}, make([][3]float32, 5)}, + // Vec4 + {"[][4]uint8", args{gltf.ComponentUbyte, gltf.AccessorVec4, 5}, make([][4]uint8, 5)}, + {"[][4]int8", args{gltf.ComponentByte, gltf.AccessorVec4, 5}, make([][4]int8, 5)}, + {"[][4]uint16", args{gltf.ComponentUshort, gltf.AccessorVec4, 5}, make([][4]uint16, 5)}, + {"[][4]int16", args{gltf.ComponentShort, gltf.AccessorVec4, 5}, make([][4]int16, 5)}, + {"[][4]uint32", args{gltf.ComponentUint, gltf.AccessorVec4, 5}, make([][4]uint32, 5)}, + {"[][4]float32", args{gltf.ComponentFloat, gltf.AccessorVec4, 5}, make([][4]float32, 5)}, + // Mat2 + {"[][2][2]uint8", args{gltf.ComponentUbyte, gltf.AccessorMat2, 5}, make([][2][2]uint8, 5)}, + {"[][2][2]int8", args{gltf.ComponentByte, gltf.AccessorMat2, 5}, make([][2][2]int8, 5)}, + {"[][2][2]uint16", args{gltf.ComponentUshort, gltf.AccessorMat2, 5}, make([][2][2]uint16, 5)}, + {"[][2][2]int16", args{gltf.ComponentShort, gltf.AccessorMat2, 5}, make([][2][2]int16, 5)}, + {"[][2][2]uint32", args{gltf.ComponentUint, gltf.AccessorMat2, 5}, make([][2][2]uint32, 5)}, + {"[][2][2]float32", args{gltf.ComponentFloat, gltf.AccessorMat2, 5}, make([][2][2]float32, 5)}, + // Mat3 + {"[][3][3]uint8", args{gltf.ComponentUbyte, gltf.AccessorMat3, 5}, make([][3][3]uint8, 5)}, + {"[][3][3]int8", args{gltf.ComponentByte, gltf.AccessorMat3, 5}, make([][3][3]int8, 5)}, + {"[][3][3]uint16", args{gltf.ComponentUshort, gltf.AccessorMat3, 5}, make([][3][3]uint16, 5)}, + {"[][3][3]int16", args{gltf.ComponentShort, gltf.AccessorMat3, 5}, make([][3][3]int16, 5)}, + {"[][3][3]uint32", args{gltf.ComponentUint, gltf.AccessorMat3, 5}, make([][3][3]uint32, 5)}, + {"[][3][3]float32", args{gltf.ComponentFloat, gltf.AccessorMat3, 5}, make([][3][3]float32, 5)}, + // Mat4 + {"[][4][4]uint8", args{gltf.ComponentUbyte, gltf.AccessorMat4, 5}, make([][4][4]uint8, 5)}, + {"[][4][4]int8", args{gltf.ComponentByte, gltf.AccessorMat4, 5}, make([][4][4]int8, 5)}, + {"[][4][4]uint16", args{gltf.ComponentUshort, gltf.AccessorMat4, 5}, make([][4][4]uint16, 5)}, + {"[][4][4]int16", args{gltf.ComponentShort, gltf.AccessorMat4, 5}, make([][4][4]int16, 5)}, + {"[][4][4]uint32", args{gltf.ComponentUint, gltf.AccessorMat4, 5}, make([][4][4]uint32, 5)}, + {"[][4][4]float32", args{gltf.ComponentFloat, gltf.AccessorMat4, 5}, make([][4][4]float32, 5)}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := MakeSlice(tt.args.c, tt.args.t, tt.args.count); !reflect.DeepEqual(got, tt.want) { + t.Errorf("MakeSlice() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/decoder_test.go b/decoder_test.go index 63302cb..f18a03a 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -44,7 +44,7 @@ func TestOpen(t *testing.T) { }, Buffers: []*Buffer{{ByteLength: 1800, URI: "Cube.bin", Data: readFile("testdata/Cube/glTF/Cube.bin")}}, Images: []*Image{{URI: "Cube_BaseColor.png"}, {URI: "Cube_MetallicRoughness.png"}}, - Materials: []*Material{{Name: "Cube", AlphaMode: AlphaOpaque, AlphaCutoff: Float64(0.5), PBRMetallicRoughness: &PBRMetallicRoughness{BaseColorFactor: NewRGBA(), MetallicFactor: Float64(1), RoughnessFactor: Float64(1), BaseColorTexture: &TextureInfo{Index: 0}, MetallicRoughnessTexture: &TextureInfo{Index: 1}}}}, + Materials: []*Material{{Name: "Cube", AlphaMode: AlphaOpaque, AlphaCutoff: Float64(0.5), PBRMetallicRoughness: &PBRMetallicRoughness{BaseColorFactor: &[4]float32{1, 1, 1, 1}, MetallicFactor: Float64(1), RoughnessFactor: Float64(1), BaseColorTexture: &TextureInfo{Index: 0}, MetallicRoughnessTexture: &TextureInfo{Index: 1}}}}, Meshes: []*Mesh{{Name: "Cube", Primitives: []*Primitive{{Indices: Index(0), Material: Index(0), Mode: PrimitiveTriangles, Attributes: map[string]uint32{"NORMAL": 2, "POSITION": 1, "TANGENT": 3, "TEXCOORD_0": 4}}}}}, Nodes: []*Node{{Mesh: Index(0), Name: "Cube", Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}}}, Samplers: []*Sampler{{WrapS: WrapRepeat, WrapT: WrapRepeat}}, @@ -95,7 +95,7 @@ func TestOpen(t *testing.T) { {Buffer: 0, ByteLength: 192, ByteOffset: 1032, Target: TargetArrayBuffer}, }, Buffers: []*Buffer{{ByteLength: 1224, Data: readFile("testdata/BoxVertexColors/glTF-Binary/BoxVertexColors.glb")[1628+20+8:]}}, - Materials: []*Material{{Name: "Default", AlphaMode: AlphaOpaque, AlphaCutoff: Float64(0.5), PBRMetallicRoughness: &PBRMetallicRoughness{BaseColorFactor: &RGBA{R: 0.8, G: 0.8, B: 0.8, A: 1}, MetallicFactor: Float64(0.1), RoughnessFactor: Float64(0.99)}}}, + Materials: []*Material{{Name: "Default", AlphaMode: AlphaOpaque, AlphaCutoff: Float64(0.5), PBRMetallicRoughness: &PBRMetallicRoughness{BaseColorFactor: &[4]float32{0.8, 0.8, 0.8, 1}, MetallicFactor: Float64(0.1), RoughnessFactor: Float64(0.99)}}}, Meshes: []*Mesh{{Name: "Cube", Primitives: []*Primitive{{Indices: Index(0), Material: Index(0), Mode: PrimitiveTriangles, Attributes: map[string]uint32{"POSITION": 1, "COLOR_0": 3, "NORMAL": 2, "TEXCOORD_0": 4}}}}}, Nodes: []*Node{ {Name: "RootNode", Children: []uint32{1, 2, 3}, Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}}, diff --git a/encode_test.go b/encode_test.go index c3531bb..9ad85d8 100644 --- a/encode_test.go +++ b/encode_test.go @@ -93,7 +93,7 @@ func TestEncoder_Encode(t *testing.T) { {Extras: 8.0, Name: "base", EmissiveFactor: [3]float64{1.0, 1.0, 1.0}, DoubleSided: true, AlphaCutoff: Float64(0.5), AlphaMode: AlphaOpaque}, {Extras: 8.0, Name: "pbr", AlphaCutoff: Float64(0.5), AlphaMode: AlphaOpaque, PBRMetallicRoughness: &PBRMetallicRoughness{ - Extras: 8.0, MetallicFactor: Float64(1), RoughnessFactor: Float64(2), BaseColorFactor: &RGBA{R: 0.8, G: 0.8, B: 0.8, A: 1}, + Extras: 8.0, MetallicFactor: Float64(1), RoughnessFactor: Float64(2), BaseColorFactor: &[4]float32{0.8, 0.8, 0.8, 1}, BaseColorTexture: &TextureInfo{Extras: 8.0, Index: 1, TexCoord: 3}, MetallicRoughnessTexture: &TextureInfo{Extras: 8.0, Index: 6, TexCoord: 5}, }, diff --git a/example_test.go b/example_test.go index 4591ca5..0fa80a0 100644 --- a/example_test.go +++ b/example_test.go @@ -34,7 +34,7 @@ func ExampleSave() { Buffers: []*gltf.Buffer{{ByteLength: 1224, Data: []byte{97, 110, 121, 32, 99, 97, 114, 110, 97, 108, 32, 112, 108, 101, 97, 115}}}, Materials: []*gltf.Material{{ Name: "Default", AlphaMode: gltf.AlphaOpaque, AlphaCutoff: gltf.Float64(0.5), - PBRMetallicRoughness: &gltf.PBRMetallicRoughness{BaseColorFactor: &gltf.RGBA{R: 0.8, G: 0.8, B: 0.8, A: 1}, MetallicFactor: gltf.Float64(0.1), RoughnessFactor: gltf.Float64(0.99)}, + PBRMetallicRoughness: &gltf.PBRMetallicRoughness{BaseColorFactor: &[4]float32{0.8, 0.8, 0.8, 1}, MetallicFactor: gltf.Float64(0.1), RoughnessFactor: gltf.Float64(0.99)}, }}, Meshes: []*gltf.Mesh{{Name: "Cube", Primitives: []*gltf.Primitive{{Indices: gltf.Index(0), Material: gltf.Index(0), Mode: gltf.PrimitiveTriangles, Attributes: map[string]uint32{"POSITION": 1, "COLOR_0": 3, "NORMAL": 2, "TEXCOORD_0": 4}}}}}, Nodes: []*gltf.Node{ diff --git a/ext/lightspuntual/lightspuntual.go b/ext/lightspuntual/lightspuntual.go index 430c40a..90c3256 100644 --- a/ext/lightspuntual/lightspuntual.go +++ b/ext/lightspuntual/lightspuntual.go @@ -79,12 +79,12 @@ type Lights []*Light // Light defines a directional, point, or spot light. // When a light's type is spot, the spot property on the light is required. type Light struct { - Type string `json:"type"` - Name string `json:"name,omitempty"` - Color *gltf.RGB `json:"color,omitempty"` - Intensity *float64 `json:"intensity,omitempty"` - Range *float64 `json:"range,omitempty"` - Spot *Spot `json:"spot,omitempty"` + Type string `json:"type"` + Name string `json:"name,omitempty"` + Color *[3]float32 `json:"color,omitempty" validate:"omitempty,dive,gte=0,lte=1"` + Intensity *float64 `json:"intensity,omitempty"` + Range *float64 `json:"range,omitempty"` + Spot *Spot `json:"spot,omitempty"` } // IntensityOrDefault returns the itensity if it is not nil, else return the default one. @@ -96,9 +96,9 @@ func (l *Light) IntensityOrDefault() float64 { } // ColorOrDefault returns the color if it is not nil, else return the default one. -func (l *Light) ColorOrDefault() gltf.RGB { +func (l *Light) ColorOrDefault() [3]float32 { if l.Color == nil { - return *gltf.NewRGB() + return [3]float32{1, 1, 1} } return *l.Color } @@ -106,7 +106,7 @@ func (l *Light) ColorOrDefault() gltf.RGB { // UnmarshalJSON unmarshal the light with the correct default values. func (l *Light) UnmarshalJSON(data []byte) error { type alias Light - tmp := alias(Light{Color: gltf.NewRGB(), Intensity: gltf.Float64(1), Range: gltf.Float64(math.Inf(0))}) + tmp := alias(Light{Color: &[3]float32{1, 1, 1}, Intensity: gltf.Float64(1), Range: gltf.Float64(math.Inf(0))}) err := json.Unmarshal(data, &tmp) if err == nil { *l = Light(tmp) diff --git a/ext/lightspuntual/lightspuntual_test.go b/ext/lightspuntual/lightspuntual_test.go index a635568..3c360ea 100644 --- a/ext/lightspuntual/lightspuntual_test.go +++ b/ext/lightspuntual/lightspuntual_test.go @@ -31,10 +31,10 @@ func TestLight_ColorOrDefault(t *testing.T) { tests := []struct { name string l *Light - want gltf.RGB + want [3]float32 }{ - {"empty", &Light{}, *gltf.NewRGB()}, - {"other", &Light{Color: &gltf.RGB{R: 0.8, G: 0.8, B: 0.8}}, gltf.RGB{R: 0.8, G: 0.8, B: 0.8}}, + {"empty", &Light{}, [3]float32{1, 1, 1}}, + {"other", &Light{Color: &[3]float32{0.8, 0.8, 0.8}}, [3]float32{0.8, 0.8, 0.8}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -75,7 +75,7 @@ func TestLight_UnmarshalJSON(t *testing.T) { wantErr bool }{ {"default", new(Light), args{[]byte("{}")}, &Light{ - Color: gltf.NewRGB(), Intensity: gltf.Float64(1), Range: gltf.Float64(math.Inf(0)), + Color: &[3]float32{1, 1, 1}, Intensity: gltf.Float64(1), Range: gltf.Float64(math.Inf(0)), }, false}, {"nodefault", new(Light), args{[]byte(`{ "color": [0.3, 0.7, 1.0], @@ -88,7 +88,7 @@ func TestLight_UnmarshalJSON(t *testing.T) { "outerConeAngle": 2.0 } }`)}, &Light{ - Name: "AAA", Type: "spot", Color: &gltf.RGB{R: 0.3, G: 0.7, B: 1.0}, Intensity: gltf.Float64(40), Range: gltf.Float64(10), + Name: "AAA", Type: "spot", Color: &[3]float32{0.3, 0.7, 1}, Intensity: gltf.Float64(40), Range: gltf.Float64(10), Spot: &Spot{ InnerConeAngle: 1.0, OuterConeAngle: gltf.Float64(2.0), @@ -133,8 +133,8 @@ func TestUnmarshal(t *testing.T) { "type": "point" } ]}`)}, Lights{ - {Color: &gltf.RGB{R: 1.0, G: 0.9, B: 0.7}, Name: "Directional", Intensity: gltf.Float64(3.0), Type: "directional", Range: gltf.Float64(math.Inf(0))}, - {Color: &gltf.RGB{R: 1.0}, Name: "Point", Intensity: gltf.Float64(20.0), Type: "point", Range: gltf.Float64(math.Inf(0))}, + {Color: &[3]float32{1, 0.9, 0.7}, Name: "Directional", Intensity: gltf.Float64(3.0), Type: "directional", Range: gltf.Float64(math.Inf(0))}, + {Color: &[3]float32{1, 0, 0}, Name: "Point", Intensity: gltf.Float64(20.0), Type: "point", Range: gltf.Float64(math.Inf(0))}, }, false}, } for _, tt := range tests { diff --git a/ext/specular/specular.go b/ext/specular/specular.go index d3ba46c..3bd31bf 100644 --- a/ext/specular/specular.go +++ b/ext/specular/specular.go @@ -25,9 +25,9 @@ func init() { // PBRSpecularGlossiness defines a specular-glossiness material model. type PBRSpecularGlossiness struct { - DiffuseFactor *gltf.RGBA `json:"diffuseFactor,omitempty"` + DiffuseFactor *[4]float32 `json:"diffuseFactor,omitempty" validate:"omitempty,dive,gte=0,lte=1"` DiffuseTexture *gltf.TextureInfo `json:"diffuseTexture,omitempty"` - SpecularFactor *gltf.RGB `json:"specularFactor,omitempty"` + SpecularFactor *[3]float32 `json:"specularFactor,omitempty" validate:"omitempty,dive,gte=0,lte=1"` GlossinessFactor *float64 `json:"glossinessFactor,omitempty" validate:"omitempty,gte=0,lte=1"` SpecularGlossinessTexture *gltf.TextureInfo `json:"specularGlossinessTexture,omitempty"` } @@ -35,7 +35,7 @@ type PBRSpecularGlossiness struct { // UnmarshalJSON unmarshal the pbr with the correct default values. func (p *PBRSpecularGlossiness) UnmarshalJSON(data []byte) error { type alias PBRSpecularGlossiness - tmp := alias(PBRSpecularGlossiness{DiffuseFactor: gltf.NewRGBA(), SpecularFactor: gltf.NewRGB(), GlossinessFactor: gltf.Float64(1)}) + tmp := alias(PBRSpecularGlossiness{DiffuseFactor: &[4]float32{1, 1, 1, 1}, SpecularFactor: &[3]float32{1, 1, 1}, GlossinessFactor: gltf.Float64(1)}) err := json.Unmarshal(data, &tmp) if err == nil { *p = PBRSpecularGlossiness(tmp) @@ -51,10 +51,10 @@ func (p *PBRSpecularGlossiness) MarshalJSON() ([]byte, error) { if p.GlossinessFactor != nil && *p.GlossinessFactor == 1 { out = removeProperty([]byte(`"glossinessFactor":1`), out) } - if p.DiffuseFactor != nil && *p.DiffuseFactor == *gltf.NewRGBA() { + if p.DiffuseFactor != nil && *p.DiffuseFactor == [4]float32{1, 1, 1, 1} { out = removeProperty([]byte(`"diffuseFactor":[1,1,1,1]`), out) } - if p.SpecularFactor != nil && *p.SpecularFactor == *gltf.NewRGB() { + if p.SpecularFactor != nil && *p.SpecularFactor == [3]float32{1, 1, 1} { out = removeProperty([]byte(`"specularFactor":[1,1,1]`), out) } out = sanitizeJSON(out) diff --git a/ext/specular/specular_test.go b/ext/specular/specular_test.go index bcb9521..cc58b29 100644 --- a/ext/specular/specular_test.go +++ b/ext/specular/specular_test.go @@ -18,9 +18,9 @@ func TestPBRSpecularGlossiness_UnmarshalJSON(t *testing.T) { want *PBRSpecularGlossiness wantErr bool }{ - {"default", new(PBRSpecularGlossiness), args{[]byte("{}")}, &PBRSpecularGlossiness{DiffuseFactor: gltf.NewRGBA(), SpecularFactor: gltf.NewRGB(), GlossinessFactor: gltf.Float64(1)}, false}, + {"default", new(PBRSpecularGlossiness), args{[]byte("{}")}, &PBRSpecularGlossiness{DiffuseFactor: &[4]float32{1, 1, 1, 1}, SpecularFactor: &[3]float32{1, 1, 1}, GlossinessFactor: gltf.Float64(1)}, false}, {"nodefault", new(PBRSpecularGlossiness), args{[]byte(`{"diffuseFactor": [0.1,0.2,0.3,0.4],"specularFactor":[0.5,0.6,0.7],"glossinessFactor":0.5}`)}, &PBRSpecularGlossiness{ - DiffuseFactor: &gltf.RGBA{R: 0.1, G: 0.2, B: 0.3, A: 0.4}, SpecularFactor: &gltf.RGB{R: 0.5, G: 0.6, B: 0.7}, GlossinessFactor: gltf.Float64(0.5), + DiffuseFactor: &[4]float32{0.1, 0.2, 0.3, 0.4}, SpecularFactor: &[3]float32{0.5, 0.6, 0.7}, GlossinessFactor: gltf.Float64(0.5), }, false}, } for _, tt := range tests { @@ -43,9 +43,9 @@ func TestPBRSpecularGlossiness_MarshalJSON(t *testing.T) { want []byte wantErr bool }{ - {"default", &PBRSpecularGlossiness{GlossinessFactor: gltf.Float64(1), DiffuseFactor: gltf.NewRGBA(), SpecularFactor: gltf.NewRGB()}, []byte(`{}`), false}, + {"default", &PBRSpecularGlossiness{GlossinessFactor: gltf.Float64(1), DiffuseFactor: &[4]float32{1, 1, 1, 1}, SpecularFactor: &[3]float32{1, 1, 1}}, []byte(`{}`), false}, {"empty", &PBRSpecularGlossiness{}, []byte(`{}`), false}, - {"nodefault", &PBRSpecularGlossiness{GlossinessFactor: gltf.Float64(0.5), DiffuseFactor: &gltf.RGBA{R: 1, G: 0.5, B: 1, A: 1}, SpecularFactor: &gltf.RGB{R: 1, G: 1, B: 0.5}}, []byte(`{"diffuseFactor":[1,0.5,1,1],"specularFactor":[1,1,0.5],"glossinessFactor":0.5}`), false}, + {"nodefault", &PBRSpecularGlossiness{GlossinessFactor: gltf.Float64(0.5), DiffuseFactor: &[4]float32{1, 0.5, 1, 1}, SpecularFactor: &[3]float32{1, 1, 0.5}}, []byte(`{"diffuseFactor":[1,0.5,1,1],"specularFactor":[1,1,0.5],"glossinessFactor":0.5}`), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -71,7 +71,7 @@ func TestUnmarshal(t *testing.T) { want interface{} wantErr bool }{ - {"base", args{[]byte("{}")}, &PBRSpecularGlossiness{DiffuseFactor: gltf.NewRGBA(), SpecularFactor: gltf.NewRGB(), GlossinessFactor: gltf.Float64(1)}, false}, + {"base", args{[]byte("{}")}, &PBRSpecularGlossiness{DiffuseFactor: &[4]float32{1, 1, 1, 1}, SpecularFactor: &[3]float32{1, 1, 1}, GlossinessFactor: gltf.Float64(1)}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/marshal.go b/marshal.go index f667f3e..8cc1ecd 100644 --- a/marshal.go +++ b/marshal.go @@ -154,40 +154,10 @@ func (o *OcclusionTexture) MarshalJSON() ([]byte, error) { return json.Marshal(&struct{ *alias }{alias: (*alias)(o)}) } -// UnmarshalJSON unmarshal the color with the correct default values. -func (c *RGBA) UnmarshalJSON(data []byte) error { - tmp := [4]float64{1, 1, 1, 1} - err := json.Unmarshal(data, &tmp) - if err == nil { - c.R, c.G, c.B, c.A = tmp[0], tmp[1], tmp[2], tmp[3] - } - return err -} - -// MarshalJSON marshal the color with the correct default values. -func (c *RGBA) MarshalJSON() ([]byte, error) { - return json.Marshal([4]float64{c.R, c.G, c.B, c.A}) -} - -// UnmarshalJSON unmarshal the color with the correct default values. -func (c *RGB) UnmarshalJSON(data []byte) error { - tmp := [3]float64{1, 1, 1} - err := json.Unmarshal(data, &tmp) - if err == nil { - c.R, c.G, c.B = tmp[0], tmp[1], tmp[2] - } - return err -} - -// MarshalJSON marshal the color with the correct default values. -func (c *RGB) MarshalJSON() ([]byte, error) { - return json.Marshal([3]float64{c.R, c.G, c.B}) -} - // UnmarshalJSON unmarshal the pbr with the correct default values. func (p *PBRMetallicRoughness) UnmarshalJSON(data []byte) error { type alias PBRMetallicRoughness - tmp := alias(PBRMetallicRoughness{BaseColorFactor: NewRGBA(), MetallicFactor: Float64(1), RoughnessFactor: Float64(1)}) + tmp := alias(PBRMetallicRoughness{BaseColorFactor: &[4]float32{1, 1, 1, 1}, MetallicFactor: Float64(1), RoughnessFactor: Float64(1)}) err := json.Unmarshal(data, &tmp) if err == nil { *p = PBRMetallicRoughness(tmp) @@ -206,7 +176,7 @@ func (p *PBRMetallicRoughness) MarshalJSON() ([]byte, error) { if p.RoughnessFactor != nil && *p.RoughnessFactor == 1 { out = removeProperty([]byte(`"roughnessFactor":1`), out) } - if p.BaseColorFactor != nil && *p.BaseColorFactor == *NewRGBA() { + if p.BaseColorFactor != nil && *p.BaseColorFactor == [4]float32{1, 1, 1, 1} { out = removeProperty([]byte(`"baseColorFactor":[1,1,1,1]`), out) } out = sanitizeJSON(out) diff --git a/marshal_test.go b/marshal_test.go index e0b7fb2..f2f7e65 100644 --- a/marshal_test.go +++ b/marshal_test.go @@ -174,9 +174,9 @@ func TestPBRMetallicRoughness_UnmarshalJSON(t *testing.T) { want *PBRMetallicRoughness wantErr bool }{ - {"default", new(PBRMetallicRoughness), args{[]byte("{}")}, &PBRMetallicRoughness{BaseColorFactor: NewRGBA(), MetallicFactor: Float64(1), RoughnessFactor: Float64(1)}, false}, + {"default", new(PBRMetallicRoughness), args{[]byte("{}")}, &PBRMetallicRoughness{BaseColorFactor: &[4]float32{1, 1, 1, 1}, MetallicFactor: Float64(1), RoughnessFactor: Float64(1)}, false}, {"nodefault", new(PBRMetallicRoughness), args{[]byte(`{"baseColorFactor": [0.1,0.2,0.6,0.7],"metallicFactor":0.5,"roughnessFactor":0.1}`)}, &PBRMetallicRoughness{ - BaseColorFactor: &RGBA{R: 0.1, G: 0.2, B: 0.6, A: 0.7}, MetallicFactor: Float64(0.5), RoughnessFactor: Float64(0.1), + BaseColorFactor: &[4]float32{0.1, 0.2, 0.6, 0.7}, MetallicFactor: Float64(0.5), RoughnessFactor: Float64(0.1), }, false}, } for _, tt := range tests { @@ -323,9 +323,9 @@ func TestPBRMetallicRoughness_MarshalJSON(t *testing.T) { want []byte wantErr bool }{ - {"default", &PBRMetallicRoughness{MetallicFactor: Float64(1), RoughnessFactor: Float64(1), BaseColorFactor: NewRGBA()}, []byte(`{}`), false}, + {"default", &PBRMetallicRoughness{MetallicFactor: Float64(1), RoughnessFactor: Float64(1), BaseColorFactor: &[4]float32{1, 1, 1, 1}}, []byte(`{}`), false}, {"empty", &PBRMetallicRoughness{MetallicFactor: Float64(0), RoughnessFactor: Float64(0)}, []byte(`{"metallicFactor":0,"roughnessFactor":0}`), false}, - {"nodefault", &PBRMetallicRoughness{MetallicFactor: Float64(0.5), RoughnessFactor: Float64(0.5), BaseColorFactor: &RGBA{R: 1, G: 0.5, B: 1, A: 1}}, []byte(`{"baseColorFactor":[1,0.5,1,1],"metallicFactor":0.5,"roughnessFactor":0.5}`), false}, + {"nodefault", &PBRMetallicRoughness{MetallicFactor: Float64(0.5), RoughnessFactor: Float64(0.5), BaseColorFactor: &[4]float32{1, 0.5, 1, 1}}, []byte(`{"baseColorFactor":[1,0.5,1,1],"metallicFactor":0.5,"roughnessFactor":0.5}`), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/math.go b/math.go new file mode 100644 index 0000000..73d5d08 --- /dev/null +++ b/math.go @@ -0,0 +1,141 @@ +package gltf + +import "math" + +// NormalizeByte normalize a float32 into a int8 +func NormalizeByte(v float32) int8 { + return int8(math.Round(float64(v) * 127)) +} + +// NormalizeByte denormalize a int8 into a float32 +func DenormalizeByte(v int8) float32 { + return float32(math.Max(float64(v)/127, -1)) +} + +// NormalizeUbyte normalize a float32 into a uint8 +func NormalizeUbyte(v float32) uint8 { + return uint8(math.Round(float64(v) * 255)) +} + +// DenormalizeUbyte denormalize a uint8 into a float32 +func DenormalizeUbyte(v uint8) float32 { + return float32(v) / 255 +} + +// NormalizeShort normalize a float32 into a int16 +func NormalizeShort(v float32) int16 { + return int16(math.Round(float64(v) * 32767)) +} + +// DenormalizeShort denormalize a int16 into a float32 +func DenormalizeShort(v int16) float32 { + return float32(math.Max(float64(v)/32767, -1)) +} + +// NormalizeUshort normalize a float32 into a uint16 +func NormalizeUshort(v float32) uint16 { + return uint16(math.Round(float64(v) * 65535)) +} + +// DenormalizeuShort denormalize a uint16 into a float32 +func DenormalizeUshort(v uint16) float32 { + return float32(v) / 65535 +} + +// NormalizeRGB transform a RGB float32 color (from 0 to 1) +// to its uint8 represtation (from 0 to 255). +func NormalizeRGB(v [3]float32) [3]uint8 { + return [3]uint8{ + uint8(deliniarize(v[0]) * 255), + uint8(deliniarize(v[1]) * 255), + uint8(deliniarize(v[2]) * 255), + } +} + +// DenormalizeRGB transform a RGB uint8 color (from 0 to 255) +// to its float represtation (from 0 to 1). +func DenormalizeRGB(v [3]uint8) [3]float32 { + return [3]float32{ + linearize(float64(v[0]) / 255), + linearize(float64(v[1]) / 255), + linearize(float64(v[2]) / 255), + } +} + +// NormalizeRGB transform a RGBA float32 color (from 0 to 1) +// to its uint8 represtation (from 0 to 255). +func NormalizeRGBA(v [4]float32) [4]uint8 { + return [4]uint8{ + uint8(deliniarize(v[0]) * 255), + uint8(deliniarize(v[1]) * 255), + uint8(deliniarize(v[2]) * 255), + uint8(v[3] * 255), + } +} + +// DenormalizeRGBA transform a RGBA uint8 color (from 0 to 255) +// to its float represtation (from 0 to 1). +func DenormalizeRGBA(v [4]uint8) [4]float32 { + return [4]float32{ + linearize(float64(v[0]) / 255), + linearize(float64(v[1]) / 255), + linearize(float64(v[2]) / 255), + float32(v[3]) / 255, + } +} + +// NormalizeRGB64 transform a RGB float32 color (from 0 to 1) +// to its uint16 represtation (from 0 to 65535). +func NormalizeRGB64(v [3]float32) [3]uint16 { + return [3]uint16{ + uint16(deliniarize(v[0]) * 65535), + uint16(deliniarize(v[1]) * 65535), + uint16(deliniarize(v[2]) * 65535), + } +} + +// DenormalizeRGB64 transform a RGB uint16 color (from 0 to 65535) +// to its float represtation (from 0 to 1). +func DenormalizeRGB64(v [3]uint16) [3]float32 { + return [3]float32{ + linearize(float64(v[0]) / 65535), + linearize(float64(v[1]) / 65535), + linearize(float64(v[2]) / 65535), + } +} + +// NormalizeRGBA64 transform a RGBA float32 color (from 0 to 1) +// to its uint16 represtation (from 0 to 65535). +func NormalizeRGBA64(v [4]float32) [4]uint16 { + return [4]uint16{ + uint16(deliniarize(v[0]) * 65535), + uint16(deliniarize(v[1]) * 65535), + uint16(deliniarize(v[2]) * 65535), + uint16(v[3] * 65535), + } +} + +// DenormalizeRGBA64 transform a RGBA uint16 color (from 0 to 65535) +// to its float represtation (from 0 to 1). +func DenormalizeRGBA64(v [4]uint16) [4]float32 { + return [4]float32{ + linearize(float64(v[0]) / 65535), + linearize(float64(v[1]) / 65535), + linearize(float64(v[2]) / 65535), + float32(v[3]) / 65535, + } +} + +func linearize(v float64) float32 { + if v <= 0.04045 { + return float32(v / 12.92) + } + return float32(math.Pow((v+0.055)/1.055, 2.4)) +} + +func deliniarize(v float32) float32 { + if v < 0.0031308 { + return v * 12.92 + } + return float32(1.055*math.Pow(float64(v), 1.0/2.4) - 0.055) +} diff --git a/math_test.go b/math_test.go new file mode 100644 index 0000000..c52524e --- /dev/null +++ b/math_test.go @@ -0,0 +1,63 @@ +package gltf + +import ( + "math" + "testing" + + "github.com/go-test/deep" +) + +func TestNormalize(t *testing.T) { + if got := NormalizeByte(0.0236); got != 3 { + t.Errorf("NormalizeByte = %v, want %v", got, 3) + } + if got := NormalizeUbyte(0.2); got != 51 { + t.Errorf("NormalizeUbyte = %v, want %v", got, 51) + } + if got := NormalizeShort(0.03053); got != 1000 { + t.Errorf("NormalizeShort = %v, want %v", got, 1000) + } + if got := NormalizeUshort(0.2); got != 13107 { + t.Errorf("NormalizeUshort = %v, want %v", got, 13107) + } +} + +func TestDenormalize(t *testing.T) { + if got := DenormalizeByte(3); math.Abs(float64(got)-0.0236) > 1e-4 { + t.Errorf("DenormalizeByte = %v, want %v", got, 0.0236) + } + if got := DenormalizeUbyte(51); got != 0.2 { + t.Errorf("DenormalizeUbyte = %v, want %v", got, 0.2) + } + if got := DenormalizeShort(1000); math.Abs(float64(got)-0.03053) > 1e-4 { + t.Errorf("DenormalizeShort = %v, want %v", got, 0.03053) + } + if got := DenormalizeUshort(13107); got != 0.2 { + t.Errorf("DenormalizeUshort = %v, want %v", got, 0.2) + } +} + +func TestDenormalizeRGBA(t *testing.T) { + deep.FloatPrecision = 6 + type args struct { + v [4]uint8 + } + tests := []struct { + name string + args args + want [4]float32 + }{ + {"empty", args{[4]uint8{}}, [4]float32{}}, + {"base", args{[4]uint8{1, 1, 1, 1}}, [4]float32{0.0003035, 0.0003035, 0.0003035, 0.00392156}}, + {"max", args{[4]uint8{255, 255, 255, 255}}, [4]float32{1, 1, 1, 1}}, + {"other", args{[4]uint8{60, 120, 180, 220}}, [4]float32{0.045186, 0.1878207, 0.4564110, 0.86274509}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := DenormalizeRGBA(tt.args.v) + if diff := deep.Equal(got, tt.want); diff != nil { + t.Errorf("DenormalizeRGBA() = %v", diff) + } + }) + } +} diff --git a/modeler/read.go b/modeler/read.go new file mode 100644 index 0000000..de505e9 --- /dev/null +++ b/modeler/read.go @@ -0,0 +1,438 @@ +package modeler + +import ( + "errors" + "fmt" + "io" + "reflect" + + "github.com/qmuntal/gltf" + "github.com/qmuntal/gltf/binary" +) + +// ReadAccessor returns the data references by acr +// as an slice whose element types are the ones associated with +// acr.ComponentType and acr.Type. +// +// If data is an slice whose elements type matches the accessor type +// then data will be used as backing slice, else a new slice will be allocated. +// +// ReadAccessor supports all types of accessors: non-interleaved, interleaved, sparse, +// without buffer views, ..., and any combinations of them. +// +// ReadAccessor is safe to use even with malformed documents. +// If that happens it will return an error instead of panic. +func ReadAccessor(doc *gltf.Document, acr *gltf.Accessor, data interface{}) (interface{}, error) { + if acr.BufferView == nil && acr.Sparse == nil { + return nil, nil + } + if data != nil { + c, t, count := binary.Type(data) + if count > 0 && c == acr.ComponentType && t == acr.Type { + if uint32(count) < acr.Count { + tmpSlice := binary.MakeSlice(acr.ComponentType, acr.Type, acr.Count-uint32(count)) + data = reflect.AppendSlice(reflect.ValueOf(data), reflect.ValueOf(tmpSlice)).Interface() + } + } else { + data = binary.MakeSlice(acr.ComponentType, acr.Type, acr.Count) + } + } else { + data = binary.MakeSlice(acr.ComponentType, acr.Type, acr.Count) + } + if acr.BufferView != nil { + buffer, err := readBufferView(doc, *acr.BufferView) + if err != nil { + return nil, err + } + byteStride := doc.BufferViews[*acr.BufferView].ByteStride + err = binary.Read(buffer[acr.ByteOffset:], byteStride, data) + if err != nil { + return nil, err + } + } + + if acr.Sparse != nil { + indicesBuffer, err := readBufferView(doc, acr.Sparse.Indices.BufferView) + if err != nil { + return nil, err + } + + byteStride := doc.BufferViews[acr.Sparse.Indices.BufferView].ByteStride + indices := binary.MakeSlice(acr.Sparse.Indices.ComponentType, gltf.AccessorScalar, acr.Sparse.Count) + err = binary.Read(indicesBuffer[acr.Sparse.Indices.ByteOffset:], byteStride, indices) + if err != nil { + return nil, err + } + + valuesBuffer, err := readBufferView(doc, acr.Sparse.Values.BufferView) + if err != nil { + return nil, err + } + byteStride = doc.BufferViews[acr.Sparse.Values.ByteOffset].ByteStride + values := binary.MakeSlice(acr.ComponentType, acr.Type, acr.Sparse.Count) + err = binary.Read(valuesBuffer[acr.Sparse.Values.ByteOffset:], byteStride, values) + if err != nil { + return nil, err + } + + s := reflect.ValueOf(data) + ind := reflect.ValueOf(indices) + vals := reflect.ValueOf(values) + tp := reflect.TypeOf((*int)(nil)).Elem() + for i := 0; i < int(acr.Sparse.Count); i++ { + id := ind.Index(i).Convert(tp).Interface().(int) + s.Index(id).Set(vals.Index(i)) + } + } + return data, nil +} + +func readBufferView(doc *gltf.Document, bufferViewIndex uint32) ([]byte, error) { + if uint32(len(doc.BufferViews)) <= bufferViewIndex { + return nil, errors.New("gltf: bufferview index overflows") + } + return ReadBufferView(doc, doc.BufferViews[bufferViewIndex]) +} + +// ReadBufferView returns the slice of bytes associated with the BufferView. +// +// It is safe to use even with malformed documents. +// If that happens it will return an error instead of panic. +func ReadBufferView(doc *gltf.Document, bv *gltf.BufferView) ([]byte, error) { + if uint32(len(doc.Buffers)) <= bv.Buffer { + return nil, errors.New("gltf: buffer index overflows") + } + buf := doc.Buffers[bv.Buffer].Data + + high := bv.ByteOffset + bv.ByteLength + if uint32(len(buf)) < high { + return nil, io.ErrShortBuffer + } + return buf[bv.ByteOffset:high], nil +} + +// ReadIndices returns the data referenced by acr. +// If acr.ComponentType is other than Uint the data +// will be converted appropriately. +// +// See ReadAccessor for more info. +func ReadIndices(doc *gltf.Document, acr *gltf.Accessor, buffer []uint32) ([]uint32, error) { + switch acr.ComponentType { + case gltf.ComponentUbyte, gltf.ComponentUshort, gltf.ComponentUint: + default: + return nil, errComponentType(acr.ComponentType) + } + if acr.Type != gltf.AccessorScalar { + return nil, errAccessorType(acr.Type) + } + data, err := ReadAccessor(doc, acr, buffer) + if err != nil { + return nil, err + } + if uint32(len(buffer)) < acr.Count { + buffer = append(buffer, make([]uint32, acr.Count-uint32(len(buffer)))...) + } + switch acr.ComponentType { + case gltf.ComponentUbyte: + for i, e := range data.([]uint8) { + buffer[i] = uint32(e) + } + case gltf.ComponentUshort: + for i, e := range data.([]uint16) { + buffer[i] = uint32(e) + } + case gltf.ComponentUint: + buffer = data.([]uint32) + } + return buffer[:acr.Count], nil +} + +// ReadNormal returns the data referenced by acr. +// +// See ReadAccessor for more info. +func ReadNormal(doc *gltf.Document, acr *gltf.Accessor, buffer [][3]float32) ([][3]float32, error) { + if acr.ComponentType != gltf.ComponentFloat { + return nil, errComponentType(acr.ComponentType) + } + if acr.Type != gltf.AccessorVec3 { + return nil, errAccessorType(acr.Type) + } + data, err := ReadAccessor(doc, acr, buffer) + if err != nil { + return nil, err + } + return data.([][3]float32)[:acr.Count], nil +} + +// ReadTangent returns the data referenced by acr. +// +// See ReadAccessor for more info. +func ReadTangent(doc *gltf.Document, acr *gltf.Accessor, buffer [][4]float32) ([][4]float32, error) { + if acr.ComponentType != gltf.ComponentFloat { + return nil, errComponentType(acr.ComponentType) + } + if acr.Type != gltf.AccessorVec4 { + return nil, errAccessorType(acr.Type) + } + data, err := ReadAccessor(doc, acr, buffer) + if err != nil { + return nil, err + } + return data.([][4]float32)[:acr.Count], nil +} + +// ReadTextureCoord returns the data referenced by acr. +// If acr.ComponentType is other than Float the data +// will be converted and denormalized appropriately. +// +// See ReadAccessor for more info. +func ReadTextureCoord(doc *gltf.Document, acr *gltf.Accessor, buffer [][2]float32) ([][2]float32, error) { + switch acr.ComponentType { + case gltf.ComponentUbyte, gltf.ComponentUshort, gltf.ComponentFloat: + default: + return nil, errComponentType(acr.ComponentType) + } + if acr.Type != gltf.AccessorVec2 { + return nil, errAccessorType(acr.Type) + } + data, err := ReadAccessor(doc, acr, buffer) + if err != nil { + return nil, err + } + if uint32(len(buffer)) < acr.Count { + buffer = append(buffer, make([][2]float32, acr.Count-uint32(len(buffer)))...) + } + switch acr.ComponentType { + case gltf.ComponentUbyte: + for i, e := range data.([][2]uint8) { + buffer[i] = [2]float32{ + gltf.DenormalizeUbyte(e[0]), gltf.DenormalizeUbyte(e[1]), + } + } + case gltf.ComponentUshort: + for i, e := range data.([][2]uint16) { + buffer[i] = [2]float32{ + gltf.DenormalizeUshort(e[0]), gltf.DenormalizeUshort(e[1]), + } + } + case gltf.ComponentFloat: + buffer = data.([][2]float32) + } + return buffer[:acr.Count], nil +} + +// ReadWeights returns the data referenced by acr. +// If acr.ComponentType is other than Float the data +// will be converted and denormalized appropriately. +// +// See ReadAccessor for more info. +func ReadWeights(doc *gltf.Document, acr *gltf.Accessor, buffer [][4]float32) ([][4]float32, error) { + switch acr.ComponentType { + case gltf.ComponentUbyte, gltf.ComponentUshort, gltf.ComponentFloat: + default: + return nil, errComponentType(acr.ComponentType) + } + if acr.Type != gltf.AccessorVec4 { + return nil, errAccessorType(acr.Type) + } + data, err := ReadAccessor(doc, acr, buffer) + if err != nil { + return nil, err + } + if uint32(len(buffer)) < acr.Count { + buffer = append(buffer, make([][4]float32, acr.Count-uint32(len(buffer)))...) + } + switch acr.ComponentType { + case gltf.ComponentUbyte: + for i, e := range data.([][4]uint8) { + buffer[i] = [4]float32{ + gltf.DenormalizeUbyte(e[0]), gltf.DenormalizeUbyte(e[1]), + gltf.DenormalizeUbyte(e[2]), gltf.DenormalizeUbyte(e[3]), + } + } + case gltf.ComponentUshort: + for i, e := range data.([][4]uint16) { + buffer[i] = [4]float32{ + gltf.DenormalizeUshort(e[0]), gltf.DenormalizeUshort(e[1]), + gltf.DenormalizeUshort(e[2]), gltf.DenormalizeUshort(e[3]), + } + } + case gltf.ComponentFloat: + buffer = data.([][4]float32) + } + return buffer[:acr.Count], nil +} + +// ReadJoints returns the data referenced by acr. +// If acr.ComponentType is other than Ushort the data +// will be converted and denormalized appropriately. +// +// See ReadAccessor for more info. +func ReadJoints(doc *gltf.Document, acr *gltf.Accessor, buffer [][4]uint16) ([][4]uint16, error) { + switch acr.ComponentType { + case gltf.ComponentUbyte, gltf.ComponentUshort: + default: + return nil, errComponentType(acr.ComponentType) + } + if acr.Type != gltf.AccessorVec4 { + return nil, errAccessorType(acr.Type) + } + data, err := ReadAccessor(doc, acr, buffer) + if err != nil { + return nil, err + } + if uint32(len(buffer)) < acr.Count { + buffer = append(buffer, make([][4]uint16, acr.Count-uint32(len(buffer)))...) + } + switch acr.ComponentType { + case gltf.ComponentUbyte: + for i, e := range data.([][4]uint8) { + buffer[i] = [4]uint16{ + uint16(e[0]), uint16(e[1]), + uint16(e[2]), uint16(e[3]), + } + } + case gltf.ComponentUshort: + buffer = data.([][4]uint16) + } + return buffer[:acr.Count], nil +} + +// ReadPosition returns the data referenced by acr. +// +// See ReadAccessor for more info. +func ReadPosition(doc *gltf.Document, acr *gltf.Accessor, buffer [][3]float32) ([][3]float32, error) { + if acr.ComponentType != gltf.ComponentFloat { + return nil, errComponentType(acr.ComponentType) + } + if acr.Type != gltf.AccessorVec3 { + return nil, errAccessorType(acr.Type) + } + data, err := ReadAccessor(doc, acr, buffer) + if err != nil { + return nil, err + } + return data.([][3]float32)[:acr.Count], nil +} + +// ReadColor returns the data referenced by acr. +// If acr.ComponentType is other than Ubyte the data +// will be converted and normalized appropriately. +// +// See ReadAccessor for more info. +func ReadColor(doc *gltf.Document, acr *gltf.Accessor, buffer [][4]uint8) ([][4]uint8, error) { + switch acr.ComponentType { + case gltf.ComponentUbyte, gltf.ComponentUshort, gltf.ComponentFloat: + default: + return nil, errComponentType(acr.ComponentType) + } + switch acr.Type { + case gltf.AccessorVec3, gltf.AccessorVec4: + default: + return nil, errAccessorType(acr.Type) + } + data, err := ReadAccessor(doc, acr, buffer) + if err != nil { + return nil, err + } + if uint32(len(buffer)) < acr.Count { + buffer = append(buffer, make([][4]uint8, acr.Count-uint32(len(buffer)))...) + } + switch acr.ComponentType { + case gltf.ComponentUbyte: + if acr.Type == gltf.AccessorVec3 { + for i, e := range data.([][3]uint8) { + buffer[i] = [4]uint8{e[0], e[1], e[2], 255} + } + } else { + buffer = data.([][4]uint8) + } + case gltf.ComponentUshort: + if acr.Type == gltf.AccessorVec3 { + for i, e := range data.([][3]uint16) { + buffer[i] = [4]uint8{uint8(e[0]), uint8(e[1]), uint8(e[2]), 255} + } + } else { + for i, e := range data.([][4]uint16) { + buffer[i] = [4]uint8{uint8(e[0]), uint8(e[1]), uint8(e[2]), uint8(e[3])} + } + } + case gltf.ComponentFloat: + if acr.Type == gltf.AccessorVec3 { + for i, e := range data.([][3]float32) { + tmp := gltf.NormalizeRGB(e) + buffer[i] = [4]uint8{tmp[0], tmp[1], tmp[2], 255} + } + } else { + for i, e := range data.([][4]float32) { + buffer[i] = gltf.NormalizeRGBA(e) + } + } + } + return buffer[:acr.Count], nil +} + +// ReadColor returns the data referenced by acr. +// If acr.ComponentType is other than Ushort the data +// will be converted and normalized appropriately. +// +// See ReadAccessor for more info. +func ReadColor64(doc *gltf.Document, acr *gltf.Accessor, buffer [][4]uint16) ([][4]uint16, error) { + switch acr.ComponentType { + case gltf.ComponentUbyte, gltf.ComponentUshort, gltf.ComponentFloat: + default: + return nil, errComponentType(acr.ComponentType) + } + switch acr.Type { + case gltf.AccessorVec3, gltf.AccessorVec4: + default: + return nil, errAccessorType(acr.Type) + } + data, err := ReadAccessor(doc, acr, buffer) + if err != nil { + return nil, err + } + if uint32(len(buffer)) < acr.Count { + buffer = append(buffer, make([][4]uint16, acr.Count-uint32(len(buffer)))...) + } + switch acr.ComponentType { + case gltf.ComponentUbyte: + if acr.Type == gltf.AccessorVec3 { + for i, e := range data.([][3]uint8) { + buffer[i] = [4]uint16{uint16(e[0]), uint16(e[1]), uint16(e[2]), 65535} + } + } else { + for i, e := range data.([][4]uint8) { + buffer[i] = [4]uint16{uint16(e[0]), uint16(e[1]), uint16(e[2]), uint16(e[3])} + } + } + case gltf.ComponentUshort: + if acr.Type == gltf.AccessorVec3 { + for i, e := range data.([][3]uint16) { + buffer[i] = [4]uint16{e[0], e[1], e[2], 65535} + } + } else { + buffer = data.([][4]uint16) + } + case gltf.ComponentFloat: + if acr.Type == gltf.AccessorVec3 { + for i, e := range data.([][3]float32) { + tmp := gltf.NormalizeRGB64(e) + buffer[i] = [4]uint16{tmp[0], tmp[1], tmp[2], 65535} + } + } else { + for i, e := range data.([][4]float32) { + buffer[i] = gltf.NormalizeRGBA64(e) + } + } + } + return buffer[:acr.Count], nil +} + +func errAccessorType(tp gltf.AccessorType) error { + return fmt.Errorf("gltf: accessor type %v not allowed", tp) +} + +func errComponentType(tp gltf.ComponentType) error { + return fmt.Errorf("gltf: component type %v not allowed", tp) +} diff --git a/modeler/read_test.go b/modeler/read_test.go new file mode 100644 index 0000000..cde4b78 --- /dev/null +++ b/modeler/read_test.go @@ -0,0 +1,643 @@ +package modeler + +import ( + "reflect" + "testing" + + "github.com/qmuntal/gltf" +) + +func TestReadBufferView(t *testing.T) { + type args struct { + doc *gltf.Document + bv *gltf.BufferView + } + tests := []struct { + name string + args args + want []byte + wantErr bool + }{ + {"base", args{&gltf.Document{Buffers: []*gltf.Buffer{ + {ByteLength: 9, Data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}}, + }}, &gltf.BufferView{ + Buffer: 0, ByteLength: 3, ByteOffset: 6, + }}, []byte{7, 8, 9}, false}, + {"errbuffer", args{&gltf.Document{Buffers: []*gltf.Buffer{ + {ByteLength: 9, Data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}}, + }}, &gltf.BufferView{ + Buffer: 1, ByteLength: 3, ByteOffset: 6, + }}, nil, true}, + {"shortbuffer", args{&gltf.Document{Buffers: []*gltf.Buffer{ + {ByteLength: 9, Data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}}, + }}, &gltf.BufferView{ + Buffer: 0, ByteLength: 10, ByteOffset: 6, + }}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ReadBufferView(tt.args.doc, tt.args.bv) + if (err != nil) != tt.wantErr { + t.Errorf("ReadBufferView() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ReadBufferView() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestReadAccessor(t *testing.T) { + type args struct { + doc *gltf.Document + acr *gltf.Accessor + } + tests := []struct { + name string + args args + want interface{} + wantErr bool + }{ + {"nodata", args{&gltf.Document{}, &gltf.Accessor{}}, nil, false}, + {"base", args{&gltf.Document{Buffers: []*gltf.Buffer{ + {ByteLength: 9, Data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}}, + }, BufferViews: []*gltf.BufferView{{ + Buffer: 0, ByteLength: 6, ByteOffset: 3, + }}}, &gltf.Accessor{ + BufferView: gltf.Index(0), ByteOffset: 3, ComponentType: gltf.ComponentUbyte, Type: gltf.AccessorScalar, Count: 3, + }}, []byte{7, 8, 9}, false}, + {"shortbuffer", args{&gltf.Document{Buffers: []*gltf.Buffer{ + {ByteLength: 9, Data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}}, + }, BufferViews: []*gltf.BufferView{{ + Buffer: 0, ByteLength: 3, ByteOffset: 3, + }}}, &gltf.Accessor{ + BufferView: gltf.Index(0), ByteOffset: 3, ComponentType: gltf.ComponentUbyte, Type: gltf.AccessorScalar, Count: 3, + }}, nil, true}, + {"viewoverflow", args{&gltf.Document{Buffers: []*gltf.Buffer{ + {ByteLength: 9, Data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}}, + }, BufferViews: []*gltf.BufferView{{ + Buffer: 0, ByteLength: 6, ByteOffset: 3, + }}}, &gltf.Accessor{ + BufferView: gltf.Index(1), ByteOffset: 3, ComponentType: gltf.ComponentUbyte, Type: gltf.AccessorScalar, Count: 3, + }}, []byte{7, 8, 9}, false}, + {"interleaved", args{&gltf.Document{ + Buffers: []*gltf.Buffer{{ByteLength: 52, Data: []byte{ + 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 191, + }}}, BufferViews: []*gltf.BufferView{{Buffer: 0, ByteOffset: 4, ByteLength: 48, ByteStride: 24}}, + }, &gltf.Accessor{ + BufferView: gltf.Index(0), ByteOffset: 12, ComponentType: gltf.ComponentFloat, Type: gltf.AccessorVec3, Count: 2, + }}, [][3]float32{{1, 2, 3}, {0, 0, -1}}, false}, + {"sparse", args{&gltf.Document{ + Buffers: []*gltf.Buffer{{ByteLength: 284, Data: []byte{ + 0, 0, 8, 0, 7, 0, 0, 0, 1, 0, 8, 0, 1, 0, 9, 0, 8, 0, 1, 0, 2, 0, 9, 0, + 2, 0, 10, 0, 9, 0, 2, 0, 3, 0, 10, 0, 3, 0, 11, 0, 10, 0, 3, 0, 4, 0, 11, + 0, 4, 0, 12, 0, 11, 0, 4, 0, 5, 0, 12, 0, 5, 0, 13, 0, 12, 0, 5, 0, 6, 0, + 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 128, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 160, 64, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 192, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 0, + 0, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 128, 63, + 0, 0, 0, 0, 0, 0, 64, 64, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 128, 64, 0, 0, 128, + 63, 0, 0, 0, 0, 0, 0, 160, 64, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 192, 64, 0, 0, + 128, 63, 0, 0, 0, 0, 8, 0, 10, 0, 12, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, + 0, 0, 0, 0, 64, 64, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0, 160, 64, 0, 0, 128, 64, 0, 0, 0, 0}}}, + BufferViews: []*gltf.BufferView{ + {Buffer: 0, ByteOffset: 72, ByteLength: 168}, + {Buffer: 0, ByteOffset: 240, ByteLength: 6}, + {Buffer: 0, ByteOffset: 248, ByteLength: 36}, + }, + }, &gltf.Accessor{ + BufferView: gltf.Index(0), ComponentType: gltf.ComponentFloat, Type: gltf.AccessorVec3, Count: 14, + Sparse: &gltf.Sparse{ + Count: 3, + Indices: gltf.SparseIndices{BufferView: 1, ComponentType: gltf.ComponentUshort}, + Values: gltf.SparseValues{BufferView: 2}, + }, + }}, [][3]float32{ + {0, 0, 0}, {1, 0, 0}, {2, 0, 0}, {3, 0, 0}, {4, 0, 0}, {5, 0, 0}, {6, 0, 0}, + {0, 1, 0}, {1, 2, 0}, {2, 1, 0}, {3, 3, 0}, {4, 1, 0}, {5, 4, 0}, {6, 1, 0}}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ReadAccessor(tt.args.doc, tt.args.acr, nil) + if (err != nil) != tt.wantErr { + t.Errorf("ReadAccessor() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ReadAccessor() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestReadAccessorAllocs(t *testing.T) { + doc := &gltf.Document{ + Buffers: []*gltf.Buffer{{ByteLength: 52, Data: []byte{ + 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, + 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, + 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, + 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, + }}}, BufferViews: []*gltf.BufferView{{Buffer: 0, ByteLength: 48}}, + } + acr := &gltf.Accessor{ + BufferView: gltf.Index(0), ComponentType: gltf.ComponentFloat, Type: gltf.AccessorVec3, Count: 4, + } + + testFunc := func(t *testing.T, buf [][3]float32, want float64) { + allocs := testing.AllocsPerRun(10, func() { + ReadAccessor(doc, acr, buf) + }) + if allocs != want { + t.Errorf("ReadAccessor expected %v allocs got %v", want, allocs) + } + + } + t.Run("nil", func(t *testing.T) { + testFunc(t, nil, 2) + }) + t.Run("2", func(t *testing.T) { + buf := make([][3]float32, 2) + testFunc(t, buf, 6) + testFunc(t, buf, 6) + testFunc(t, buf, 6) + testFunc(t, buf, 6) + }) + t.Run("4", func(t *testing.T) { + buf := make([][3]float32, 4) + testFunc(t, buf, 1) + testFunc(t, buf, 1) + testFunc(t, buf, 1) + testFunc(t, buf, 1) + }) +} + +func TestReadIndices(t *testing.T) { + type args struct { + data []byte + acr *gltf.Accessor + buffer []uint32 + } + tests := []struct { + name string + args args + want []uint32 + wantErr bool + }{ + {"uint8", args{[]byte{1, 2}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorScalar, ComponentType: gltf.ComponentUbyte, + }, nil}, []uint32{1, 2}, false}, + {"uint16", args{[]byte{1, 0, 2, 0}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorScalar, ComponentType: gltf.ComponentUshort, + }, nil}, []uint32{1, 2}, false}, + {"uint32", args{[]byte{1, 0, 0, 0, 2, 0, 0, 0}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorScalar, ComponentType: gltf.ComponentUint, + }, nil}, []uint32{1, 2}, false}, + {"uint32-withbuffer", args{[]byte{1, 0, 0, 0, 2, 0, 0, 0}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorScalar, ComponentType: gltf.ComponentUint, + }, make([]uint32, 1)}, []uint32{1, 2}, false}, + {"incorrect-type", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorMat2, ComponentType: gltf.ComponentUint, + }, nil}, nil, true}, + {"incorrect-componenttype", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorScalar, ComponentType: gltf.ComponentFloat, + }, nil}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + doc := &gltf.Document{ + BufferViews: []*gltf.BufferView{ + {Buffer: 0, ByteLength: uint32(len(tt.args.data))}, + }, + Buffers: []*gltf.Buffer{ + {Data: tt.args.data, ByteLength: uint32(len(tt.args.data))}, + }, + } + got, err := ReadIndices(doc, tt.args.acr, tt.args.buffer) + if (err != nil) != tt.wantErr { + t.Errorf("ReadIndices() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ReadIndices() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestReadNormal(t *testing.T) { + type args struct { + data []byte + acr *gltf.Accessor + buffer [][3]float32 + } + tests := []struct { + name string + args args + want [][3]float32 + wantErr bool + }{ + {"float32", args{[]byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec3, ComponentType: gltf.ComponentFloat, + }, nil}, [][3]float32{{1, 2, 3}}, false}, + {"float32-withbuffer", args{[]byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec3, ComponentType: gltf.ComponentFloat, + }, make([][3]float32, 1)}, [][3]float32{{1, 2, 3}}, false}, + {"incorrect-type", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorMat2, ComponentType: gltf.ComponentFloat, + }, nil}, nil, true}, + {"incorrect-componenttype", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorVec3, ComponentType: gltf.ComponentByte, + }, nil}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + doc := &gltf.Document{ + BufferViews: []*gltf.BufferView{ + {Buffer: 0, ByteLength: uint32(len(tt.args.data))}, + }, + Buffers: []*gltf.Buffer{ + {Data: tt.args.data, ByteLength: uint32(len(tt.args.data))}, + }, + } + got, err := ReadNormal(doc, tt.args.acr, tt.args.buffer) + if (err != nil) != tt.wantErr { + t.Errorf("ReadNormal() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ReadNormal() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestReadTangent(t *testing.T) { + type args struct { + data []byte + acr *gltf.Accessor + buffer [][4]float32 + } + tests := []struct { + name string + args args + want [][4]float32 + wantErr bool + }{ + {"float32", args{[]byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64, 0, 0, 0, 0, 0, 0}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentFloat, + }, nil}, [][4]float32{{1, 2, 3, 4}}, false}, + {"float32-withbuffer", args{[]byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64, 0, 0, 0, 0, 0, 0}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentFloat, + }, make([][4]float32, 1)}, [][4]float32{{1, 2, 3, 4}}, false}, + {"incorrect-type", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorMat2, ComponentType: gltf.ComponentFloat, + }, nil}, nil, true}, + {"incorrect-componenttype", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorVec4, ComponentType: gltf.ComponentByte, + }, nil}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + doc := &gltf.Document{ + BufferViews: []*gltf.BufferView{ + {Buffer: 0, ByteLength: uint32(len(tt.args.data))}, + }, + Buffers: []*gltf.Buffer{ + {Data: tt.args.data, ByteLength: uint32(len(tt.args.data))}, + }, + } + got, err := ReadTangent(doc, tt.args.acr, tt.args.buffer) + if (err != nil) != tt.wantErr { + t.Errorf("ReadTangent() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ReadTangent() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestReadTextureCoord(t *testing.T) { + type args struct { + data []byte + acr *gltf.Accessor + buffer [][2]float32 + } + tests := []struct { + name string + args args + want [][2]float32 + wantErr bool + }{ + {"uint8", args{[]byte{255, 0, 0, 0}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec2, ComponentType: gltf.ComponentUbyte, + }, nil}, [][2]float32{{1, 0}}, false}, + {"uint16", args{[]byte{255, 255, 0, 0}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec2, ComponentType: gltf.ComponentUshort, + }, nil}, [][2]float32{{1, 0}}, false}, + {"float32", args{[]byte{0, 0, 128, 63, 0, 0, 0, 64}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec2, ComponentType: gltf.ComponentFloat, + }, nil}, [][2]float32{{1, 2}}, false}, + {"float32-withbuffer", args{[]byte{0, 0, 128, 63, 0, 0, 0, 64}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec2, ComponentType: gltf.ComponentFloat, + }, make([][2]float32, 1)}, [][2]float32{{1, 2}}, false}, + {"incorrect-type", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorMat2, ComponentType: gltf.ComponentFloat, + }, nil}, nil, true}, + {"incorrect-componenttype", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorVec2, ComponentType: gltf.ComponentByte, + }, nil}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + doc := &gltf.Document{ + BufferViews: []*gltf.BufferView{ + {Buffer: 0, ByteLength: uint32(len(tt.args.data))}, + }, + Buffers: []*gltf.Buffer{ + {Data: tt.args.data, ByteLength: uint32(len(tt.args.data))}, + }, + } + got, err := ReadTextureCoord(doc, tt.args.acr, tt.args.buffer) + if (err != nil) != tt.wantErr { + t.Errorf("ReadTextureCoord() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ReadTextureCoord() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestReadWeights(t *testing.T) { + type args struct { + data []byte + acr *gltf.Accessor + buffer [][4]float32 + } + tests := []struct { + name string + args args + want [][4]float32 + wantErr bool + }{ + {"uint8", args{[]byte{255, 0, 255, 0}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUbyte, + }, nil}, [][4]float32{{1, 0, 1, 0}}, false}, + {"uint16", args{[]byte{0, 0, 255, 255, 0, 0, 255, 255}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUshort, + }, nil}, [][4]float32{{0, 1, 0, 1}}, false}, + {"float32", args{[]byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentFloat, + }, nil}, [][4]float32{{1, 2, 3, 4}}, false}, + {"float32-withbuffer", args{[]byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentFloat, + }, make([][4]float32, 1)}, [][4]float32{{1, 2, 3, 4}}, false}, + {"incorrect-type", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorMat2, ComponentType: gltf.ComponentFloat, + }, nil}, nil, true}, + {"incorrect-componenttype", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorVec4, ComponentType: gltf.ComponentByte, + }, nil}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + doc := &gltf.Document{ + BufferViews: []*gltf.BufferView{ + {Buffer: 0, ByteLength: uint32(len(tt.args.data))}, + }, + Buffers: []*gltf.Buffer{ + {Data: tt.args.data, ByteLength: uint32(len(tt.args.data))}, + }, + } + got, err := ReadWeights(doc, tt.args.acr, tt.args.buffer) + if (err != nil) != tt.wantErr { + t.Errorf("ReadWeights() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ReadWeights() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestReadJoints(t *testing.T) { + type args struct { + data []byte + acr *gltf.Accessor + buffer [][4]uint16 + } + tests := []struct { + name string + args args + want [][4]uint16 + wantErr bool + }{ + {"uint8", args{[]byte{255, 0, 255, 0}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUbyte, + }, nil}, [][4]uint16{{255, 0, 255, 0}}, false}, + {"uint16", args{[]byte{0, 0, 255, 255, 0, 0, 255, 255}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUshort, + }, nil}, [][4]uint16{{0, 65535, 0, 65535}}, false}, + {"incorrect-type", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorMat2, ComponentType: gltf.ComponentUshort, + }, nil}, nil, true}, + {"incorrect-componenttype", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorVec4, ComponentType: gltf.ComponentByte, + }, nil}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + doc := &gltf.Document{ + BufferViews: []*gltf.BufferView{ + {Buffer: 0, ByteLength: uint32(len(tt.args.data))}, + }, + Buffers: []*gltf.Buffer{ + {Data: tt.args.data, ByteLength: uint32(len(tt.args.data))}, + }, + } + got, err := ReadJoints(doc, tt.args.acr, tt.args.buffer) + if (err != nil) != tt.wantErr { + t.Errorf("ReadJoints() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ReadJoints() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestReadPosition(t *testing.T) { + type args struct { + data []byte + acr *gltf.Accessor + buffer [][3]float32 + } + tests := []struct { + name string + args args + want [][3]float32 + wantErr bool + }{ + {"float32", args{[]byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec3, ComponentType: gltf.ComponentFloat, + }, nil}, [][3]float32{{1, 2, 3}}, false}, + {"float32-withbuffer", args{[]byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec3, ComponentType: gltf.ComponentFloat, + }, make([][3]float32, 1)}, [][3]float32{{1, 2, 3}}, false}, + {"incorrect-type", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorMat2, ComponentType: gltf.ComponentFloat, + }, nil}, nil, true}, + {"incorrect-componenttype", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorVec3, ComponentType: gltf.ComponentByte, + }, nil}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + doc := &gltf.Document{ + BufferViews: []*gltf.BufferView{ + {Buffer: 0, ByteLength: uint32(len(tt.args.data))}, + }, + Buffers: []*gltf.Buffer{ + {Data: tt.args.data, ByteLength: uint32(len(tt.args.data))}, + }, + } + got, err := ReadPosition(doc, tt.args.acr, tt.args.buffer) + if (err != nil) != tt.wantErr { + t.Errorf("ReadPosition() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ReadPosition() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestReadColor(t *testing.T) { + type args struct { + data []byte + acr *gltf.Accessor + buffer [][4]uint8 + } + tests := []struct { + name string + args args + want [][4]uint8 + wantErr bool + }{ + {"[4]uint8", args{[]byte{1, 2, 3, 4}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUbyte, + }, nil}, [][4]uint8{{1, 2, 3, 4}}, false}, + {"[4]uint16", args{[]byte{0, 0, 255, 255, 0, 0, 255, 255}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUshort, + }, nil}, [][4]uint8{{0, 255, 0, 255}}, false}, + {"[4]float32", args{[]byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentFloat, + }, nil}, [][4]uint8{{255, 89, 155, 252}}, false}, + {"[3]uint8", args{[]byte{1, 2, 3, 0}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec3, ComponentType: gltf.ComponentUbyte, + }, nil}, [][4]uint8{{1, 2, 3, 255}}, false}, + {"[3]uint16", args{[]byte{0, 0, 255, 0, 255, 0, 0, 0}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec3, ComponentType: gltf.ComponentUshort, + }, nil}, [][4]uint8{{0, 255, 255, 255}}, false}, + {"[3]float32", args{[]byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec3, ComponentType: gltf.ComponentFloat, + }, nil}, [][4]uint8{{255, 89, 155, 255}}, false}, + {"incorrect-type", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorMat2, ComponentType: gltf.ComponentUbyte, + }, nil}, nil, true}, + {"incorrect-componenttype", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorVec4, ComponentType: gltf.ComponentByte, + }, nil}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + doc := &gltf.Document{ + BufferViews: []*gltf.BufferView{ + {Buffer: 0, ByteLength: uint32(len(tt.args.data))}, + }, + Buffers: []*gltf.Buffer{ + {Data: tt.args.data, ByteLength: uint32(len(tt.args.data))}, + }, + } + got, err := ReadColor(doc, tt.args.acr, tt.args.buffer) + if (err != nil) != tt.wantErr { + t.Errorf("ReadColor() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ReadColor() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestReadColor64(t *testing.T) { + type args struct { + data []byte + acr *gltf.Accessor + buffer [][4]uint16 + } + tests := []struct { + name string + args args + want [][4]uint16 + wantErr bool + }{ + {"[4]uint8", args{[]byte{1, 2, 3, 4}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUbyte, + }, nil}, [][4]uint16{{1, 2, 3, 4}}, false}, + {"[4]uint16", args{[]byte{0, 0, 255, 255, 0, 0, 255, 255}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUshort, + }, nil}, [][4]uint16{{0, 65535, 0, 65535}}, false}, + {"[4]float32", args{[]byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentFloat, + }, nil}, [][4]uint16{{65535, 23149, 40135, 65532}}, false}, + {"[3]uint8", args{[]byte{1, 2, 3, 0}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec3, ComponentType: gltf.ComponentUbyte, + }, nil}, [][4]uint16{{1, 2, 3, 65535}}, false}, + {"[3]uint16", args{[]byte{0, 0, 255, 0, 255, 0, 0, 0}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec3, ComponentType: gltf.ComponentUshort, + }, nil}, [][4]uint16{{0, 255, 255, 65535}}, false}, + {"[3]float32", args{[]byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64}, &gltf.Accessor{ + BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec3, ComponentType: gltf.ComponentFloat, + }, nil}, [][4]uint16{{65535, 23149, 40135, 65535}}, false}, + {"incorrect-type", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorMat2, ComponentType: gltf.ComponentUbyte, + }, nil}, nil, true}, + {"incorrect-componenttype", args{[]byte{}, &gltf.Accessor{ + BufferView: gltf.Index(0), Type: gltf.AccessorVec4, ComponentType: gltf.ComponentByte, + }, nil}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + doc := &gltf.Document{ + BufferViews: []*gltf.BufferView{ + {Buffer: 0, ByteLength: uint32(len(tt.args.data))}, + }, + Buffers: []*gltf.Buffer{ + {Data: tt.args.data, ByteLength: uint32(len(tt.args.data))}, + }, + } + got, err := ReadColor64(doc, tt.args.acr, tt.args.buffer) + if (err != nil) != tt.wantErr { + t.Errorf("ReadColor64() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ReadColor64() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/modeler/modeler.go b/modeler/write.go similarity index 99% rename from modeler/modeler.go rename to modeler/write.go index 02309c3..25d1ff3 100644 --- a/modeler/modeler.go +++ b/modeler/write.go @@ -112,7 +112,7 @@ func WriteColor(doc *gltf.Document, data interface{}) uint32 { switch data.(type) { case []color.RGBA, []color.RGBA64, [][4]uint8, [][3]uint8, [][4]uint16, [][3]uint16: normalized = true - case []gltf.RGB, []gltf.RGBA, [][3]float32, [][4]float32: + case [][3]float32, [][4]float32: default: panic(fmt.Sprintf("modeler.WriteColor: invalid type %T", data)) } diff --git a/modeler/modeler_test.go b/modeler/write_test.go similarity index 100% rename from modeler/modeler_test.go rename to modeler/write_test.go diff --git a/struct.go b/struct.go index 6fa271c..8539669 100644 --- a/struct.go +++ b/struct.go @@ -3,8 +3,6 @@ package gltf import ( "encoding/base64" "fmt" - "image/color" - "math" "strings" ) @@ -323,53 +321,11 @@ func (o *OcclusionTexture) StrengthOrDefault() float64 { return *o.Strength } -// The RGBA components of a color. -// Each element must be greater than or equal to 0 and less than or equal to 1. -type RGBA struct { - R, G, B, A float64 `validate:"gte=0,lte=1"` -} - -// NewRGBA returns a default RGBA color. -func NewRGBA() *RGBA { - return &RGBA{1, 1, 1, 1} -} - -// NewRGBAColor transform a RGB uint8 color (from 0 to 255) to its float represtation (from 0 to 1). -func NewRGBAColor(c color.RGBA) *RGBA { - linear := NewRGBColor(c) - return &RGBA{R: linear.R, G: linear.G, B: linear.B, A: float64(c.A) / 255} -} - -// The RGB components of a color. -// Each element must be greater than or equal to 0 and less than or equal to 1. -type RGB struct { - R, G, B float64 `validate:"gte=0,lte=1"` -} - -// NewRGB returns a default RGB color. -func NewRGB() *RGB { - return &RGB{1, 1, 1} -} - -// NewRGBColor transform a RGB uint8 color (from 0 to 255) to its float represtation (from 0 to 1). -func NewRGBColor(c color.RGBA) *RGB { - sRGB := [3]float64{float64(c.R) / 255, float64(c.G) / 255, float64(c.B) / 255} - var linear [3]float64 - for i := 0; i < 3; i++ { - if sRGB[i] <= 0.04045 { - linear[i] = sRGB[i] / 12.92 - } else { - linear[i] = math.Pow(((sRGB[i] + 0.055) / 1.055), 2.4) - } - } - return &RGB{R: linear[0], G: linear[1], B: linear[2]} -} - // PBRMetallicRoughness defines a set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology. type PBRMetallicRoughness struct { Extensions Extensions `json:"extensions,omitempty"` Extras interface{} `json:"extras,omitempty"` - BaseColorFactor *RGBA `json:"baseColorFactor,omitempty"` + BaseColorFactor *[4]float32 `json:"baseColorFactor,omitempty" validate:"omitempty,dive,gte=0,lte=1"` BaseColorTexture *TextureInfo `json:"baseColorTexture,omitempty"` MetallicFactor *float64 `json:"metallicFactor,omitempty" validate:"omitempty,gte=0,lte=1"` RoughnessFactor *float64 `json:"roughnessFactor,omitempty" validate:"omitempty,gte=0,lte=1"` @@ -393,9 +349,9 @@ func (p *PBRMetallicRoughness) RoughnessFactorOrDefault() float64 { } // BaseColorFactorOrDefault returns the base color factor if it is not nil, else return the default one. -func (p *PBRMetallicRoughness) BaseColorFactorOrDefault() RGBA { +func (p *PBRMetallicRoughness) BaseColorFactorOrDefault() [4]float32 { if p.BaseColorFactor == nil { - return *NewRGBA() + return [4]float32{1, 1, 1, 1} } return *p.BaseColorFactor } diff --git a/struct_test.go b/struct_test.go index 6f935d7..a865663 100644 --- a/struct_test.go +++ b/struct_test.go @@ -1,11 +1,8 @@ package gltf import ( - "image/color" "reflect" "testing" - - "github.com/go-test/deep" ) func TestDocument(t *testing.T) { @@ -192,10 +189,10 @@ func TestPBRMetallicRoughness_BaseColorFactorOrDefault(t *testing.T) { tests := []struct { name string p *PBRMetallicRoughness - want RGBA + want [4]float32 }{ - {"empty", &PBRMetallicRoughness{}, *NewRGBA()}, - {"other", &PBRMetallicRoughness{BaseColorFactor: &RGBA{0.8, 0.8, 0.8, 0.5}}, RGBA{0.8, 0.8, 0.8, 0.5}}, + {"empty", &PBRMetallicRoughness{}, [4]float32{1, 1, 1, 1}}, + {"other", &PBRMetallicRoughness{BaseColorFactor: &[4]float32{0.8, 0.8, 0.8, 0.5}}, [4]float32{0.8, 0.8, 0.8, 0.5}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -295,69 +292,3 @@ func TestPBRMetallicRoughness_RoughnessFactorOrDefault(t *testing.T) { }) } } - -func TestNewRGBColor(t *testing.T) { - deep.FloatPrecision = 6 - type args struct { - c color.RGBA - } - tests := []struct { - name string - args args - want *RGB - }{ - {"empty", args{color.RGBA{}}, &RGB{}}, - {"base", args{color.RGBA{R: 1, G: 1, B: 1}}, &RGB{R: 0.0003035, G: 0.0003035, B: 0.0003035}}, - {"max", args{color.RGBA{R: 255, G: 255, B: 255}}, &RGB{R: 1, G: 1, B: 1}}, - {"other", args{color.RGBA{R: 60, G: 120, B: 180}}, &RGB{R: 0.045186, G: 0.1878207, B: 0.4564110}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := NewRGBColor(tt.args.c) - if diff := deep.Equal(got, tt.want); diff != nil { - t.Errorf("NewRGBColor() = %v", diff) - } - }) - } -} - -func TestNewRGB(t *testing.T) { - tests := []struct { - name string - want *RGB - }{ - {"base", &RGB{1, 1, 1}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := NewRGB(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewRGB() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestNewRGBAColor(t *testing.T) { - deep.FloatPrecision = 6 - type args struct { - c color.RGBA - } - tests := []struct { - name string - args args - want *RGBA - }{ - {"empty", args{color.RGBA{}}, &RGBA{}}, - {"base", args{color.RGBA{R: 1, G: 1, B: 1, A: 1}}, &RGBA{R: 0.0003035, G: 0.0003035, B: 0.0003035, A: 0.00392156}}, - {"max", args{color.RGBA{R: 255, G: 255, B: 255, A: 255}}, &RGBA{R: 1, G: 1, B: 1, A: 1}}, - {"other", args{color.RGBA{R: 60, G: 120, B: 180, A: 220}}, &RGBA{R: 0.045186, G: 0.1878207, B: 0.4564110, A: 0.86274509}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := NewRGBAColor(tt.args.c) - if diff := deep.Equal(got, tt.want); diff != nil { - t.Errorf("NewRGBAColor() = %v", diff) - } - }) - } -}