From 51b91b095a1a01075f7577770ccb31aebc9f4ddd Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Thu, 25 Feb 2021 17:59:47 +0100 Subject: [PATCH] support glb files without binary chunks --- encode.go | 64 ++++++++++++++++++++++++++++++-------------------- encode_test.go | 53 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 85 insertions(+), 32 deletions(-) diff --git a/encode.go b/encode.go index d0a82f9..4c4bffb 100644 --- a/encode.go +++ b/encode.go @@ -8,7 +8,6 @@ import ( "io" "os" "path/filepath" - "unsafe" ) // WriteHandler is the interface that wraps the Write method. @@ -73,8 +72,11 @@ func (e *Encoder) Encode(doc *Document) error { var err error var externalBufferIndex = 0 if e.AsBinary { - err = e.encodeBinary(doc) - externalBufferIndex = 1 + var hasBinChunk bool + hasBinChunk, err = e.encodeBinary(doc) + if hasBinChunk { + externalBufferIndex = 1 + } } else { err = json.NewEncoder(e.w).Encode(doc) } @@ -103,44 +105,54 @@ func (e *Encoder) encodeBuffer(buffer *Buffer) error { return e.WriteHandler.WriteResource(buffer.URI, buffer.Data) } -func (e *Encoder) encodeBinary(doc *Document) error { +func (e *Encoder) encodeBinary(doc *Document) (bool, error) { jsonText, err := json.Marshal(doc) if err != nil { - return err + return false, err } - header := glbHeader{Magic: glbHeaderMagic, Version: 2, Length: 0, JSONHeader: chunkHeader{Length: 0, Type: glbChunkJSON}} - binHeader := chunkHeader{Length: 0, Type: glbChunkBIN} - var binBufferLength uint32 - var binBuffer *Buffer - if len(doc.Buffers) > 0 { - binBuffer = doc.Buffers[0] - binBufferLength = binBuffer.ByteLength + jsonHeader := chunkHeader{ + Length: uint32(((len(jsonText) + 3) / 4) * 4), + Type: glbChunkJSON, + } + header := glbHeader{ + Magic: glbHeaderMagic, + Version: 2, + Length: 12 + 8 + jsonHeader.Length, // 12-byte glb header + 8-byte json chunk header + JSONHeader: jsonHeader, } - binPaddedLength := ((binBufferLength + 3) / 4) * 4 - binPadding := make([]byte, binPaddedLength-binBufferLength) - binHeader.Length = binPaddedLength - - header.JSONHeader.Length = uint32(((len(jsonText) + 3) / 4) * 4) - header.Length = uint32(unsafe.Sizeof(header)+unsafe.Sizeof(binHeader)) + header.JSONHeader.Length + binHeader.Length headerPadding := make([]byte, header.JSONHeader.Length-uint32(len(jsonText))) for i := range headerPadding { headerPadding[i] = ' ' } - for i := range binPadding { - binPadding[i] = 0 - } err = binary.Write(e.w, binary.LittleEndian, &header) if err != nil { - return err + return false, err } e.w.Write(jsonText) e.w.Write(headerPadding) - binary.Write(e.w, binary.LittleEndian, &binHeader) - if binBuffer != nil { + + hasBinChunk := len(doc.Buffers) > 0 && doc.Buffers[0].URI == "" + if hasBinChunk { + var binBufferLength uint32 + var binBuffer *Buffer + if len(doc.Buffers) > 0 { + binBuffer = doc.Buffers[0] + binBufferLength = binBuffer.ByteLength + } + binPaddedLength := ((binBufferLength + 3) / 4) * 4 + binPadding := make([]byte, binPaddedLength-binBufferLength) + binHeader := chunkHeader{Length: 0, Type: glbChunkBIN} + binHeader.Length = binPaddedLength + header.Length = uint32(8) + binHeader.Length + for i := range binPadding { + binPadding[i] = 0 + } + binary.Write(e.w, binary.LittleEndian, &binHeader) e.w.Write(binBuffer.Data) + _, err = e.w.Write(binPadding) } - _, err = e.w.Write(binPadding) - return err + + return hasBinChunk, err } // UnmarshalJSON unmarshal the node with the correct default values. diff --git a/encode_test.go b/encode_test.go index ed46990..46430b8 100644 --- a/encode_test.go +++ b/encode_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "reflect" + "strings" "testing" "github.com/go-test/deep" @@ -36,6 +37,52 @@ func saveMemory(doc *Document, asBinary bool) (*Decoder, error) { return NewDecoder(buff).WithReadHandler(m), nil } +func TestEncoder_Encode_AsBinary_WithoutBuffer(t *testing.T) { + doc := &Document{} + buff := new(bytes.Buffer) + e := NewEncoder(buff) + e.AsBinary = true + if err := e.Encode(doc); err != nil { + t.Errorf("Encoder.Encode() error = %v", err) + } + if strings.Contains(buff.String(), "BIN") { + t.Error("Encoder.Encode() as binary without bin buffer should not contain bin chunk") + } +} + +func TestEncoder_Encode_AsBinary_WithoutBinChunk(t *testing.T) { + doc := &Document{Buffers: []*Buffer{ + {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: []byte{4, 5, 6, 7}}, + {Extras: 8.0, Name: "external", ByteLength: 4, URI: "a.drc", Data: []byte{0, 0, 0, 0}}, + }} + buff := new(bytes.Buffer) + m := &mockChunkReadHandler{Chunks: make(map[string][]byte)} + e := NewEncoder(buff).WithWriteHandler(m) + e.AsBinary = true + if err := e.Encode(doc); err != nil { + t.Errorf("Encoder.Encode() error = %v", err) + } + if strings.Contains(buff.String(), "BIN") { + t.Error("Encoder.Encode() as binary without bin buffer should not contain bin chunk") + } +} + +func TestEncoder_Encode_AsBinary_WithBinChunk(t *testing.T) { + doc := &Document{Buffers: []*Buffer{ + {Extras: 8.0, Name: "binary", ByteLength: 3, Data: []byte{1, 2, 3}}, + }} + buff := new(bytes.Buffer) + e := NewEncoder(buff) + e.AsBinary = true + if err := e.Encode(doc); err != nil { + t.Errorf("Encoder.Encode() error = %v", err) + } + if !strings.Contains(buff.String(), "BIN") { + t.Error("Encoder.Encode() as binary with bin buffer should contain bin chunk") + } +} + func TestEncoder_Encode(t *testing.T) { type args struct { doc *Document @@ -72,12 +119,6 @@ func TestEncoder_Encode(t *testing.T) { {Extras: 8.0, Input: Index(1), Output: Index(1), Interpolation: InterpolationCubicSpline}, }}, }}}, false}, - {"withBuffer", args{&Document{Buffers: []*Buffer{ - {Extras: 8.0, Name: "binary", ByteLength: 3, URI: "a.bin", Data: []byte{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: []byte{4, 5, 6, 7}}, - {Extras: 8.0, Name: "external", ByteLength: 4, URI: "a.drc", Data: []byte{0, 0, 0, 0}}, - }}}, false}, {"withBufView", args{&Document{BufferViews: []*BufferView{ {Extras: 8.0, Buffer: 0, ByteOffset: 1, ByteLength: 2, ByteStride: 5, Target: TargetArrayBuffer}, {Buffer: 10, ByteOffset: 10, ByteLength: 20, ByteStride: 50, Target: TargetElementArrayBuffer},