Browse Source

Added Image.AlternateWriteFileName to replace the default texture names written by the library

Vicente Penades 3 years ago
parent
commit
180c803085

+ 30 - 1
src/SharpGLTF.Core/Schema2/gltf.Images.cs

@@ -2,6 +2,8 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
+using System.Threading;
+
 using SharpGLTF.Validation;
 using SharpGLTF.Validation;
 using BYTES = System.ArraySegment<byte>;
 using BYTES = System.ArraySegment<byte>;
 
 
@@ -52,6 +54,20 @@ namespace SharpGLTF.Schema2
             set => SetSatelliteContent(value);
             set => SetSatelliteContent(value);
         }
         }
 
 
+        /// <summary>
+        /// When set to a FileName or a relative File Path, it will be used to write the texture.
+        /// </summary>
+        /// <remarks>
+        /// <para>
+        /// When null, the default file name will be used.
+        /// </para>
+        /// <para>
+        /// if not sure about the image extension, using "name.*" as extension will replace
+        /// the extension with the appropiate one before writing.
+        /// </para>
+        /// </remarks>
+        public String AlternateWriteFileName { get; set; }
+
         internal int _SourceBufferViewIndex => _bufferView.AsValue(-1);
         internal int _SourceBufferViewIndex => _bufferView.AsValue(-1);
 
 
         internal bool _HasContent
         internal bool _HasContent
@@ -199,7 +215,20 @@ namespace SharpGLTF.Schema2
             var imimg = _SatelliteContent.Value;
             var imimg = _SatelliteContent.Value;
             Memory.MemoryImage._Verify(imimg, nameof(imimg));
             Memory.MemoryImage._Verify(imimg, nameof(imimg));
 
 
-            satelliteUri = System.IO.Path.ChangeExtension(satelliteUri, imimg.FileExtension);
+            if (string.IsNullOrWhiteSpace(AlternateWriteFileName))
+            {
+                satelliteUri = System.IO.Path.ChangeExtension(satelliteUri, imimg.FileExtension);
+            }
+            else
+            {
+                satelliteUri = AlternateWriteFileName;
+                if (satelliteUri.EndsWith(".*"))
+                {
+                    satelliteUri = System.IO.Path.ChangeExtension(satelliteUri, imimg.FileExtension);
+                }
+
+                if (System.IO.Path.IsPathRooted(satelliteUri)) throw new InvalidOperationException(nameof(AlternateWriteFileName) + " must be a relative path");
+            }            
 
 
             satelliteUri = writer.WriteImage(satelliteUri, imimg);
             satelliteUri = writer.WriteImage(satelliteUri, imimg);
 
 

+ 3 - 2
src/SharpGLTF.Core/Schema2/gltf.Root.cs

@@ -63,13 +63,13 @@ namespace SharpGLTF.Schema2
 
 
             // write the model to the temporary storage
             // write the model to the temporary storage
 
 
-            wcontext.WriteTextSchema2("deepclone", this);
+            wcontext.WriteTextSchema2("$$$deepclone$$$", this);
 
 
             // restore the model from the temporary storage
             // restore the model from the temporary storage
 
 
             var rcontext = ReadContext.CreateFromDictionary(dict, wcontext._UpdateSupportedExtensions);
             var rcontext = ReadContext.CreateFromDictionary(dict, wcontext._UpdateSupportedExtensions);
             rcontext.Validation = Validation.ValidationMode.Skip;
             rcontext.Validation = Validation.ValidationMode.Skip;
-            var cloned = rcontext.ReadSchema2("deepclone.gltf");
+            var cloned = rcontext.ReadSchema2("$$$deepclone$$$.gltf");
 
 
             // Restore MemoryImage source URIs (they're not cloned as part of the serialization)
             // Restore MemoryImage source URIs (they're not cloned as part of the serialization)
             foreach (var srcImg in this.LogicalImages)
             foreach (var srcImg in this.LogicalImages)
@@ -77,6 +77,7 @@ namespace SharpGLTF.Schema2
                 var dstImg = cloned.LogicalImages[srcImg.LogicalIndex];
                 var dstImg = cloned.LogicalImages[srcImg.LogicalIndex];
                 var img = dstImg.Content;
                 var img = dstImg.Content;
                 dstImg.Content = new Memory.MemoryImage(img._GetBuffer(), srcImg.Content.SourcePath);
                 dstImg.Content = new Memory.MemoryImage(img._GetBuffer(), srcImg.Content.SourcePath);
+                dstImg.AlternateWriteFileName = srcImg.AlternateWriteFileName;
             }
             }
 
 
             return cloned;
             return cloned;

+ 23 - 3
src/SharpGLTF.Toolkit/Materials/ImageBuilder.cs

@@ -65,8 +65,26 @@ namespace SharpGLTF.Materials
 
 
         #endregion
         #endregion
 
 
-        #region data
-        public IMAGEFILE Content { get; set; }
+        #region data
+
+        /// <summary>
+        /// Gets or sets the in-memory representation of the image file.
+        /// </summary>
+        public IMAGEFILE Content { get; set; }        
+
+        /// <summary>
+        /// When set to a FileName or a relative File Path, it will be used to write the texture.
+        /// </summary>
+        /// <remarks>
+        /// <para>
+        /// When null, the default file name will be used.
+        /// </para>
+        /// <para>
+        /// if not sure about the image extension, using ".*" as extension will replace
+        /// the extension with the appropiate one before writing.
+        /// </para>
+        /// </remarks>
+        public String AlternateWriteFileName { get; set; }
 
 
         public static bool AreEqualByContent(ImageBuilder x, ImageBuilder y)
         public static bool AreEqualByContent(ImageBuilder x, ImageBuilder y)
         {
         {
@@ -92,7 +110,9 @@ namespace SharpGLTF.Materials
 
 
         #endregion
         #endregion
 
 
-        #region API
+        #region API
+
+        public static bool IsEmpty(ImageBuilder ib) { return ib == null || ib.Content.IsEmpty; }
 
 
         public static bool IsValid(ImageBuilder ib) { return ib != null && ib.Content.IsValid; }
         public static bool IsValid(ImageBuilder ib) { return ib != null && ib.Content.IsValid; }
 
 

+ 22 - 24
src/SharpGLTF.Toolkit/Materials/MaterialBuilder.cs

@@ -6,8 +6,6 @@ using System.Numerics;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using System.Text;
 using System.Text;
 
 
-using IMAGEFILE = SharpGLTF.Memory.MemoryImage;
-
 namespace SharpGLTF.Materials
 namespace SharpGLTF.Materials
 {
 {
     /// <summary>
     /// <summary>
@@ -291,7 +289,7 @@ namespace SharpGLTF.Materials
         #region API * With
         #region API * With
 
 
         /// <summary>
         /// <summary>
-        /// Sets <see cref="MaterialBuilder.ShaderStyle"/>.
+        /// Sets <see cref="ShaderStyle"/>.
         /// </summary>
         /// </summary>
         /// <param name="shader">
         /// <param name="shader">
         /// A valid shader style, which can be one of these values:
         /// A valid shader style, which can be one of these values:
@@ -357,9 +355,9 @@ namespace SharpGLTF.Materials
             return this;
             return this;
         }
         }
 
 
-        public MaterialBuilder WithChannelImage(KnownChannel channelKey, IMAGEFILE primaryImage)
+        public MaterialBuilder WithChannelImage(KnownChannel channelKey, ImageBuilder primaryImage)
         {
         {
-            if (primaryImage.IsEmpty)
+            if (ImageBuilder.IsEmpty(primaryImage))
             {
             {
                 this.GetChannel(channelKey)?.RemoveTexture();
                 this.GetChannel(channelKey)?.RemoveTexture();
                 return this;
                 return this;
@@ -372,7 +370,7 @@ namespace SharpGLTF.Materials
             return this;
             return this;
         }
         }
 
 
-        public MaterialBuilder WithChannelImage(string channelKey, IMAGEFILE primaryImage)
+        public MaterialBuilder WithChannelImage(string channelKey, ImageBuilder primaryImage)
         {
         {
             this.UseChannel(channelKey)
             this.UseChannel(channelKey)
                 .UseTexture()
                 .UseTexture()
@@ -386,7 +384,7 @@ namespace SharpGLTF.Materials
         /// </summary>
         /// </summary>
         /// <param name="fallback">
         /// <param name="fallback">
         /// A <see cref="MaterialBuilder"/> instance
         /// A <see cref="MaterialBuilder"/> instance
-        /// that must have a <see cref="MaterialBuilder.ShaderStyle"/>
+        /// that must have a <see cref="ShaderStyle"/>
         /// of type <see cref="SHADERPBRMETALLICROUGHNESS"/></param>
         /// of type <see cref="SHADERPBRMETALLICROUGHNESS"/></param>
         /// <returns>This <see cref="MaterialBuilder"/>.</returns>
         /// <returns>This <see cref="MaterialBuilder"/>.</returns>
         public MaterialBuilder WithFallback(MaterialBuilder fallback)
         public MaterialBuilder WithFallback(MaterialBuilder fallback)
@@ -396,7 +394,7 @@ namespace SharpGLTF.Materials
             return this;
             return this;
         }
         }
 
 
-        public MaterialBuilder WithMetallicRoughnessFallback(IMAGEFILE baseColor, Vector4? rgba, IMAGEFILE metallicRoughness, float? metallic, float? roughness)
+        public MaterialBuilder WithMetallicRoughnessFallback(ImageBuilder baseColor, Vector4? rgba, ImageBuilder metallicRoughness, float? metallic, float? roughness)
         {
         {
             var fallback = this
             var fallback = this
                 .Clone()
                 .Clone()
@@ -411,14 +409,14 @@ namespace SharpGLTF.Materials
 
 
         #region API * With for specific channels
         #region API * With for specific channels
 
 
-        public MaterialBuilder WithNormal(IMAGEFILE imageFile, float scale = 1)
+        public MaterialBuilder WithNormal(ImageBuilder imageFile, float scale = 1)
         {
         {
             WithChannelImage(KnownChannel.Normal, imageFile);
             WithChannelImage(KnownChannel.Normal, imageFile);
             WithChannelParam(KnownChannel.Normal, KnownProperty.NormalScale, scale);
             WithChannelParam(KnownChannel.Normal, KnownProperty.NormalScale, scale);
             return this;
             return this;
         }
         }
 
 
-        public MaterialBuilder WithOcclusion(IMAGEFILE imageFile, float strength = 1)
+        public MaterialBuilder WithOcclusion(ImageBuilder imageFile, float strength = 1)
         {
         {
             WithChannelImage(KnownChannel.Occlusion, imageFile);
             WithChannelImage(KnownChannel.Occlusion, imageFile);
             WithChannelParam(KnownChannel.Occlusion, KnownProperty.OcclusionStrength, strength);
             WithChannelParam(KnownChannel.Occlusion, KnownProperty.OcclusionStrength, strength);
@@ -432,7 +430,7 @@ namespace SharpGLTF.Materials
             return this;
             return this;
         }
         }
 
 
-        public MaterialBuilder WithEmissive(IMAGEFILE imageFile, Vector3? rgb = null, float strength = 1)
+        public MaterialBuilder WithEmissive(ImageBuilder imageFile, Vector3? rgb = null, float strength = 1)
         {
         {
             WithChannelImage(KnownChannel.Emissive, imageFile);
             WithChannelImage(KnownChannel.Emissive, imageFile);
             if (rgb.HasValue) WithEmissive(rgb.Value, strength);
             if (rgb.HasValue) WithEmissive(rgb.Value, strength);
@@ -444,7 +442,7 @@ namespace SharpGLTF.Materials
             return WithChannelParam(KnownChannel.BaseColor, KnownProperty.RGBA, rgba);
             return WithChannelParam(KnownChannel.BaseColor, KnownProperty.RGBA, rgba);
         }
         }
 
 
-        public MaterialBuilder WithBaseColor(IMAGEFILE imageFile, Vector4? rgba = null)
+        public MaterialBuilder WithBaseColor(ImageBuilder imageFile, Vector4? rgba = null)
         {
         {
             WithChannelImage(KnownChannel.BaseColor, imageFile);
             WithChannelImage(KnownChannel.BaseColor, imageFile);
             if (rgba.HasValue) WithBaseColor(rgba.Value);
             if (rgba.HasValue) WithBaseColor(rgba.Value);
@@ -461,20 +459,20 @@ namespace SharpGLTF.Materials
             return this;
             return this;
         }
         }
 
 
-        public MaterialBuilder WithMetallicRoughness(IMAGEFILE imageFile, float? metallic = null, float? roughness = null)
+        public MaterialBuilder WithMetallicRoughness(ImageBuilder imageFile, float? metallic = null, float? roughness = null)
         {
         {
             WithChannelImage(KnownChannel.MetallicRoughness, imageFile);
             WithChannelImage(KnownChannel.MetallicRoughness, imageFile);
             WithMetallicRoughness(metallic, roughness);
             WithMetallicRoughness(metallic, roughness);
             return this;
             return this;
         }        
         }        
 
 
-        public MaterialBuilder WithClearCoatNormal(IMAGEFILE imageFile)
+        public MaterialBuilder WithClearCoatNormal(ImageBuilder imageFile)
         {
         {
             WithChannelImage(KnownChannel.ClearCoatNormal, imageFile);
             WithChannelImage(KnownChannel.ClearCoatNormal, imageFile);
             return this;
             return this;
         }
         }
 
 
-        public MaterialBuilder WithClearCoat(IMAGEFILE imageFile, float intensity)
+        public MaterialBuilder WithClearCoat(ImageBuilder imageFile, float intensity)
         {
         {
             WithChannelImage(KnownChannel.ClearCoat, imageFile);
             WithChannelImage(KnownChannel.ClearCoat, imageFile);
             // WithChannelParam(KnownChannel.ClearCoat, new Vector4(intensity, 0, 0, 0));
             // WithChannelParam(KnownChannel.ClearCoat, new Vector4(intensity, 0, 0, 0));
@@ -482,7 +480,7 @@ namespace SharpGLTF.Materials
             return this;
             return this;
         }
         }
 
 
-        public MaterialBuilder WithClearCoatRoughness(IMAGEFILE imageFile, float roughness)
+        public MaterialBuilder WithClearCoatRoughness(ImageBuilder imageFile, float roughness)
         {
         {
             WithChannelImage(KnownChannel.ClearCoatRoughness, imageFile);
             WithChannelImage(KnownChannel.ClearCoatRoughness, imageFile);
             // WithChannelParam(KnownChannel.ClearCoatRoughness, new Vector4(roughness, 0, 0, 0));
             // WithChannelParam(KnownChannel.ClearCoatRoughness, new Vector4(roughness, 0, 0, 0));
@@ -490,7 +488,7 @@ namespace SharpGLTF.Materials
             return this;
             return this;
         }
         }
 
 
-        public MaterialBuilder WithTransmission(IMAGEFILE imageFile, float intensity)
+        public MaterialBuilder WithTransmission(ImageBuilder imageFile, float intensity)
         {
         {
             WithChannelImage(KnownChannel.Transmission, imageFile);
             WithChannelImage(KnownChannel.Transmission, imageFile);
             // WithChannelParam(KnownChannel.Transmission, new Vector4(intensity, 0, 0, 0));
             // WithChannelParam(KnownChannel.Transmission, new Vector4(intensity, 0, 0, 0));
@@ -498,7 +496,7 @@ namespace SharpGLTF.Materials
             return this;
             return this;
         }
         }
 
 
-        public MaterialBuilder WithSpecularColor(IMAGEFILE imageFile, Vector3? rgb = null)
+        public MaterialBuilder WithSpecularColor(ImageBuilder imageFile, Vector3? rgb = null)
         {
         {
             WithChannelImage(KnownChannel.SpecularColor, imageFile);
             WithChannelImage(KnownChannel.SpecularColor, imageFile);
             // if (rgb.HasValue) WithChannelParam(KnownChannel.SpecularColor, new Vector4(rgb.Value, 0));
             // if (rgb.HasValue) WithChannelParam(KnownChannel.SpecularColor, new Vector4(rgb.Value, 0));
@@ -506,7 +504,7 @@ namespace SharpGLTF.Materials
             return this;
             return this;
         }
         }
 
 
-        public MaterialBuilder WithSpecularFactor(IMAGEFILE imageFile, float factor)
+        public MaterialBuilder WithSpecularFactor(ImageBuilder imageFile, float factor)
         {
         {
             WithChannelImage(KnownChannel.SpecularFactor, imageFile);
             WithChannelImage(KnownChannel.SpecularFactor, imageFile);
             // WithChannelParam(KnownChannel.SpecularFactor, new Vector4(factor, 0, 0, 0));
             // WithChannelParam(KnownChannel.SpecularFactor, new Vector4(factor, 0, 0, 0));
@@ -514,7 +512,7 @@ namespace SharpGLTF.Materials
             return this;
             return this;
         }
         }
 
 
-        public MaterialBuilder WithVolumeThickness(IMAGEFILE imageFile, float factor)
+        public MaterialBuilder WithVolumeThickness(ImageBuilder imageFile, float factor)
         {
         {
             WithChannelImage(KnownChannel.VolumeThickness, imageFile);
             WithChannelImage(KnownChannel.VolumeThickness, imageFile);
             // WithChannelParam(KnownChannel.VolumeThickness, new Vector4(factor, 0, 0, 0));
             // WithChannelParam(KnownChannel.VolumeThickness, new Vector4(factor, 0, 0, 0));
@@ -530,7 +528,7 @@ namespace SharpGLTF.Materials
             return this;
             return this;
         }
         }
 
 
-        public MaterialBuilder WithIridiscence(IMAGEFILE imageFile, float factor = 0f, float ior = 1.3f)
+        public MaterialBuilder WithIridiscence(ImageBuilder imageFile, float factor = 0f, float ior = 1.3f)
         {
         {
             WithChannelImage(KnownChannel.Iridescence, imageFile);            
             WithChannelImage(KnownChannel.Iridescence, imageFile);            
             WithChannelParam(KnownChannel.Iridescence, KnownProperty.IridescenceFactor, factor);
             WithChannelParam(KnownChannel.Iridescence, KnownProperty.IridescenceFactor, factor);
@@ -538,7 +536,7 @@ namespace SharpGLTF.Materials
             return this;
             return this;
         }
         }
 
 
-        public MaterialBuilder WithIridiscenceThickness(IMAGEFILE imageFile, float min = 100f, float max = 400f)
+        public MaterialBuilder WithIridiscenceThickness(ImageBuilder imageFile, float min = 100f, float max = 400f)
         {
         {
             WithChannelImage(KnownChannel.IridescenceThickness, imageFile);
             WithChannelImage(KnownChannel.IridescenceThickness, imageFile);
             WithChannelParam(KnownChannel.IridescenceThickness, KnownProperty.Minimum, min);
             WithChannelParam(KnownChannel.IridescenceThickness, KnownProperty.Minimum, min);
@@ -554,7 +552,7 @@ namespace SharpGLTF.Materials
         public MaterialBuilder WithDiffuse(Vector4 rgba) { return WithChannelParam(KnownChannel.Diffuse, KnownProperty.RGBA, rgba); }
         public MaterialBuilder WithDiffuse(Vector4 rgba) { return WithChannelParam(KnownChannel.Diffuse, KnownProperty.RGBA, rgba); }
 
 
         [Obsolete("This channel is used by KHR_materials_pbrSpecularGlossiness extension, which has been deprecated by Khronos; use WithBaseColor instead.")]
         [Obsolete("This channel is used by KHR_materials_pbrSpecularGlossiness extension, which has been deprecated by Khronos; use WithBaseColor instead.")]
-        public MaterialBuilder WithDiffuse(IMAGEFILE imageFile, Vector4? rgba = null)
+        public MaterialBuilder WithDiffuse(ImageBuilder imageFile, Vector4? rgba = null)
         {
         {
             WithChannelImage(KnownChannel.Diffuse, imageFile);
             WithChannelImage(KnownChannel.Diffuse, imageFile);
             if (rgba.HasValue) WithDiffuse(rgba.Value);
             if (rgba.HasValue) WithDiffuse(rgba.Value);
@@ -575,7 +573,7 @@ namespace SharpGLTF.Materials
         }
         }
 
 
         [Obsolete("This channel is used by KHR_materials_pbrSpecularGlossiness extension, which has been deprecated by Khronos; use WithSpecularColor instead.")]
         [Obsolete("This channel is used by KHR_materials_pbrSpecularGlossiness extension, which has been deprecated by Khronos; use WithSpecularColor instead.")]
-        public MaterialBuilder WithSpecularGlossiness(IMAGEFILE imageFile, Vector3? specular = null, float? glossiness = null)
+        public MaterialBuilder WithSpecularGlossiness(ImageBuilder imageFile, Vector3? specular = null, float? glossiness = null)
         {
         {
             WithChannelImage(KnownChannel.SpecularGlossiness, imageFile);
             WithChannelImage(KnownChannel.SpecularGlossiness, imageFile);
             WithSpecularGlossiness(specular, glossiness);
             WithSpecularGlossiness(specular, glossiness);

+ 19 - 13
src/SharpGLTF.Toolkit/Schema2/MaterialExtensions.cs

@@ -366,7 +366,9 @@ namespace SharpGLTF.Schema2
             ImageBuilder _convert(Image src)
             ImageBuilder _convert(Image src)
             {
             {
                 if (src == null) return null;
                 if (src == null) return null;
-                return ImageBuilder.From(src.Content, src.Name, src.Extras.DeepClone());
+                var dst = ImageBuilder.From(src.Content, src.Name, src.Extras.DeepClone());
+                dst.AlternateWriteFileName = src.AlternateWriteFileName;
+                return dst;
             }
             }
 
 
             dstChannel.Texture.PrimaryImage = _convert(srcChannel.Texture.PrimaryImage);
             dstChannel.Texture.PrimaryImage = _convert(srcChannel.Texture.PrimaryImage);
@@ -485,24 +487,14 @@ namespace SharpGLTF.Schema2
 
 
             if (ImageBuilder.IsValid(srcTex.PrimaryImage))
             if (ImageBuilder.IsValid(srcTex.PrimaryImage))
             {
             {
-                primary = dstChannel
-                    .LogicalParent
-                    .LogicalParent
-                    .UseImageWithContent(srcTex.PrimaryImage.Content);
-
-                srcTex.PrimaryImage.TryCopyNameAndExtrasTo(primary);
+                primary = _ConvertToImage(dstChannel, srcTex.PrimaryImage);
             }
             }
 
 
             if (primary == null) return;
             if (primary == null) return;
 
 
             if (ImageBuilder.IsValid(srcTex.FallbackImage))
             if (ImageBuilder.IsValid(srcTex.FallbackImage))
             {
             {
-                fallback = dstChannel
-                    .LogicalParent
-                    .LogicalParent
-                    .UseImageWithContent(srcTex.FallbackImage.Content);
-
-                srcTex.FallbackImage.TryCopyNameAndExtrasTo(fallback);
+                fallback = _ConvertToImage(dstChannel, srcTex.FallbackImage);
             }
             }
 
 
             var dstTex = dstChannel.SetTexture(srcTex.CoordinateSet, primary, fallback, srcTex.WrapS, srcTex.WrapT, srcTex.MinFilter, srcTex.MagFilter);
             var dstTex = dstChannel.SetTexture(srcTex.CoordinateSet, primary, fallback, srcTex.WrapS, srcTex.WrapT, srcTex.MinFilter, srcTex.MagFilter);
@@ -517,6 +509,20 @@ namespace SharpGLTF.Schema2
             }
             }
         }
         }
 
 
+        private static Image _ConvertToImage(MaterialChannel dstChannel, ImageBuilder srcImage)
+        {
+            var dstImage = dstChannel
+                    .LogicalParent
+                    .LogicalParent
+                    .UseImageWithContent(srcImage.Content);
+
+            dstImage.AlternateWriteFileName = srcImage.AlternateWriteFileName;
+
+            srcImage.TryCopyNameAndExtrasTo(dstImage);
+
+            return dstImage;
+        }
+
         #endregion
         #endregion
 
 
         #region evaluation API
         #region evaluation API

+ 4 - 0
tests/SharpGLTF.ThirdParty.Tests/SharpGLTF.ThirdParty.Tests.csproj

@@ -11,6 +11,10 @@
     <Content Include="..\Assets\**" LinkBase="Assets" CopyToOutputDirectory="PreserveNewest" />
     <Content Include="..\Assets\**" LinkBase="Assets" CopyToOutputDirectory="PreserveNewest" />
   </ItemGroup>
   </ItemGroup>
 
 
+  <ItemGroup>
+    <Compile Include="..\SharpGLTF.Toolkit.Tests\Geometry\Parametric\SolidMeshUtils.cs" Link="Geometry\Parametric\SolidMeshUtils.cs" />
+  </ItemGroup>
+
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="NUnit" Version="3.13.3" />
     <PackageReference Include="NUnit" Version="3.13.3" />
     <PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
     <PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />

+ 56 - 0
tests/SharpGLTF.ThirdParty.Tests/SteamDbTests.cs

@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+using NUnit.Framework;
+
+using SharpGLTF.Materials;
+using SharpGLTF.Memory;
+using SharpGLTF.Scenes;
+
+using SharpGLTF.Geometry.Parametric;
+using SharpGLTF.Geometry;
+
+namespace SharpGLTF.ThirdParty
+{
+    [ResourcePathFormat("*\\Assets")]
+    [AttachmentPathFormat("*\\?")]
+    internal class SteamDbTests
+    {
+        [Test]
+        public void TestWriteAlternateTexturePath()
+        {
+            // create material
+            var material = new MaterialBuilder()
+                .WithDoubleSide(true)
+                .WithMetallicRoughnessShader();
+
+            var imgBuilder = ImageBuilder.From(new MemoryImage(ResourceInfo.From("shannon.png")));
+            imgBuilder.AlternateWriteFileName = "alternateTextureName.*";
+
+            material.WithBaseColor(imgBuilder);            
+
+            var meshBuilder = new MeshBuilder<MaterialBuilder, Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexEmpty, Geometry.VertexTypes.VertexEmpty>();
+            meshBuilder.AddCube(material, System.Numerics.Matrix4x4.Identity);
+
+            var sceneBuilder = new SceneBuilder();
+            sceneBuilder.AddRigidMesh(meshBuilder, System.Numerics.Matrix4x4.Identity);
+
+            var model = sceneBuilder.ToGltf2();
+
+            Assert.AreEqual("alternateTextureName.*", model.LogicalImages[0].AlternateWriteFileName);
+
+            model = model.DeepClone();
+
+            Assert.AreEqual("alternateTextureName.*", model.LogicalImages[0].AlternateWriteFileName);
+
+            var dstPath = AttachmentInfo
+                .From("model.gltf")
+                .WriteObject(f => model.Save(f));
+
+            var altPath = System.IO.Path.Combine(dstPath.Directory.FullName, "alternateTextureName.png");
+
+            Assert.IsTrue(System.IO.File.Exists(altPath));
+        }
+    }
+}