Repository: qmuntal/gltf (go)

Issue:
Panic when calling modeler.ReadPosition()
By unknown reason blender exported a glb file with some accessors having nil bufferView and Sparse.
Calling modeler.ReadPosition() to read these accessors will cause a panic.
Could it return an error instead?

Pull request description:
Return non-nil slice in ReadAccessor when bufferView and sparse are nil
Returning `(nil, nil)` in `ReadAccessor` is problematic, as callers might expect `data` to be non-nil when there is no error. In fact, all `Read*` functions in this package assume that.

This PR improves `ReadAccessor` ergonomics by always returning a non-nil slice when there is no error.

Fixes #93.

Patch:
diff --git a/modeler/read.go b/modeler/read.go
index acba2fb..fc4d6f8 100644
--- a/modeler/read.go
+++ b/modeler/read.go
@@ -29,9 +29,6 @@ var uint32Pool = sync.Pool{
 // 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, buffer []byte) (any, error) {
-	if acr.BufferView == nil && acr.Sparse == nil {
-		return nil, nil
-	}
 	data, err := binary.MakeSliceBuffer(acr.ComponentType, acr.Type, acr.Count, buffer)
 	if err != nil {
 		return nil, err
@@ -119,8 +116,10 @@ var bufPool = sync.Pool{
 func makeBufferOf[T any](count int, buffer []T) []T {
 	if len(buffer) < count {
 		buffer = append(buffer, make([]T, count-len(buffer))...)
-	} else {
+	} else if count != 0 {
 		buffer = buffer[:count]
+	} else if buffer == nil {
+		buffer = make([]T, 0)
 	}
 	return buffer
 }


Test patch:
diff --git a/modeler/read_test.go b/modeler/read_test.go
index b602cd0..5904ad0 100644
--- a/modeler/read_test.go
+++ b/modeler/read_test.go
@@ -112,7 +112,7 @@ func TestReadAccessor(t *testing.T) {
 		want    any
 		wantErr bool
 	}{
-		{"nodata", args{&gltf.Document{}, &gltf.Accessor{}}, nil, false},
+		{"nodata", args{&gltf.Document{}, &gltf.Accessor{}}, []float32{}, false},
 		{"base", args{&gltf.Document{Buffers: []*gltf.Buffer{
 			{ByteLength: 9, Data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}},
 		}, BufferViews: []*gltf.BufferView{{
@@ -542,6 +542,9 @@ func TestReadPosition(t *testing.T) {
 		want    [][3]float32
 		wantErr bool
 	}{
+		{"nil-bufferView", args{nil, &gltf.Accessor{
+			Type: gltf.AccessorVec3, ComponentType: gltf.ComponentFloat,
+		}, nil}, [][3]float32{}, false},
 		{"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},
@@ -557,13 +560,14 @@ func TestReadPosition(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			doc := &gltf.Document{
-				BufferViews: []*gltf.BufferView{
+			doc := new(gltf.Document)
+			if tt.args.data != nil {
+				doc.BufferViews = []*gltf.BufferView{
 					{Buffer: 0, ByteLength: len(tt.args.data)},
-				},
-				Buffers: []*gltf.Buffer{
+				}
+				doc.Buffers = []*gltf.Buffer{
 					{Data: tt.args.data, ByteLength: len(tt.args.data)},
-				},
+				}
 			}
 			got, err := modeler.ReadPosition(doc, tt.args.acr, tt.args.buffer)
 			if (err != nil) != tt.wantErr {