diff --git a/README.md b/README.md index 3005fb7..1bd4e55 100644 --- a/README.md +++ b/README.md @@ -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}}), }, @@ -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{{ @@ -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() { diff --git a/const.go b/const.go index 98fd5da..6f125fe 100644 --- a/const.go +++ b/const.go @@ -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 diff --git a/decoder_test.go b/decoder_test.go index 37da88a..a36e1e5 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -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), @@ -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}}, @@ -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}}, @@ -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}}}, diff --git a/encode_test.go b/encode_test.go index 986ed23..e48ca4b 100644 --- a/encode_test.go +++ b/encode_test.go @@ -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{ diff --git a/example_test.go b/example_test.go index 7f196b5..4999cd9 100644 --- a/example_test.go +++ b/example_test.go @@ -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"}, diff --git a/gltf.go b/gltf.go index 532a3ae..edb1419 100644 --- a/gltf.go +++ b/gltf.go @@ -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. diff --git a/modeler/example_test.go b/modeler/example_test.go index 07aef74..67e4f29 100644 --- a/modeler/example_test.go +++ b/modeler/example_test.go @@ -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, }, @@ -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, }, @@ -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], }, @@ -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", diff --git a/modeler/write.go b/modeler/write.go index bf8ef8b..21b5928 100644 --- a/modeler/write.go +++ b/modeler/write.go @@ -45,60 +45,70 @@ func WriteTangent(doc *gltf.Document, data [][4]float32) uint32 { // and fills the last buffer with data. // If success it returns the index of the new accessor. func WriteTextureCoord(doc *gltf.Document, data any) uint32 { - normalized := checkTextureCoord(data) + normalized, err := checkTextureCoord(data) + if err != nil { + panic(err) + } index := WriteAccessor(doc, gltf.TargetArrayBuffer, data) doc.Accessors[index].Normalized = normalized return index } -func checkTextureCoord(data any) bool { +func checkTextureCoord(data any) (bool, error) { var normalized bool switch data.(type) { case [][2]uint8, [][2]uint16: normalized = true case [][2]float32: default: - panic(fmt.Sprintf("modeler.WriteTextureCoord: invalid type %T", data)) + return false, fmt.Errorf("invalid type %T", data) } - return normalized + return normalized, nil } // WriteWeights adds a new WEIGHTS accessor to doc // and fills the last buffer with data. // If success it returns the index of the new accessor. func WriteWeights(doc *gltf.Document, data any) uint32 { - normalized := checkWeights(data) + normalized, err := checkWeights(data) + if err != nil { + panic(err) + } index := WriteAccessor(doc, gltf.TargetArrayBuffer, data) doc.Accessors[index].Normalized = normalized return index } -func checkWeights(data any) bool { +func checkWeights(data any) (bool, error) { var normalized bool switch data.(type) { case [][4]uint8, [][4]uint16: normalized = true case [][4]float32: default: - panic(fmt.Sprintf("modeler.WriteWeights: invalid type %T", data)) + return false, fmt.Errorf("invalid type %T", data) } - return normalized + return normalized, nil } // WriteJoints adds a new JOINTS accessor to doc // and fills the last buffer with data. // If success it returns the index of the new accessor. func WriteJoints(doc *gltf.Document, data any) uint32 { - checkJoints(data) + err := checkJoints(data) + if err != nil { + panic(err) + } return WriteAccessor(doc, gltf.TargetArrayBuffer, data) } -func checkJoints(data any) { +func checkJoints(data any) error { switch data.(type) { case [][4]uint8, [][4]uint16: default: - panic(fmt.Sprintf("modeler.WriteJoints: invalid type %T", data)) + return fmt.Errorf("invalid type %T", data) } + return nil } // WritePosition adds a new POSITION accessor to doc @@ -128,22 +138,25 @@ func minMaxFloat32(data [][3]float32) ([3]float64, [3]float64) { // and fills the buffer with data. // If success it returns the index of the new accessor. func WriteColor(doc *gltf.Document, data any) uint32 { - normalized := checkColor(data) + normalized, err := checkColor(data) + if err != nil { + panic(err) + } index := WriteAccessor(doc, gltf.TargetArrayBuffer, data) doc.Accessors[index].Normalized = normalized return index } -func checkColor(data any) bool { +func checkColor(data any) (bool, error) { var normalized bool switch data.(type) { case []color.RGBA, []color.RGBA64, [][4]uint8, [][3]uint8, [][4]uint16, [][3]uint16: normalized = true case [][3]float32, [][4]float32: default: - panic(fmt.Sprintf("modeler.WriteColor: invalid type %T", data)) + return false, fmt.Errorf("invalid type %T", data) } - return normalized + return normalized, nil } // WriteImage adds a new image to doc @@ -216,97 +229,61 @@ func WriteAccessorsInterleaved(doc *gltf.Document, data ...any) ([]uint32, error return indices, nil } -// CustomAttribute defines an application-specific attribute -type CustomAttribute struct { +// PrimitiveAttribute holds the data referenced by a gltf.PrimitiveAttributes entry. +type PrimitiveAttribute struct { Name string Data any } -// Attributes defines all the vertex attributes that can -// be associated to a primitive. -type Attributes struct { - Position [][3]float32 - Normal [][3]float32 - Tangent [][4]float32 - // [][2]uint8, [][2]uint16 or [][2]float32 - TextureCoord_0, TextureCoord_1 any - // [][4]uint8, [][4]uint16 or [][4]float32 - Weights any - // [][4]uint8 or [][4]uint16 - Joints any - //[]color.RGBA, []color.RGBA64, [][4]uint8, [][3]uint8, [][4]uint16, [][3]uint16, [][3]float32 or [][4]float32 - Color any - CustomAttributes []CustomAttribute -} - -// WriteAttributesInterleaved write all the attributes in v -// which are not nil and have a non-zero length. -// Returns an attribute map that can be directly used -// as a primitive attributes. -func WriteAttributesInterleaved(doc *gltf.Document, v Attributes) (map[string]uint32, error) { +// WritePrimitiveAttributes write all the primitives attributes to doc as interleaved data. +// Returns an attribute map that can be directly used as a primitive attributes. +func WritePrimitiveAttributes(doc *gltf.Document, attr ...PrimitiveAttribute) (gltf.PrimitiveAttributes, error) { type attrProps struct { Name string Normalized bool } - var ( - props []attrProps - data []any - ) - if len(v.Position) != 0 { - props = append(props, attrProps{Name: gltf.POSITION}) - data = append(data, v.Position) - } - if len(v.Normal) != 0 { - props = append(props, attrProps{Name: gltf.NORMAL}) - data = append(data, v.Normal) - } - if len(v.Tangent) != 0 { - props = append(props, attrProps{Name: gltf.TANGENT}) - data = append(data, v.Tangent) - } - if sliceLength(v.TextureCoord_0) != 0 { - normalized := checkTextureCoord(v.TextureCoord_0) - props = append(props, attrProps{Name: gltf.TEXCOORD_0, Normalized: normalized}) - data = append(data, v.TextureCoord_0) - } - if sliceLength(v.TextureCoord_1) != 0 { - normalized := checkTextureCoord(v.TextureCoord_1) - props = append(props, attrProps{Name: gltf.TEXCOORD_1, Normalized: normalized}) - data = append(data, v.TextureCoord_1) - } - if sliceLength(v.Weights) != 0 { - normalized := checkWeights(v.Weights) - props = append(props, attrProps{Name: gltf.WEIGHTS_0, Normalized: normalized}) - data = append(data, v.Weights) - } - if sliceLength(v.Joints) != 0 { - checkJoints(v.Joints) - props = append(props, attrProps{Name: gltf.JOINTS_0}) - data = append(data, v.Joints) - } - if sliceLength(v.Color) != 0 { - normalized := checkColor(v.Color) - props = append(props, attrProps{Name: gltf.COLOR_0, Normalized: normalized}) - data = append(data, v.Color) - } - for _, c := range v.CustomAttributes { - if sliceLength(c.Data) != 0 { - props = append(props, attrProps{Name: c.Name}) - data = append(data, c.Data) + data := make([]any, 0, len(attr)) + props := make([]attrProps, 0, len(attr)) + var min, max [3]float64 + var err error + for _, a := range attr { + if sliceLength(a.Data) == 0 { + continue + } + var normalized bool + switch a.Name { + case gltf.POSITION: + if v, ok := a.Data.([][3]float32); ok { + min, max = minMaxFloat32(v) + } else { + err = fmt.Errorf("invalid type %T", data) + } + case gltf.TEXCOORD_0, gltf.TEXCOORD_1: + normalized, err = checkTextureCoord(a.Data) + case gltf.WEIGHTS_0: + normalized, err = checkWeights(a.Data) + case gltf.JOINTS_0: + err = checkJoints(a.Data) + case gltf.COLOR_0: + normalized, err = checkColor(a.Data) + } + if err != nil { + return nil, fmt.Errorf("%s: %w", a.Name, err) } + data = append(data, a.Data) + props = append(props, attrProps{Name: a.Name, Normalized: normalized}) } indices, err := WriteAccessorsInterleaved(doc, data...) if err != nil { return nil, err } - attrs := make(map[string]uint32, len(props)) + attrs := make(gltf.PrimitiveAttributes, len(props)) for i, index := range indices { prop := props[i] attrs[prop.Name] = index doc.Accessors[index].Normalized = prop.Normalized } if pos, ok := attrs[gltf.POSITION]; ok { - min, max := minMaxFloat32(v.Position) doc.Accessors[pos].Min = min[:] doc.Accessors[pos].Max = max[:] } diff --git a/modeler/write_test.go b/modeler/write_test.go index 7822597..527f71f 100644 --- a/modeler/write_test.go +++ b/modeler/write_test.go @@ -31,20 +31,18 @@ func TestAlignment(t *testing.T) { func TestWriteAttributesInterleaved(t *testing.T) { data := [][3]float32{{1, 2, 3}, {0, 0, -1}} doc := gltf.NewDocument() - attrs, err := WriteAttributesInterleaved(doc, Attributes{ - Position: data, - Normal: data, - Tangent: [][4]float32{{1, 2, 3, 4}, {1, 2, 3, 4}}, - TextureCoord_0: [][2]float32{{1, 2}, {1, 2}}, - 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: data, - CustomAttributes: []CustomAttribute{ - {Name: "COLOR_1", Data: data}, - {Name: "COLOR_2", Data: data}, - }, - }) + attrs, err := WritePrimitiveAttributes(doc, + PrimitiveAttribute{Name: gltf.POSITION, Data: data}, + PrimitiveAttribute{Name: gltf.NORMAL, Data: data}, + PrimitiveAttribute{Name: gltf.TANGENT, Data: [][4]float32{{1, 2, 3, 4}, {1, 2, 3, 4}}}, + PrimitiveAttribute{Name: gltf.TEXCOORD_0, Data: [][2]float32{{1, 2}, {1, 2}}}, + PrimitiveAttribute{Name: gltf.TEXCOORD_1, Data: [][2]float32{{1, 2}, {1, 2}}}, + PrimitiveAttribute{Name: gltf.WEIGHTS_0, Data: [][4]uint8{{1, 2, 3, 4}, {1, 2, 3, 4}}}, + PrimitiveAttribute{Name: gltf.JOINTS_0, Data: [][4]uint8{{1, 2, 3, 4}, {1, 2, 3, 4}}}, + PrimitiveAttribute{Name: gltf.COLOR_0, Data: data}, + PrimitiveAttribute{Name: "COLOR_1", Data: data}, + PrimitiveAttribute{Name: "COLOR_2", Data: data}, + ) if err != nil { t.Fatalf("TestWriteAttributesInterleaved() got error = %v", err) } @@ -91,11 +89,10 @@ func TestWriteAttributesInterleaved(t *testing.T) { func TestWriteAttributesInterleaved_OnlyPosition(t *testing.T) { doc := gltf.NewDocument() - _, err := WriteAttributesInterleaved(doc, Attributes{ - Position: [][3]float32{{1, 2, 3}, {0, 0, -1}}, - Tangent: make([][4]float32, 0), - CustomAttributes: []CustomAttribute{{Name: "COLOR_1"}}, - }) + _, err := WritePrimitiveAttributes(doc, + PrimitiveAttribute{Name: gltf.POSITION, Data: [][3]float32{{1, 2, 3}, {0, 0, -1}}}, + PrimitiveAttribute{Name: gltf.TANGENT, Data: make([][4]float32, 0)}, + PrimitiveAttribute{Name: "COLOR_1"}) if err != nil { t.Fatalf("TestWriteAttributesInterleaved_OnlyPosition() got error = %v", err) } @@ -106,10 +103,10 @@ func TestWriteAttributesInterleaved_OnlyPosition(t *testing.T) { func TestWriteAttributesInterleaved_Error(t *testing.T) { doc := gltf.NewDocument() - _, err := WriteAttributesInterleaved(doc, Attributes{ - Position: [][3]float32{{1, 2, 3}, {0, 0, -1}}, - Color: [][3]float32{{1, 2, 3}}, - }) + _, err := WritePrimitiveAttributes(doc, + PrimitiveAttribute{Name: gltf.POSITION, Data: [][3]float32{{1, 2, 3}, {0, 0, -1}}}, + PrimitiveAttribute{Name: gltf.COLOR_0, Data: [][3]float32{{1, 2, 3}}}, + ) if err == nil { t.Error("TestWriteAttributesInterleaved_Error() expected an error") }