Repository: qmuntal/gltf
Pull request #7 (2019-03-25 21:32:19)

Handle empty external buffers
Do not overwritte files when Buffer is not loaded in memory 


Patch:
diff --git a/decoder.go b/decoder.go
index 3d3ca7a..f28cb41 100644
--- a/decoder.go
+++ b/decoder.go
@@ -22,6 +22,7 @@ type ReadQuotas struct {
 
 // ReadResourceCallback defines a callback that will be called when an external resource should be loaded.
 // The string parameter is the URI of the resource.
+// If the reader and the error are nil the buffer data won't be loaded into memory.
 type ReadResourceCallback = func(string) (io.ReadCloser, error)
 
 // Open will open a glTF or GLB file specified by name and return the Document.
@@ -149,7 +150,7 @@ func (d *Decoder) decodeBuffer(buffer *Buffer) error {
 		buffer.Data, err = buffer.marshalData()
 	} else if err = validateBufferURI(buffer.URI); err == nil {
 		r, err = d.cb(buffer.URI)
-		if err == nil {
+		if r != nil && err == nil {
 			buffer.Data = make([]uint8, buffer.ByteLength)
 			_, err = r.Read(buffer.Data)
 			r.Close()
diff --git a/doc.go b/doc.go
index 4eb0a74..b3cc06a 100644
--- a/doc.go
+++ b/doc.go
@@ -7,12 +7,12 @@ Optional Properties
 All optional properties whose default value does not match with the golang type zero value are defines as pointers or as zero arrays.
 
 Guidelines when working with optional values:
-* It is safe to not define them when saving the glTF if the desired value is the default one.
-* It is safe to expect that the optional values are not nil when opening a glTF.
-* When assigning values to optional properties one can use the utility functions that take the reference of basic types. Examples:
+It is safe to not define them when saving the glTF if the desired value is the default one.
+It is safe to expect that the optional values are not nil when opening a glTF.
+When quering values of optional properties that are not indices it is recommended to use the utility functions that returns the property as value
+if not nil or the default value if nil.
+When assigning values to optional properties one can use the utility functions that take the reference of basic types. Examples:
  gltf.Index(1)
  gltf.Float64(0.5)
-* When quering values of optional properties that are not indices it is recommended to use the utility functions that returns the property as value
-if not nil or the default value if nil.
 */
 package gltf
diff --git a/encode.go b/encode.go
index 0657585..989053e 100644
--- a/encode.go
+++ b/encode.go
@@ -75,6 +75,11 @@ func (e *Encoder) encodeBuffer(buffer *Buffer) error {
 	if buffer.IsEmbeddedResource() {
 		return nil
 	}
+
+	if len(buffer.Data) == 0 {
+		return nil
+	}
+
 	if err := validateBufferURI(buffer.URI); err != nil {
 		return err
 	}
diff --git a/struct.go b/struct.go
index eeb8456..e4b0796 100644
--- a/struct.go
+++ b/struct.go
@@ -97,6 +97,8 @@ type SparseIndices struct {
 }
 
 // A Buffer points to binary geometry, animation, or skins.
+// If Data length is 0 and the Buffer is an external resource the Data won't be flushed,
+// which can be useful when there is no need to load data in memory.
 type Buffer struct {
 	Extensions Extensions  `json:"extensions,omitempty"`
 	Extras     interface{} `json:"extras,omitempty"`
@@ -119,10 +121,14 @@ func (b *Buffer) EmbeddedResource() {
 // marshalData decode the buffer from the URI. If the buffer is not en embedded resource the returned array will be empty.
 func (b *Buffer) marshalData() ([]uint8, error) {
 	if !b.IsEmbeddedResource() {
-		return []uint8{}, nil
+		return nil, nil
 	}
 	startPos := len(mimetypeApplicationOctet) + 1
-	return base64.StdEncoding.DecodeString(b.URI[startPos:])
+	sl, err := base64.StdEncoding.DecodeString(b.URI[startPos:])
+	if len(sl) == 0 || err != nil {
+		return nil, err
+	}
+	return sl, nil
 }
 
 // BufferView is a view into a buffer generally representing a subset of the buffer.


Test patch:
diff --git a/encode_test.go b/encode_test.go
index aa72529..1e86a30 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -27,7 +27,11 @@ func saveMemory(doc *Document, asBinary bool) (*Decoder, error) {
 		return nil, err
 	}
 	rcb := func(uri string) (io.ReadCloser, error) {
-		return ioutil.NopCloser(chunks[uri]), nil
+		if chunk, ok := chunks[uri]; ok {
+			return ioutil.NopCloser(chunk), nil
+		} else {
+			return nil, nil
+		}
 	}
 	return NewDecoder(buff, rcb), nil
 }
@@ -72,6 +76,7 @@ func TestEncoder_Encode(t *testing.T) {
 			{Extras: 8.0, Name: "binary", ByteLength: 3, URI: "a.bin", Data: []uint8{1, 2, 3}},
 			{Extras: 8.0, Name: "embedded", ByteLength: 2, URI: "data:application/octet-stream;base64,YW55ICsgb2xkICYgZGF0YQ==", Data: []byte("any + old & data")},
 			{Extras: 8.0, Name: "external", ByteLength: 4, URI: "b.bin", Data: []uint8{4, 5, 6, 7}},
+			{Extras: 8.0, Name: "external", ByteLength: 4, URI: "a.drc"},
 		}}}, false},
 		{"withBufView", args{&Document{BufferViews: []BufferView{
 			{Extras: 8.0, Buffer: 0, ByteOffset: 1, ByteLength: 2, ByteStride: 5, Target: ArrayBuffer},
diff --git a/struct_test.go b/struct_test.go
index 168a403..99f38f9 100644
--- a/struct_test.go
+++ b/struct_test.go
@@ -403,9 +403,9 @@ func TestBuffer_marshalData(t *testing.T) {
 		want    []uint8
 		wantErr bool
 	}{
-		{"error", &Buffer{URI: "data:application/octet-stream;base64,_"}, []uint8{}, true},
-		{"external", &Buffer{URI: "http://web.com"}, []uint8{}, false},
-		{"empty", &Buffer{URI: "data:application/octet-stream;base64,"}, []uint8{}, false},
+		{"error", &Buffer{URI: "data:application/octet-stream;base64,_"}, nil, true},
+		{"external", &Buffer{URI: "http://web.com"}, nil, false},
+		{"empty", &Buffer{URI: "data:application/octet-stream;base64,"}, nil, false},
 		{"test", &Buffer{URI: "data:application/octet-stream;base64,TEST"}, []uint8{76, 68, 147}, false},
 		{"complex", &Buffer{URI: "data:application/octet-stream;base64,YW55IGNhcm5hbCBwbGVhcw=="}, []uint8{97, 110, 121, 32, 99, 97, 114, 110, 97, 108, 32, 112, 108, 101, 97, 115}, false},
 	}