Skip to content

Commit

Permalink
Merge pull request #54 from qmuntal/external-buffers
Browse files Browse the repository at this point in the history
Auto embed resources without URI when encoding
  • Loading branch information
qmuntal committed Mar 5, 2022
2 parents f45cd9a + 932b5be commit f34b1df
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 20 deletions.
6 changes: 6 additions & 0 deletions README.md
Expand Up @@ -123,6 +123,9 @@ doc.Meshes = []*gltf.Mesh{{
},
}},
}}
doc.Nodes = []*gltf.Node{{Name: "Pyramid", Mesh: gltf.Index(0)}}
doc.Scenes[0].Nodes = append(doc.Scenes[0].Nodes, 0)
gltf.Save(doc, "./test.gltf")
```

### Data interleaving
Expand All @@ -142,6 +145,9 @@ doc.Meshes = []*gltf.Mesh{{
Attributes: attrs,
}},
}}
doc.Nodes = []*gltf.Node{{Name: "Pyramid", Mesh: gltf.Index(0)}}
doc.Scenes[0].Nodes = append(doc.Scenes[0].Nodes, 0)
gltf.Save(doc, "./test.gltf")
```

### Manipulating bytes
Expand Down
85 changes: 75 additions & 10 deletions encode.go
Expand Up @@ -81,9 +81,6 @@ func NewEncoderFS(w io.Writer, fsys CreateFS) *Encoder {

// Encode writes the encoding of doc to the stream.
func (e *Encoder) Encode(doc *Document) error {
if doc.Asset.Version == "" {
doc.Asset.Version = "2.0"
}
var err error
var externalBufferIndex = 0
if e.AsBinary {
Expand All @@ -93,18 +90,23 @@ func (e *Encoder) Encode(doc *Document) error {
externalBufferIndex = 1
}
} else {
err = json.NewEncoder(e.w).Encode(doc)
var jsonData []byte
jsonData, err = e.marshalJSONDoc(doc)
if err != nil {
return err
}
_, err = e.w.Write(jsonData)
}
if err != nil {
return err
}

for i := externalBufferIndex; i < len(doc.Buffers); i++ {
buffer := doc.Buffers[i]
if len(buffer.Data) == 0 || buffer.IsEmbeddedResource() {
buf := doc.Buffers[i]
if len(buf.Data) == 0 || buf.URI == "" {
continue
}
if err = e.encodeBuffer(buffer); err != nil {
if err = e.encodeBuffer(buf); err != nil {
return err
}
}
Expand Down Expand Up @@ -135,7 +137,7 @@ func (e *Encoder) encodeBuffer(buffer *Buffer) error {
}

func (e *Encoder) encodeBinary(doc *Document) (bool, error) {
jsonText, err := json.Marshal(doc)
jsonText, err := e.marshalJSONDoc(doc)
if err != nil {
return false, err
}
Expand Down Expand Up @@ -183,6 +185,69 @@ func (e *Encoder) encodeBinary(doc *Document) (bool, error) {
return hasBinChunk, err
}

// MarshalJSON marshal the document with the correct default values.
func (e *Encoder) marshalJSONDoc(doc *Document) ([]byte, error) {
type alias Document
tmp := &struct {
CustomBuffers []*Buffer `json:"buffers,omitempty"`
Buffers []*Buffer `json:"-"`
*alias
}{
CustomBuffers: make([]*Buffer, len(doc.Buffers)),
alias: (*alias)(doc),
}
// Embed buffers without URI.
for i, buf := range doc.Buffers {
if i == 0 && e.AsBinary && buf.URI == "" {
// First buffer will be encoded in the binary chunk.
tmp.CustomBuffers[i] = buf
continue
}
if len(buf.Data) > 0 && buf.URI == "" && !buf.IsEmbeddedResource() {
tmpBuf := &Buffer{
Extensions: buf.Extensions,
Extras: buf.Extras,
Name: buf.Name,
ByteLength: buf.ByteLength,
Data: buf.Data,
}
tmpBuf.EmbeddedResource()
tmp.CustomBuffers[i] = tmpBuf
} else {
tmp.CustomBuffers[i] = buf
}
}
return json.Marshal(tmp)
}

// UnmarshalJSON unmarshal the asset with the correct default values.
func (as *Asset) UnmarshalJSON(data []byte) error {
type alias Asset
tmp := alias(Asset{
Version: "2.0",
})
err := json.Unmarshal(data, &tmp)
if err == nil {
*as = Asset(tmp)
}
return err
}

// MarshalJSON marshal the asset with the correct default values.
func (as *Asset) MarshalJSON() ([]byte, error) {
type alias Asset
if as.Version == "" {
return json.Marshal(&struct {
Version string `json:"version,omitempty"`
*alias
}{
Version: "2.0",
alias: (*alias)(as),
})
}
return json.Marshal((*alias)(as))
}

// UnmarshalJSON unmarshal the node with the correct default values.
func (n *Node) UnmarshalJSON(data []byte) error {
type alias Node
Expand Down Expand Up @@ -301,7 +366,7 @@ func (n *NormalTexture) MarshalJSON() ([]byte, error) {
alias: (*alias)(n),
})
}
return json.Marshal(&struct{ *alias }{alias: (*alias)(n)})
return json.Marshal((*alias)(n))
}

// UnmarshalJSON unmarshal the texture info with the correct default values.
Expand All @@ -327,7 +392,7 @@ func (o *OcclusionTexture) MarshalJSON() ([]byte, error) {
alias: (*alias)(o),
})
}
return json.Marshal(&struct{ *alias }{alias: (*alias)(o)})
return json.Marshal((*alias)(o))
}

// UnmarshalJSON unmarshal the pbr with the correct default values.
Expand Down
38 changes: 30 additions & 8 deletions encode_test.go
Expand Up @@ -101,6 +101,34 @@ func TestEncoder_Encode_AsBinary_WithBinChunk(t *testing.T) {
}
}

func TestEncoder_Encode_Buffers_Without_URI(t *testing.T) {
doc := &Document{Buffers: []*Buffer{
{Name: "binary", ByteLength: 3, Data: []byte{1, 2, 3}},
{Name: "binary2", ByteLength: 3, Data: []byte{4, 5, 6}},
}}
buf := new(bytes.Buffer)
e := NewEncoder(buf)
e.AsBinary = false
if err := e.Encode(doc); err != nil {
t.Errorf("Encoder.Encode() error = %v", err)
}
if !strings.Contains(buf.String(), mimetypeApplicationOctet+",AQID") ||
!strings.Contains(buf.String(), mimetypeApplicationOctet+",BAUG") {
t.Error("Encoder.Encode() should auto embed buffers without URI")
}
buf.Reset()
e.AsBinary = true
if err := e.Encode(doc); err != nil {
t.Errorf("Encoder.Encode() error = %v", err)
}
if strings.Contains(buf.String(), mimetypeApplicationOctet+",AQID") {
t.Error("Encoder.Encode() as binary should not embed fur buffer")
}
if !strings.Contains(buf.String(), mimetypeApplicationOctet+",BAUG") {
t.Error("Encoder.Encode() should auto embed buffers without URI")
}
}

func TestEncoder_Encode(t *testing.T) {
type args struct {
doc *Document
Expand Down Expand Up @@ -199,16 +227,10 @@ func TestEncoder_Encode(t *testing.T) {
}}}, false},
}
for _, tt := range tests {
tt.args.doc.Asset.Version = "2.0"
for _, method := range []string{"json", "binary"} {
t.Run(fmt.Sprintf("%s_%s", tt.name, method), func(t *testing.T) {
var asBinary bool
if method == "binary" && !tt.wantErr {
asBinary = true
for i := 1; i < len(tt.args.doc.Buffers); i++ {
tt.args.doc.Buffers[i].EmbeddedResource()
}
}
d, err := saveMemory(tt.args.doc, asBinary)
d, err := saveMemory(tt.args.doc, method == "binary")
if (err != nil) != tt.wantErr {
t.Errorf("Encoder.Encode() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down
3 changes: 1 addition & 2 deletions gltf.go
Expand Up @@ -2,7 +2,6 @@ package gltf

import (
"encoding/base64"
"fmt"
"strings"
"sync"
)
Expand Down Expand Up @@ -125,7 +124,7 @@ func (b *Buffer) IsEmbeddedResource() bool {

// EmbeddedResource defines the buffer as an embedded resource and encodes the URI so it points to the the resource.
func (b *Buffer) EmbeddedResource() {
b.URI = fmt.Sprintf("%s,%s", mimetypeApplicationOctet, base64.StdEncoding.EncodeToString(b.Data))
b.URI = mimetypeApplicationOctet + "," + base64.StdEncoding.EncodeToString(b.Data)
}

// marshalData decode the buffer from the URI. If the buffer is not en embedded resource the returned array will be empty.
Expand Down

0 comments on commit f34b1df

Please sign in to comment.