Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify interleaved primitive attribute writing #84

Merged
merged 6 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ doc.Meshes = []*gltf.Mesh{{
Name: "Pyramid",
Primitives: []*gltf.Primitive{{
Indices: gltf.Index(modeler.WriteIndices(doc, []uint16{0, 1, 2})),
Attributes: map[string]uint32{
Attributes: gltf.PrimitiveAttributes{
gltf.POSITION: modeler.WritePosition(doc, [][3]float32{{0, 0, 0}, {0, 10, 0}, {0, 0, 10}}),
gltf.COLOR_0: modeler.WriteColor(doc, [][3]uint8{{255, 0, 0}, {0, 255, 0}, {0, 0, 255}}),
},
Expand All @@ -130,14 +130,14 @@ gltf.Save(doc, "./test.gltf")

### Data interleaving

The data of the attributes that are stored in a single bufferView may be stored as an Array-Of-Structures, which may produce a rendering perfomance boost in static attributes. `qmuntal/gltf/modeler` facilitates the creation of interleaved accessors and buffer views with the methods [WriteAttributesInterleaved](https://pkg.go.dev/github.com/qmuntal/gltf/modeler#WriteAttributesInterleaved), [WriteAccessorsInterleaved](https://pkg.go.dev/github.com/qmuntal/gltf/modeler#WriteAccessorsInterleaved), and [WriteBufferViewInterleaved](https://pkg.go.dev/github.com/qmuntal/gltf/modeler#WriteBufferViewInterleaved) being the first one the most recommended for creating mesh primitives:
The data of the attributes that are stored in a single bufferView may be stored as an Array-Of-Structures, which may produce a rendering perfomance boost in static attributes. `qmuntal/gltf/modeler` facilitates the creation of interleaved accessors and buffer views with the methods [WritePrimitiveAttributes](https://pkg.go.dev/github.com/qmuntal/gltf/modeler#WritePrimitiveAttributes), [WriteAccessorsInterleaved](https://pkg.go.dev/github.com/qmuntal/gltf/modeler#WriteAccessorsInterleaved), and [WriteBufferViewInterleaved](https://pkg.go.dev/github.com/qmuntal/gltf/modeler#WriteBufferViewInterleaved) being the first one the most recommended for creating mesh primitives:

```go
doc := gltf.NewDocument()
attrs, _ := modeler.WriteAttributesInterleaved(doc, modeler.Attributes{
Position: [][3]float32{{0, 0, 0}, {0, 10, 0}, {0, 0, 10}},
Color: [][3]uint8{{255, 0, 0}, {0, 255, 0}, {0, 0, 255}},
})
attrs, _ := modeler.WritePrimitiveAttributes(doc,
modeler.PrimitiveAttribute{Name: gltf.POSITION, Data: [][3]float32{{0, 0, 0}, {0, 10, 0}, {0, 0, 10}}},
modeler.PrimitiveAttribute{Name: gltf.COLOR_0, Data: [][3]uint8{{255, 0, 0}, {0, 255, 0}, {0, 0, 255}}},
)
doc.Meshes = []*gltf.Mesh{{
Name: "Pyramid",
Primitives: []*gltf.Primitive{{
Expand Down Expand Up @@ -199,7 +199,7 @@ const ExtensionName = "FAKE_Extension"

type Foo struct {
BufferView uint32 `json:"bufferView"`
Attributes gltf.Attribute `json:"attributes"`
Attributes gltf.Attributes `json:"attributes"`
}

func init() {
Expand Down
4 changes: 2 additions & 2 deletions const.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@ const (
TargetElementArrayBuffer Target = 34963 // ELEMENT_ARRAY_BUFFER
)

// Attribute is a map that each key corresponds to mesh attribute semantic and each value is the index of the accessor containing attribute's data.
type Attribute = map[string]uint32
// PrimitiveAttributes is a map that each key corresponds to mesh attribute semantic and each value is the index of the accessor containing attribute's data.
type PrimitiveAttributes = map[string]uint32

// PrimitiveMode defines the type of primitives to render. All valid values correspond to WebGL enums.
type PrimitiveMode uint8
Expand Down
8 changes: 4 additions & 4 deletions decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestOpen(t *testing.T) {
Buffers: []*Buffer{{ByteLength: 1800, URI: "Cube.bin", Data: readFile("testdata/Cube/glTF/Cube.bin")}},
Images: []*Image{{URI: "Cube_BaseColor.png"}, {URI: "Cube_MetallicRoughness.png"}},
Materials: []*Material{{Name: "Cube", AlphaMode: AlphaOpaque, AlphaCutoff: Float(0.5), PBRMetallicRoughness: &PBRMetallicRoughness{BaseColorFactor: &[4]float64{1, 1, 1, 1}, MetallicFactor: Float(1), RoughnessFactor: Float(1), BaseColorTexture: &TextureInfo{Index: 0}, MetallicRoughnessTexture: &TextureInfo{Index: 1}}}},
Meshes: []*Mesh{{Name: "Cube", Primitives: []*Primitive{{Indices: Index(0), Material: Index(0), Mode: PrimitiveTriangles, Attributes: map[string]uint32{NORMAL: 2, POSITION: 1, TANGENT: 3, TEXCOORD_0: 4}}}}},
Meshes: []*Mesh{{Name: "Cube", Primitives: []*Primitive{{Indices: Index(0), Material: Index(0), Mode: PrimitiveTriangles, Attributes: PrimitiveAttributes{NORMAL: 2, POSITION: 1, TANGENT: 3, TEXCOORD_0: 4}}}}},
Nodes: []*Node{{Mesh: Index(0), Name: "Cube", Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}}},
Samplers: []*Sampler{{WrapS: WrapRepeat, WrapT: WrapRepeat}},
Scene: Index(0),
Expand All @@ -70,7 +70,7 @@ func TestOpen(t *testing.T) {
{Perspective: &Perspective{AspectRatio: Float(1.0), Yfov: 0.7, Zfar: Float(100), Znear: 0.01}},
{Orthographic: &Orthographic{Xmag: 1.0, Ymag: 1.0, Zfar: 100, Znear: 0.01}},
},
Meshes: []*Mesh{{Primitives: []*Primitive{{Indices: Index(0), Mode: PrimitiveTriangles, Attributes: map[string]uint32{POSITION: 1}}}}},
Meshes: []*Mesh{{Primitives: []*Primitive{{Indices: Index(0), Mode: PrimitiveTriangles, Attributes: PrimitiveAttributes{POSITION: 1}}}}},
Nodes: []*Node{
{Mesh: Index(0), Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{-0.3, 0, 0, 0.9}, Scale: [3]float64{1, 1, 1}},
{Camera: Index(0), Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}, Translation: [3]float64{0.5, 0.5, 3.0}},
Expand All @@ -97,7 +97,7 @@ func TestOpen(t *testing.T) {
},
Buffers: []*Buffer{{ByteLength: 1224, Data: readFile("testdata/BoxVertexColors/glTF-Binary/BoxVertexColors.glb")[1628+20+8:]}},
Materials: []*Material{{Name: "Default", AlphaMode: AlphaOpaque, AlphaCutoff: Float(0.5), PBRMetallicRoughness: &PBRMetallicRoughness{BaseColorFactor: &[4]float64{0.8, 0.8, 0.8, 1}, MetallicFactor: Float(0.1), RoughnessFactor: Float(0.99)}}},
Meshes: []*Mesh{{Name: "Cube", Primitives: []*Primitive{{Indices: Index(0), Material: Index(0), Mode: PrimitiveTriangles, Attributes: map[string]uint32{POSITION: 1, COLOR_0: 3, NORMAL: 2, TEXCOORD_0: 4}}}}},
Meshes: []*Mesh{{Name: "Cube", Primitives: []*Primitive{{Indices: Index(0), Material: Index(0), Mode: PrimitiveTriangles, Attributes: PrimitiveAttributes{POSITION: 1, COLOR_0: 3, NORMAL: 2, TEXCOORD_0: 4}}}}},
Nodes: []*Node{
{Name: "RootNode", Children: []uint32{1, 2, 3}, Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
{Name: "Mesh", Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}},
Expand Down Expand Up @@ -132,7 +132,7 @@ func TestOpen(t *testing.T) {
Name: "Material", AlphaMode: AlphaOpaque, AlphaCutoff: Float(0.5), NormalTexture: &NormalTexture{Index: Index(0), Scale: Float(1)}, PBRMetallicRoughness: &PBRMetallicRoughness{
BaseColorFactor: &[4]float64{1, 1, 1, 1}, MetallicFactor: Float(1), RoughnessFactor: Float(1), BaseColorTexture: &TextureInfo{Index: 1}, MetallicRoughnessTexture: &TextureInfo{Index: 2},
}}},
Meshes: []*Mesh{{Name: "Cube", Primitives: []*Primitive{{Indices: Index(3), Material: Index(0), Mode: PrimitiveTriangles, Attributes: map[string]uint32{NORMAL: 1, POSITION: 0, TEXCOORD_0: 2}}}}},
Meshes: []*Mesh{{Name: "Cube", Primitives: []*Primitive{{Indices: Index(3), Material: Index(0), Mode: PrimitiveTriangles, Attributes: PrimitiveAttributes{NORMAL: 1, POSITION: 0, TEXCOORD_0: 2}}}}},
Nodes: []*Node{{Mesh: Index(0), Name: "Cube", Matrix: [16]float64{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float64{0, 0, 0, 1}, Scale: [3]float64{1, 1, 1}}},
Scene: Index(0),
Scenes: []*Scene{{Name: "Scene", Nodes: []uint32{0}}},
Expand Down
4 changes: 2 additions & 2 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,8 @@ func TestEncoder_Encode(t *testing.T) {
{"withMeshes", args{&Document{Meshes: []*Mesh{
{Extras: 8.0, Name: "mesh_1", Weights: []float64{1.2, 2}},
{Extras: 8.0, Name: "mesh_2", Primitives: []*Primitive{
{Extras: 8.0, Attributes: Attribute{POSITION: 1}, Indices: Index(2), Material: Index(1), Mode: PrimitiveLines},
{Extras: 8.0, Targets: []Attribute{{POSITION: 1, "THEN": 4}, {"OTHER": 2}}, Indices: Index(2), Material: Index(1), Mode: PrimitiveLines},
{Extras: 8.0, Attributes: PrimitiveAttributes{POSITION: 1}, Indices: Index(2), Material: Index(1), Mode: PrimitiveLines},
{Extras: 8.0, Targets: []PrimitiveAttributes{{POSITION: 1, "THEN": 4}, {"OTHER": 2}}, Indices: Index(2), Material: Index(1), Mode: PrimitiveLines},
}},
}}}, false},
{"withNodes", args{&Document{Nodes: []*Node{
Expand Down
2 changes: 1 addition & 1 deletion example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func ExampleSave() {
Name: "Default", AlphaMode: gltf.AlphaOpaque, AlphaCutoff: gltf.Float(0.5),
PBRMetallicRoughness: &gltf.PBRMetallicRoughness{BaseColorFactor: &[4]float64{0.8, 0.8, 0.8, 1}, MetallicFactor: gltf.Float(0.1), RoughnessFactor: gltf.Float(0.99)},
}},
Meshes: []*gltf.Mesh{{Name: "Cube", Primitives: []*gltf.Primitive{{Indices: gltf.Index(0), Material: gltf.Index(0), Mode: gltf.PrimitiveTriangles, Attributes: map[string]uint32{gltf.POSITION: 1, gltf.COLOR_0: 3, gltf.NORMAL: 2, gltf.TEXCOORD_0: 4}}}}},
Meshes: []*gltf.Mesh{{Name: "Cube", Primitives: []*gltf.Primitive{{Indices: gltf.Index(0), Material: gltf.Index(0), Mode: gltf.PrimitiveTriangles, Attributes: gltf.PrimitiveAttributes{gltf.POSITION: 1, gltf.COLOR_0: 3, gltf.NORMAL: 2, gltf.TEXCOORD_0: 4}}}}},
Nodes: []*gltf.Node{
{Name: "RootNode", Children: []uint32{1, 2, 3}},
{Name: "Mesh"},
Expand Down
14 changes: 7 additions & 7 deletions gltf.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,13 +260,13 @@ type Mesh struct {

// Primitive defines the geometry to be rendered with the given material.
type Primitive struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Attributes Attribute `json:"attributes"`
Indices *uint32 `json:"indices,omitempty"` // The index of the accessor that contains the indices.
Material *uint32 `json:"material,omitempty"`
Mode PrimitiveMode `json:"mode,omitempty" validate:"lte=6"`
Targets []Attribute `json:"targets,omitempty" validate:"omitempty,dive,dive,keys,oneof=POSITION NORMAL TANGENT,endkeys"` // Only POSITION, NORMAL, and TANGENT supported.
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Attributes PrimitiveAttributes `json:"attributes"`
Indices *uint32 `json:"indices,omitempty"` // The index of the accessor that contains the indices.
Material *uint32 `json:"material,omitempty"`
Mode PrimitiveMode `json:"mode,omitempty" validate:"lte=6"`
Targets []PrimitiveAttributes `json:"targets,omitempty" validate:"omitempty,dive,dive,keys,oneof=POSITION NORMAL TANGENT,endkeys"` // Only POSITION, NORMAL, and TANGENT supported.
}

// The Material appearance of a primitive.
Expand Down
32 changes: 15 additions & 17 deletions modeler/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func Example() {
Primitives: []*gltf.Primitive{
{
Indices: gltf.Index(indicesAccessor),
Attributes: map[string]uint32{
Attributes: gltf.PrimitiveAttributes{
gltf.POSITION: positionAccessor,
gltf.COLOR_0: colorIndices,
},
Expand Down Expand Up @@ -60,7 +60,7 @@ func ExampleWriteImage() {
Primitives: []*gltf.Primitive{
{
Indices: gltf.Index(indicesAccessor),
Attributes: map[string]uint32{
Attributes: gltf.PrimitiveAttributes{
gltf.POSITION: positionAccessor,
gltf.TEXCOORD_0: textureAccessor,
},
Expand All @@ -87,7 +87,7 @@ func ExampleWriteAccessorsInterleaved() {
Primitives: []*gltf.Primitive{
{
Indices: gltf.Index(indicesAccessor),
Attributes: map[string]uint32{
Attributes: gltf.PrimitiveAttributes{
gltf.POSITION: indices[0],
gltf.COLOR_0: indices[1],
},
Expand All @@ -103,20 +103,18 @@ func ExampleWriteAccessorsInterleaved() {

func ExampleWriteAttributesInterleaved() {
doc := gltf.NewDocument()
attrs, _ := modeler.WriteAttributesInterleaved(doc, modeler.Attributes{
Position: [][3]float32{{1, 2, 3}, {0, 0, -1}},
Normal: [][3]float32{{1, 2, 3}, {0, 0, -1}},
Tangent: [][4]float32{{1, 2, 3, 4}, {1, 2, 3, 4}},
TextureCoord_0: [][2]uint8{{0, 255}, {255, 0}},
TextureCoord_1: [][2]float32{{1, 2}, {1, 2}},
Joints: [][4]uint8{{1, 2, 3, 4}, {1, 2, 3, 4}},
Weights: [][4]uint8{{1, 2, 3, 4}, {1, 2, 3, 4}},
Color: [][3]uint8{{255, 255, 255}, {0, 255, 0}},
CustomAttributes: []modeler.CustomAttribute{
{Name: "COLOR_1", Data: [][3]uint8{{0, 0, 255}, {100, 200, 0}}},
{Name: "COLOR_2", Data: [][4]uint8{{23, 58, 188, 1}, {0, 155, 0, 0}}},
},
})
attrs, _ := modeler.WritePrimitiveAttributes(doc,
modeler.PrimitiveAttribute{Name: gltf.POSITION, Data: [][3]float32{{1, 2, 3}, {0, 0, -1}}},
modeler.PrimitiveAttribute{Name: gltf.NORMAL, Data: [][3]float32{{1, 2, 3}, {0, 0, -1}}},
modeler.PrimitiveAttribute{Name: gltf.TANGENT, Data: [][4]float32{{1, 2, 3, 4}, {1, 2, 3, 4}}},
modeler.PrimitiveAttribute{Name: gltf.TEXCOORD_0, Data: [][2]uint8{{0, 255}, {255, 0}}},
modeler.PrimitiveAttribute{Name: gltf.TEXCOORD_1, Data: [][2]float32{{1, 2}, {1, 2}}},
modeler.PrimitiveAttribute{Name: gltf.JOINTS_0, Data: [][4]uint8{{1, 2, 3, 4}, {1, 2, 3, 4}}},
modeler.PrimitiveAttribute{Name: gltf.WEIGHTS_0, Data: [][4]uint8{{1, 2, 3, 4}, {1, 2, 3, 4}}},
modeler.PrimitiveAttribute{Name: gltf.COLOR_0, Data: [][3]uint8{{255, 255, 255}, {0, 255, 0}}},
modeler.PrimitiveAttribute{Name: "COLOR_1", Data: [][3]uint8{{0, 0, 255}, {100, 200, 0}}},
modeler.PrimitiveAttribute{Name: "COLOR_2", Data: [][4]uint8{{23, 58, 188, 1}, {0, 155, 0, 0}}},
)
indicesAccessor := modeler.WriteIndices(doc, []uint16{0, 1, 2, 3, 1, 0, 0, 2, 3, 1, 4, 2, 4, 3, 2, 4, 1, 3})
doc.Meshes = []*gltf.Mesh{{
Name: "Pyramid",
Expand Down
Loading
Loading