diff --git a/formats/gltf/extensions.go b/formats/gltf/extensions.go index 6ccc7b1..b27cbaf 100644 --- a/formats/gltf/extensions.go +++ b/formats/gltf/extensions.go @@ -62,7 +62,7 @@ func (sg PolyformPbrSpecularGlossiness) ToMaterialExtensionData(w *Writer) map[s } if sg.DiffuseTexture != nil { - metadata["diffuseTexture"] = w.AddTexture(*sg.DiffuseTexture) + metadata["diffuseTexture"] = w.AddTexture(sg.DiffuseTexture) } if sg.SpecularFactor != nil { @@ -74,7 +74,7 @@ func (sg PolyformPbrSpecularGlossiness) ToMaterialExtensionData(w *Writer) map[s } if sg.SpecularGlossinessTexture != nil { - metadata["specularGlossinessTexture"] = w.AddTexture(*sg.SpecularGlossinessTexture) + metadata["specularGlossinessTexture"] = w.AddTexture(sg.SpecularGlossinessTexture) } return metadata } @@ -101,7 +101,7 @@ func (tr PolyformTransmission) ToMaterialExtensionData(w *Writer) map[string]any metadata["transmissionFactor"] = tr.Factor if tr.Texture != nil { - metadata["transmissionTexture"] = w.AddTexture(*tr.Texture) + metadata["transmissionTexture"] = w.AddTexture(tr.Texture) } return metadata @@ -142,7 +142,7 @@ func (v PolyformVolume) ToMaterialExtensionData(w *Writer) map[string]any { metadata["thicknessFactor"] = v.ThicknessFactor if v.ThicknessTexture != nil { - metadata["thicknessTexture"] = w.AddTexture(*v.ThicknessTexture) + metadata["thicknessTexture"] = w.AddTexture(v.ThicknessTexture) } if v.AttenuationDistance != nil { @@ -216,7 +216,7 @@ func (ps PolyformSpecular) ToMaterialExtensionData(w *Writer) map[string]any { } if ps.Texture != nil { - metadata["specularTexture"] = w.AddTexture(*ps.Texture) + metadata["specularTexture"] = w.AddTexture(ps.Texture) } if ps.ColorFactor != nil { @@ -224,7 +224,7 @@ func (ps PolyformSpecular) ToMaterialExtensionData(w *Writer) map[string]any { } if ps.ColorTexture != nil { - metadata["specularColorTexture"] = w.AddTexture(*ps.ColorTexture) + metadata["specularColorTexture"] = w.AddTexture(ps.ColorTexture) } return metadata @@ -260,12 +260,12 @@ func (pmc PolyformClearcoat) ToMaterialExtensionData(w *Writer) map[string]any { metadata["clearcoatFactor"] = pmc.ClearcoatFactor if pmc.ClearcoatTexture != nil { - metadata["clearcoatTexture"] = w.AddTexture(*pmc.ClearcoatTexture) + metadata["clearcoatTexture"] = w.AddTexture(pmc.ClearcoatTexture) } metadata["clearcoatRoughnessFactor"] = pmc.ClearcoatRoughnessFactor if pmc.ClearcoatRoughnessTexture != nil { - metadata["clearcoatRoughnessTexture"] = w.AddTexture(*pmc.ClearcoatRoughnessTexture) + metadata["clearcoatRoughnessTexture"] = w.AddTexture(pmc.ClearcoatRoughnessTexture) } // if pmc.ClearcoatNormalTexture != nil { @@ -344,7 +344,7 @@ func (pmi PolyformIridescence) ToMaterialExtensionData(w *Writer) map[string]any metadata["iridescenceFactor"] = pmi.IridescenceFactor if pmi.IridescenceTexture != nil { - metadata["iridescenceTexture"] = w.AddTexture(*pmi.IridescenceTexture) + metadata["iridescenceTexture"] = w.AddTexture(pmi.IridescenceTexture) } if pmi.IridescenceIor != nil { @@ -360,7 +360,7 @@ func (pmi PolyformIridescence) ToMaterialExtensionData(w *Writer) map[string]any } if pmi.IridescenceThicknessTexture != nil { - metadata["iridescenceThicknessTexture"] = w.AddTexture(*pmi.IridescenceThicknessTexture) + metadata["iridescenceThicknessTexture"] = w.AddTexture(pmi.IridescenceThicknessTexture) } return metadata @@ -397,13 +397,13 @@ func (ps PolyformSheen) ToMaterialExtensionData(w *Writer) map[string]any { } if ps.SheenColorTexture != nil { - metadata["sheenColorTexture"] = w.AddTexture(*ps.SheenColorTexture) + metadata["sheenColorTexture"] = w.AddTexture(ps.SheenColorTexture) } metadata["sheenRoughnessFactor"] = ps.SheenRoughnessFactor if ps.SheenRoughnessTexture != nil { - metadata["sheenRoughnessTexture"] = w.AddTexture(*ps.SheenRoughnessTexture) + metadata["sheenRoughnessTexture"] = w.AddTexture(ps.SheenRoughnessTexture) } return metadata @@ -441,7 +441,7 @@ func (pa PolyformAnisotropy) ToMaterialExtensionData(w *Writer) map[string]any { metadata["anisotropyRotation"] = pa.AnisotropyRotation if pa.AnisotropyTexture != nil { - metadata["anisotropyTexture"] = w.AddTexture(*pa.AnisotropyTexture) + metadata["anisotropyTexture"] = w.AddTexture(pa.AnisotropyTexture) } return metadata diff --git a/formats/gltf/extensions_test.go b/formats/gltf/extensions_test.go index 8f9b3ce..4e81055 100644 --- a/formats/gltf/extensions_test.go +++ b/formats/gltf/extensions_test.go @@ -470,7 +470,7 @@ func TestMaterialExtension_ToExtensionData(t *testing.T) { } for k, v := range tc.want { - assert.Equal(t, v, data[k]) + assert.Equal(t, v, data[k], "key %s not matching", k) } }) } diff --git a/formats/gltf/model.go b/formats/gltf/model.go index 20dfcce..713dcda 100644 --- a/formats/gltf/model.go +++ b/formats/gltf/model.go @@ -54,7 +54,7 @@ type PolyformPbrMetallicRoughness struct { } type PolyformNormal struct { - PolyformTexture + *PolyformTexture Scale *float64 } @@ -125,7 +125,7 @@ func (pt *PolyformTexture) equal(other *PolyformTexture) bool { } func (pt *PolyformNormal) equal(other *PolyformNormal) bool { - if !pt.PolyformTexture.equal(&other.PolyformTexture) { + if !pt.PolyformTexture.equal(other.PolyformTexture) { return false } return float64PtrsEqual(pt.Scale, other.Scale) diff --git a/formats/gltf/model_trackers.go b/formats/gltf/model_trackers.go index dea3c80..ea526c9 100644 --- a/formats/gltf/model_trackers.go +++ b/formats/gltf/model_trackers.go @@ -32,3 +32,9 @@ type meshEntry struct { // materialIndices handle deduplication of GLTF materials type meshIndices map[meshEntry]int + +// samplerIndices handle deduplication of texture samplers +type samplerIndices map[*Sampler]int + +// textureIndices handle deduplication of textures +type textureIndices map[*PolyformTexture]int diff --git a/formats/gltf/write_test.go b/formats/gltf/write_test.go index df98ba0..209a06f 100644 --- a/formats/gltf/write_test.go +++ b/formats/gltf/write_test.go @@ -305,17 +305,258 @@ func TestWrite_TexturedTriWithMaterialWithColor(t *testing.T) { Mesh: &tri, Material: &gltf.PolyformMaterial{ Name: "My Material", + PbrMetallicRoughness: &gltf.PolyformPbrMetallicRoughness{ + BaseColorFactor: color.RGBA{255, 100, 80, 255}, + RoughnessFactor: &roughness, + BaseColorTexture: &gltf.PolyformTexture{URI: "this_is_a_test.png"}, + }, + }, + }, + }, + }, &buf) + + // ASSERT ================================================================= + assert.NoError(t, err) + assert.Equal(t, `{ + "accessors": [ + { + "bufferView": 0, + "componentType": 5126, + "type": "VEC3", + "count": 3, + "max": [ + 1, + 1, + 1 + ], + "min": [ + 0, + 0, + 0 + ] + }, + { + "bufferView": 1, + "componentType": 5126, + "type": "VEC3", + "count": 3, + "max": [ + 1, + 1, + 0 + ], + "min": [ + 0, + 0, + 0 + ] + }, + { + "bufferView": 2, + "componentType": 5126, + "type": "VEC2", + "count": 3, + "max": [ + 1, + 1 + ], + "min": [ + 0, + 0 + ] + }, + { + "bufferView": 3, + "componentType": 5123, + "type": "SCALAR", + "count": 3 + } + ], + "asset": { + "version": "2.0", + "generator": "https://github.com/EliCDavis/polyform" + }, + "buffers": [ + { + "byteLength": 102, + "uri": "data:application/octet-stream;base64,AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAAAAAAABAAIA" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 36, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 36, + "byteLength": 36, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 72, + "byteLength": 24, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 96, + "byteLength": 6, + "target": 34963 + } + ], + "images": [ + { + "uri": "this_is_a_test.png" + } + ], + "materials": [ + { + "name": "My Material", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 1, + 0.392, + 0.314, + 1 + ], + "baseColorTexture": { + "index": 0 + }, + "roughnessFactor": 0 + } + } + ], + "meshes": [ + { + "name": "mesh", + "primitives": [ + { + "attributes": { + "NORMAL": 0, + "POSITION": 1, + "TEXCOORD_0": 2 + }, + "indices": 3, + "material": 0 + } + ] + } + ], + "nodes": [ + { + "mesh": 0, + "name": "mesh" + } + ], + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "textures": [ + { + "source": 0 + } + ] +}`, buf.String()) +} + +func TestWrite_TexturedTriWithMaterialWithColor_ImageSampleDedupe(t *testing.T) { + // ARRANGE ================================================================ + tri1 := modeling.NewTriangleMesh([]int{0, 1, 2}). + SetFloat3Attribute( + modeling.PositionAttribute, + []vector3.Float64{ + vector3.New(0., 0., 0.), + vector3.New(0., 1., 0.), + vector3.New(1., 0., 0.), + }, + ). + SetFloat3Attribute( + modeling.NormalAttribute, + []vector3.Float64{ + vector3.New(1., 0., 0.), + vector3.New(0., 1., 0.), + vector3.New(0., 0., 1.), + }, + ). + SetFloat2Attribute( + modeling.TexCoordAttribute, + []vector2.Float64{ + vector2.New(0., 0.), + vector2.New(0., 1.), + vector2.New(1., 0.), + }, + ) + tri2 := modeling.NewTriangleMesh([]int{0, 1, 2}). + SetFloat3Attribute( + modeling.PositionAttribute, + []vector3.Float64{ + vector3.New(0., 0., 0.), + vector3.New(0., 1., 0.), + vector3.New(1., 0., 0.), + }, + ). + SetFloat3Attribute( + modeling.NormalAttribute, + []vector3.Float64{ + vector3.New(1., 0., 0.), + vector3.New(0., 1., 0.), + vector3.New(0., 0., 1.), + }, + ). + SetFloat2Attribute( + modeling.TexCoordAttribute, + []vector2.Float64{ + vector2.New(0., 0.), + vector2.New(0., 1.), + vector2.New(1., 0.), + }, + ) + + buf := bytes.Buffer{} + + // ACT ==================================================================== + roughness := 0. + sampler := &gltf.Sampler{ + WrapS: gltf.SamplerWrap_REPEAT, + WrapT: gltf.SamplerWrap_REPEAT, + MinFilter: gltf.SamplerMinFilter_LINEAR_MIPMAP_LINEAR, + MagFilter: gltf.SamplerMagFilter_LINEAR, + } + err := gltf.WriteText(gltf.PolyformScene{ + Models: []gltf.PolyformModel{ + { + Name: "mesh", + Mesh: &tri1, + Material: &gltf.PolyformMaterial{ + Name: "My Material1", PbrMetallicRoughness: &gltf.PolyformPbrMetallicRoughness{ BaseColorFactor: color.RGBA{255, 100, 80, 255}, RoughnessFactor: &roughness, BaseColorTexture: &gltf.PolyformTexture{ - URI: "this_is_a_test.png", - Sampler: &gltf.Sampler{ - WrapS: gltf.SamplerWrap_REPEAT, - WrapT: gltf.SamplerWrap_REPEAT, - MinFilter: gltf.SamplerMinFilter_LINEAR_MIPMAP_LINEAR, - MagFilter: gltf.SamplerMagFilter_LINEAR, - }, + URI: "this_is_a_test.png", + Sampler: sampler, + }, + }, + }, + }, + { + Name: "mesh", + Mesh: &tri2, + Material: &gltf.PolyformMaterial{ + Name: "My Material2", + PbrMetallicRoughness: &gltf.PolyformPbrMetallicRoughness{ + BaseColorFactor: color.RGBA{255, 100, 80, 255}, + RoughnessFactor: &roughness, + BaseColorTexture: &gltf.PolyformTexture{ + URI: "this_is_a_test.png", + Sampler: sampler, }, }, }, @@ -378,6 +619,428 @@ func TestWrite_TexturedTriWithMaterialWithColor(t *testing.T) { "componentType": 5123, "type": "SCALAR", "count": 3 + }, + { + "bufferView": 4, + "componentType": 5126, + "type": "VEC3", + "count": 3, + "max": [ + 1, + 1, + 1 + ], + "min": [ + 0, + 0, + 0 + ] + }, + { + "bufferView": 5, + "componentType": 5126, + "type": "VEC3", + "count": 3, + "max": [ + 1, + 1, + 0 + ], + "min": [ + 0, + 0, + 0 + ] + }, + { + "bufferView": 6, + "componentType": 5126, + "type": "VEC2", + "count": 3, + "max": [ + 1, + 1 + ], + "min": [ + 0, + 0 + ] + }, + { + "bufferView": 7, + "componentType": 5123, + "type": "SCALAR", + "count": 3 + } + ], + "asset": { + "version": "2.0", + "generator": "https://github.com/EliCDavis/polyform" + }, + "buffers": [ + { + "byteLength": 204, + "uri": "data:application/octet-stream;base64,AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAAAAAAABAAIAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAAAAAAABAAIA" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 36, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 36, + "byteLength": 36, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 72, + "byteLength": 24, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 96, + "byteLength": 6, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 102, + "byteLength": 36, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 138, + "byteLength": 36, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 174, + "byteLength": 24, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 198, + "byteLength": 6, + "target": 34963 + } + ], + "images": [ + { + "uri": "this_is_a_test.png" + } + ], + "materials": [ + { + "name": "My Material1", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 1, + 0.392, + 0.314, + 1 + ], + "baseColorTexture": { + "index": 0 + }, + "roughnessFactor": 0 + } + }, + { + "name": "My Material2", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 1, + 0.392, + 0.314, + 1 + ], + "baseColorTexture": { + "index": 1 + }, + "roughnessFactor": 0 + } + } + ], + "meshes": [ + { + "name": "mesh", + "primitives": [ + { + "attributes": { + "NORMAL": 0, + "POSITION": 1, + "TEXCOORD_0": 2 + }, + "indices": 3, + "material": 0 + } + ] + }, + { + "name": "mesh", + "primitives": [ + { + "attributes": { + "NORMAL": 4, + "POSITION": 5, + "TEXCOORD_0": 6 + }, + "indices": 7, + "material": 1 + } + ] + } + ], + "nodes": [ + { + "mesh": 0, + "name": "mesh" + }, + { + "mesh": 1, + "name": "mesh" + } + ], + "samplers": [ + { + "magFilter": 9729, + "minFilter": 9987, + "wrapS": 10497, + "wrapT": 10497 + } + ], + "scenes": [ + { + "nodes": [ + 0, + 1 + ] + } + ], + "textures": [ + { + "sampler": 0, + "source": 0 + }, + { + "sampler": 0, + "source": 0 + } + ] +}`, buf.String()) +} + +func TestWrite_TexturedTriWithMaterialWithColor_TextureDedupe(t *testing.T) { + // ARRANGE ================================================================ + tri1 := modeling.NewTriangleMesh([]int{0, 1, 2}). + SetFloat3Attribute( + modeling.PositionAttribute, + []vector3.Float64{ + vector3.New(0., 0., 0.), + vector3.New(0., 1., 0.), + vector3.New(1., 0., 0.), + }, + ). + SetFloat3Attribute( + modeling.NormalAttribute, + []vector3.Float64{ + vector3.New(1., 0., 0.), + vector3.New(0., 1., 0.), + vector3.New(0., 0., 1.), + }, + ). + SetFloat2Attribute( + modeling.TexCoordAttribute, + []vector2.Float64{ + vector2.New(0., 0.), + vector2.New(0., 1.), + vector2.New(1., 0.), + }, + ) + tri2 := modeling.NewTriangleMesh([]int{0, 1, 2}). + SetFloat3Attribute( + modeling.PositionAttribute, + []vector3.Float64{ + vector3.New(0., 0., 0.), + vector3.New(0., 1., 0.), + vector3.New(1., 0., 0.), + }, + ). + SetFloat3Attribute( + modeling.NormalAttribute, + []vector3.Float64{ + vector3.New(1., 0., 0.), + vector3.New(0., 1., 0.), + vector3.New(0., 0., 1.), + }, + ). + SetFloat2Attribute( + modeling.TexCoordAttribute, + []vector2.Float64{ + vector2.New(0., 0.), + vector2.New(0., 1.), + vector2.New(1., 0.), + }, + ) + + buf := bytes.Buffer{} + + // ACT ==================================================================== + roughness := 0. + texture := &gltf.PolyformTexture{ + URI: "this_is_a_test.png", + Sampler: &gltf.Sampler{ + WrapS: gltf.SamplerWrap_REPEAT, + WrapT: gltf.SamplerWrap_REPEAT, + MinFilter: gltf.SamplerMinFilter_LINEAR_MIPMAP_LINEAR, + MagFilter: gltf.SamplerMagFilter_LINEAR, + }, + } + + err := gltf.WriteText(gltf.PolyformScene{ + Models: []gltf.PolyformModel{ + { + Name: "mesh", + Mesh: &tri1, + Material: &gltf.PolyformMaterial{ + Name: "My Material1", + PbrMetallicRoughness: &gltf.PolyformPbrMetallicRoughness{ + BaseColorFactor: color.RGBA{255, 100, 80, 255}, + RoughnessFactor: &roughness, + BaseColorTexture: texture, + }, + }, + }, + { + Name: "mesh", + Mesh: &tri2, + Material: &gltf.PolyformMaterial{ + Name: "My Material2", + PbrMetallicRoughness: &gltf.PolyformPbrMetallicRoughness{ + BaseColorFactor: color.RGBA{255, 100, 80, 255}, + RoughnessFactor: &roughness, + BaseColorTexture: texture, + }, + }, + }, + }, + }, &buf) + + // ASSERT ================================================================= + assert.NoError(t, err) + assert.Equal(t, `{ + "accessors": [ + { + "bufferView": 0, + "componentType": 5126, + "type": "VEC3", + "count": 3, + "max": [ + 1, + 1, + 1 + ], + "min": [ + 0, + 0, + 0 + ] + }, + { + "bufferView": 1, + "componentType": 5126, + "type": "VEC3", + "count": 3, + "max": [ + 1, + 1, + 0 + ], + "min": [ + 0, + 0, + 0 + ] + }, + { + "bufferView": 2, + "componentType": 5126, + "type": "VEC2", + "count": 3, + "max": [ + 1, + 1 + ], + "min": [ + 0, + 0 + ] + }, + { + "bufferView": 3, + "componentType": 5123, + "type": "SCALAR", + "count": 3 + }, + { + "bufferView": 4, + "componentType": 5126, + "type": "VEC3", + "count": 3, + "max": [ + 1, + 1, + 1 + ], + "min": [ + 0, + 0, + 0 + ] + }, + { + "bufferView": 5, + "componentType": 5126, + "type": "VEC3", + "count": 3, + "max": [ + 1, + 1, + 0 + ], + "min": [ + 0, + 0, + 0 + ] + }, + { + "bufferView": 6, + "componentType": 5126, + "type": "VEC2", + "count": 3, + "max": [ + 1, + 1 + ], + "min": [ + 0, + 0 + ] + }, + { + "bufferView": 7, + "componentType": 5123, + "type": "SCALAR", + "count": 3 } ], "asset": { @@ -386,8 +1049,8 @@ func TestWrite_TexturedTriWithMaterialWithColor(t *testing.T) { }, "buffers": [ { - "byteLength": 102, - "uri": "data:application/octet-stream;base64,AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAAAAAAABAAIA" + "byteLength": 204, + "uri": "data:application/octet-stream;base64,AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAAAAAAABAAIAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAAAAAAABAAIA" } ], "bufferViews": [ @@ -413,6 +1076,30 @@ func TestWrite_TexturedTriWithMaterialWithColor(t *testing.T) { "byteOffset": 96, "byteLength": 6, "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 102, + "byteLength": 36, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 138, + "byteLength": 36, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 174, + "byteLength": 24, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 198, + "byteLength": 6, + "target": 34963 } ], "images": [ @@ -422,7 +1109,22 @@ func TestWrite_TexturedTriWithMaterialWithColor(t *testing.T) { ], "materials": [ { - "name": "My Material", + "name": "My Material1", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 1, + 0.392, + 0.314, + 1 + ], + "baseColorTexture": { + "index": 0 + }, + "roughnessFactor": 0 + } + }, + { + "name": "My Material2", "pbrMetallicRoughness": { "baseColorFactor": [ 1, @@ -451,12 +1153,30 @@ func TestWrite_TexturedTriWithMaterialWithColor(t *testing.T) { "material": 0 } ] + }, + { + "name": "mesh", + "primitives": [ + { + "attributes": { + "NORMAL": 4, + "POSITION": 5, + "TEXCOORD_0": 6 + }, + "indices": 7, + "material": 1 + } + ] } ], "nodes": [ { "mesh": 0, "name": "mesh" + }, + { + "mesh": 1, + "name": "mesh" } ], "samplers": [ @@ -470,7 +1190,8 @@ func TestWrite_TexturedTriWithMaterialWithColor(t *testing.T) { "scenes": [ { "nodes": [ - 0 + 0, + 1 ] } ], diff --git a/formats/gltf/writer.go b/formats/gltf/writer.go index 2e28886..7bb7068 100644 --- a/formats/gltf/writer.go +++ b/formats/gltf/writer.go @@ -33,8 +33,10 @@ type Writer struct { nodes []Node materials []Material - matIndices materialIndices // Tracks and deduplicates unique materials - meshIndices meshIndices // Tracks and deduplicates unique meshes&materials + matIndices materialIndices // Tracks and deduplicates unique materials + meshIndices meshIndices // Tracks and deduplicates unique meshes&materials + samplerIndices samplerIndices // Tracks and deduplicates unique samplers + textureIndices textureIndices // Tracks and deduplicates unique textures skins []Skin animations []Animation @@ -65,7 +67,9 @@ func NewWriter() *Writer { skins: make([]Skin, 0), animations: make([]Animation, 0), - meshIndices: make(meshIndices), + meshIndices: make(meshIndices), + samplerIndices: make(samplerIndices), + textureIndices: make(textureIndices), // Extensions lights: make([]KHR_LightsPunctual, 0), @@ -495,21 +499,22 @@ func (w *Writer) AddMesh(model PolyformModel) (_ int, err error) { return meshIndex, nil } -func (w *Writer) AddTexture(polyTex PolyformTexture) *TextureInfo { - newTexInfo := &TextureInfo{Index: len(w.textures)} - - newTex := Texture{ - Sampler: ptrI(len(w.samplers)), - Source: ptrI(len(w.images)), - } - - texInfoExt := make(map[string]any) - texExt := make(map[string]any) +func (w *Writer) AddTexture(polyTex *PolyformTexture) *TextureInfo { + var texInfoExt map[string]any + var texExt map[string]any for _, ext := range polyTex.Extensions { id := ext.ExtensionID() if ext.IsInfo() { + if texInfoExt == nil { + texInfoExt = make(map[string]any) + } + texInfoExt[id] = ext.ToTextureExtensionData(w) } else { + if texExt == nil { + texExt = make(map[string]any) + } + texExt[id] = ext.ToTextureExtensionData(w) } @@ -518,25 +523,61 @@ func (w *Writer) AddTexture(polyTex PolyformTexture) *TextureInfo { w.extensionsRequired[id] = true } } + newTexInfo := &TextureInfo{Extensions: texInfoExt} - if len(texInfoExt) > 0 { - newTexInfo.Extensions = texInfoExt + texIndex := -1 + var texFound bool + for texPtr, index := range w.textureIndices { + if texPtr == polyTex { + texIndex = index + texFound = true + break + } } - if len(texExt) > 0 { - newTex.Extensions = texExt + if texFound { // This texture already exists, reference it and return early + newTexInfo.Index = texIndex + return newTexInfo } - w.textures = append(w.textures, newTex) + // If we're here - new texture needs to be made, but it can still have reusable parts + newTex := Texture{Extensions: texExt} + { + imageIndex := len(w.images) + var imageFound bool + for i, im := range w.images { + if im.URI == polyTex.URI { + imageIndex = i + imageFound = true + break + } + } + if !imageFound { + w.images = append(w.images, Image{URI: polyTex.URI}) + } + newTex.Source = ptrI(imageIndex) + } - w.images = append(w.images, Image{ - URI: polyTex.URI, - }) - var sampler Sampler if polyTex.Sampler != nil { - sampler = *polyTex.Sampler + samplerIndex := len(w.samplers) + var samplerFound bool + for samPtr, samIndex := range w.samplerIndices { + if samPtr == polyTex.Sampler { + samplerIndex = samIndex + samplerFound = true + break + } + } + if !samplerFound { + w.samplerIndices[polyTex.Sampler] = samplerIndex + w.samplers = append(w.samplers, *polyTex.Sampler) + } + newTex.Sampler = ptrI(samplerIndex) } - w.samplers = append(w.samplers, sampler) + texIndex = len(w.textures) + newTexInfo.Index = texIndex + w.textureIndices[polyTex] = texIndex + w.textures = append(w.textures, newTex) return newTexInfo } @@ -563,11 +604,11 @@ func (w *Writer) AddMaterial(mat *PolyformMaterial) (*int, error) { } if polyPBR.BaseColorTexture != nil { - pbr.BaseColorTexture = w.AddTexture(*polyPBR.BaseColorTexture) + pbr.BaseColorTexture = w.AddTexture(polyPBR.BaseColorTexture) } if polyPBR.MetallicRoughnessTexture != nil { - pbr.MetallicRoughnessTexture = w.AddTexture(*polyPBR.MetallicRoughnessTexture) + pbr.MetallicRoughnessTexture = w.AddTexture(polyPBR.MetallicRoughnessTexture) } }