Repository: qmuntal/gltf
Pull request #27 (2020-10-13 16:40:25)

Refactor Writer


Patch:
diff --git a/.travis.yml b/.travis.yml
index cc8ea4f..974b901 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,9 +4,9 @@ language: go
 go_import_path: github.com/qmuntal/gltf
 
 go:
-  - 1.11.x
-  - 1.12.x
   - 1.13.x
+  - 1.14.x
+  - 1.15.x
 
 env:
   - GO111MODULE=on
diff --git a/README.md b/README.md
index 6ddf213..54be919 100644
--- a/README.md
+++ b/README.md
@@ -94,11 +94,11 @@ import (
 )
 
 func main() {
-    m := modeler.NewModeler()
-    positionAccessor := m.AddPosition(0, [][3]float32{{43, 43, 0}, {83, 43, 0}, {63, 63, 40}, {43, 83, 0}, {83, 83, 0}})
-    indicesAccessor := m.AddIndices(0, []uint8{0, 1, 2, 3, 1, 0, 0, 2, 3, 1, 4, 2, 4, 3, 2, 4, 1, 3})
-    colorIndices := m.AddColor(0, [][3]uint8{{50, 155, 255}, {0, 100, 200}, {255, 155, 50}, {155, 155, 155}, {25, 25, 25}})
-    m.Document.Meshes = []*gltf.Mesh{{
+    doc := gltf.NewDocument()
+    positionAccessor := modeler.WritePosition(doc, [][3]float32{{43, 43, 0}, {83, 43, 0}, {63, 63, 40}, {43, 83, 0}, {83, 83, 0}})
+    indicesAccessor := modeler.WriteIndices(doc, []uint8{0, 1, 2, 3, 1, 0, 0, 2, 3, 1, 4, 2, 4, 3, 2, 4, 1, 3})
+    colorIndices := modeler.WriteColor(doc, [][3]uint8{{50, 155, 255}, {0, 100, 200}, {255, 155, 50}, {155, 155, 155}, {25, 25, 25}})
+    doc.Meshes = []*gltf.Mesh{{
         Name: "Pyramid",
         Primitives: []*gltf.Primitive{{
             Indices: gltf.Index(indicesAccessor),
@@ -108,9 +108,9 @@ func main() {
             },
         }},
     }}
-    m.Nodes = []*gltf.Node{{Name: "Root", Mesh: gltf.Index(0)}}
-    m.Scenes[0].Nodes = append(m.Scenes[0].Nodes, 0)
-    if err := gltf.SaveBinary(m.Document, "./example.glb"); err != nil {
+    doc.Nodes = []*gltf.Node{{Name: "Root", Mesh: gltf.Index(0)}}
+    doc.Scenes[0].Nodes = append(doc.Scenes[0].Nodes, 0)
+    if err := gltf.SaveBinary(doc, "./example.glb"); err != nil {
         panic(err)
     }
 }
diff --git a/binary/binary.go b/binary/binary.go
index 7833f26..821a541 100644
--- a/binary/binary.go
+++ b/binary/binary.go
@@ -45,8 +45,8 @@ var Short shortComponent
 // Ushort is the usnigned short implementation of Component.
 var Ushort ushortComponent
 
-// UInt is the unsigned int implementation of Component.
-var UInt uintComponent
+// Uint is the unsigned int implementation of Component.
+var Uint uintComponent
 
 // Float is the float implementation of Component.
 var Float floatComponent
diff --git a/binary/encode.go b/binary/encode.go
index a5b883c..2c348e2 100644
--- a/binary/encode.go
+++ b/binary/encode.go
@@ -9,18 +9,18 @@ import (
 	"github.com/qmuntal/gltf"
 )
 
-// Read reads structured binary data from r into data.
+// 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`.
 //
 // If data length is greater than the length of b, Read returns io.ErrShortBuffer.
 func Read(b []byte, data interface{}) error {
 	c, t, n := Type(data)
-	if n <= 0 {
+	if n == 0 {
 		return binary.Read(bytes.NewReader(b), binary.LittleEndian, data)
 	}
-	e := SizeOfElement(c, t)
-	if len(b) < n*e {
+	e := int(SizeOfElement(c, t))
+	if len(b) < int(n)*e {
 		return io.ErrShortBuffer
 	}
 	switch data := data.(type) {
@@ -184,46 +184,51 @@ func Read(b []byte, data interface{}) error {
 		}
 	case []uint32:
 		for i := range data {
-			data[i] = UInt.Scalar(b[e*i:])
+			data[i] = Uint.Scalar(b[e*i:])
 		}
 	case [][2]uint32:
 		for i := range data {
-			data[i] = UInt.Vec2(b[e*i:])
+			data[i] = Uint.Vec2(b[e*i:])
 		}
 	case [][3]uint32:
 		for i := range data {
-			data[i] = UInt.Vec3(b[e*i:])
+			data[i] = Uint.Vec3(b[e*i:])
 		}
 	case [][4]uint32:
 		for i := range data {
-			data[i] = UInt.Vec4(b[e*i:])
+			data[i] = Uint.Vec4(b[e*i:])
 		}
 	case [][2][2]uint32:
 		for i := range data {
-			data[i] = UInt.Mat2(b[e*i:])
+			data[i] = Uint.Mat2(b[e*i:])
 		}
 	case [][3][3]uint32:
 		for i := range data {
-			data[i] = UInt.Mat3(b[e*i:])
+			data[i] = Uint.Mat3(b[e*i:])
 		}
 	case [][4][4]uint32:
 		for i := range data {
-			data[i] = UInt.Mat4(b[e*i:])
+			data[i] = Uint.Mat4(b[e*i:])
 		}
 	}
 	return nil
 }
 
 // Write writes the binary representation of data into b.
+// If stride is diferent than zero data will be interleaved.
+//
 // Data must be a slice of glTF predefined fixed-size types,
 // else it fallbacks to `encoding/binary.Write`.
-func Write(b []byte, data interface{}) error {
+func Write(b []byte, stride uint32, data interface{}) error {
 	c, t, n := Type(data)
-	if n <= 0 {
+	if n == 0 {
 		return binary.Write(bytes.NewBuffer(b), binary.LittleEndian, data)
 	}
-	e := SizeOfElement(c, t)
-	if len(b) < e*n {
+	e := int(stride)
+	if stride == 0 {
+		e = int(SizeOfElement(c, t))
+	}
+	if len(b) < e*int(n) {
 		return io.ErrShortBuffer
 	}
 	switch data := data.(type) {
@@ -245,7 +250,7 @@ func Write(b []byte, data interface{}) error {
 		}
 	case []int8:
 		for i, x := range data {
-			b[i] = byte(x)
+			b[e*i] = byte(x)
 		}
 	case [][2]int8:
 		for i := range data {
@@ -383,31 +388,31 @@ func Write(b []byte, data interface{}) error {
 		}
 	case []uint32:
 		for i := range data {
-			UInt.PutScalar(b[e*i:], data[i])
+			Uint.PutScalar(b[e*i:], data[i])
 		}
 	case [][2]uint32:
 		for i := range data {
-			UInt.PutVec2(b[e*i:], data[i])
+			Uint.PutVec2(b[e*i:], data[i])
 		}
 	case [][3]uint32:
 		for i := range data {
-			UInt.PutVec3(b[e*i:], data[i])
+			Uint.PutVec3(b[e*i:], data[i])
 		}
 	case [][4]uint32:
 		for i := range data {
-			UInt.PutVec4(b[e*i:], data[i])
+			Uint.PutVec4(b[e*i:], data[i])
 		}
 	case [][2][2]uint32:
 		for i := range data {
-			UInt.PutMat2(b[e*i:], data[i])
+			Uint.PutMat2(b[e*i:], data[i])
 		}
 	case [][3][3]uint32:
 		for i := range data {
-			UInt.PutMat3(b[e*i:], data[i])
+			Uint.PutMat3(b[e*i:], data[i])
 		}
 	case [][4][4]uint32:
 		for i := range data {
-			UInt.PutMat4(b[e*i:], data[i])
+			Uint.PutMat4(b[e*i:], data[i])
 		}
 	}
 	return nil
diff --git a/binary/size.go b/binary/size.go
index 62066a1..59d3be2 100644
--- a/binary/size.go
+++ b/binary/size.go
@@ -7,36 +7,11 @@ import (
 	"github.com/qmuntal/gltf"
 )
 
-// SizeOf returns the size, in bytes, of a component type.
-func SizeOf(c gltf.ComponentType) int {
-	return map[gltf.ComponentType]int{
-		gltf.ComponentByte:   1,
-		gltf.ComponentUbyte:  1,
-		gltf.ComponentShort:  2,
-		gltf.ComponentUshort: 2,
-		gltf.ComponentUint:   4,
-		gltf.ComponentFloat:  4,
-	}[c]
-}
-
-// ComponentsOf returns the number of components of an accessor type.
-func ComponentsOf(t gltf.AccessorType) int {
-	return map[gltf.AccessorType]int{
-		gltf.AccessorScalar: 1,
-		gltf.AccessorVec2:   2,
-		gltf.AccessorVec3:   3,
-		gltf.AccessorVec4:   4,
-		gltf.AccessorMat2:   4,
-		gltf.AccessorMat3:   9,
-		gltf.AccessorMat4:   16,
-	}[t]
-}
-
 // SizeOfElement returns the size, in bytes, of an element.
 // The element size may not be (component size) * (number of components),
 // as some of the elements are tightly packed in order to ensure
 // that they are aligned to 4-byte boundaries.
-func SizeOfElement(c gltf.ComponentType, t gltf.AccessorType) int {
+func SizeOfElement(c gltf.ComponentType, t gltf.AccessorType) uint32 {
 	// special cases
 	switch {
 	case (t == gltf.AccessorVec3 || t == gltf.AccessorVec2) && (c == gltf.ComponentByte || c == gltf.ComponentUbyte):
@@ -50,17 +25,17 @@ func SizeOfElement(c gltf.ComponentType, t gltf.AccessorType) int {
 	case t == gltf.AccessorMat3 && (c == gltf.ComponentShort || c == gltf.ComponentUshort):
 		return 24
 	}
-	return SizeOf(c) * ComponentsOf(t)
+	return c.ByteSize() * t.Components()
 }
 
 // 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 -1.
-func Type(data interface{}) (c gltf.ComponentType, t gltf.AccessorType, length int) {
+// 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) {
 	v := reflect.ValueOf(data)
 	switch v.Kind() {
 	case reflect.Slice:
-		length = v.Len()
+		length = uint32(v.Len())
 	}
 	switch data.(type) {
 	case []int8, int8:
@@ -148,7 +123,7 @@ func Type(data interface{}) (c gltf.ComponentType, t gltf.AccessorType, length i
 	case [][4][4]float32, [4][4]float32:
 		c, t = gltf.ComponentFloat, gltf.AccessorMat4
 	default:
-		length = -1
+		length = 0
 	}
 	return
 }
diff --git a/binary/unsafe.go b/binary/unsafe.go
index 910d86c..e4be5a1 100644
--- a/binary/unsafe.go
+++ b/binary/unsafe.go
@@ -135,64 +135,64 @@ func (shortComponent) PutMat4(b []byte, v [4][4]int16) {
 }
 
 func (floatComponent) Scalar(b []byte) float32 {
-	v := UInt.Scalar(b)
+	v := Uint.Scalar(b)
 	return *(*float32)(unsafe.Pointer(&v))
 }
 
 func (floatComponent) PutScalar(b []byte, v float32) {
-	UInt.PutScalar(b, *(*uint32)(unsafe.Pointer(&v)))
+	Uint.PutScalar(b, *(*uint32)(unsafe.Pointer(&v)))
 }
 
 func (floatComponent) Vec2(b []byte) [2]float32 {
-	v := UInt.Vec2(b)
+	v := Uint.Vec2(b)
 	return *(*[2]float32)(unsafe.Pointer(&v))
 }
 
 func (floatComponent) PutVec2(b []byte, v [2]float32) {
-	UInt.PutVec2(b, *(*[2]uint32)(unsafe.Pointer(&v)))
+	Uint.PutVec2(b, *(*[2]uint32)(unsafe.Pointer(&v)))
 }
 
 func (floatComponent) Vec3(b []byte) [3]float32 {
-	v := UInt.Vec3(b)
+	v := Uint.Vec3(b)
 	return *(*[3]float32)(unsafe.Pointer(&v))
 }
 
 func (floatComponent) PutVec3(b []byte, v [3]float32) {
-	UInt.PutVec3(b, *(*[3]uint32)(unsafe.Pointer(&v)))
+	Uint.PutVec3(b, *(*[3]uint32)(unsafe.Pointer(&v)))
 }
 
 func (floatComponent) Vec4(b []byte) [4]float32 {
-	v := UInt.Vec4(b)
+	v := Uint.Vec4(b)
 	return *(*[4]float32)(unsafe.Pointer(&v))
 }
 
 func (floatComponent) PutVec4(b []byte, v [4]float32) {
-	UInt.PutVec4(b, *(*[4]uint32)(unsafe.Pointer(&v)))
+	Uint.PutVec4(b, *(*[4]uint32)(unsafe.Pointer(&v)))
 }
 
 func (floatComponent) Mat2(b []byte) [2][2]float32 {
-	v := UInt.Mat2(b)
+	v := Uint.Mat2(b)
 	return *(*[2][2]float32)(unsafe.Pointer(&v))
 }
 
 func (floatComponent) PutMat2(b []byte, v [2][2]float32) {
-	UInt.PutMat2(b, *(*[2][2]uint32)(unsafe.Pointer(&v)))
+	Uint.PutMat2(b, *(*[2][2]uint32)(unsafe.Pointer(&v)))
 }
 
 func (floatComponent) Mat3(b []byte) [3][3]float32 {
-	v := UInt.Mat3(b)
+	v := Uint.Mat3(b)
 	return *(*[3][3]float32)(unsafe.Pointer(&v))
 }
 
 func (floatComponent) PutMat3(b []byte, v [3][3]float32) {
-	UInt.PutMat3(b, *(*[3][3]uint32)(unsafe.Pointer(&v)))
+	Uint.PutMat3(b, *(*[3][3]uint32)(unsafe.Pointer(&v)))
 }
 
 func (floatComponent) Mat4(b []byte) [4][4]float32 {
-	v := UInt.Mat4(b)
+	v := Uint.Mat4(b)
 	return *(*[4][4]float32)(unsafe.Pointer(&v))
 }
 
 func (floatComponent) PutMat4(b []byte, v [4][4]float32) {
-	UInt.PutMat4(b, *(*[4][4]uint32)(unsafe.Pointer(&v)))
+	Uint.PutMat4(b, *(*[4][4]uint32)(unsafe.Pointer(&v)))
 }
diff --git a/const.go b/const.go
index 7c408ce..a2ee956 100644
--- a/const.go
+++ b/const.go
@@ -38,6 +38,18 @@ const (
 	ComponentUint
 )
 
+// ByteSize returns the size of a component in bytes.
+func (c ComponentType) ByteSize() uint32 {
+	return map[ComponentType]uint32{
+		ComponentByte:   1,
+		ComponentUbyte:  1,
+		ComponentShort:  2,
+		ComponentUshort: 2,
+		ComponentUint:   4,
+		ComponentFloat:  4,
+	}[c]
+}
+
 // UnmarshalJSON unmarshal the component type with the correct default values.
 func (c *ComponentType) UnmarshalJSON(data []byte) error {
 	var tmp uint16
@@ -87,6 +99,19 @@ const (
 	AccessorMat4
 )
 
+// Components returns the number of components of an accessor type.
+func (a AccessorType) Components() uint32 {
+	return map[AccessorType]uint32{
+		AccessorScalar: 1,
+		AccessorVec2:   2,
+		AccessorVec3:   3,
+		AccessorVec4:   4,
+		AccessorMat2:   4,
+		AccessorMat3:   9,
+		AccessorMat4:   16,
+	}[a]
+}
+
 // UnmarshalJSON unmarshal the accessor type with the correct default values.
 func (a *AccessorType) UnmarshalJSON(data []byte) error {
 	var tmp string
diff --git a/decoder.go b/decoder.go
index b7fd099..d79949f 100644
--- a/decoder.go
+++ b/decoder.go
@@ -1,7 +1,6 @@
 package gltf
 
 import (
-	"math"
 	"bufio"
 	"bytes"
 	"encoding/binary"
@@ -9,6 +8,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"math"
 	"os"
 	"path/filepath"
 	"strings"
@@ -16,8 +16,8 @@ import (
 )
 
 const (
-	defaultMaxExternalBufferCount  = 10
-	defaultMaxMemoryAllocation     = math.MaxUint32 // 4GB
+	defaultMaxExternalBufferCount = 10
+	defaultMaxMemoryAllocation    = math.MaxUint32 // 4GB
 )
 
 // ReadHandler is the interface that wraps the ReadFullResource method.
diff --git a/modeler/compression.go b/modeler/compression.go
deleted file mode 100644
index a8a6111..0000000
--- a/modeler/compression.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package modeler
-
-import "math"
-
-func compressUint32(data []uint32) interface{} {
-	u8, u16 := true, true
-	// Start at the end, as there are more possibilities to have a higher numbers.
-	for i := len(data) - 1; i >= 0; i-- {
-		x := data[i]
-		if u16 && x >= math.MaxUint16 {
-			u8, u16 = false, false
-			break
-		} else if u8 && x >= math.MaxUint8 {
-			u8 = false
-		}
-	}
-	if u8 {
-		optData := make([]uint8, len(data))
-		for i, x := range data {
-			optData[i] = uint8(x)
-		}
-		return optData
-	}
-	if u16 {
-		optData := make([]uint16, len(data))
-		for i, x := range data {
-			optData[i] = uint16(x)
-		}
-		return optData
-	}
-	return data
-}
-
-func compressUint16(data []uint16) interface{} {
-	u8 := true
-	// Start at the end, as there are more possibilities to have a higher numbers.
-	for i := len(data) - 1; i >= 0; i-- {
-		x := data[i]
-		if u8 && x >= math.MaxUint8 {
-			u8 = false
-			break
-		}
-	}
-	if u8 {
-		optData := make([]uint8, len(data))
-		for i, x := range data {
-			optData[i] = uint8(x)
-		}
-		return optData
-	}
-	return data
-}
diff --git a/modeler/modeler.go b/modeler/modeler.go
index 7f280ac..8a46bd4 100644
--- a/modeler/modeler.go
+++ b/modeler/modeler.go
@@ -14,143 +14,82 @@ import (
 	"github.com/qmuntal/gltf/binary"
 )
 
-// CompressionLevel defines the different levels of compression.
-type CompressionLevel uint8
-
-const (
-	// CompressionNone will not apply any compression.
-	CompressionNone CompressionLevel = iota
-	// CompressionLossless will reduce the byte size without sacrificing quality.
-	CompressionLossless
-)
-
-// Modeler wraps a Document and add useful methods to build it.
-// If Compress is true, all the data added to accessors that support different component types
-// will be evaluated to see if it fits in a smaller component type.
-type Modeler struct {
-	*gltf.Document
-	Compression CompressionLevel
-}
-
-// NewModeler returns a new Modeler instance.
-func NewModeler() *Modeler {
-	return &Modeler{
-		Document: &gltf.Document{
-			Scene:  gltf.Index(0),
-			Scenes: []*gltf.Scene{{Name: "Root Scene"}},
-		},
-		Compression: CompressionLossless,
-	}
-}
-
-// AddIndices adds a new INDICES accessor to the Document
-// and fills the buffer with the indices data.
+// WriteIndices adds a new INDICES accessor to doc
+// and fills the last buffer with the indices data.
 // If success it returns the index of the new accessor.
-func (m *Modeler) AddIndices(bufferIndex uint32, data interface{}) uint32 {
-	var ok bool
+func WriteIndices(doc *gltf.Document, data interface{}) uint32 {
 	switch data.(type) {
-	case []uint8:
-		ok = true
-	case []uint16:
-		ok = true
-		if m.Compression >= CompressionLossless {
-			data = compressUint16(data.([]uint16))
-		}
-	case []uint32:
-		ok = true
-		if m.Compression >= CompressionLossless {
-			data = compressUint32(data.([]uint32))
-		}
-	}
-	componentType, accessorType, length := binary.Type(data)
-	if !ok || length <= 0 {
-		panic(fmt.Sprintf("modeler.AddIndices: invalid type %T", data))
+	case []uint8, []uint16, []uint32:
+	default:
+		panic(fmt.Sprintf("modeler.WriteIndices: invalid type %T", data))
 	}
-	index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, true)
-	return uint32(index)
+	return WriteAccessor(doc, gltf.TargetElementArrayBuffer, data)
 }
 
-// AddNormal adds a new NORMAL accessor to the Document
-// and fills the buffer with the indices data.
+// WriteNormal adds a new NORMAL accessor to doc
+// and fills the last buffer with the indices data.
 // If success it returns the index of the new accessor.
-func (m *Modeler) AddNormal(bufferIndex uint32, data [][3]float32) uint32 {
-	componentType, accessorType, length := binary.Type(data)
-	index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, false)
-	return uint32(index)
+func WriteNormal(doc *gltf.Document, data [][3]float32) uint32 {
+	return WriteAccessor(doc, gltf.TargetArrayBuffer, data)
 }
 
-// AddTangent adds a new TANGENT accessor to the Document
-// and fills the buffer with the indices data.
+// WriteTangent adds a new TANGENT accessor to doc
+// and fills the last buffer with the indices data.
 // If success it returns the index of the new accessor.
-func (m *Modeler) AddTangent(bufferIndex uint32, data [][4]float32) uint32 {
-	componentType, accessorType, length := binary.Type(data)
-	index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, false)
-	return uint32(index)
+func WriteTangent(doc *gltf.Document, data [][4]float32) uint32 {
+	return WriteAccessor(doc, gltf.TargetArrayBuffer, data)
 }
 
-// AddTextureCoord adds a new TEXTURECOORD accessor to the Document
-// and fills the buffer with the texturecoord data.
+// WriteTextureCoord adds a new TEXTURECOORD accessor to doc
+// and fills the last buffer with the texturecoord data.
 // If success it returns the index of the new accessor.
-func (m *Modeler) AddTextureCoord(bufferIndex uint32, data interface{}) uint32 {
-	var normalized, ok bool
+func WriteTextureCoord(doc *gltf.Document, data interface{}) uint32 {
+	var normalized bool
 	switch data.(type) {
 	case [][2]uint8, [][2]uint16:
-		ok = true
 		normalized = true
 	case [][2]float32:
-		ok = true
-	}
-	componentType, accessorType, length := binary.Type(data)
-	if !ok || length <= 0 {
-		panic(fmt.Sprintf("modeler.AddTextureCoord: invalid type %T", data))
+	default:
+		panic(fmt.Sprintf("modeler.WriteTextureCoord: invalid type %T", data))
 	}
-	index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, false)
-	m.Document.Accessors[index].Normalized = normalized
-	return uint32(index)
+	index := WriteAccessor(doc, gltf.TargetArrayBuffer, data)
+	doc.Accessors[index].Normalized = normalized
+	return index
 }
 
-// AddWeights adds a new WEIGHTS accessor to the Document
-// and fills the buffer with the weights data.
+// WriteWeights adds a new WEIGHTS accessor to doc
+// and fills the last buffer with the weights data.
 // If success it returns the index of the new accessor.
-func (m *Modeler) AddWeights(bufferIndex uint32, data interface{}) uint32 {
-	var normalized, ok bool
+func WriteWeights(doc *gltf.Document, data interface{}) uint32 {
+	var normalized bool
 	switch data.(type) {
 	case [][4]uint8, [][4]uint16:
-		ok = true
 		normalized = true
 	case [][4]float32:
-		ok = true
-	}
-	componentType, accessorType, length := binary.Type(data)
-	if !ok || length <= 0 {
-		panic(fmt.Sprintf("modeler.AddWeights: invalid type %T", data))
+	default:
+		panic(fmt.Sprintf("modeler.WriteWeights: invalid type %T", data))
 	}
-	index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, false)
-	m.Document.Accessors[index].Normalized = normalized
-	return uint32(index)
+	index := WriteAccessor(doc, gltf.TargetArrayBuffer, data)
+	doc.Accessors[index].Normalized = normalized
+	return index
 }
 
-// AddJoints adds a new JOINTS accessor to the Document
-// and fills the buffer with the joints data.
+// WriteJoints adds a new JOINTS accessor to doc
+// and fills the last buffer with the joints data.
 // If success it returns the index of the new accessor.
-func (m *Modeler) AddJoints(bufferIndex uint32, data interface{}) uint32 {
-	var ok bool
+func WriteJoints(doc *gltf.Document, data interface{}) uint32 {
 	switch data.(type) {
 	case [][4]uint8, [][4]uint16:
-		ok = true
-	}
-	componentType, accessorType, length := binary.Type(data)
-	if !ok || length <= 0 {
-		panic(fmt.Sprintf("modeler.AddJoints: invalid type %T", data))
+	default:
+		panic(fmt.Sprintf("modeler.WriteJoints: invalid type %T", data))
 	}
-	index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, false)
-	return uint32(index)
+	return WriteAccessor(doc, gltf.TargetArrayBuffer, data)
 }
 
-// AddPosition adds a new POSITION accessor to the Document
-// and fills the buffer with the vertices data.
+// WritePosition adds a new POSITION accessor to doc
+// and fills the last buffer with the vertices data.
 // If success it returns the index of the new accessor.
-func (m *Modeler) AddPosition(bufferIndex uint32, data [][3]float32) uint32 {
+func WritePosition(doc *gltf.Document, data [][3]float32) uint32 {
 	min := [3]float64{math.MaxFloat64, math.MaxFloat64, math.MaxFloat64}
 	max := [3]float64{-math.MaxFloat64, -math.MaxFloat64, -math.MaxFloat64}
 	for _, v := range data {
@@ -159,105 +98,103 @@ func (m *Modeler) AddPosition(bufferIndex uint32, data [][3]float32) uint32 {
 			max[i] = math.Max(max[i], float64(x))
 		}
 	}
-	componentType, accessorType, length := binary.Type(data)
-	index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, false)
-	m.Accessors[index].Min = min[:]
-	m.Accessors[index].Max = max[:]
-	return uint32(index)
+	index := WriteAccessor(doc, gltf.TargetArrayBuffer, data)
+	doc.Accessors[index].Min = min[:]
+	doc.Accessors[index].Max = max[:]
+	return index
 }
 
-// AddColor adds a new COLOR accessor to the Document
+// WriteColor adds a new COLOR accessor to doc
 // and fills the buffer with the color data.
 // If success it returns the index of the new accessor.
-func (m *Modeler) AddColor(bufferIndex uint32, data interface{}) uint32 {
-	var normalized, ok bool
+func WriteColor(doc *gltf.Document, data interface{}) uint32 {
+	var normalized bool
 	switch data.(type) {
 	case []color.RGBA, []color.RGBA64, [][4]uint8, [][3]uint8, [][4]uint16, [][3]uint16:
 		normalized = true
-		ok = true
-	case []gltf.RGBA, []gltf.RGB, [][3]float32, [][4]float32:
-		ok = true
-	}
-	componentType, accessorType, length := binary.Type(data)
-	if !ok || length <= 0 {
-		panic(fmt.Sprintf("modeler.AddColor: invalid type %T", data))
+	case []gltf.RGB, []gltf.RGBA, [][3]float32, [][4]float32:
+	default:
+		panic(fmt.Sprintf("modeler.WriteColor: invalid type %T", data))
 	}
-	index := m.addAccessor(bufferIndex, length, data, componentType, accessorType, false)
-	m.Document.Accessors[index].Normalized = normalized
-	return uint32(index)
+	index := WriteAccessor(doc, gltf.TargetArrayBuffer, data)
+	doc.Accessors[index].Normalized = normalized
+	return index
 }
 
-// AddImage adds a new image to the Document
+// WriteImage adds a new image to doc
 // and fills the buffer with the image data.
 // If success it returns the index of the new image.
-func (m *Modeler) AddImage(bufferIndex uint32, name, mimeType string, r io.Reader) (uint32, error) {
-	buffer := m.buffer(bufferIndex)
-	offset := uint32(len(buffer.Data))
+func WriteImage(doc *gltf.Document, name string, mimeType string, r io.Reader) (uint32, error) {
+	var data []byte
 	switch r := r.(type) {
 	case *bytes.Buffer:
-		buffer.Data = append(buffer.Data, r.Bytes()...)
+		data = r.Bytes()
 	default:
-		data, err := ioutil.ReadAll(r)
+		var err error
+		data, err = ioutil.ReadAll(r)
 		if err != nil {
 			return 0, err
 		}
-		buffer.Data = append(buffer.Data, data...)
 	}
-	index := m.addBufferView(bufferIndex, uint32(len(buffer.Data))-offset, offset, 0, false)
-	buffer.ByteLength = uint32(len(buffer.Data))
-	m.BufferViews[index].Target = gltf.TargetNone
-	m.Images = append(m.Images, &gltf.Image{
+	index := WriteBufferView(doc, gltf.TargetNone, data)
+	doc.Images = append(doc.Images, &gltf.Image{
 		Name:       name,
 		MimeType:   mimeType,
 		BufferView: gltf.Index(index),
 	})
-	return uint32(len(m.Images) - 1), nil
+	return uint32(len(doc.Images) - 1), nil
 }
 
-func (m *Modeler) addAccessor(bufferIndex uint32, count int, data interface{}, componentType gltf.ComponentType, accessorType gltf.AccessorType, isIndex bool) uint32 {
-	buffer := m.buffer(bufferIndex)
-	offset := uint32(len(buffer.Data))
-	padding := ((offset+3)/4)*4 - offset
-	elementSize := binary.SizeOfElement(componentType, accessorType)
-	size := uint32(count * elementSize)
-	buffer.ByteLength += uint32(size + padding)
-	buffer.Data = append(buffer.Data, make([]byte, size+padding)...)
-	// Cannot return error as the buffer has enough size and the data type is controlled.
-	_ = binary.Write(buffer.Data[offset+padding:], data)
-	index := m.addBufferView(bufferIndex, size, offset+padding, uint32(elementSize), isIndex)
-	m.Accessors = append(m.Accessors, &gltf.Accessor{
+// WriteAccessor adds a new Accessor to doc
+// and fills the buffer with data.
+// If success it returns the index of the new accessor.
+func WriteAccessor(doc *gltf.Document, target gltf.Target, data interface{}) uint32 {
+	c, a, l := binary.Type(data)
+	index := WriteBufferView(doc, target, data)
+	doc.Accessors = append(doc.Accessors, &gltf.Accessor{
 		BufferView:    gltf.Index(index),
 		ByteOffset:    0,
-		ComponentType: componentType,
-		Type:          accessorType,
-		Count:         uint32(count),
+		ComponentType: c,
+		Type:          a,
+		Count:         l,
 	})
-	return uint32(len(m.Accessors) - 1)
+	return uint32(len(doc.Accessors) - 1)
 }
 
-func (m *Modeler) addBufferView(buffer, size, offset, stride uint32, isIndices bool) uint32 {
+// WriteBufferView adds a new BufferView to doc
+// and fills the buffer with the data.
+// If success it returns the index of the new buffer view.
+func WriteBufferView(doc *gltf.Document, target gltf.Target, data interface{}) uint32 {
+	c, a, l := binary.Type(data)
+	size := l * binary.SizeOfElement(c, a)
+	buffer := lastBuffer(doc)
+	offset := uint32(len(buffer.Data))
+	padding := getPadding(offset, c.ByteSize())
+	buffer.ByteLength += size + padding
+	buffer.Data = append(buffer.Data, make([]byte, size+padding)...)
+	// Cannot return error as the buffer has enough size and the data type is controlled.
+	_ = binary.Write(buffer.Data[offset+padding:], 0, data)
 	bufferView := &gltf.BufferView{
-		Buffer:     buffer,
+		Buffer:     uint32(len(doc.Buffers)) - 1,
 		ByteLength: size,
 		ByteOffset: offset,
+		Target:     target,
 	}
-	if isIndices {
-		bufferView.Target = gltf.TargetElementArrayBuffer
-	} else {
-		bufferView.Target = gltf.TargetArrayBuffer
-		bufferView.ByteStride = stride
+	doc.BufferViews = append(doc.BufferViews, bufferView)
+	return uint32(len(doc.BufferViews)) - 1
+}
+
+func lastBuffer(doc *gltf.Document) *gltf.Buffer {
+	if len(doc.Buffers) == 0 {
+		doc.Buffers = append(doc.Buffers, new(gltf.Buffer))
 	}
-	m.BufferViews = append(m.BufferViews, bufferView)
-	return uint32(len(m.BufferViews)) - 1
+	return doc.Buffers[len(doc.Buffers)-1]
 }
 
-func (m *Modeler) buffer(bufferIndex uint32) *gltf.Buffer {
-	if int(bufferIndex) >= len(m.Buffers) {
-		l := len(m.Buffers)
-		m.Buffers = append(m.Buffers, make([]*gltf.Buffer, int(bufferIndex)-l+1)...)
-		for i := l; i < len(m.Buffers); i++ {
-			m.Buffers[i] = new(gltf.Buffer)
-		}
+func getPadding(offset uint32, alignment uint32) uint32 {
+	padAlign := offset % alignment
+	if padAlign == 0 {
+		return 0
 	}
-	return m.Buffers[bufferIndex]
+	return alignment - padAlign
 }
diff --git a/struct.go b/struct.go
index 36a214d..6fa271c 100644
--- a/struct.go
+++ b/struct.go
@@ -51,6 +51,18 @@ type Document struct {
 	Textures           []*Texture    `json:"textures,omitempty" validate:"dive"`
 }
 
+// NewDocument returns a new Document with sane defaults.
+func NewDocument() *Document {
+	return &Document{
+		Scene:  Index(0),
+		Scenes: []*Scene{{Name: "Root Scene"}},
+		Asset: Asset{
+			Generator: "qmuntal/gltf",
+			Version:   "2.0",
+		},
+	}
+}
+
 // An Accessor is a typed view into a bufferView.
 // An accessor provides a typed view into a bufferView or a subset of a bufferView
 // similar to how WebGL's vertexAttribPointer() defines an attribute in a buffer.


Test patch:
diff --git a/binary/bench_test.go b/binary/bench_test.go
index 1a308c4..598cf56 100644
--- a/binary/bench_test.go
+++ b/binary/bench_test.go
@@ -9,7 +9,7 @@ import (
 )
 
 func BenchmarkNative(b *testing.B) {
-	s := 1000
+	var s uint32 = 1000
 	bs := make([]byte, s*SizeOfElement(gltf.ComponentFloat, gltf.AccessorVec3))
 	data := make([][3]float32, s)
 	b.ResetTimer()
@@ -21,17 +21,17 @@ func BenchmarkNative(b *testing.B) {
 }
 
 func BenchmarkWrite(b *testing.B) {
-	s := 1000
+	var s uint32 = 1000
 	bs := make([]byte, s*SizeOfElement(gltf.ComponentFloat, gltf.AccessorVec3))
 	data := make([][3]float32, s)
 	b.ResetTimer()
 	for n := 0; n < b.N; n++ {
-		Write(bs, data)
+		Write(bs, 0, data)
 	}
 }
 
 func BenchmarkWrite_builtint(b *testing.B) {
-	s := 1000
+	var s uint32 = 1000
 	bs := bytes.NewBuffer(make([]byte, s*SizeOfElement(gltf.ComponentFloat, gltf.AccessorVec3)))
 	data := make([][3]float32, s)
 	b.ResetTimer()
diff --git a/binary/encode_test.go b/binary/encode_test.go
index b1aee06..e14d44f 100644
--- a/binary/encode_test.go
+++ b/binary/encode_test.go
@@ -305,7 +305,7 @@ func TestWrite(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			b := make([]byte, tt.args.n)
-			if err := Write(b, tt.args.data); (err != nil) != tt.wantErr {
+			if err := Write(b, 0, tt.args.data); (err != nil) != tt.wantErr {
 				t.Errorf("Write() error = %v, wantErr %v", err, tt.wantErr)
 			}
 			if !tt.wantErr && !reflect.DeepEqual(b, tt.want) {
diff --git a/binary/example_test.go b/binary/example_test.go
index 3e9bc73..151952d 100644
--- a/binary/example_test.go
+++ b/binary/example_test.go
@@ -2,6 +2,7 @@ package binary_test
 
 import (
 	"fmt"
+
 	"github.com/qmuntal/gltf"
 	"github.com/qmuntal/gltf/binary"
 )
@@ -12,13 +13,13 @@ func ExampleWrite() {
 	vertices := [][3]float32{{43, 43, 0}, {83, 43, 0}, {63, 63, 40}, {43, 83, 0}, {83, 83, 0}}
 
 	// Allocate buffer
-	sizeIndices := len(indices) * binary.SizeOfElement(gltf.ComponentUbyte, gltf.AccessorScalar)
-	sizeVertices := len(vertices) * binary.SizeOfElement(gltf.ComponentFloat, gltf.AccessorVec3)
+	sizeIndices := uint32(len(indices)) * binary.SizeOfElement(gltf.ComponentUbyte, gltf.AccessorScalar)
+	sizeVertices := uint32(len(vertices)) * binary.SizeOfElement(gltf.ComponentFloat, gltf.AccessorVec3)
 	b := make([]byte, sizeIndices+sizeVertices)
 
 	// Write
-	binary.Write(b, indices)
-	binary.Write(b[sizeIndices:], vertices)
+	binary.Write(b, 0, indices)
+	binary.Write(b[sizeIndices:], 0, vertices)
 
 	fmt.Print(b)
 	// Output:
@@ -32,7 +33,7 @@ func ExampleRead() {
 
 	// Define buffer
 	b := []byte{0, 1, 2, 3, 1, 0, 0, 2, 3, 1, 4, 2, 4, 3, 2, 4, 1, 3, 0, 0, 44, 66, 0, 0, 44, 66, 0, 0, 0, 0, 0, 0, 166, 66, 0, 0, 44, 66, 0, 0, 0, 0, 0, 0, 124, 66, 0, 0, 124, 66, 0, 0, 32, 66, 0, 0, 44, 66, 0, 0, 166, 66, 0, 0, 0, 0, 0, 0, 166, 66, 0, 0, 166, 66, 0, 0, 0, 0}
-	sizeIndices := len(indices) * binary.SizeOfElement(gltf.ComponentUbyte, gltf.AccessorScalar)
+	sizeIndices := uint32(len(indices)) * binary.SizeOfElement(gltf.ComponentUbyte, gltf.AccessorScalar)
 
 	// Write
 	binary.Read(b, indices)
diff --git a/modeler/compression_test.go b/modeler/compression_test.go
deleted file mode 100644
index e029ba3..0000000
--- a/modeler/compression_test.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package modeler
-
-import (
-	"math"
-	"reflect"
-	"testing"
-)
-
-func Test_compressUint32(t *testing.T) {
-	type args struct {
-		data []uint32
-	}
-	tests := []struct {
-		name string
-		args args
-		want interface{}
-	}{
-		{"empty", args{[]uint32{}}, []uint8{}},
-		{"32", args{[]uint32{1, math.MaxUint16}}, []uint32{1, math.MaxUint16}},
-		{"32-2", args{[]uint32{1, math.MaxUint8, math.MaxUint16}}, []uint32{1, math.MaxUint8, math.MaxUint16}},
-		{"32-3", args{[]uint32{1, math.MaxUint16, math.MaxUint8}}, []uint32{1, math.MaxUint16, math.MaxUint8}},
-		{"32-4", args{[]uint32{1, math.MaxUint8}}, []uint16{1, math.MaxUint8}},
-		{"16", args{[]uint32{1, math.MaxUint16 - 1}}, []uint16{1, math.MaxUint16 - 1}},
-		{"8", args{[]uint32{1, math.MaxUint8 - 1}}, []uint8{1, math.MaxUint8 - 1}},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if got := compressUint32(tt.args.data); !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("compressUint32() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
-
-func Test_compressUint16(t *testing.T) {
-	type args struct {
-		data []uint16
-	}
-	tests := []struct {
-		name string
-		args args
-		want interface{}
-	}{
-		{"empty", args{[]uint16{}}, []uint8{}},
-		{"16", args{[]uint16{1, math.MaxUint8}}, []uint16{1, math.MaxUint8}},
-		{"8", args{[]uint16{1, math.MaxUint8 - 1}}, []uint8{1, math.MaxUint8 - 1}},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if got := compressUint16(tt.args.data); !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("compressUint16() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
diff --git a/modeler/example_test.go b/modeler/example_test.go
index 4e2e99f..ad67b04 100644
--- a/modeler/example_test.go
+++ b/modeler/example_test.go
@@ -6,11 +6,11 @@ import (
 )
 
 func Example() {
-	m := modeler.NewModeler()
-	positionAccessor := m.AddPosition(0, [][3]float32{{43, 43, 0}, {83, 43, 0}, {63, 63, 40}, {43, 83, 0}, {83, 83, 0}})
-	indicesAccessor := m.AddIndices(0, []uint8{0, 1, 2, 3, 1, 0, 0, 2, 3, 1, 4, 2, 4, 3, 2, 4, 1, 3})
-	colorIndices := m.AddColor(0, [][3]uint8{{50, 155, 255}, {0, 100, 200}, {255, 155, 50}, {155, 155, 155}, {25, 25, 25}})
-	m.Document.Meshes = []*gltf.Mesh{{
+	doc := gltf.NewDocument()
+	positionAccessor := modeler.WritePosition(doc, [][3]float32{{43, 43, 0}, {83, 43, 0}, {63, 63, 40}, {43, 83, 0}, {83, 83, 0}})
+	indicesAccessor := modeler.WriteIndices(doc, []uint8{0, 1, 2, 3, 1, 0, 0, 2, 3, 1, 4, 2, 4, 3, 2, 4, 1, 3})
+	colorIndices := modeler.WriteColor(doc, [][3]uint8{{50, 155, 255}, {0, 100, 200}, {255, 155, 50}, {155, 155, 155}, {25, 25, 25}})
+	doc.Meshes = []*gltf.Mesh{{
 		Name: "Pyramid",
 		Primitives: []*gltf.Primitive{
 			{
@@ -22,9 +22,9 @@ func Example() {
 			},
 		},
 	}}
-	m.Nodes = []*gltf.Node{{Name: "Root", Mesh: gltf.Index(0)}}
-	m.Scenes[0].Nodes = append(m.Scenes[0].Nodes, 0)
-	if err := gltf.SaveBinary(m.Document, "./example.glb"); err != nil {
+	doc.Nodes = []*gltf.Node{{Name: "Root", Mesh: gltf.Index(0)}}
+	doc.Scenes[0].Nodes = append(doc.Scenes[0].Nodes, 0)
+	if err := gltf.SaveBinary(doc, "./example.glb"); err != nil {
 		panic(err)
 	}
 }
diff --git a/modeler/modeler_test.go b/modeler/modeler_test.go
index 134b85f..0a1930b 100644
--- a/modeler/modeler_test.go
+++ b/modeler/modeler_test.go
@@ -4,54 +4,51 @@ import (
 	"bytes"
 	"errors"
 	"io"
-	"reflect"
 	"testing"
 
 	"github.com/go-test/deep"
 	"github.com/qmuntal/gltf"
 )
 
-func TestNewModeler(t *testing.T) {
-	tests := []struct {
-		name string
-		want *Modeler
-	}{
-		{"base", &Modeler{Document: &gltf.Document{
-			Scene:  gltf.Index(0),
-			Scenes: []*gltf.Scene{{Name: "Root Scene"}},
-		}, Compression: CompressionLossless}},
+func TestAlignment(t *testing.T) {
+	doc := gltf.NewDocument()
+	WriteIndices(doc, []uint16{0, 1, 2})
+	WritePosition(doc, [][3]float32{{0, 0, 0}, {1, 0, 0}, {0, 1, 0}})
+	if len(doc.Buffers) != 1 {
+		t.Errorf("Testalignment() buffer size = %v, want 1", len(doc.Buffers))
 	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if got := NewModeler(); !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("NewModeler() = %v, want %v", got, tt.want)
-			}
-		})
+	buffer := doc.Buffers[0]
+	want := make([]byte, 44)
+	want[2], want[4] = 1, 2
+	want[22], want[23] = 0x80, 0x3f
+	want[38], want[39] = 0x80, 0x3f
+	if diff := deep.Equal(buffer.Data, want); diff != nil {
+		t.Errorf("Testalignment() = %v", diff)
+		return
 	}
 }
 
-func TestModeler_AddNormal(t *testing.T) {
+func TestWriteNormal(t *testing.T) {
 	type args struct {
-		bufferIndex uint32
-		data        [][3]float32
+		data [][3]float32
 	}
 	tests := []struct {
 		name    string
-		m       *Modeler
+		m       *gltf.Document
 		args    args
 		want    uint32
 		wantDoc *gltf.Document
 	}{
-		{"base", &Modeler{Document: &gltf.Document{
+		{"base", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, [][3]float32{{1, 2, 3}}}, 1, &gltf.Document{
+		}, args{[][3]float32{{1, 2, 3}}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec3, ComponentType: gltf.ComponentFloat},
 			},
 			BufferViews: []*gltf.BufferView{
-				{ByteLength: 12, Target: gltf.TargetArrayBuffer, ByteStride: 12},
+				{ByteLength: 12, Target: gltf.TargetArrayBuffer},
 			},
 			Buffers: []*gltf.Buffer{
 				{ByteLength: 22, Data: []byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64}},
@@ -60,41 +57,40 @@ func TestModeler_AddNormal(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got := tt.m.AddNormal(tt.args.bufferIndex, tt.args.data)
+			got := WriteNormal(tt.m, tt.args.data)
 			if tt.want != got {
-				t.Errorf("Modeler.AddNormal() = %v, want %v", got, tt.want)
+				t.Errorf("WriteNormal() = %v, want %v", got, tt.want)
 				return
 			}
-			if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil {
-				t.Errorf("Modeler.AddNormal() = %v", diff)
+			if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil {
+				t.Errorf("WriteNormal() = %v", diff)
 				return
 			}
 		})
 	}
 }
 
-func TestModeler_AddTangent(t *testing.T) {
+func TestWriteTangent(t *testing.T) {
 	type args struct {
-		bufferIndex uint32
-		data        [][4]float32
+		data [][4]float32
 	}
 	tests := []struct {
 		name    string
-		m       *Modeler
+		m       *gltf.Document
 		args    args
 		want    uint32
 		wantDoc *gltf.Document
 	}{
-		{"base", &Modeler{Document: &gltf.Document{
+		{"base", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, [][4]float32{{1, 2, 3, 4}, {}}}, 1, &gltf.Document{
+		}, args{[][4]float32{{1, 2, 3, 4}, {}}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentFloat},
 			},
 			BufferViews: []*gltf.BufferView{
-				{ByteLength: 32, Target: gltf.TargetArrayBuffer, ByteStride: 16},
+				{ByteLength: 32, Target: gltf.TargetArrayBuffer},
 			},
 			Buffers: []*gltf.Buffer{
 				{ByteLength: 42, Data: []byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
@@ -103,41 +99,40 @@ func TestModeler_AddTangent(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got := tt.m.AddTangent(tt.args.bufferIndex, tt.args.data)
+			got := WriteTangent(tt.m, tt.args.data)
 			if tt.want != got {
-				t.Errorf("Modeler.AddTangent() = %v, want %v", got, tt.want)
+				t.Errorf("WriteTangent() = %v, want %v", got, tt.want)
 				return
 			}
-			if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil {
-				t.Errorf("Modeler.AddTangent() = %v", diff)
+			if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil {
+				t.Errorf("WriteTangent() = %v", diff)
 				return
 			}
 		})
 	}
 }
 
-func TestModeler_AddPosition(t *testing.T) {
+func TestWritePosition(t *testing.T) {
 	type args struct {
-		bufferIndex uint32
-		data        [][3]float32
+		data [][3]float32
 	}
 	tests := []struct {
 		name    string
-		m       *Modeler
+		m       *gltf.Document
 		args    args
 		want    uint32
 		wantDoc *gltf.Document
 	}{
-		{"base", &Modeler{Document: &gltf.Document{
+		{"base", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, [][3]float32{{1, 2, 3}, {0, 0, -1}}}, 1, &gltf.Document{
+		}, args{[][3]float32{{1, 2, 3}, {0, 0, -1}}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorVec3, ComponentType: gltf.ComponentFloat, Max: []float64{1, 2, 3}, Min: []float64{0, 0, -1}},
 			},
 			BufferViews: []*gltf.BufferView{
-				{ByteLength: 24, Target: gltf.TargetArrayBuffer, ByteStride: 12},
+				{ByteLength: 24, Target: gltf.TargetArrayBuffer},
 			},
 			Buffers: []*gltf.Buffer{
 				{ByteLength: 34, Data: []byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 191}},
@@ -146,56 +141,55 @@ func TestModeler_AddPosition(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got := tt.m.AddPosition(tt.args.bufferIndex, tt.args.data)
+			got := WritePosition(tt.m, tt.args.data)
 			if tt.want != got {
-				t.Errorf("Modeler.AddPosition() = %v, want %v", got, tt.want)
+				t.Errorf("WritePosition() = %v, want %v", got, tt.want)
 				return
 			}
-			if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil {
-				t.Errorf("Modeler.AddPosition() = %v", diff)
+			if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil {
+				t.Errorf("WritePosition() = %v", diff)
 				return
 			}
 		})
 	}
 }
 
-func TestModeler_AddJoints(t *testing.T) {
+func TestWriteJoints(t *testing.T) {
 	type args struct {
-		bufferIndex uint32
-		data        interface{}
+		data interface{}
 	}
 	tests := []struct {
 		name    string
-		m       *Modeler
+		m       *gltf.Document
 		args    args
 		want    uint32
 		wantDoc *gltf.Document
 	}{
-		{"uint8", &Modeler{Document: &gltf.Document{
+		{"uint8", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, [][4]uint8{{1, 2, 3, 4}}}, 1, &gltf.Document{
+		}, args{[][4]uint8{{1, 2, 3, 4}}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUbyte},
 			},
 			BufferViews: []*gltf.BufferView{
-				{ByteLength: 4, Target: gltf.TargetArrayBuffer, ByteStride: 4},
+				{ByteLength: 4, Target: gltf.TargetArrayBuffer},
 			},
 			Buffers: []*gltf.Buffer{
 				{ByteLength: 14, Data: []byte{1, 2, 3, 4}},
 			},
 		}},
-		{"uint16", &Modeler{Document: &gltf.Document{
+		{"uint16", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, [][4]uint16{{1, 2, 3, 4}}}, 1, &gltf.Document{
+		}, args{[][4]uint16{{1, 2, 3, 4}}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUshort},
 			},
 			BufferViews: []*gltf.BufferView{
-				{ByteLength: 8, Target: gltf.TargetArrayBuffer, ByteStride: 8},
+				{ByteLength: 8, Target: gltf.TargetArrayBuffer},
 			},
 			Buffers: []*gltf.Buffer{
 				{ByteLength: 18, Data: []byte{1, 0, 2, 0, 3, 0, 4, 0}},
@@ -204,71 +198,70 @@ func TestModeler_AddJoints(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got := tt.m.AddJoints(tt.args.bufferIndex, tt.args.data)
+			got := WriteJoints(tt.m, tt.args.data)
 			if tt.want != got {
-				t.Errorf("Modeler.AddJoints() = %v, want %v", got, tt.want)
+				t.Errorf("WriteJoints() = %v, want %v", got, tt.want)
 				return
 			}
-			if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil {
-				t.Errorf("Modeler.AddJoints() = %v", diff)
+			if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil {
+				t.Errorf("WriteJoints() = %v", diff)
 				return
 			}
 		})
 	}
 }
 
-func TestModeler_AddWeights(t *testing.T) {
+func TestWriteWeights(t *testing.T) {
 	type args struct {
-		bufferIndex uint32
-		data        interface{}
+		data interface{}
 	}
 	tests := []struct {
 		name    string
-		m       *Modeler
+		m       *gltf.Document
 		args    args
 		want    uint32
 		wantDoc *gltf.Document
 	}{
-		{"uint8", &Modeler{Document: &gltf.Document{
+		{"uint8", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, [][4]uint8{{1, 2, 3, 4}}}, 1, &gltf.Document{
+		}, args{[][4]uint8{{1, 2, 3, 4}}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUbyte, Normalized: true},
 			},
 			BufferViews: []*gltf.BufferView{
-				{ByteLength: 4, Target: gltf.TargetArrayBuffer, ByteStride: 4},
+				{ByteLength: 4, Target: gltf.TargetArrayBuffer},
 			},
 			Buffers: []*gltf.Buffer{
 				{ByteLength: 14, Data: []byte{1, 2, 3, 4}},
 			},
 		}},
-		{"uint16", &Modeler{Document: &gltf.Document{
+		{"uint16", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, [][4]uint16{{1, 2, 3, 4}}}, 1, &gltf.Document{
+		}, args{[][4]uint16{{1, 2, 3, 4}}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUshort, Normalized: true},
 			},
 			BufferViews: []*gltf.BufferView{
-				{ByteLength: 8, Target: gltf.TargetArrayBuffer, ByteStride: 8},
+				{ByteLength: 8, Target: gltf.TargetArrayBuffer},
 			},
 			Buffers: []*gltf.Buffer{
 				{ByteLength: 18, Data: []byte{1, 0, 2, 0, 3, 0, 4, 0}},
 			},
 		}},
-		{"float", &Modeler{Document: &gltf.Document{
+		{"float", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, [][4]float32{{1, 2, 3, 4}, {}}}, 1, &gltf.Document{
+		}, args{[][4]float32{{1, 2, 3, 4}, {}}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentFloat},
 			},
 			BufferViews: []*gltf.BufferView{
-				{ByteLength: 32, Target: gltf.TargetArrayBuffer, ByteStride: 16},
+				{ByteLength: 32, Target: gltf.TargetArrayBuffer},
 			},
 			Buffers: []*gltf.Buffer{
 				{ByteLength: 42, Data: []byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
@@ -277,110 +270,106 @@ func TestModeler_AddWeights(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got := tt.m.AddWeights(tt.args.bufferIndex, tt.args.data)
+			got := WriteWeights(tt.m, tt.args.data)
 			if tt.want != got {
-				t.Errorf("Modeler.AddWeights() = %v, want %v", got, tt.want)
+				t.Errorf("WriteWeights() = %v, want %v", got, tt.want)
 				return
 			}
-			if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil {
-				t.Errorf("Modeler.AddWeights() = %v", diff)
+			if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil {
+				t.Errorf("WriteWeights() = %v", diff)
 				return
 			}
 		})
 	}
 }
 
-func TestModeler_AddTextureCoord(t *testing.T) {
+func TestWriteTextureCoord(t *testing.T) {
 	type args struct {
-		bufferIndex uint32
-		data        interface{}
+		data interface{}
 	}
 	tests := []struct {
 		name    string
-		m       *Modeler
+		m       *gltf.Document
 		args    args
 		want    uint32
 		wantDoc *gltf.Document
 	}{
-		{"uint8", &Modeler{Document: &gltf.Document{
+		{"uint8", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, [][2]uint8{{1, 2}}}, 1, &gltf.Document{
+		}, args{[][2]uint8{{1, 2}}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec2, ComponentType: gltf.ComponentUbyte, Normalized: true},
 			},
 			BufferViews: []*gltf.BufferView{
-				{ByteLength: 4, Target: gltf.TargetArrayBuffer, ByteStride: 4},
+				{ByteLength: 4, Target: gltf.TargetArrayBuffer},
 			},
 			Buffers: []*gltf.Buffer{
 				{ByteLength: 14, Data: []byte{1, 2, 0, 0}},
 			},
 		}},
-		{"uint16", &Modeler{Document: &gltf.Document{
+		{"uint16", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, [][2]uint16{{1, 2}}}, 1, &gltf.Document{
+		}, args{[][2]uint16{{1, 2}}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec2, ComponentType: gltf.ComponentUshort, Normalized: true},
 			},
 			BufferViews: []*gltf.BufferView{
-				{ByteLength: 4, Target: gltf.TargetArrayBuffer, ByteStride: 4},
+				{ByteLength: 4, Target: gltf.TargetArrayBuffer},
 			},
 			Buffers: []*gltf.Buffer{
 				{ByteLength: 14, Data: []byte{1, 0, 2, 0}},
 			},
 		}},
-		{"float", &Modeler{Document: &gltf.Document{
+		{"float", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{2, [][2]float32{{1, 2}, {}}}, 1, &gltf.Document{
+		}, args{[][2]float32{{1, 2}, {}}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorVec2, ComponentType: gltf.ComponentFloat},
 			},
 			BufferViews: []*gltf.BufferView{
-				{ByteLength: 16, Target: gltf.TargetArrayBuffer, Buffer: 2, ByteStride: 8},
+				{ByteLength: 16, Target: gltf.TargetArrayBuffer, Buffer: 0},
 			},
 			Buffers: []*gltf.Buffer{
-				{ByteLength: 10},
-				{},
-				{ByteLength: 16, Data: []byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0}},
+				{ByteLength: 26, Data: []byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0}},
 			},
 		}},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got := tt.m.AddTextureCoord(tt.args.bufferIndex, tt.args.data)
+			got := WriteTextureCoord(tt.m, tt.args.data)
 			if tt.want != got {
-				t.Errorf("Modeler.AddTextureCoord() = %v, want %v", got, tt.want)
+				t.Errorf("WriteTextureCoord() = %v, want %v", got, tt.want)
 				return
 			}
-			if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil {
-				t.Errorf("Modeler.AddTextureCoord() = %v", diff)
+			if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil {
+				t.Errorf("WriteTextureCoord() = %v", diff)
 				return
 			}
 		})
 	}
 }
 
-func TestModeler_AddIndices(t *testing.T) {
+func TestWriteIndices(t *testing.T) {
 	type args struct {
-		bufferIndex uint32
-		data        interface{}
+		data interface{}
 	}
 	tests := []struct {
 		name    string
-		m       *Modeler
+		m       *gltf.Document
 		args    args
 		want    uint32
 		wantDoc *gltf.Document
 	}{
-		{"uint8", &Modeler{Document: &gltf.Document{
+		{"uint8", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, []uint8{1, 2}}, 1, &gltf.Document{
+		}, args{[]uint8{1, 2}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorScalar, ComponentType: gltf.ComponentUbyte},
@@ -392,10 +381,10 @@ func TestModeler_AddIndices(t *testing.T) {
 				{ByteLength: 12, Data: []byte{1, 2}},
 			},
 		}},
-		{"uint16", &Modeler{Document: &gltf.Document{
+		{"uint16", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, []uint16{1, 2}}, 1, &gltf.Document{
+		}, args{[]uint16{1, 2}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorScalar, ComponentType: gltf.ComponentUshort},
@@ -407,25 +396,10 @@ func TestModeler_AddIndices(t *testing.T) {
 				{ByteLength: 14, Data: []byte{1, 0, 2, 0}},
 			},
 		}},
-		{"uint16-compress", &Modeler{Document: &gltf.Document{
-			Accessors: []*gltf.Accessor{{}},
-			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}, Compression: CompressionLossless}, args{0, []uint16{1, 2}}, 1, &gltf.Document{
-			Accessors: []*gltf.Accessor{
-				{},
-				{BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorScalar, ComponentType: gltf.ComponentUbyte},
-			},
-			BufferViews: []*gltf.BufferView{
-				{ByteLength: 2, Target: gltf.TargetElementArrayBuffer},
-			},
-			Buffers: []*gltf.Buffer{
-				{ByteLength: 12, Data: []byte{1, 2}},
-			},
-		}},
-		{"uint32", &Modeler{Document: &gltf.Document{
+		{"uint32", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, []uint32{1, 2}}, 1, &gltf.Document{
+		}, args{[]uint32{1, 2}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorScalar, ComponentType: gltf.ComponentUint},
@@ -437,88 +411,72 @@ func TestModeler_AddIndices(t *testing.T) {
 				{ByteLength: 18, Data: []byte{1, 0, 0, 0, 2, 0, 0, 0}},
 			},
 		}},
-		{"uint32-compress", &Modeler{Document: &gltf.Document{
-			Accessors: []*gltf.Accessor{{}},
-			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}, Compression: CompressionLossless}, args{0, []uint32{1, 2}}, 1, &gltf.Document{
-			Accessors: []*gltf.Accessor{
-				{},
-				{BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorScalar, ComponentType: gltf.ComponentUbyte},
-			},
-			BufferViews: []*gltf.BufferView{
-				{ByteLength: 2, Target: gltf.TargetElementArrayBuffer},
-			},
-			Buffers: []*gltf.Buffer{
-				{ByteLength: 12, Data: []byte{1, 2}},
-			},
-		}},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got := tt.m.AddIndices(tt.args.bufferIndex, tt.args.data)
+			got := WriteIndices(tt.m, tt.args.data)
 			if tt.want != got {
-				t.Errorf("Modeler.AddIndices() = %v, want %v", got, tt.want)
+				t.Errorf("WriteIndices() = %v, want %v", got, tt.want)
 				return
 			}
-			if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil {
-				t.Errorf("Modeler.AddIndices() = %v", diff)
+			if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil {
+				t.Errorf("WriteIndices() = %v", diff)
 				return
 			}
 		})
 	}
 }
 
-func TestModeler_AddColor(t *testing.T) {
+func TestWriteColor(t *testing.T) {
 	type args struct {
-		bufferIndex uint32
-		data        interface{}
+		data interface{}
 	}
 	tests := []struct {
 		name    string
-		m       *Modeler
+		m       *gltf.Document
 		args    args
 		want    uint32
 		wantDoc *gltf.Document
 	}{
-		{"uint8", &Modeler{Document: &gltf.Document{
+		{"uint8", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, [][4]uint8{{1, 2, 3, 4}}}, 1, &gltf.Document{
+		}, args{[][4]uint8{{1, 2, 3, 4}}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUbyte, Normalized: true},
 			},
 			BufferViews: []*gltf.BufferView{
-				{ByteLength: 4, Target: gltf.TargetArrayBuffer, ByteStride: 4},
+				{ByteLength: 4, Target: gltf.TargetArrayBuffer},
 			},
 			Buffers: []*gltf.Buffer{
 				{ByteLength: 14, Data: []byte{1, 2, 3, 4}},
 			},
 		}},
-		{"uint16", &Modeler{Document: &gltf.Document{
+		{"uint16", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
 			Buffers:   []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, [][4]uint16{{1, 2, 3, 4}}}, 1, &gltf.Document{
+		}, args{[][4]uint16{{1, 2, 3, 4}}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 1, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentUshort, Normalized: true},
 			},
 			BufferViews: []*gltf.BufferView{
-				{ByteLength: 8, Target: gltf.TargetArrayBuffer, ByteStride: 8},
+				{ByteLength: 8, Target: gltf.TargetArrayBuffer},
 			},
 			Buffers: []*gltf.Buffer{
 				{ByteLength: 18, Data: []byte{1, 0, 2, 0, 3, 0, 4, 0}},
 			},
 		}},
-		{"float", &Modeler{Document: &gltf.Document{
+		{"float", &gltf.Document{
 			Accessors: []*gltf.Accessor{{}},
-		}}, args{0, [][4]float32{{1, 2, 3, 4}, {}}}, 1, &gltf.Document{
+		}, args{[][4]float32{{1, 2, 3, 4}, {}}}, 1, &gltf.Document{
 			Accessors: []*gltf.Accessor{
 				{},
 				{BufferView: gltf.Index(0), Count: 2, Type: gltf.AccessorVec4, ComponentType: gltf.ComponentFloat},
 			},
 			BufferViews: []*gltf.BufferView{
-				{ByteLength: 32, Target: gltf.TargetArrayBuffer, ByteStride: 16},
+				{ByteLength: 32, Target: gltf.TargetArrayBuffer},
 			},
 			Buffers: []*gltf.Buffer{
 				{ByteLength: 32, Data: []byte{0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
@@ -527,38 +485,37 @@ func TestModeler_AddColor(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got := tt.m.AddColor(tt.args.bufferIndex, tt.args.data)
+			got := WriteColor(tt.m, tt.args.data)
 			if tt.want != got {
-				t.Errorf("Modeler.AddColor() = %v, want %v", got, tt.want)
+				t.Errorf("WriteColor() = %v, want %v", got, tt.want)
 				return
 			}
-			if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil {
-				t.Errorf("Modeler.AddColor() = %v", diff)
+			if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil {
+				t.Errorf("WriteColor() = %v", diff)
 				return
 			}
 		})
 	}
 }
 
-func TestModeler_AddImage(t *testing.T) {
+func TestWriteImage(t *testing.T) {
 	type args struct {
-		bufferIndex uint32
-		name        string
-		mimeType    string
-		r           io.Reader
+		name     string
+		mimeType string
+		r        io.Reader
 	}
 	tests := []struct {
 		name    string
-		m       *Modeler
+		m       *gltf.Document
 		args    args
 		want    uint32
 		wantDoc *gltf.Document
 		wantErr bool
 	}{
-		{"base", &Modeler{Document: &gltf.Document{
+		{"base", &gltf.Document{
 			Images:  []*gltf.Image{{}},
 			Buffers: []*gltf.Buffer{{ByteLength: 10, Data: make([]byte, 10)}},
-		}}, args{0, "fake", "fake/type", bytes.NewReader([]byte{1, 2})}, 1, &gltf.Document{
+		}, args{"fake", "fake/type", bytes.NewReader([]byte{1, 2})}, 1, &gltf.Document{
 			Images: []*gltf.Image{
 				{},
 				{BufferView: gltf.Index(0), Name: "fake", MimeType: "fake/type"},
@@ -570,10 +527,10 @@ func TestModeler_AddImage(t *testing.T) {
 				{ByteLength: 12, Data: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2}},
 			},
 		}, false},
-		{"buffer", &Modeler{Document: &gltf.Document{
+		{"buffer", &gltf.Document{
 			Images:  []*gltf.Image{{}},
 			Buffers: []*gltf.Buffer{{ByteLength: 10, Data: make([]byte, 10)}},
-		}}, args{0, "fake", "fake/type", bytes.NewBuffer([]byte{1, 2})}, 1, &gltf.Document{
+		}, args{"fake", "fake/type", bytes.NewBuffer([]byte{1, 2})}, 1, &gltf.Document{
 			Images: []*gltf.Image{
 				{},
 				{BufferView: gltf.Index(0), Name: "fake", MimeType: "fake/type"},
@@ -585,10 +542,10 @@ func TestModeler_AddImage(t *testing.T) {
 				{ByteLength: 12, Data: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2}},
 			},
 		}, false},
-		{"err", &Modeler{Document: &gltf.Document{
+		{"err", &gltf.Document{
 			Images:  []*gltf.Image{{}},
 			Buffers: []*gltf.Buffer{{ByteLength: 10}},
-		}}, args{0, "fake", "fake/type", &errReader{}}, 0, &gltf.Document{
+		}, args{"fake", "fake/type", &errReader{}}, 0, &gltf.Document{
 			Images: []*gltf.Image{
 				{},
 			},
@@ -599,17 +556,17 @@ func TestModeler_AddImage(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.m.AddImage(tt.args.bufferIndex, tt.args.name, tt.args.mimeType, tt.args.r)
+			got, err := WriteImage(tt.m, tt.args.name, tt.args.mimeType, tt.args.r)
 			if (err != nil) != tt.wantErr {
-				t.Errorf("Modeler.AddImage() error = %v, wantErr %v", err, tt.wantErr)
+				t.Errorf("WriteImage() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
 			if tt.want != got {
-				t.Errorf("Modeler.AddImage() = %v, want %v", got, tt.want)
+				t.Errorf("WriteImage() = %v, want %v", got, tt.want)
 				return
 			}
-			if diff := deep.Equal(tt.m.Document, tt.wantDoc); diff != nil {
-				t.Errorf("Modeler.AddImage() = %v", diff)
+			if diff := deep.Equal(tt.m, tt.wantDoc); diff != nil {
+				t.Errorf("WriteImage() = %v", diff)
 				return
 			}
 		})
diff --git a/struct_test.go b/struct_test.go
index 95299df..6f935d7 100644
--- a/struct_test.go
+++ b/struct_test.go
@@ -8,6 +8,29 @@ import (
 	"github.com/go-test/deep"
 )
 
+func TestDocument(t *testing.T) {
+	tests := []struct {
+		name string
+		want *Document
+	}{
+		{"base", &Document{
+			Scene:  Index(0),
+			Scenes: []*Scene{{Name: "Root Scene"}},
+			Asset: Asset{
+				Generator: "qmuntal/gltf",
+				Version:   "2.0",
+			},
+		}},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := NewDocument(); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("NewDocument() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
 func TestBuffer_IsEmbeddedResource(t *testing.T) {
 	tests := []struct {
 		name string