diff --git a/sources/tools/Stride.Importer.Assimp/Material/Flags.cs b/sources/tools/Stride.Importer.Assimp/Material/Flags.cs new file mode 100644 index 0000000000..13dd304ca4 --- /dev/null +++ b/sources/tools/Stride.Importer.Assimp/Material/Flags.cs @@ -0,0 +1,20 @@ + +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; + +namespace Stride.Importer.Assimp.Material +{ + /// + /// Enumeration of the new Assimp's flags. + /// + + [Flags] + public enum Flags + { + None = 0, + Invert = 1, + ReplaceAlpha = 2 + } +} diff --git a/sources/tools/Stride.Importer.Assimp/Material/MappingMode.cs b/sources/tools/Stride.Importer.Assimp/Material/MappingMode.cs new file mode 100644 index 0000000000..c1a95b8b24 --- /dev/null +++ b/sources/tools/Stride.Importer.Assimp/Material/MappingMode.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +namespace Stride.Importer.Assimp.Material +{ + /// + /// Enumeration of the different mapping modes in the new Assimp's material stack. + /// + public enum MappingMode + { + Wrap, + Clamp, + Decal, + Mirror + } +} diff --git a/sources/tools/Stride.Importer.Assimp/Material/MaterialStack.cs b/sources/tools/Stride.Importer.Assimp/Material/MaterialStack.cs index 14d2affe51..86256080e1 100644 --- a/sources/tools/Stride.Importer.Assimp/Material/MaterialStack.cs +++ b/sources/tools/Stride.Importer.Assimp/Material/MaterialStack.cs @@ -1,243 +1,18 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Stride.Core.Mathematics; +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. +using System.Collections.Generic; namespace Stride.Importer.Assimp.Material { - /// - /// Enumeration of the different types of node in the new Assimp's material stack. - /// - public enum StackType - { - Color = 0, - Texture, - Operation - } - /// - /// Enumeration of the new Assimp's flags. - /// - public enum Flags - { - Invert = 1, - ReplaceAlpha = 2 - } - /// - /// Enumeration of the different operations in the new Assimp's material stack. - /// - public enum Operation - { - Add = 0, - Add3ds, - AddMaya, - Average, - Color, - ColorBurn, - ColorDodge, - Darken3ds, - DarkenMaya, - Desaturate, - Difference3ds, - DifferenceMaya, - Divide, - Exclusion, - HardLight, - HardMix, - Hue, - Illuminate, - In, - Lighten3ds, - LightenMaya, - LinearBurn, - LinearDodge, - Multiply3ds, - MultiplyMaya, - None, - Out, - Over3ds, - Overlay3ds, - OverMaya, - PinLight, - Saturate, - Saturation, - Screen, - SoftLight, - Substract3ds, - SubstractMaya, - Value, - Mask - } - /// - /// Enumeration of the different mapping modes in the new Assimp's material stack. - /// - public enum MappingMode - { - Wrap, - Clamp, - Decal, - Mirror - } - /// - /// Class representing an element in the new Assimp's material stack. - /// - public abstract class StackElement - { - /// - /// Initializes a new instance of the class. - /// - /// The alpha of the node. - /// The blending coefficient of the node. - /// The flags of the node. - /// The type of the node. - public StackElement(float Alpha, float Blend, int flags, StackType Type) - { - alpha = Alpha; - blend = Blend; - type = Type; - } - /// - /// Gets the alpha of the node. - /// - /// - /// The alpha of the node. - /// - public float alpha { get; private set; } - /// - /// Gets the blending coefficient of the node. - /// - /// - /// The blending coefficient of the node. - /// - public float blend { get; private set; } - /// - /// Gets the flags of the node. - /// - /// - /// The flags of the node. - /// - public int flags { get; private set; } - /// - /// Gets the type of the node. - /// - /// - /// The type of the node. - /// - public StackType type { get; private set; } - } - /// - /// Class representing an operation in the new Assimp's material stack. - /// - public class StackOperation : StackElement - { - /// - /// Initializes a new instance of the class. - /// - /// The operation of the node. - /// The alpha of the node. - /// The blending coefficient of the node. - /// The flags. - public StackOperation(Operation Operation, float Alpha = 1.0f, float Blend = 1.0f, int Flags = 0) - : base(Alpha, Blend, Flags, StackType.Operation) - { - operation = Operation; - } - /// - /// Gets the operation of the node. - /// - /// - /// The operation of the node. - /// - public Operation operation { get; private set; } - } - /// - /// Class representing a color in the new Assimp's material stack. - /// - public class StackColor : StackElement - { - /// - /// Initializes a new instance of the class. - /// - /// The color of the node. - /// The alpha of the node. - /// The blending coefficient of the node. - /// The flags of the node. - public StackColor(Color3 Color, float Alpha = 1.0f, float Blend = 1.0f, int Flags = 0) - : base(Alpha, Blend, Flags, StackType.Color) - { - color = Color; - } - /// - /// Gets the color of the node. - /// - /// - /// The color of the node. - /// - public Color3 color { get; private set; } - } - /// - /// Class representing a texture in the new Assimp's material stack. - /// - public class StackTexture : StackElement - { - /// - /// Initializes a new instance of the class. - /// - /// The texture path. - /// The uv channel used by the texture. - /// The U mapping mode. - /// The V mapping mode. - /// The alpha of the node. - /// The blending coefficient of the node. - /// The flags of the node. - public StackTexture(String TexturePath, int Channel, MappingMode MappingModeU, MappingMode MappingModeV, float Alpha = 1.0f, float Blend = 1.0F, int Flags = 0) - : base(Alpha, Blend, Flags, StackType.Texture) - { - texturePath = TexturePath; - channel = Channel; - mappingModeU = MappingModeU; - mappingModeV = MappingModeV; - } - /// - /// Gets the texture path. - /// - /// - /// The texture path. - /// - public String texturePath { get; private set; } - /// - /// Gets the uv channel. - /// - /// - /// The uv channel. - /// - public int channel { get; private set; } - /// - /// Gets the U mapping mode. - /// - /// - /// The U mapping mode. - /// - public MappingMode mappingModeU { get; private set; } - /// - /// Gets the Vmapping mode. - /// - /// - /// The V mapping mode. - /// - public MappingMode mappingModeV { get; private set; } - } /// /// Class representing the new Assimp's material stack in c#. /// - public class Stack + public class MaterialStack { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public Stack() + public MaterialStack() { stack = new Stack(); } diff --git a/sources/tools/Stride.Importer.Assimp/Material/Materials.cs b/sources/tools/Stride.Importer.Assimp/Material/Materials.cs index 32560bc259..87a2175c50 100644 --- a/sources/tools/Stride.Importer.Assimp/Material/Materials.cs +++ b/sources/tools/Stride.Importer.Assimp/Material/Materials.cs @@ -1,154 +1,152 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Silk.NET.Assimp; using Stride.Core.Mathematics; namespace Stride.Importer.Assimp.Material { - public static unsafe class Materials - { - public const string MatKeyTexTypeBase = "$tex.type"; - public const string MatKeyTexColorBase = "$tex.color"; - public const string MatKeyTexAlphaBase = "$tex.alpha"; + public static unsafe class Materials + { + public const string MatKeyTexTypeBase = "$tex.type"; + public const string MatKeyTexColorBase = "$tex.color"; + public const string MatKeyTexAlphaBase = "$tex.alpha"; - /// - /// Converts an Assimp's material stack operation From c++ to c#. - /// - public static readonly Operation[] ConvertAssimpStackOperationCppToCs = new Operation[] - { - Operation.Add, //aiStackOperation_Add - Operation.Add3ds, //aiStackOperation_Add3ds - Operation.AddMaya, //aiStackOperation_AddMaya - Operation.Average, //aiStackOperation_Average - Operation.Color, //aiStackOperation_Color - Operation.ColorBurn, //aiStackOperation_ColorBurn - Operation.ColorDodge, //aiStackOperation_ColorDodge - Operation.Darken3ds, //aiStackOperation_Darken3ds - Operation.DarkenMaya, //aiStackOperation_DarkenMaya - Operation.Desaturate, //aiStackOperation_Desaturate - Operation.Difference3ds, //aiStackOperation_Difference3ds - Operation.DifferenceMaya,//aiStackOperation_DifferenceMaya - Operation.Divide, //aiStackOperation_Divide - Operation.Exclusion, //aiStackOperation_Exclusion - Operation.HardLight, //aiStackOperation_HardLight - Operation.HardMix, //aiStackOperation_HardMix - Operation.Hue, //aiStackOperation_Hue - Operation.Illuminate, //aiStackOperation_Illuminate - Operation.In, //aiStackOperation_In - Operation.Lighten3ds, //aiStackOperation_Lighten3ds - Operation.LightenMaya, //aiStackOperation_LightenMaya - Operation.LinearBurn, //aiStackOperation_LinearBurn - Operation.LinearDodge, //aiStackOperation_LinearDodge - Operation.Multiply3ds, //aiStackOperation_Multiply3ds - Operation.MultiplyMaya, //aiStackOperation_MultiplyMaya - Operation.None, //aiStackOperation_None - Operation.Out, //aiStackOperation_Out - Operation.Over3ds, //aiStackOperation_Over3ds - Operation.Overlay3ds, //aiStackOperation_Overlay3ds - Operation.OverMaya, //aiStackOperation_OverMaya - Operation.PinLight, //aiStackOperation_PinLight - Operation.Saturate, //aiStackOperation_Saturate - Operation.Saturation, //aiStackOperation_Saturation - Operation.Screen, //aiStackOperation_Screen - Operation.SoftLight, //aiStackOperation_SoftLight - Operation.Substract3ds, //aiStackOperation_Substract3ds - Operation.SubstractMaya, //aiStackOperation_SubstractMaya - Operation.Value, //aiStackOperation_Value - Operation.Mask //aiStackOperation_Mask - }; + /// + /// Converts an Assimp's material stack operation From c++ to c#. + /// + public static readonly Operation[] ConvertAssimpStackOperationCppToCs = new Operation[] + { + Operation.Add, //aiStackOperation_Add + Operation.Add3ds, //aiStackOperation_Add3ds + Operation.AddMaya, //aiStackOperation_AddMaya + Operation.Average, //aiStackOperation_Average + Operation.Color, //aiStackOperation_Color + Operation.ColorBurn, //aiStackOperation_ColorBurn + Operation.ColorDodge, //aiStackOperation_ColorDodge + Operation.Darken3ds, //aiStackOperation_Darken3ds + Operation.DarkenMaya, //aiStackOperation_DarkenMaya + Operation.Desaturate, //aiStackOperation_Desaturate + Operation.Difference3ds, //aiStackOperation_Difference3ds + Operation.DifferenceMaya,//aiStackOperation_DifferenceMaya + Operation.Divide, //aiStackOperation_Divide + Operation.Exclusion, //aiStackOperation_Exclusion + Operation.HardLight, //aiStackOperation_HardLight + Operation.HardMix, //aiStackOperation_HardMix + Operation.Hue, //aiStackOperation_Hue + Operation.Illuminate, //aiStackOperation_Illuminate + Operation.In, //aiStackOperation_In + Operation.Lighten3ds, //aiStackOperation_Lighten3ds + Operation.LightenMaya, //aiStackOperation_LightenMaya + Operation.LinearBurn, //aiStackOperation_LinearBurn + Operation.LinearDodge, //aiStackOperation_LinearDodge + Operation.Multiply3ds, //aiStackOperation_Multiply3ds + Operation.MultiplyMaya, //aiStackOperation_MultiplyMaya + Operation.None, //aiStackOperation_None + Operation.Out, //aiStackOperation_Out + Operation.Over3ds, //aiStackOperation_Over3ds + Operation.Overlay3ds, //aiStackOperation_Overlay3ds + Operation.OverMaya, //aiStackOperation_OverMaya + Operation.PinLight, //aiStackOperation_PinLight + Operation.Saturate, //aiStackOperation_Saturate + Operation.Saturation, //aiStackOperation_Saturation + Operation.Screen, //aiStackOperation_Screen + Operation.SoftLight, //aiStackOperation_SoftLight + Operation.Substract3ds, //aiStackOperation_Substract3ds + Operation.SubstractMaya, //aiStackOperation_SubstractMaya + Operation.Value, //aiStackOperation_Value + Operation.Mask //aiStackOperation_Mask + }; - /// - /// Converts an Assimp's material stack node type From c++ to c#. - /// - public static readonly StackType[] ConvertAssimpStackTypeCppToCs = new StackType[] - { - StackType.Color, // aiStackType_ColorType - StackType.Texture, // aiStackType_TextureType - StackType.Operation // aiStackType_BlemdOpType - }; + /// + /// Converts an Assimp's material stack node type From c++ to c#. + /// + public static readonly StackType[] ConvertAssimpStackTypeCppToCs = new StackType[] + { + StackType.Color, // aiStackType_ColorType + StackType.Texture, // aiStackType_TextureType + StackType.Operation // aiStackType_BlemdOpType + }; - public static readonly MappingMode[] ConvertAssimpMappingModeCppToCs = new MappingMode[] - { - MappingMode.Wrap, // aiTextureMapMode_Wrap - MappingMode.Clamp, // aiTextureMapMode_Clamp - MappingMode.Mirror, // aiTextureMapMode_Mirror - MappingMode.Decal // aiTextureMapMode_Decal - }; + public static readonly MappingMode[] ConvertAssimpMappingModeCppToCs = new MappingMode[] + { + MappingMode.Wrap, // aiTextureMapMode_Wrap + MappingMode.Clamp, // aiTextureMapMode_Clamp + MappingMode.Mirror, // aiTextureMapMode_Mirror + MappingMode.Decal // aiTextureMapMode_Decal + }; - public static unsafe Stack ConvertAssimpStackCppToCs(Silk.NET.Assimp.Assimp assimp, Silk.NET.Assimp.Material* material, Silk.NET.Assimp.TextureType type) - { - var ret = new Stack(); - var count = (int)assimp.GetMaterialTextureCount(material, type); + public static unsafe MaterialStack ConvertAssimpStackCppToCs(Silk.NET.Assimp.Assimp assimp, Silk.NET.Assimp.Material* material, Silk.NET.Assimp.TextureType type) + { + var ret = new MaterialStack(); + var count = (int)assimp.GetMaterialTextureCount(material, type); - // Process the material stack - for (int iEl = count - 1; iEl >= 0; --iEl) - { - StackElement el; - // Common properties - int elType = 0, elFlags = 0; - float elAlpha = 0.0f, elBlend = 0.0f; - // Operation-specific properties - int elOp = 0; - // Color-specific properties - var elColor = new System.Numerics.Vector4(); - // Texture-specific properties - var elTexPath = new AssimpString(); - int elTexChannel = 0, elMappingModeU = 0, elMappingModeV = 0; - uint pMax = 0; + // Process the material stack + for (int iEl = count - 1; iEl >= 0; --iEl) + { + StackElement el; + // Common properties + int elType = 0, elFlags = 0; + float elAlpha = 0.0f, elBlend = 0.0f; + // Operation-specific properties + int elOp = 0; + // Color-specific properties + var elColor = new System.Numerics.Vector4(); + // Texture-specific properties + var elTexPath = new AssimpString(); + int elTexChannel = 0, elMappingModeU = 0, elMappingModeV = 0; + uint pMax = 0; - if (assimp.GetMaterialFloatArray(material, MatKeyTexAlphaBase, (uint)type, (uint)iEl, ref elAlpha, ref pMax) != Return.ReturnSuccess) - elAlpha = 1.0f; // default alpha - if (assimp.GetMaterialFloatArray(material, Silk.NET.Assimp.Assimp.MaterialTexblendBase, (uint)type, (uint)iEl, ref elBlend, ref pMax) != Return.ReturnSuccess) - elBlend = 1.0f; // default blend - if (assimp.GetMaterialIntegerArray(material, Silk.NET.Assimp.Assimp.MaterialTexflagsBase, (uint)type, (uint)iEl, ref elFlags, ref pMax) != Return.ReturnSuccess) - elFlags = 0; // default flags (no flags) - if (assimp.GetMaterialIntegerArray(material, MatKeyTexTypeBase, (uint)type, (uint)iEl, ref elType, ref pMax) != Return.ReturnSuccess) - elType = (int)StackType.Texture;//continue; // error ! + if (assimp.GetMaterialFloatArray(material, MatKeyTexAlphaBase, (uint)type, (uint)iEl, ref elAlpha, ref pMax) != Return.Success) + elAlpha = 1.0f; // default alpha + if (assimp.GetMaterialFloatArray(material, Silk.NET.Assimp.Assimp.MaterialTexblendBase, (uint)type, (uint)iEl, ref elBlend, ref pMax) != Return.Success) + elBlend = 1.0f; // default blend + if (assimp.GetMaterialIntegerArray(material, Silk.NET.Assimp.Assimp.MaterialTexflagsBase, (uint)type, (uint)iEl, ref elFlags, ref pMax) != Return.Success) + elFlags = 0; // default flags (no flags) + if (assimp.GetMaterialIntegerArray(material, MatKeyTexTypeBase, (uint)type, (uint)iEl, ref elType, ref pMax) != Return.Success) + elType = (int)StackType.Texture;//continue; // error ! - switch ((StackType)elType) - { - case StackType.Operation: - if (assimp.GetMaterialIntegerArray(material, Silk.NET.Assimp.Assimp.MaterialTexopBase, (uint)type, (uint)iEl, ref elOp, ref pMax) != Return.ReturnSuccess) - continue; // error ! + switch ((StackType)elType) + { + case StackType.Operation: + if (assimp.GetMaterialIntegerArray(material, Silk.NET.Assimp.Assimp.MaterialTexopBase, (uint)type, (uint)iEl, ref elOp, ref pMax) != Return.Success) + continue; // error ! - el = new StackOperation(ConvertAssimpStackOperationCppToCs[elOp], elAlpha, elBlend, elFlags); - break; - case StackType.Color: - if (assimp.GetMaterialColor(material, MatKeyTexColorBase, (uint)type, (uint)iEl, ref elColor) != Return.ReturnSuccess) - continue; // error ! - el = new StackColor(new Color3(elColor.X, elColor.Y, elColor.Z), elAlpha, elBlend, elFlags); - break; - case StackType.Texture: - if (assimp.GetMaterialString(material, Silk.NET.Assimp.Assimp.MaterialTextureBase, (uint)type, (uint)iEl, ref elTexPath) != Return.ReturnSuccess) - continue; // error ! - if (assimp.GetMaterialIntegerArray(material, Silk.NET.Assimp.Assimp.MaterialUvwsrcBase, (uint)type, (uint)iEl, ref elTexChannel, ref pMax) != Return.ReturnSuccess) - elTexChannel = 0; // default channel - if (assimp.GetMaterialIntegerArray(material, Silk.NET.Assimp.Assimp.MaterialMappingmodeUBase, (uint)type, (uint)iEl, ref elMappingModeU, ref pMax) != Return.ReturnSuccess) - elMappingModeU = (int)TextureMapMode.TextureMapModeWrap; // default mapping mode - if (assimp.GetMaterialIntegerArray(material, Silk.NET.Assimp.Assimp.MaterialMappingmodeVBase, (uint)type, (uint)iEl, ref elMappingModeV, ref pMax) != Return.ReturnSuccess) - elMappingModeV = (int)TextureMapMode.TextureMapModeWrap; // default mapping mode + el = new StackOperation(ConvertAssimpStackOperationCppToCs[elOp], elAlpha, elBlend, elFlags); + break; + case StackType.Color: + if (assimp.GetMaterialColor(material, MatKeyTexColorBase, (uint)type, (uint)iEl, ref elColor) != Return.Success) + continue; // error ! + el = new StackColor(new Color3(elColor.X, elColor.Y, elColor.Z), elAlpha, elBlend, elFlags); + break; + case StackType.Texture: + if (assimp.GetMaterialString(material, Silk.NET.Assimp.Assimp.MaterialTextureBase, (uint)type, (uint)iEl, ref elTexPath) != Return.Success) + continue; // error ! + if (assimp.GetMaterialIntegerArray(material, Silk.NET.Assimp.Assimp.MaterialUvwsrcBase, (uint)type, (uint)iEl, ref elTexChannel, ref pMax) != Return.Success) + elTexChannel = 0; // default channel + if (assimp.GetMaterialIntegerArray(material, Silk.NET.Assimp.Assimp.MaterialMappingmodeUBase, (uint)type, (uint)iEl, ref elMappingModeU, ref pMax) != Return.Success) + elMappingModeU = (int)TextureMapMode.Wrap; // default mapping mode + if (assimp.GetMaterialIntegerArray(material, Silk.NET.Assimp.Assimp.MaterialMappingmodeVBase, (uint)type, (uint)iEl, ref elMappingModeV, ref pMax) != Return.Success) + elMappingModeV = (int)TextureMapMode.Wrap; // default mapping mode - el = new StackTexture( - elTexPath.AsString, - elTexChannel, - ConvertAssimpMappingModeCppToCs[elMappingModeU], - ConvertAssimpMappingModeCppToCs[elMappingModeV], - elAlpha, - elBlend, - elFlags); - break; - default: - // error ! - continue; - } + el = new StackTexture( + elTexPath.AsString, + elTexChannel, + ConvertAssimpMappingModeCppToCs[elMappingModeU], + ConvertAssimpMappingModeCppToCs[elMappingModeV], + elAlpha, + elBlend, + elFlags); + break; + default: + // error ! + continue; + } - ret.Push(el); - } + ret.Push(el); + } - return ret; - } - } + return ret; + } + } } diff --git a/sources/tools/Stride.Importer.Assimp/Material/Operation.cs b/sources/tools/Stride.Importer.Assimp/Material/Operation.cs new file mode 100644 index 0000000000..707300b8e3 --- /dev/null +++ b/sources/tools/Stride.Importer.Assimp/Material/Operation.cs @@ -0,0 +1,51 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +namespace Stride.Importer.Assimp.Material +{ + /// + /// Enumeration of the different operations in the new Assimp's material stack. + /// + public enum Operation + { + Add = 0, + Add3ds, + AddMaya, + Average, + Color, + ColorBurn, + ColorDodge, + Darken3ds, + DarkenMaya, + Desaturate, + Difference3ds, + DifferenceMaya, + Divide, + Exclusion, + HardLight, + HardMix, + Hue, + Illuminate, + In, + Lighten3ds, + LightenMaya, + LinearBurn, + LinearDodge, + Multiply3ds, + MultiplyMaya, + None, + Out, + Over3ds, + Overlay3ds, + OverMaya, + PinLight, + Saturate, + Saturation, + Screen, + SoftLight, + Substract3ds, + SubstractMaya, + Value, + Mask + } +} diff --git a/sources/tools/Stride.Importer.Assimp/Material/StackColor.cs b/sources/tools/Stride.Importer.Assimp/Material/StackColor.cs new file mode 100644 index 0000000000..0acd723ffb --- /dev/null +++ b/sources/tools/Stride.Importer.Assimp/Material/StackColor.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using Stride.Core.Mathematics; + +namespace Stride.Importer.Assimp.Material +{ + /// + /// Class representing a color in the new Assimp's material stack. + /// + public class StackColor : StackElement + { + /// + /// Initializes a new instance of the class. + /// + /// The color of the node. + /// The alpha of the node. + /// The blending coefficient of the node. + /// The flags of the node. + public StackColor(Color3 color, float alpha = 1.0f, float blend = 1.0f, int flags = 0) + : base(alpha, blend, flags, StackType.Color) + { + Color = color; + } + /// + /// Gets the color of the node. + /// + /// + /// The color of the node. + /// + public Color3 Color { get; private set; } + } +} diff --git a/sources/tools/Stride.Importer.Assimp/Material/StackElement.cs b/sources/tools/Stride.Importer.Assimp/Material/StackElement.cs new file mode 100644 index 0000000000..e0be7e3fcc --- /dev/null +++ b/sources/tools/Stride.Importer.Assimp/Material/StackElement.cs @@ -0,0 +1,53 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +namespace Stride.Importer.Assimp.Material +{ + /// + /// Class representing an element in the new Assimp's material stack. + /// + public abstract class StackElement + { + /// + /// Initializes a new instance of the class. + /// + /// The alpha of the node. + /// The blending coefficient of the node. + /// The flags of the node. + /// The type of the node. + public StackElement(float alpha, float blend, int flags, StackType type) + { + Alpha = alpha; + Blend = blend; + Type = type; + } + /// + /// Gets the alpha of the node. + /// + /// + /// The alpha of the node. + /// + public float Alpha { get; private set; } + /// + /// Gets the blending coefficient of the node. + /// + /// + /// The blending coefficient of the node. + /// + public float Blend { get; private set; } + /// + /// Gets the flags of the node. + /// + /// + /// The flags of the node. + /// + public int Flags { get; private set; } + /// + /// Gets the type of the node. + /// + /// + /// The type of the node. + /// + public StackType Type { get; private set; } + } +} diff --git a/sources/tools/Stride.Importer.Assimp/Material/StackOperation.cs b/sources/tools/Stride.Importer.Assimp/Material/StackOperation.cs new file mode 100644 index 0000000000..e4586d5781 --- /dev/null +++ b/sources/tools/Stride.Importer.Assimp/Material/StackOperation.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +namespace Stride.Importer.Assimp.Material +{ + /// + /// Class representing an operation in the new Assimp's material stack. + /// + public class StackOperation : StackElement + { + /// + /// Initializes a new instance of the class. + /// + /// The operation of the node. + /// The alpha of the node. + /// The blending coefficient of the node. + /// The flags. + public StackOperation(Operation operation, float alpha = 1.0f, float blend = 1.0f, int flags = 0) + : base(alpha, blend, flags, StackType.Operation) + { + Operation = operation; + } + /// + /// Gets the operation of the node. + /// + /// + /// The operation of the node. + /// + public Operation Operation { get; private set; } + } +} diff --git a/sources/tools/Stride.Importer.Assimp/Material/StackTexture.cs b/sources/tools/Stride.Importer.Assimp/Material/StackTexture.cs new file mode 100644 index 0000000000..cae6eb9210 --- /dev/null +++ b/sources/tools/Stride.Importer.Assimp/Material/StackTexture.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +namespace Stride.Importer.Assimp.Material +{ + /// + /// Class representing a texture in the new Assimp's material stack. + /// + public class StackTexture : StackElement + { + /// + /// Initializes a new instance of the class. + /// + /// The texture path. + /// The uv channel used by the texture. + /// The U mapping mode. + /// The V mapping mode. + /// The alpha of the node. + /// The blending coefficient of the node. + /// The flags of the node. + public StackTexture(string texturePath, int channel, MappingMode mappingModeU, MappingMode mappingModeV, float alpha = 1.0f, float blend = 1.0F, int flags = 0) + : base(alpha, blend, flags, StackType.Texture) + { + TexturePath = texturePath; + Channel = channel; + MappingModeU = mappingModeU; + MappingModeV = mappingModeV; + } + /// + /// Gets the texture path. + /// + /// + /// The texture path. + /// + public string TexturePath { get; private set; } + /// + /// Gets the uv channel. + /// + /// + /// The uv channel. + /// + public int Channel { get; private set; } + /// + /// Gets the U mapping mode. + /// + /// + /// The U mapping mode. + /// + public MappingMode MappingModeU { get; private set; } + /// + /// Gets the Vmapping mode. + /// + /// + /// The V mapping mode. + /// + public MappingMode MappingModeV { get; private set; } + } +} diff --git a/sources/tools/Stride.Importer.Assimp/Material/StackType.cs b/sources/tools/Stride.Importer.Assimp/Material/StackType.cs new file mode 100644 index 0000000000..0ccae7ac5a --- /dev/null +++ b/sources/tools/Stride.Importer.Assimp/Material/StackType.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +namespace Stride.Importer.Assimp.Material +{ + /// + /// Enumeration of the different types of node in the new Assimp's material stack. + /// + public enum StackType + { + Color = 0, + Texture, + Operation + } +} diff --git a/sources/tools/Stride.Importer.Assimp/MeshConverter.cs b/sources/tools/Stride.Importer.Assimp/MeshConverter.cs index 1a609497c8..e26824f3e3 100644 --- a/sources/tools/Stride.Importer.Assimp/MeshConverter.cs +++ b/sources/tools/Stride.Importer.Assimp/MeshConverter.cs @@ -1,1375 +1,1379 @@ -using Stride.Core.Diagnostics; -using Stride.Core.Mathematics; -using Stride.Rendering; +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; using System.Collections.Generic; +using System.Globalization; +using System.IO; using Silk.NET.Assimp; -using Mesh = Stride.Rendering.Mesh; -using Stride.Core.IO; -using Stride.Rendering.Materials; +using Stride.Animations; using Stride.Assets.Materials; -using System.Globalization; +using Stride.Core.Assets.Diagnostics; +using Stride.Core.Diagnostics; +using Stride.Core.IO; +using Stride.Core.Mathematics; +using Stride.Core.Serialization; using Stride.Graphics; using Stride.Graphics.Data; -using PrimitiveType = Stride.Graphics.PrimitiveType; using Stride.Importer.Common; -using Stride.Animations; +using Stride.Rendering; +using Stride.Rendering.Materials; using Stride.Rendering.Materials.ComputeColors; -using System.IO; -using Stride.Core.Serialization; +using Mesh = Stride.Rendering.Mesh; +using PrimitiveType = Stride.Graphics.PrimitiveType; namespace Stride.Importer.Assimp { - public class MeshConverter - { - private const int NumberOfBonesPerVertex = 4; - - public Logger Logger { get; set; } - - private readonly Silk.NET.Assimp.Assimp assimp = Silk.NET.Assimp.Assimp.GetApi(); - - public bool AllowUnsignedBlendIndices { get; set; } - - // Conversion data - - private string vfsInputFilename; - private string vfsOutputFilename; - private string vfsInputPath; - - private Quaternion rootOrientation; - private Quaternion rootOrientationInverse; - private Matrix rootTransform; - private Matrix rootTransformInverse; - private Model modelData; - - private readonly List nodes = new(); - private readonly Dictionary textureNameCount = new(); - - public MeshConverter(Logger logger) - { - Logger = logger ?? GlobalLogger.GetLogger("Import Assimp"); - } - - private void ResetConvertionData() - { - textureNameCount.Clear(); - } - - public unsafe EntityInfo ExtractEntity(string inptuFilename, string outputFilename, bool extractTextureDependencies, bool deduplicateMaterials) - { - try - { - uint importFlags = 0; - var postProcessFlags = PostProcessSteps.SortByPrimitiveType; - - if (deduplicateMaterials) - { - postProcessFlags |= PostProcessSteps.RemoveRedundantMaterials; - } - - var scene = Initialize(inptuFilename, outputFilename, importFlags, (uint)postProcessFlags); - - // If scene is null, something went wrong inside Assimp - if (scene == null) - { - var error = assimp.GetErrorStringS(); - if (error.Length > 0) - { - Logger.Error($"Assimp: {error}"); - } - - return null; - } - - var materialNames = new Dictionary(); - var meshNames = new Dictionary(); - var animationNames = new Dictionary(); - var nodeNames = new Dictionary(); - - GenerateNodeNames(scene, nodeNames); - - var entityInfo = new EntityInfo - { - Materials = ExtractMaterials(scene, materialNames), - Models = ExtractModels(scene, meshNames, materialNames, nodeNames), - Nodes = ExtractNodeHierarchy(scene, nodeNames), - AnimationNodes = ExtractAnimations(scene, animationNames) - }; - - if (extractTextureDependencies) - entityInfo.TextureDependencies = ExtractTextureDependencies(scene); - - return entityInfo; - } - catch - { - return null; - } - } - - public unsafe Model Convert(string inptuFilename, string outputFilename, bool deduplicateMaterials) - { - uint importFlags = 0; - var postProcessFlags = - PostProcessSteps.CalculateTangentSpace - | PostProcessSteps.Triangulate - | PostProcessSteps.GenerateNormals - | PostProcessSteps.JoinIdenticalVertices - | PostProcessSteps.LimitBoneWeights - | PostProcessSteps.SortByPrimitiveType - | PostProcessSteps.FlipWindingOrder - | PostProcessSteps.FlipUVs; - - if (deduplicateMaterials) - { - postProcessFlags |= PostProcessSteps.RemoveRedundantMaterials; - } - - var scene = Initialize(inptuFilename, outputFilename, importFlags, (uint)postProcessFlags); - return ConvertAssimpScene(scene); - } - - public unsafe AnimationInfo ConvertAnimation(string inputFilename, string outputFilename) - { - uint importFlags = 0; - var postProcessFlags = PostProcessSteps.None; - - var scene = Initialize(inputFilename, outputFilename, importFlags, (uint)postProcessFlags); - - return ProcessAnimations(scene); - } - - public unsafe Skeleton ConvertSkeleton(string inputFilename, string outputFilename) - { - uint importFlags = 0; - var postProcessFlags = PostProcessSteps.None; - - var scene = Initialize(inputFilename, outputFilename, importFlags, (uint)postProcessFlags); - - return ProcessSkeleton(scene); - } - - private unsafe Scene* Initialize(string inputFilename, string outputFilename, uint importFlags, uint postProcessFlags) - { - ResetConvertionData(); - - vfsInputFilename = inputFilename; - vfsOutputFilename = outputFilename; - vfsInputPath = VirtualFileSystem.GetParentFolder(inputFilename); - - var scene = assimp.ImportFile(inputFilename, importFlags); - scene = assimp.ApplyPostProcessing(scene, postProcessFlags); - - return scene; - } - - private unsafe Model ConvertAssimpScene(Scene* scene) - { - modelData = new Model(); - - var meshNames = new Dictionary(); - GenerateMeshNames(scene, meshNames); - - var nodeNames = new Dictionary(); - GenerateNodeNames(scene, nodeNames); - - // register the nodes and fill hierarchy - var meshIndexToNodeIndex = new Dictionary>(); - RegisterNodes(scene->MRootNode, -1, nodeNames, meshIndexToNodeIndex); - - // meshes - for (var i = 0; i < scene->MNumMeshes; ++i) - { - if (meshIndexToNodeIndex.ContainsKey(i)) - { - var meshInfo = ProcessMesh(scene, scene->MMeshes[i], meshNames); - - foreach (var nodeIndex in meshIndexToNodeIndex[i]) - { - var nodeMeshData = new Mesh - { - Draw = meshInfo.Draw, - Name = meshInfo.Name, - MaterialIndex = meshInfo.MaterialIndex, - NodeIndex = nodeIndex - }; - - if (meshInfo.Bones != null) - { - nodeMeshData.Skinning = new MeshSkinningDefinition - { - Bones = meshInfo.Bones.ToArray() - }; - } - - if (meshInfo.HasSkinningPosition && meshInfo.TotalClusterCount > 0) - nodeMeshData.Parameters.Set(MaterialKeys.HasSkinningPosition, true); - - if (meshInfo.HasSkinningNormal && meshInfo.TotalClusterCount > 0) - nodeMeshData.Parameters.Set(MaterialKeys.HasSkinningNormal, true); - - modelData.Meshes.Add(nodeMeshData); - } - } - } - - // embedded texture - only to log the warning for now - for (uint i = 0; i < scene->MNumTextures; ++i) - { - ExtractEmbededTexture(scene->MTextures[i]); - } - - return modelData; - } - - private unsafe Skeleton ProcessSkeleton(Scene* scene) - { - var nodeNames = new Dictionary(); - GenerateNodeNames(scene, nodeNames); - - // register the nodes and fill hierarchy - var meshIndexToNodeIndex = new Dictionary>(); - RegisterNodes(scene->MRootNode, -1, nodeNames, meshIndexToNodeIndex); - - return new Skeleton - { - Nodes = nodes.ToArray() - }; - } - - private unsafe AnimationInfo ProcessAnimations(Scene* scene) - { - var animationData = new AnimationInfo(); - var visitedNodeNames = new HashSet(); - - if (scene->MNumAnimations > 1) - Logger.Warning($"There are {scene->MNumAnimations} animations in this file, using only the first one."); - - var nodeNames = new Dictionary(); - GenerateNodeNames(scene, nodeNames); - - // register the nodes and fill hierarchy - var meshIndexToNodeIndex = new Dictionary>(); - RegisterNodes(scene->MRootNode, -1, nodeNames, meshIndexToNodeIndex); - - for (uint i = 0; i < Math.Min(1, scene->MNumAnimations); ++i) - { - var aiAnim = scene->MAnimations[i]; - - // animation speed - var ticksPerSec = aiAnim->MTicksPerSecond; - - animationData.Duration = Utils.AiTimeToXkTimeSpan(aiAnim->MDuration, ticksPerSec); ; - - // Assimp animations have two different channels of animations ((1) on Nodes, (2) on Meshes). - // Nevertheless the second one do not seems to be usable in assimp 3.0 so it will be ignored here. - - // name of the animation (dropped) - var animName = aiAnim->MName.AsString; // used only be the logger - - // animation using meshes (not supported) - for (uint meshAnimId = 0; meshAnimId < aiAnim->MNumMeshChannels; ++meshAnimId) - { - var meshName = aiAnim->MMeshChannels[meshAnimId]->MName.AsString; - Logger.Warning($"Mesh animation are not currently supported. Animation '{animName}' on mesh {meshName} will be ignored"); - } - - // animation on nodes - for (uint nodeAnimId = 0; nodeAnimId < aiAnim->MNumChannels; ++nodeAnimId) - { - var nodeAnim = aiAnim->MChannels[nodeAnimId]; - var nodeName = nodeAnim->MNodeName.AsString; - - if (!visitedNodeNames.Contains(nodeName)) - { - visitedNodeNames.Add(nodeName); - ProcessNodeAnimation(animationData.AnimationClips, nodeAnim, ticksPerSec); - } - else - { - Logger.Error($"Animation '{animName}' uses two nodes with the same name ({nodeAnim->MNodeName.AsString}). The animation cannot be resolved."); - return null; - } - } - } - - return animationData; - } - - private unsafe void ProcessNodeAnimation(Dictionary animationClips, NodeAnim* nodeAnim, double ticksPerSec) - { - // Find the nodes on which the animation is performed - var nodeName = nodeAnim->MNodeName.AsString; - - var animationClip = new AnimationClip(); - - // The translation - ProcessAnimationCurveVector(animationClip, nodeAnim->MPositionKeys, nodeAnim->MNumPositionKeys, "Transform.Position", ticksPerSec, true); - // The rotation - ProcessAnimationCurveQuaternion(animationClip, nodeAnim->MRotationKeys, nodeAnim->MNumRotationKeys, "Transform.Rotation", ticksPerSec); - // The scales - ProcessAnimationCurveVector(animationClip, nodeAnim->MScalingKeys, nodeAnim->MNumScalingKeys, "Transform.Scale", ticksPerSec, false); - - if (animationClip.Curves.Count > 0) - animationClips.Add(nodeName, animationClip); - } - - private unsafe void ProcessAnimationCurveVector(AnimationClip animationClip, VectorKey* keys, uint nbKeys, string partialTargetName, double ticksPerSec, bool isTranslation) - { - var animationCurve = new AnimationCurve(); - - // Switch to cubic implicit interpolation mode for Vector3 - animationCurve.InterpolationType = AnimationCurveInterpolationType.Cubic; - - var lastKeyTime = new CompressedTimeSpan(); - - for (uint keyId = 0; keyId < nbKeys; ++keyId) - { - var aiKey = keys[keyId]; - - var key = new KeyFrameData - { - Time = lastKeyTime = Utils.AiTimeToXkTimeSpan(aiKey.MTime, ticksPerSec), - Value = aiKey.MValue.ToStrideVector3() - }; - - if (isTranslation) - { - // Change of basis: key.Value = (rootTransformInverse * Matrix::Translation(key.Value) * rootTransform).TranslationVector; - Vector3.TransformCoordinate(ref key.Value, ref rootTransform, out key.Value); - } - else - { - // Change of basis: key.Value = (rootTransformInverse * Matrix::Scaling(key.Value) * rootTransform).ScaleVector; - var scale = Vector3.One; - Vector3.TransformNormal(ref scale, ref rootTransformInverse, out scale); - scale *= key.Value; - Vector3.TransformNormal(ref scale, ref rootTransform, out key.Value); - } - - animationCurve.KeyFrames.Add(key); - if (keyId == 0 || keyId == nbKeys - 1) // discontinuity at animation first and last frame - animationCurve.KeyFrames.Add(key); // add 2 times the same frame at discontinuities to have null gradient - } - - animationClip.AddCurve(partialTargetName, animationCurve, false); - - if (nbKeys > 0 && animationClip.Duration < lastKeyTime) - { - animationClip.Duration = lastKeyTime; - } - } - - private unsafe void ProcessAnimationCurveQuaternion(AnimationClip animationClip, QuatKey* keys, uint nbKeys, string partialTargetName, double ticksPerSec) - { - var animationCurve = new AnimationCurve(); - - var lastKeyTime = new CompressedTimeSpan(); - - for (uint keyId = 0; keyId < nbKeys; ++keyId) - { - var aiKey = keys[keyId]; - var key = new KeyFrameData - { - Time = lastKeyTime = Utils.AiTimeToXkTimeSpan(aiKey.MTime, ticksPerSec), - Value = aiKey.MValue.ToStrideQuaternion() - }; - - key.Value = rootOrientationInverse * key.Value * rootOrientation; - - animationCurve.KeyFrames.Add(key); - } - - animationClip.AddCurve(partialTargetName, animationCurve, false); - - if (nbKeys > 0 && animationClip.Duration < lastKeyTime) - { - animationClip.Duration = lastKeyTime; - } - } - - private unsafe void GenerateUniqueNames(Dictionary finalNames, List baseNames, Func objectToName) - { - var itemNameTotalCount = new Dictionary(); - var itemNameCurrentCount = new Dictionary(); - var tempNames = new List(); - - for (var i = 0; i < baseNames.Count; ++i) - { - // Clean the name by removing unwanted characters - var itemName = baseNames[i]; - - var itemNameSplitPosition = itemName.IndexOf('#'); - if (itemNameSplitPosition != -1) - { - itemName = itemName.Substring(0, itemNameSplitPosition); - } - - itemNameSplitPosition = itemName.IndexOf("__"); - if (itemNameSplitPosition != -1) - { - itemName = itemName.Substring(0, itemNameSplitPosition); - } - - // remove all bad characters - itemName = itemName.Replace(':', '_'); - itemName = itemName.Replace(" ", string.Empty); - - tempNames.Add(itemName); - - // count the occurences of this name - if (!itemNameTotalCount.ContainsKey(itemName)) - itemNameTotalCount.Add(itemName, 1); - else - itemNameTotalCount[itemName]++; - } - - for (var i = 0; i < baseNames.Count; ++i) - { - var lItem = objectToName(i); - var itemName = tempNames[i]; - - if (itemNameTotalCount[itemName] > 1) - { - if (!itemNameCurrentCount.ContainsKey(itemName)) - itemNameCurrentCount.Add(itemName, 1); - else - itemNameCurrentCount[itemName]++; - - itemName = itemName + "_" + itemNameCurrentCount[itemName].ToString(CultureInfo.InvariantCulture); - } - - finalNames.Add(lItem, itemName); - } - } - - private unsafe void GenerateMeshNames(Scene* scene, Dictionary meshNames) - { - var baseNames = new List(); - for (uint i = 0; i < scene->MNumMeshes; i++) - { - var lMesh = scene->MMeshes[i]; - baseNames.Add(lMesh->MName.AsString); - } - - GenerateUniqueNames(meshNames, baseNames, i => (IntPtr)scene->MMeshes[i]); - } - - private unsafe void GenerateAnimationNames(Scene* scene, Dictionary animationNames) - { - var baseNames = new List(); - for (uint i = 0; i < scene->MNumAnimations; i++) - { - var lAnimation = scene->MAnimations[i]; - var animationName = lAnimation->MName.AsString; - baseNames.Add(animationName); - } - - GenerateUniqueNames(animationNames, baseNames, i => (IntPtr)scene->MAnimations[i]); - } - - private unsafe void GenerateNodeNames(Scene* scene, Dictionary nodeNames) - { - var baseNames = new List(); - var orderedNodes = new List(); - - GetNodeNames(scene->MRootNode, baseNames, orderedNodes); - GenerateUniqueNames(nodeNames, baseNames, i => orderedNodes[i]); - } - - private unsafe void GetNodeNames(Node* node, List nodeNames, List orderedNodes) - { - nodeNames.Add(node->MName.AsString); - orderedNodes.Add((IntPtr)node); - - for (uint i = 0; i < node->MNumChildren; ++i) - { - GetNodeNames(node->MChildren[i], nodeNames, orderedNodes); - } - } - - private unsafe void RegisterNodes(Node* fromNode, int parentIndex, Dictionary nodeNames, Dictionary> meshIndexToNodeIndex) - { - var nodeIndex = nodes.Count; - - // assign the index of the node to the index of the mesh - for (uint m = 0; m < fromNode->MNumMeshes; ++m) - { - var meshIndex = fromNode->MMeshes[m]; - - if (!meshIndexToNodeIndex.TryGetValue((int)meshIndex, out var nodeIndices)) - { - nodeIndices = new List(); - meshIndexToNodeIndex.Add((int)meshIndex, nodeIndices); - } - - nodeIndices.Add(nodeIndex); - } - - // Create node - var modelNodeDefinition = new ModelNodeDefinition - { - ParentIndex = parentIndex, - Name = nodeNames[(IntPtr)fromNode], - Flags = ModelNodeFlags.Default - }; - - // Extract scene scaling and rotation from the root node. - // Bake scaling into all node's positions and rotation into the 1st-level nodes. - if (parentIndex == -1) - { - rootTransform = fromNode->MTransformation.ToStrideMatrix(); - - rootTransform.Decompose(out var rootScaling, out rootOrientation, out var rootTranslation); - - rootTransformInverse = Matrix.Invert(rootTransform); - rootOrientationInverse = Quaternion.Invert(rootOrientation); - - modelNodeDefinition.Transform.Rotation = Quaternion.Identity; - modelNodeDefinition.Transform.Scale = Vector3.One; - } - else - { - var transform = rootTransformInverse * fromNode->MTransformation.ToStrideMatrix() * rootTransform; - transform.Decompose(out modelNodeDefinition.Transform.Scale, out modelNodeDefinition.Transform.Rotation, out modelNodeDefinition.Transform.Position); - } - - nodes.Add(modelNodeDefinition); - - // register the children - for (uint child = 0; child < fromNode->MNumChildren; ++child) - { - RegisterNodes(fromNode->MChildren[child], nodeIndex, nodeNames, meshIndexToNodeIndex); - } - } - - private unsafe MeshInfo ProcessMesh(Scene* scene, Silk.NET.Assimp.Mesh* mesh, Dictionary meshNames) - { - List bones = null; - var hasSkinningPosition = false; - var hasSkinningNormal = false; - var totalClusterCount = 0; - - // Build the bone's indices/weights and attach bones to NodeData - //(bones info are present in the mesh so that is why we have to perform that here) - - var vertexIndexToBoneIdWeight = new List>(); - if (mesh->MNumBones > 0) - { - bones = new List(); - - // TODO: change this to support shared meshes across nodes - - // size of the array is already known - vertexIndexToBoneIdWeight.Capacity = (int)mesh->MNumVertices; - for (var i = 0; i < (int)mesh->MNumVertices; i++) - { - vertexIndexToBoneIdWeight.Add(new List<(short, float)>()); - } - - // Build skinning clusters and fill controls points data stutcture - for (uint boneId = 0; boneId < mesh->MNumBones; ++boneId) - { - var bone = mesh->MBones[boneId]; - - // Fill controlPts with bone controls on the mesh - for (uint vtxWeightId = 0; vtxWeightId < bone->MNumWeights; ++vtxWeightId) - { - var vtxWeight = bone->MWeights[vtxWeightId]; - vertexIndexToBoneIdWeight[(int)vtxWeight.MVertexId].Add(((short)boneId, vtxWeight.MWeight)); - } - - // find the node where the bone is mapped - based on the name(?) - var nodeIndex = -1; - var boneName = bone->MName.AsString; - for (var nodeDefId = 0; nodeDefId < nodes.Count; ++nodeDefId) - { - var nodeDef = nodes[nodeDefId]; - if (nodeDef.Name == boneName) - { - nodeIndex = nodeDefId; - break; - } - } - - if (nodeIndex == -1) - { - Logger.Error($"No node found for none {boneId}:{boneName}"); - nodeIndex = 0; - } - - bones.Add(new MeshBoneDefinition - { - NodeIndex = nodeIndex, - LinkToMeshMatrix = rootTransformInverse * bone->MOffsetMatrix.ToStrideMatrix() * rootTransform - }); - } - - NormalizeVertexWeights(vertexIndexToBoneIdWeight, NumberOfBonesPerVertex); - - totalClusterCount = (int)mesh->MNumBones; - if (totalClusterCount > 0) - hasSkinningPosition = true; - } - - // Build the vertex declaration - var vertexElements = new List(); - var vertexStride = 0; - - var positionOffset = vertexStride; - vertexElements.Add(VertexElement.Position(0, vertexStride)); - vertexStride += sizeof(Vector3); - - var normalOffset = vertexStride; - if (mesh->MNormals != null) - { - vertexElements.Add(VertexElement.Normal(0, vertexStride)); - vertexStride += sizeof(Vector3); - } - - var uvOffset = vertexStride; - var sizeUV = sizeof(Vector2); // 3D uv not supported - for (uint uvChannel = 0; uvChannel < Utils.GetNumUVChannels(mesh); ++uvChannel) - { - vertexElements.Add(VertexElement.TextureCoordinate((int)uvChannel, vertexStride)); - vertexStride += sizeUV; - } - - var colorOffset = vertexStride; - var sizeColor = sizeof(Color); - for (uint colorChannel = 0; colorChannel < Utils.GetNumColorChannels(mesh); ++colorChannel) - { - vertexElements.Add(VertexElement.Color((int)colorChannel, vertexStride)); - vertexStride += sizeColor; - } - - var tangentOffset = vertexStride; - if (mesh->MTangents != null) - { - vertexElements.Add(VertexElement.Tangent(0, vertexStride)); - vertexStride += sizeof(Vector3); - } - - var bitangentOffset = vertexStride; - if (mesh->MTangents != null) - { - vertexElements.Add(VertexElement.BiTangent(0, vertexStride)); - vertexStride += sizeof(Vector3); - } - - var blendIndicesOffset = vertexStride; - var controlPointIndices16 = (AllowUnsignedBlendIndices && totalClusterCount > 256) || (!AllowUnsignedBlendIndices && totalClusterCount > 128); - if (vertexIndexToBoneIdWeight.Count > 0) - { - if (controlPointIndices16) - { - if (AllowUnsignedBlendIndices) - { - vertexElements.Add(new VertexElement("BLENDINDICES", 0, PixelFormat.R16G16B16A16_UInt, vertexStride)); - vertexStride += sizeof(ushort) * 4; - } - else - { - vertexElements.Add(new VertexElement("BLENDINDICES", 0, PixelFormat.R16G16B16A16_SInt, vertexStride)); - vertexStride += sizeof(short) * 4; - } - } - else - { - if (AllowUnsignedBlendIndices) - { - vertexElements.Add(new VertexElement("BLENDINDICES", 0, PixelFormat.R8G8B8A8_UInt, vertexStride)); - vertexStride += sizeof(byte) * 4; - } - else - { - vertexElements.Add(new VertexElement("BLENDINDICES", 0, PixelFormat.R8G8B8A8_SInt, vertexStride)); - vertexStride += sizeof(sbyte) * 4; - } - } - } - - var blendWeightOffset = vertexStride; - if (vertexIndexToBoneIdWeight.Count > 0) - { - vertexElements.Add(new VertexElement("BLENDWEIGHT", 0, PixelFormat.R32G32B32A32_Float, vertexStride)); - vertexStride += sizeof(float) * 4; - } - - // Build the vertices data buffer - var vertexBuffer = new byte[vertexStride * mesh->MNumVertices]; - fixed (byte* vertexBufferPtr = &vertexBuffer[0]) - { - var vbPointer = vertexBufferPtr; - for (uint i = 0; i < mesh->MNumVertices; i++) - { - var positionPointer = (Vector3*)(vbPointer + positionOffset); - *positionPointer = mesh->MVertices[i].ToStrideVector3(); - - Vector3.TransformCoordinate(ref *positionPointer, ref rootTransform, out *positionPointer); - - if (mesh->MNormals != null) - { - var normalPointer = (Vector3*)(vbPointer + normalOffset); - *normalPointer = mesh->MNormals[i].ToStrideVector3(); - - Vector3.TransformNormal(ref *normalPointer, ref rootTransform, out *normalPointer); - - if (float.IsNaN(normalPointer->X) || float.IsNaN(normalPointer->Y) || float.IsNaN(normalPointer->Z)) - *normalPointer = new Vector3(1, 0, 0); - else - normalPointer->Normalize(); - } - - for (uint uvChannel = 0; uvChannel < Utils.GetNumUVChannels(mesh); ++uvChannel) - { - var textureCoord = mesh->MTextureCoords[(int)uvChannel][i]; - *((Vector2*)(vbPointer + uvOffset + sizeUV * uvChannel)) = new Vector2(textureCoord.X, textureCoord.Y); // 3D uv not supported - } - - for (uint colorChannel = 0; colorChannel < Utils.GetNumColorChannels(mesh); ++colorChannel) - { - var color = mesh->MColors[(int)colorChannel][i].ToStrideColor(); - *((Color*)(vbPointer + colorOffset + sizeColor * colorChannel)) = color; - } - - if (mesh->MTangents != null) - { - var tangentPointer = (Vector3*)(vbPointer + tangentOffset); - var bitangentPointer = (Vector3*)(vbPointer + bitangentOffset); - *tangentPointer = mesh->MTangents[i].ToStrideVector3(); - *bitangentPointer = mesh->MBitangents[i].ToStrideVector3(); - if (float.IsNaN(tangentPointer->X) || float.IsNaN(tangentPointer->Y) || float.IsNaN(tangentPointer->Z) || - float.IsNaN(bitangentPointer->X) || float.IsNaN(bitangentPointer->Y) || float.IsNaN(bitangentPointer->Z)) - { - //assert(mesh->HasNormals()); - var normalPointer = ((Vector3*)(vbPointer + normalOffset)); - Vector3 c1 = Vector3.Cross(*normalPointer, new Vector3(0.0f, 0.0f, 1.0f)); - Vector3 c2 = Vector3.Cross(*normalPointer, new Vector3(0.0f, 1.0f, 0.0f)); - - if (c1.LengthSquared() > c2.LengthSquared()) - *tangentPointer = c1; - else - *tangentPointer = c2; - *bitangentPointer = Vector3.Cross(*normalPointer, *tangentPointer); - } - tangentPointer->Normalize(); - bitangentPointer->Normalize(); - } - - if (vertexIndexToBoneIdWeight.Count > 0) - { - for (var bone = 0; bone < NumberOfBonesPerVertex; ++bone) - { - if (controlPointIndices16) - { - if (AllowUnsignedBlendIndices) - ((ushort*)(vbPointer + blendIndicesOffset))[bone] = (ushort)vertexIndexToBoneIdWeight[(int)i][bone].Item1; - else - ((short*)(vbPointer + blendIndicesOffset))[bone] = vertexIndexToBoneIdWeight[(int)i][bone].Item1; - } - else - { - if (AllowUnsignedBlendIndices) - (vbPointer + blendIndicesOffset)[bone] = (byte)vertexIndexToBoneIdWeight[(int)i][bone].Item1; - else - ((sbyte*)(vbPointer + blendIndicesOffset))[bone] = (sbyte)vertexIndexToBoneIdWeight[(int)i][bone].Item1; - } - - ((float*)(vbPointer + blendWeightOffset))[bone] = vertexIndexToBoneIdWeight[(int)i][bone].Item2; - } - } - - vbPointer += vertexStride; - } - } - - // Build the indices data buffer - var nbIndices = 3 * mesh->MNumFaces; - byte[] indexBuffer; - var is32BitIndex = mesh->MNumVertices > 65535; - if (is32BitIndex) - indexBuffer = new byte[sizeof(uint) * nbIndices]; - else - indexBuffer = new byte[sizeof(ushort) * nbIndices]; - - fixed (byte* indexBufferPtr = &indexBuffer[0]) - { - var ibPointer = indexBufferPtr; - - for (uint i = 0; i < mesh->MNumFaces; i++) - { - if (is32BitIndex) - { - for (int j = 0; j < 3; ++j) - { - *((uint*)ibPointer) = mesh->MFaces[(int)i].MIndices[j]; - ibPointer += sizeof(uint); - } - } - else - { - for (int j = 0; j < 3; ++j) - { - *((ushort*)ibPointer) = (ushort)(mesh->MFaces[(int)i].MIndices[j]); - ibPointer += sizeof(ushort); - } - } - } - } - - // Build the mesh data - var vertexDeclaration = new VertexDeclaration(vertexElements.ToArray()); - var vertexBufferBinding = new VertexBufferBinding(GraphicsSerializerExtensions.ToSerializableVersion(new BufferData(BufferFlags.VertexBuffer, vertexBuffer)), vertexDeclaration, (int)mesh->MNumVertices, vertexDeclaration.VertexStride, 0); - var indexBufferBinding = new IndexBufferBinding(GraphicsSerializerExtensions.ToSerializableVersion(new BufferData(BufferFlags.IndexBuffer, indexBuffer)), is32BitIndex, (int)nbIndices, 0); - - var drawData = new MeshDraw - { - VertexBuffers = new VertexBufferBinding[] { vertexBufferBinding }, - IndexBuffer = indexBufferBinding, - PrimitiveType = PrimitiveType.TriangleList, - DrawCount = (int)nbIndices - }; - - return new MeshInfo - { - Draw = drawData, - Name = meshNames[(IntPtr)mesh], - Bones = bones, - MaterialIndex = (int)mesh->MMaterialIndex, - HasSkinningPosition = hasSkinningPosition, - HasSkinningNormal = hasSkinningNormal, - TotalClusterCount = totalClusterCount - }; - } - - private void NormalizeVertexWeights(List> controlPts, int nbBoneByVertex) - { - for (var vertexId = 0; vertexId < controlPts.Count; ++vertexId) - { - var curVertexWeights = controlPts[vertexId]; - - // check that one vertex has not more than 'nbBoneByVertex' associated bones - if (curVertexWeights.Count > nbBoneByVertex) - { - Logger.Warning( - "The input file contains vertices that are associated to more than {0} bones. In current version of the system, a single vertex can only be associated to {0} bones. Extra bones will be ignored", - new ArgumentOutOfRangeException("To much bones influencing a single vertex")); - } - - // resize the weights so that they contains exactly the number of bone weights required - while (curVertexWeights.Count < nbBoneByVertex) - { - curVertexWeights.Add((0, 0)); - } - - var totalWeight = 0.0f; - for (var boneId = 0; boneId < nbBoneByVertex; ++boneId) - totalWeight += curVertexWeights[boneId].Item2; - - if (totalWeight == 0.0) // Assimp weights are positive, so in this case all weights are nulls - continue; - - for (var boneId = 0; boneId < nbBoneByVertex; ++boneId) - curVertexWeights[boneId] = (curVertexWeights[boneId].Item1, curVertexWeights[boneId].Item2 / totalWeight); - } - } + public class MeshConverter + { + private const int NumberOfBonesPerVertex = 4; + + public Logger Logger { get; set; } + + private readonly Silk.NET.Assimp.Assimp assimp = Silk.NET.Assimp.Assimp.GetApi(); + + public bool AllowUnsignedBlendIndices { get; set; } + + // Conversion data + + private string vfsInputFilename; + private string vfsOutputFilename; + private string vfsInputPath; + + private Quaternion rootOrientation; + private Quaternion rootOrientationInverse; + private Matrix rootTransform; + private Matrix rootTransformInverse; + private Model modelData; + + private readonly List nodes = new(); + private readonly Dictionary textureNameCount = new(); + + public MeshConverter(Logger logger) + { + Logger = logger ?? GlobalLogger.GetLogger("Import Assimp"); + } + + private void ResetConvertionData() + { + textureNameCount.Clear(); + } + + public unsafe EntityInfo ExtractEntity(string inputFilename, string outputFilename, bool extractTextureDependencies, bool deduplicateMaterials) + { + try + { + uint importFlags = 0; + var postProcessFlags = PostProcessSteps.SortByPrimitiveType; + + if (deduplicateMaterials) + { + postProcessFlags |= PostProcessSteps.RemoveRedundantMaterials; + } + + var scene = Initialize(inputFilename, outputFilename, importFlags, (uint)postProcessFlags); + + // If scene is null, something went wrong inside Assimp + if (scene == null) + { + var error = assimp.GetErrorStringS(); + if (error.Length > 0) + { + Logger.Error($"Assimp: {error}"); + } + + return null; + } + + var materialNames = new Dictionary(); + var meshNames = new Dictionary(); + var animationNames = new Dictionary(); + var nodeNames = new Dictionary(); + + GenerateNodeNames(scene, nodeNames); + + var entityInfo = new EntityInfo + { + Materials = ExtractMaterials(scene, materialNames), + Models = ExtractModels(scene, meshNames, materialNames, nodeNames), + Nodes = ExtractNodeHierarchy(scene, nodeNames), + AnimationNodes = ExtractAnimations(scene, animationNames) + }; + + if (extractTextureDependencies) + entityInfo.TextureDependencies = ExtractTextureDependencies(scene); + + return entityInfo; + } + catch(Exception ex) + { + Logger.Error($"Exception has occured during Entity extraction : {ex.Message}"); + throw; + } + } + + public unsafe Model Convert(string inptuFilename, string outputFilename, bool deduplicateMaterials) + { + uint importFlags = 0; + var postProcessFlags = + PostProcessSteps.CalculateTangentSpace + | PostProcessSteps.Triangulate + | PostProcessSteps.GenerateNormals + | PostProcessSteps.JoinIdenticalVertices + | PostProcessSteps.LimitBoneWeights + | PostProcessSteps.SortByPrimitiveType + | PostProcessSteps.FlipWindingOrder + | PostProcessSteps.FlipUVs; + + if (deduplicateMaterials) + { + postProcessFlags |= PostProcessSteps.RemoveRedundantMaterials; + } + + var scene = Initialize(inptuFilename, outputFilename, importFlags, (uint)postProcessFlags); + return ConvertAssimpScene(scene); + } + + public unsafe AnimationInfo ConvertAnimation(string inputFilename, string outputFilename) + { + uint importFlags = 0; + var postProcessFlags = PostProcessSteps.None; + + var scene = Initialize(inputFilename, outputFilename, importFlags, (uint)postProcessFlags); + + return ProcessAnimations(scene); + } + + public unsafe Rendering.Skeleton ConvertSkeleton(string inputFilename, string outputFilename) + { + uint importFlags = 0; + var postProcessFlags = PostProcessSteps.None; + + var scene = Initialize(inputFilename, outputFilename, importFlags, (uint)postProcessFlags); + + return ProcessSkeleton(scene); + } + + private unsafe Scene* Initialize(string inputFilename, string outputFilename, uint importFlags, uint postProcessFlags) + { + ResetConvertionData(); + + vfsInputFilename = inputFilename; + vfsOutputFilename = outputFilename; + vfsInputPath = VirtualFileSystem.GetParentFolder(inputFilename); + + var scene = assimp.ImportFile(inputFilename, importFlags); + scene = assimp.ApplyPostProcessing(scene, postProcessFlags); + + return scene; + } + + private unsafe Model ConvertAssimpScene(Scene* scene) + { + modelData = new Model(); + + var meshNames = new Dictionary(); + GenerateMeshNames(scene, meshNames); + + var nodeNames = new Dictionary(); + GenerateNodeNames(scene, nodeNames); + + // register the nodes and fill hierarchy + var meshIndexToNodeIndex = new Dictionary>(); + RegisterNodes(scene->MRootNode, -1, nodeNames, meshIndexToNodeIndex); + + // meshes + for (var i = 0; i < scene->MNumMeshes; ++i) + { + if (meshIndexToNodeIndex.ContainsKey(i)) + { + var meshInfo = ProcessMesh(scene, scene->MMeshes[i], meshNames); + + foreach (var nodeIndex in meshIndexToNodeIndex[i]) + { + var nodeMeshData = new Mesh + { + Draw = meshInfo.Draw, + Name = meshInfo.Name, + MaterialIndex = meshInfo.MaterialIndex, + NodeIndex = nodeIndex + }; + + if (meshInfo.Bones != null) + { + nodeMeshData.Skinning = new MeshSkinningDefinition + { + Bones = meshInfo.Bones.ToArray() + }; + } + + if (meshInfo.HasSkinningPosition && meshInfo.TotalClusterCount > 0) + nodeMeshData.Parameters.Set(MaterialKeys.HasSkinningPosition, true); + + if (meshInfo.HasSkinningNormal && meshInfo.TotalClusterCount > 0) + nodeMeshData.Parameters.Set(MaterialKeys.HasSkinningNormal, true); + + modelData.Meshes.Add(nodeMeshData); + } + } + } + + // embedded texture - only to log the warning for now + for (uint i = 0; i < scene->MNumTextures; ++i) + { + ExtractEmbededTexture(scene->MTextures[i]); + } + + return modelData; + } + + private unsafe Rendering.Skeleton ProcessSkeleton(Scene* scene) + { + var nodeNames = new Dictionary(); + GenerateNodeNames(scene, nodeNames); + + // register the nodes and fill hierarchy + var meshIndexToNodeIndex = new Dictionary>(); + RegisterNodes(scene->MRootNode, -1, nodeNames, meshIndexToNodeIndex); + + return new Rendering.Skeleton + { + Nodes = nodes.ToArray() + }; + } + + private unsafe AnimationInfo ProcessAnimations(Scene* scene) + { + var animationData = new AnimationInfo(); + var visitedNodeNames = new HashSet(); + + if (scene->MNumAnimations > 1) + Logger.Warning($"There are {scene->MNumAnimations} animations in this file, using only the first one."); + + var nodeNames = new Dictionary(); + GenerateNodeNames(scene, nodeNames); + + // register the nodes and fill hierarchy + var meshIndexToNodeIndex = new Dictionary>(); + RegisterNodes(scene->MRootNode, -1, nodeNames, meshIndexToNodeIndex); + + for (uint i = 0; i < Math.Min(1, scene->MNumAnimations); ++i) + { + var aiAnim = scene->MAnimations[i]; + + // animation speed + var ticksPerSec = aiAnim->MTicksPerSecond; + + animationData.Duration = Utils.AiTimeToStrideTimeSpan(aiAnim->MDuration, ticksPerSec); + + // Assimp animations have two different channels of animations ((1) on Nodes, (2) on Meshes). + // Nevertheless the second one do not seems to be usable in assimp 3.0 so it will be ignored here. + + // name of the animation (dropped) + var animName = aiAnim->MName.AsString; // used only be the logger + + // animation using meshes (not supported) + for (uint meshAnimId = 0; meshAnimId < aiAnim->MNumMeshChannels; ++meshAnimId) + { + var meshName = aiAnim->MMeshChannels[meshAnimId]->MName.AsString; + Logger.Warning($"Mesh animations are not currently supported. Animation '{animName}' on mesh {meshName} will be ignored"); + } + + // animation on nodes + for (uint nodeAnimId = 0; nodeAnimId < aiAnim->MNumChannels; ++nodeAnimId) + { + var nodeAnim = aiAnim->MChannels[nodeAnimId]; + var nodeName = nodeAnim->MNodeName.AsString; + + if (!visitedNodeNames.Contains(nodeName)) + { + visitedNodeNames.Add(nodeName); + ProcessNodeAnimation(animationData.AnimationClips, nodeAnim, ticksPerSec); + } + else + { + Logger.Error($"Animation '{animName}' uses two nodes with the same name ({nodeAnim->MNodeName.AsString}). The animation cannot be resolved."); + return null; + } + } + } + + return animationData; + } + + private unsafe void ProcessNodeAnimation(Dictionary animationClips, NodeAnim* nodeAnim, double ticksPerSec) + { + // Find the nodes on which the animation is performed + var nodeName = nodeAnim->MNodeName.AsString; + + var animationClip = new AnimationClip(); + + // The translation + ProcessAnimationCurveVector(animationClip, nodeAnim->MPositionKeys, nodeAnim->MNumPositionKeys, "Transform.Position", ticksPerSec, true); + // The rotation + ProcessAnimationCurveQuaternion(animationClip, nodeAnim->MRotationKeys, nodeAnim->MNumRotationKeys, "Transform.Rotation", ticksPerSec); + // The scales + ProcessAnimationCurveVector(animationClip, nodeAnim->MScalingKeys, nodeAnim->MNumScalingKeys, "Transform.Scale", ticksPerSec, false); + + if (animationClip.Curves.Count > 0) + animationClips.Add(nodeName, animationClip); + } + + private unsafe void ProcessAnimationCurveVector(AnimationClip animationClip, VectorKey* keys, uint nbKeys, string partialTargetName, double ticksPerSec, bool isTranslation) + { + var animationCurve = new AnimationCurve(); + + // Switch to cubic implicit interpolation mode for Vector3 + animationCurve.InterpolationType = AnimationCurveInterpolationType.Cubic; + + var lastKeyTime = new CompressedTimeSpan(); + + for (uint keyId = 0; keyId < nbKeys; ++keyId) + { + var aiKey = keys[keyId]; + + var key = new KeyFrameData + { + Time = lastKeyTime = Utils.AiTimeToStrideTimeSpan(aiKey.MTime, ticksPerSec), + Value = aiKey.MValue.ToStrideVector3() + }; + + if (isTranslation) + { + // Change of basis: key.Value = (rootTransformInverse * Matrix::Translation(key.Value) * rootTransform).TranslationVector; + Vector3.TransformCoordinate(ref key.Value, ref rootTransform, out key.Value); + } + else + { + // Change of basis: key.Value = (rootTransformInverse * Matrix::Scaling(key.Value) * rootTransform).ScaleVector; + var scale = Vector3.One; + Vector3.TransformNormal(ref scale, ref rootTransformInverse, out scale); + scale *= key.Value; + Vector3.TransformNormal(ref scale, ref rootTransform, out key.Value); + } + + animationCurve.KeyFrames.Add(key); + if (keyId == 0 || keyId == nbKeys - 1) // discontinuity at animation first and last frame + animationCurve.KeyFrames.Add(key); // add 2 times the same frame at discontinuities to have null gradient + } + + animationClip.AddCurve(partialTargetName, animationCurve, false); + + if (nbKeys > 0 && animationClip.Duration < lastKeyTime) + { + animationClip.Duration = lastKeyTime; + } + } + + private unsafe void ProcessAnimationCurveQuaternion(AnimationClip animationClip, QuatKey* keys, uint nbKeys, string partialTargetName, double ticksPerSec) + { + var animationCurve = new AnimationCurve(); + + var lastKeyTime = new CompressedTimeSpan(); + + for (uint keyId = 0; keyId < nbKeys; ++keyId) + { + var aiKey = keys[keyId]; + var key = new KeyFrameData + { + Time = lastKeyTime = Utils.AiTimeToStrideTimeSpan(aiKey.MTime, ticksPerSec), + Value = aiKey.MValue.ToStrideQuaternion() + }; + + key.Value = rootOrientationInverse * key.Value * rootOrientation; + + animationCurve.KeyFrames.Add(key); + } + + animationClip.AddCurve(partialTargetName, animationCurve, false); + + if (nbKeys > 0 && animationClip.Duration < lastKeyTime) + { + animationClip.Duration = lastKeyTime; + } + } + + private unsafe void GenerateUniqueNames(Dictionary finalNames, List baseNames, Func objectToName) + { + var itemNameTotalCount = new Dictionary(); + var itemNameCurrentCount = new Dictionary(); + var tempNames = new List(); + + for (var i = 0; i < baseNames.Count; ++i) + { + // Clean the name by removing unwanted characters + var itemName = baseNames[i]; + + var itemNameSplitPosition = itemName.IndexOf('#'); + if (itemNameSplitPosition != -1) + { + itemName = itemName.Substring(0, itemNameSplitPosition); + } + + itemNameSplitPosition = itemName.IndexOf("__"); + if (itemNameSplitPosition != -1) + { + itemName = itemName.Substring(0, itemNameSplitPosition); + } + + // remove all bad characters + itemName = itemName.Replace(':', '_'); + itemName = itemName.Replace(" ", string.Empty); + + tempNames.Add(itemName); + + // count the occurences of this name + if (!itemNameTotalCount.ContainsKey(itemName)) + itemNameTotalCount.Add(itemName, 1); + else + itemNameTotalCount[itemName]++; + } + + for (var i = 0; i < baseNames.Count; ++i) + { + var lItem = objectToName(i); + var itemName = tempNames[i]; + + if (itemNameTotalCount[itemName] > 1) + { + if (!itemNameCurrentCount.ContainsKey(itemName)) + itemNameCurrentCount.Add(itemName, 1); + else + itemNameCurrentCount[itemName]++; + + itemName = itemName + "_" + itemNameCurrentCount[itemName].ToString(CultureInfo.InvariantCulture); + } + + finalNames.Add(lItem, itemName); + } + } + + private unsafe void GenerateMeshNames(Scene* scene, Dictionary meshNames) + { + var baseNames = new List(); + for (uint i = 0; i < scene->MNumMeshes; i++) + { + var lMesh = scene->MMeshes[i]; + baseNames.Add(lMesh->MName.AsString); + } + + GenerateUniqueNames(meshNames, baseNames, i => (IntPtr)scene->MMeshes[i]); + } + + private unsafe void GenerateAnimationNames(Scene* scene, Dictionary animationNames) + { + var baseNames = new List(); + for (uint i = 0; i < scene->MNumAnimations; i++) + { + var lAnimation = scene->MAnimations[i]; + var animationName = lAnimation->MName.AsString; + baseNames.Add(animationName); + } + + GenerateUniqueNames(animationNames, baseNames, i => (IntPtr)scene->MAnimations[i]); + } + + private unsafe void GenerateNodeNames(Scene* scene, Dictionary nodeNames) + { + var baseNames = new List(); + var orderedNodes = new List(); + + GetNodeNames(scene->MRootNode, baseNames, orderedNodes); + GenerateUniqueNames(nodeNames, baseNames, i => orderedNodes[i]); + } + + private unsafe void GetNodeNames(Node* node, List nodeNames, List orderedNodes) + { + nodeNames.Add(node->MName.AsString); + orderedNodes.Add((IntPtr)node); + + for (uint i = 0; i < node->MNumChildren; ++i) + { + GetNodeNames(node->MChildren[i], nodeNames, orderedNodes); + } + } + + private unsafe void RegisterNodes(Node* fromNode, int parentIndex, Dictionary nodeNames, Dictionary> meshIndexToNodeIndex) + { + var nodeIndex = nodes.Count; + + // assign the index of the node to the index of the mesh + for (uint m = 0; m < fromNode->MNumMeshes; ++m) + { + var meshIndex = fromNode->MMeshes[m]; + + if (!meshIndexToNodeIndex.TryGetValue((int)meshIndex, out var nodeIndices)) + { + nodeIndices = new List(); + meshIndexToNodeIndex.Add((int)meshIndex, nodeIndices); + } + + nodeIndices.Add(nodeIndex); + } + + // Create node + var modelNodeDefinition = new ModelNodeDefinition + { + ParentIndex = parentIndex, + Name = nodeNames[(IntPtr)fromNode], + Flags = ModelNodeFlags.Default + }; + + // Extract scene scaling and rotation from the root node. + // Bake scaling into all node's positions and rotation into the 1st-level nodes. + if (parentIndex == -1) + { + rootTransform = fromNode->MTransformation.ToStrideMatrix(); + + rootTransform.Decompose(out var rootScaling, out rootOrientation, out var rootTranslation); + + rootTransformInverse = Matrix.Invert(rootTransform); + rootOrientationInverse = Quaternion.Invert(rootOrientation); + + modelNodeDefinition.Transform.Rotation = Quaternion.Identity; + modelNodeDefinition.Transform.Scale = Vector3.One; + } + else + { + var transform = rootTransformInverse * fromNode->MTransformation.ToStrideMatrix() * rootTransform; + transform.Decompose(out modelNodeDefinition.Transform.Scale, out modelNodeDefinition.Transform.Rotation, out modelNodeDefinition.Transform.Position); + } + + nodes.Add(modelNodeDefinition); + + // register the children + for (uint child = 0; child < fromNode->MNumChildren; ++child) + { + RegisterNodes(fromNode->MChildren[child], nodeIndex, nodeNames, meshIndexToNodeIndex); + } + } + + private unsafe MeshInfo ProcessMesh(Scene* scene, Silk.NET.Assimp.Mesh* mesh, Dictionary meshNames) + { + List bones = null; + var hasSkinningPosition = false; + var hasSkinningNormal = false; + var totalClusterCount = 0; + + // Build the bone's indices/weights and attach bones to NodeData + //(bones info are present in the mesh so that is why we have to perform that here) + + var vertexIndexToBoneIdWeight = new List>(); + if (mesh->MNumBones > 0) + { + bones = new List(); + + // TODO: change this to support shared meshes across nodes + + // size of the array is already known + vertexIndexToBoneIdWeight.Capacity = (int)mesh->MNumVertices; + for (var i = 0; i < (int)mesh->MNumVertices; i++) + { + vertexIndexToBoneIdWeight.Add(new List<(short, float)>()); + } + + // Build skinning clusters and fill controls points data stutcture + for (uint boneId = 0; boneId < mesh->MNumBones; ++boneId) + { + var bone = mesh->MBones[boneId]; + + // Fill controlPts with bone controls on the mesh + for (uint vtxWeightId = 0; vtxWeightId < bone->MNumWeights; ++vtxWeightId) + { + var vtxWeight = bone->MWeights[vtxWeightId]; + vertexIndexToBoneIdWeight[(int)vtxWeight.MVertexId].Add(((short)boneId, vtxWeight.MWeight)); + } + + // find the node where the bone is mapped - based on the name(?) + var nodeIndex = -1; + var boneName = bone->MName.AsString; + for (var nodeDefId = 0; nodeDefId < nodes.Count; ++nodeDefId) + { + var nodeDef = nodes[nodeDefId]; + if (nodeDef.Name == boneName) + { + nodeIndex = nodeDefId; + break; + } + } + + if (nodeIndex == -1) + { + Logger.Error($"No node found for name {boneId}:{boneName}"); + nodeIndex = 0; + } + + bones.Add(new MeshBoneDefinition + { + NodeIndex = nodeIndex, + LinkToMeshMatrix = rootTransformInverse * bone->MOffsetMatrix.ToStrideMatrix() * rootTransform + }); + } + + NormalizeVertexWeights(vertexIndexToBoneIdWeight, NumberOfBonesPerVertex); + + totalClusterCount = (int)mesh->MNumBones; + if (totalClusterCount > 0) + hasSkinningPosition = true; + } + + // Build the vertex declaration + var vertexElements = new List(); + var vertexStride = 0; + + var positionOffset = vertexStride; + vertexElements.Add(VertexElement.Position(0, vertexStride)); + vertexStride += sizeof(Vector3); + + var normalOffset = vertexStride; + if (mesh->MNormals != null) + { + vertexElements.Add(VertexElement.Normal(0, vertexStride)); + vertexStride += sizeof(Vector3); + } + + var uvOffset = vertexStride; + var sizeUV = sizeof(Vector2); // 3D uv not supported + for (uint uvChannel = 0; uvChannel < Utils.GetNumUVChannels(mesh); ++uvChannel) + { + vertexElements.Add(VertexElement.TextureCoordinate((int)uvChannel, vertexStride)); + vertexStride += sizeUV; + } + + var colorOffset = vertexStride; + var sizeColor = sizeof(Color); + for (uint colorChannel = 0; colorChannel < Utils.GetNumColorChannels(mesh); ++colorChannel) + { + vertexElements.Add(VertexElement.Color((int)colorChannel, vertexStride)); + vertexStride += sizeColor; + } + + var tangentOffset = vertexStride; + if (mesh->MTangents != null) + { + vertexElements.Add(VertexElement.Tangent(0, vertexStride)); + vertexStride += sizeof(Vector3); + } + + var bitangentOffset = vertexStride; + if (mesh->MTangents != null) + { + vertexElements.Add(VertexElement.BiTangent(0, vertexStride)); + vertexStride += sizeof(Vector3); + } + + var blendIndicesOffset = vertexStride; + var controlPointIndices16 = (AllowUnsignedBlendIndices && totalClusterCount > 256) || (!AllowUnsignedBlendIndices && totalClusterCount > 128); + if (vertexIndexToBoneIdWeight.Count > 0) + { + if (controlPointIndices16) + { + if (AllowUnsignedBlendIndices) + { + vertexElements.Add(new VertexElement("BLENDINDICES", 0, PixelFormat.R16G16B16A16_UInt, vertexStride)); + vertexStride += sizeof(ushort) * 4; + } + else + { + vertexElements.Add(new VertexElement("BLENDINDICES", 0, PixelFormat.R16G16B16A16_SInt, vertexStride)); + vertexStride += sizeof(short) * 4; + } + } + else + { + if (AllowUnsignedBlendIndices) + { + vertexElements.Add(new VertexElement("BLENDINDICES", 0, PixelFormat.R8G8B8A8_UInt, vertexStride)); + vertexStride += sizeof(byte) * 4; + } + else + { + vertexElements.Add(new VertexElement("BLENDINDICES", 0, PixelFormat.R8G8B8A8_SInt, vertexStride)); + vertexStride += sizeof(sbyte) * 4; + } + } + } + + var blendWeightOffset = vertexStride; + if (vertexIndexToBoneIdWeight.Count > 0) + { + vertexElements.Add(new VertexElement("BLENDWEIGHT", 0, PixelFormat.R32G32B32A32_Float, vertexStride)); + vertexStride += sizeof(float) * 4; + } + + // Build the vertices data buffer + var vertexBuffer = new byte[vertexStride * mesh->MNumVertices]; + fixed (byte* vertexBufferPtr = &vertexBuffer[0]) + { + var vbPointer = vertexBufferPtr; + for (uint i = 0; i < mesh->MNumVertices; i++) + { + var positionPointer = (Vector3*)(vbPointer + positionOffset); + *positionPointer = mesh->MVertices[i].ToStrideVector3(); + + Vector3.TransformCoordinate(ref *positionPointer, ref rootTransform, out *positionPointer); + + if (mesh->MNormals != null) + { + var normalPointer = (Vector3*)(vbPointer + normalOffset); + *normalPointer = mesh->MNormals[i].ToStrideVector3(); + + Vector3.TransformNormal(ref *normalPointer, ref rootTransform, out *normalPointer); + + if (float.IsNaN(normalPointer->X) || float.IsNaN(normalPointer->Y) || float.IsNaN(normalPointer->Z)) + *normalPointer = new Vector3(1, 0, 0); + else + normalPointer->Normalize(); + } + + for (uint uvChannel = 0; uvChannel < Utils.GetNumUVChannels(mesh); ++uvChannel) + { + var textureCoord = mesh->MTextureCoords[(int)uvChannel][i]; + *((Vector2*)(vbPointer + uvOffset + sizeUV * uvChannel)) = new Vector2(textureCoord.X, textureCoord.Y); // 3D uv not supported + } + + for (uint colorChannel = 0; colorChannel < Utils.GetNumColorChannels(mesh); ++colorChannel) + { + var color = mesh->MColors[(int)colorChannel][i].ToStrideColor(); + *((Color*)(vbPointer + colorOffset + sizeColor * colorChannel)) = color; + } + + if (mesh->MTangents != null) + { + var tangentPointer = (Vector3*)(vbPointer + tangentOffset); + var bitangentPointer = (Vector3*)(vbPointer + bitangentOffset); + *tangentPointer = mesh->MTangents[i].ToStrideVector3(); + *bitangentPointer = mesh->MBitangents[i].ToStrideVector3(); + if (float.IsNaN(tangentPointer->X) || float.IsNaN(tangentPointer->Y) || float.IsNaN(tangentPointer->Z) || + float.IsNaN(bitangentPointer->X) || float.IsNaN(bitangentPointer->Y) || float.IsNaN(bitangentPointer->Z)) + { + var normalPointer = ((Vector3*)(vbPointer + normalOffset)); + Vector3 c1 = Vector3.Cross(*normalPointer, new Vector3(0.0f, 0.0f, 1.0f)); + Vector3 c2 = Vector3.Cross(*normalPointer, new Vector3(0.0f, 1.0f, 0.0f)); + + if (c1.LengthSquared() > c2.LengthSquared()) + *tangentPointer = c1; + else + *tangentPointer = c2; + *bitangentPointer = Vector3.Cross(*normalPointer, *tangentPointer); + } + tangentPointer->Normalize(); + bitangentPointer->Normalize(); + } + + if (vertexIndexToBoneIdWeight.Count > 0) + { + for (var bone = 0; bone < NumberOfBonesPerVertex; ++bone) + { + if (controlPointIndices16) + { + if (AllowUnsignedBlendIndices) + ((ushort*)(vbPointer + blendIndicesOffset))[bone] = (ushort)vertexIndexToBoneIdWeight[(int)i][bone].Item1; + else + ((short*)(vbPointer + blendIndicesOffset))[bone] = vertexIndexToBoneIdWeight[(int)i][bone].Item1; + } + else + { + if (AllowUnsignedBlendIndices) + (vbPointer + blendIndicesOffset)[bone] = (byte)vertexIndexToBoneIdWeight[(int)i][bone].Item1; + else + ((sbyte*)(vbPointer + blendIndicesOffset))[bone] = (sbyte)vertexIndexToBoneIdWeight[(int)i][bone].Item1; + } + + ((float*)(vbPointer + blendWeightOffset))[bone] = vertexIndexToBoneIdWeight[(int)i][bone].Item2; + } + } + + vbPointer += vertexStride; + } + } + + // Build the indices data buffer + var nbIndices = 3 * mesh->MNumFaces; + byte[] indexBuffer; + var is32BitIndex = mesh->MNumVertices > 65535; + if (is32BitIndex) + indexBuffer = new byte[sizeof(uint) * nbIndices]; + else + indexBuffer = new byte[sizeof(ushort) * nbIndices]; + + fixed (byte* indexBufferPtr = &indexBuffer[0]) + { + var ibPointer = indexBufferPtr; + + for (uint i = 0; i < mesh->MNumFaces; i++) + { + if (is32BitIndex) + { + for (int j = 0; j < 3; ++j) + { + *((uint*)ibPointer) = mesh->MFaces[(int)i].MIndices[j]; + ibPointer += sizeof(uint); + } + } + else + { + for (int j = 0; j < 3; ++j) + { + *((ushort*)ibPointer) = (ushort)(mesh->MFaces[(int)i].MIndices[j]); + ibPointer += sizeof(ushort); + } + } + } + } + + // Build the mesh data + var vertexDeclaration = new VertexDeclaration(vertexElements.ToArray()); + var vertexBufferBinding = new VertexBufferBinding(GraphicsSerializerExtensions.ToSerializableVersion(new BufferData(BufferFlags.VertexBuffer, vertexBuffer)), vertexDeclaration, (int)mesh->MNumVertices, vertexDeclaration.VertexStride, 0); + var indexBufferBinding = new IndexBufferBinding(GraphicsSerializerExtensions.ToSerializableVersion(new BufferData(BufferFlags.IndexBuffer, indexBuffer)), is32BitIndex, (int)nbIndices, 0); + + var drawData = new MeshDraw + { + VertexBuffers = new VertexBufferBinding[] { vertexBufferBinding }, + IndexBuffer = indexBufferBinding, + PrimitiveType = PrimitiveType.TriangleList, + DrawCount = (int)nbIndices + }; + + return new MeshInfo + { + Draw = drawData, + Name = meshNames[(IntPtr)mesh], + Bones = bones, + MaterialIndex = (int)mesh->MMaterialIndex, + HasSkinningPosition = hasSkinningPosition, + HasSkinningNormal = hasSkinningNormal, + TotalClusterCount = totalClusterCount + }; + } + + private void NormalizeVertexWeights(List> controlPts, int nbBoneByVertex) + { + for (var vertexId = 0; vertexId < controlPts.Count; ++vertexId) + { + var curVertexWeights = controlPts[vertexId]; + + // check that one vertex has not more than 'nbBoneByVertex' associated bones + if (curVertexWeights.Count > nbBoneByVertex) + { + Logger.Warning( + $"The input file contains vertices that are associated to more than {curVertexWeights.Count} bones. In current version of the system, a single vertex can only be associated to {nbBoneByVertex} bones. Extra bones will be ignored", + new ArgumentOutOfRangeException("To much bones influencing a single vertex")); + } + + // resize the weights so that they contains exactly the number of bone weights required + while (curVertexWeights.Count < nbBoneByVertex) + { + curVertexWeights.Add((0, 0)); + } + + var totalWeight = 0.0f; + for (var boneId = 0; boneId < nbBoneByVertex; ++boneId) + totalWeight += curVertexWeights[boneId].Item2; + + if (totalWeight <= float.Epsilon) // Assimp weights are positive, so in this case all weights are nulls + continue; + + for (var boneId = 0; boneId < nbBoneByVertex; ++boneId) + curVertexWeights[boneId] = (curVertexWeights[boneId].Item1, curVertexWeights[boneId].Item2 / totalWeight); + } + } #pragma warning disable IDE0060 // Remove unused parameter - private unsafe void ExtractEmbededTexture(Silk.NET.Assimp.Texture* texture) + private unsafe void ExtractEmbededTexture(Silk.NET.Assimp.Texture* texture) #pragma warning restore IDE0060 // Remove unused parameter - { - Logger.Warning("The input file contains embeded textures. Embeded textures are not currently supported. This texture will be ignored", - new NotImplementedException("Embeded textures extraction")); - } - - private unsafe Dictionary ExtractMaterials(Scene* scene, Dictionary materialNames) - { - GenerateMaterialNames(scene, materialNames); - - var materials = new Dictionary(); - for (uint i = 0; i < scene->MNumMaterials; i++) - { - var lMaterial = scene->MMaterials[i]; - var materialName = materialNames[(IntPtr)lMaterial]; - materials.Add(materialName, ProcessMeshMaterial(lMaterial)); - } - return materials; - } - - private unsafe void GenerateMaterialNames(Scene* scene, Dictionary materialNames) - { - var baseNames = new List(); - for (uint i = 0; i < scene->MNumMaterials; i++) - { - var lMaterial = scene->MMaterials[i]; - - var aiMaterial = new AssimpString(); - var materialName = assimp.GetMaterialString(lMaterial, Silk.NET.Assimp.Assimp.MaterialNameBase, 0, 0, ref aiMaterial) == Return.ReturnSuccess ? aiMaterial.AsString : "Material"; - baseNames.Add(materialName); - } - - GenerateUniqueNames(materialNames, baseNames, i => (IntPtr)scene->MMaterials[i]); - } - - private unsafe MaterialAsset ProcessMeshMaterial(Silk.NET.Assimp.Material* pMaterial) - { - var finalMaterial = new MaterialAsset(); - - float specPower = 0; - float opacity = 0; - - bool hasDiffColor = false; - bool hasSpecColor = false; - bool hasAmbientColor = false; - bool hasEmissiveColor = false; - bool hasReflectiveColor = false; - bool hasSpecPower = false; - bool hasOpacity = false; - - var diffColor = System.Numerics.Vector4.Zero; - var specColor = System.Numerics.Vector4.Zero; - var ambientColor = System.Numerics.Vector4.Zero; - var emissiveColor = System.Numerics.Vector4.Zero; - var reflectiveColor = System.Numerics.Vector4.Zero; - var dummyColor = System.Numerics.Vector4.Zero; - - SetMaterialColorFlag(pMaterial, Silk.NET.Assimp.Assimp.MaterialColorDiffuseBase, ref hasDiffColor, ref diffColor, true);// always keep black color for diffuse - SetMaterialColorFlag(pMaterial, Silk.NET.Assimp.Assimp.MaterialColorSpecularBase, ref hasSpecColor, ref specColor, IsNotBlackColor(specColor)); - SetMaterialColorFlag(pMaterial, Silk.NET.Assimp.Assimp.MaterialColorAmbientBase, ref hasAmbientColor, ref ambientColor, IsNotBlackColor(specColor)); - SetMaterialColorFlag(pMaterial, Silk.NET.Assimp.Assimp.MaterialColorEmissiveBase, ref hasEmissiveColor, ref emissiveColor, IsNotBlackColor(emissiveColor)); - SetMaterialColorFlag(pMaterial, Silk.NET.Assimp.Assimp.MaterialColorReflectiveBase, ref hasReflectiveColor, ref reflectiveColor, IsNotBlackColor(reflectiveColor)); - SetMaterialFloatArrayFlag(pMaterial, Silk.NET.Assimp.Assimp.MaterialShininessBase, ref hasSpecPower, specPower, specPower > 0); - SetMaterialFloatArrayFlag(pMaterial, Silk.NET.Assimp.Assimp.MaterialOpacityBase, ref hasOpacity, opacity, opacity < 1.0); - - BuildLayeredSurface(pMaterial, hasDiffColor, false, diffColor.ToStrideColor(), 0.0f, TextureType.TextureTypeDiffuse, finalMaterial); - BuildLayeredSurface(pMaterial, hasSpecColor, false, specColor.ToStrideColor(), 0.0f, TextureType.TextureTypeSpecular, finalMaterial); - BuildLayeredSurface(pMaterial, false, false, dummyColor.ToStrideColor(), 0.0f, TextureType.TextureTypeNormals, finalMaterial); - BuildLayeredSurface(pMaterial, false, false, dummyColor.ToStrideColor(), 0.0f, TextureType.TextureTypeDisplacement, finalMaterial); - BuildLayeredSurface(pMaterial, hasAmbientColor, false, ambientColor.ToStrideColor(), 0.0f, TextureType.TextureTypeAmbient, finalMaterial); - BuildLayeredSurface(pMaterial, false, hasOpacity, dummyColor.ToStrideColor(), opacity, TextureType.TextureTypeOpacity, finalMaterial); - BuildLayeredSurface(pMaterial, false, hasSpecPower, dummyColor.ToStrideColor(), specPower, TextureType.TextureTypeShininess, finalMaterial); - BuildLayeredSurface(pMaterial, hasEmissiveColor, false, emissiveColor.ToStrideColor(), 0.0f, TextureType.TextureTypeEmissive, finalMaterial); - BuildLayeredSurface(pMaterial, false, false, dummyColor.ToStrideColor(), 0.0f, TextureType.TextureTypeHeight, finalMaterial); - BuildLayeredSurface(pMaterial, hasReflectiveColor, false, reflectiveColor.ToStrideColor(), 0.0f, TextureType.TextureTypeReflection, finalMaterial); - - return finalMaterial; - } - - private unsafe void SetMaterialColorFlag(Silk.NET.Assimp.Material* pMaterial, string materialColorBase, ref bool hasMatColor, ref System.Numerics.Vector4 matColor, bool condition) - { - if (assimp.GetMaterialColor(pMaterial, materialColorBase, 0, 0, ref matColor) == Return.ReturnSuccess && condition) - { - hasMatColor = true; - } - } - private unsafe void SetMaterialFloatArrayFlag(Silk.NET.Assimp.Material* pMaterial, string materialBase, ref bool hasMatProperty, float matColor, bool condition) - { - if(assimp.GetMaterialFloatArray(pMaterial, materialBase, 0, 0, &matColor, (uint*)0x0) == Return.ReturnSuccess && condition) - { - hasMatProperty = true; - } - } - - private bool IsNotBlackColor(System.Numerics.Vector4 diffColor) - { - return diffColor != System.Numerics.Vector4.Zero; - } - - private unsafe void BuildLayeredSurface(Silk.NET.Assimp.Material* pMat, bool hasBaseColor, bool hasBaseValue, Color4 baseColor, float baseValue, TextureType textureType, MaterialAsset finalMaterial) - { - var nbTextures = assimp.GetMaterialTextureCount(pMat, textureType); - - IComputeColor computeColorNode = null; - int textureCount = 0; - if (nbTextures == 0) - { - if (hasBaseColor) - { - computeColorNode = new ComputeColor(baseColor); - } - //else if (hasBaseValue) - //{ - // computeColorNode = gcnew MaterialFloatComputeNode(baseValue); - //} - } - else - { - computeColorNode = GenerateOneTextureTypeLayers(pMat, textureType, textureCount, finalMaterial); - } - - if (computeColorNode == null) - { - return; - } - - if (textureType == TextureType.TextureTypeDiffuse) - { - if (assimp.GetMaterialTextureCount(pMat, TextureType.TextureTypeLightmap) > 0) - { - var lightMap = GenerateOneTextureTypeLayers(pMat, TextureType.TextureTypeLightmap, textureCount, finalMaterial); - if (lightMap != null) - computeColorNode = new ComputeBinaryColor(computeColorNode, lightMap, BinaryOperator.Add); - } - - finalMaterial.Attributes.Diffuse = new MaterialDiffuseMapFeature(computeColorNode); - - // TODO TEMP: Set a default diffuse model - finalMaterial.Attributes.DiffuseModel = new MaterialDiffuseLambertModelFeature(); - } - else if (textureType == TextureType.TextureTypeSpecular) - { - var specularFeature = new MaterialSpecularMapFeature - { - SpecularMap = computeColorNode - }; - finalMaterial.Attributes.Specular = specularFeature; - - // TODO TEMP: Set a default specular model - var specularModel = new MaterialSpecularMicrofacetModelFeature - { - Fresnel = new MaterialSpecularMicrofacetFresnelSchlick(), - Visibility = new MaterialSpecularMicrofacetVisibilityImplicit(), - NormalDistribution = new MaterialSpecularMicrofacetNormalDistributionBlinnPhong() - }; - finalMaterial.Attributes.SpecularModel = specularModel; - } - else if (textureType == TextureType.TextureTypeEmissive) - { - // TODO: Add support - } - else if (textureType == TextureType.TextureTypeAmbient) - { - // TODO: Add support - } - else if (textureType == TextureType.TextureTypeReflection) - { - // TODO: Add support - } - if (textureType == TextureType.TextureTypeOpacity) - { - // TODO: Add support - } - else if (textureType == TextureType.TextureTypeShininess) - { - // TODO: Add support - } - if (textureType == TextureType.TextureTypeSpecular) - { - // TODO: Add support - } - else if (textureType == TextureType.TextureTypeNormals) - { - finalMaterial.Attributes.Surface = new MaterialNormalMapFeature(computeColorNode); - } - else if (textureType == TextureType.TextureTypeDisplacement) - { - // TODO: Add support - } - else if (textureType == TextureType.TextureTypeHeight) - { - // TODO: Add support - } - } - - private unsafe IComputeColor GenerateOneTextureTypeLayers(Silk.NET.Assimp.Material* pMat, TextureType textureType, int textureCount, MaterialAsset finalMaterial) - { - var stack = Material.Materials.ConvertAssimpStackCppToCs(assimp, pMat, textureType); - - var compositionFathers = new Stack(); - - var sets = new Stack(); - sets.Push(0); - - var nbTextures = assimp.GetMaterialTextureCount(pMat, textureType); - - IComputeColor curComposition = null, newCompositionFather = null; - - var isRootElement = true; - IComputeColor rootMaterial = null; - - while (!stack.IsEmpty) - { - var top = stack.Pop(); - - IComputeColor curCompositionFather = null; - if (!isRootElement) - { - if (compositionFathers.Count == 0) - Logger.Error("Texture Stack Invalid : Operand without Operation."); - - curCompositionFather = compositionFathers.Pop(); - } - - var type = top.type; - var strength = top.blend; - var alpha = top.alpha; - int set = sets.Peek(); - - if (type == Material.StackType.Operation) - { - set = sets.Pop(); - var realTop = (Material.StackOperation)top; - var op = realTop.operation; - var binNode = new ComputeBinaryColor(null, null, BinaryOperator.Add); - - binNode.Operator = op switch - { - Material.Operation.Add3ds or Material.Operation.AddMaya => BinaryOperator.Add, - Material.Operation.Multiply3ds or Material.Operation.MultiplyMaya => BinaryOperator.Multiply, - _ => BinaryOperator.Add, - }; - curComposition = binNode; - } - else if (type == Material.StackType.Color) - { - var realTop = (Material.StackColor)top; - var ol = realTop.color; - curComposition = new ComputeColor(new Color4(ol.R, ol.G, ol.B, alpha)); - } - else if (type == Material.StackType.Texture) - { - var realTop = (Material.StackTexture)top; - var texPath = realTop.texturePath; - var indexUV = realTop.channel; - curComposition = GetTextureReferenceNode(vfsOutputFilename, texPath, (uint)indexUV, Vector2.One, ConvertTextureMode(realTop.mappingModeU), ConvertTextureMode(realTop.mappingModeV), finalMaterial); - } - - newCompositionFather = curComposition; - - if (strength != 1.0f) - { - var strengthAlpha = strength; - if (type != Material.StackType.Color) - strengthAlpha *= alpha; - - var factorComposition = new ComputeFloat4(new Vector4(strength, strength, strength, strengthAlpha)); - curComposition = new ComputeBinaryColor(curComposition, factorComposition, BinaryOperator.Multiply); - } - else if (alpha != 1.0f && type != Material.StackType.Color) - { - var factorComposition = new ComputeFloat4(new Vector4(1.0f, 1.0f, 1.0f, alpha)); - curComposition = new ComputeBinaryColor(curComposition, factorComposition, BinaryOperator.Multiply); - } - - if (isRootElement) - { - rootMaterial = curComposition; - isRootElement = false; - compositionFathers.Push(curCompositionFather); - } - else - { - if (set == 0) - { - ((ComputeBinaryColor)curCompositionFather).LeftChild = curComposition; - compositionFathers.Push(curCompositionFather); - sets.Push(1); - } - else if (set == 1) - { - ((ComputeBinaryColor)curCompositionFather).RightChild = curComposition; - } - else - { - Logger.Error($"Texture Stack Invalid : Invalid Operand Number {set}."); - } - } - - if (type == Material.StackType.Operation) - { - compositionFathers.Push(newCompositionFather); - sets.Push(0); - } - } - - return rootMaterial; - } - - private static TextureAddressMode ConvertTextureMode(Material.MappingMode mappingMode) - { - return mappingMode switch - { - Material.MappingMode.Clamp => TextureAddressMode.Clamp, - Material.MappingMode.Decal => TextureAddressMode.Border, - Material.MappingMode.Mirror => TextureAddressMode.Mirror, - _ => TextureAddressMode.Wrap, - }; - } - - private ComputeTextureColor GetTextureReferenceNode(string vfsOutputPath, string sourceTextureFile, uint textureUVSetIndex, Vector2 textureUVscaling, TextureAddressMode addressModeU, TextureAddressMode addressModeV, MaterialAsset finalMaterial) - { - // TODO: compare with FBX importer - see if there could be some conflict between texture names - var textureValue = TextureLayerGenerator.GenerateMaterialTextureNode(vfsOutputPath, sourceTextureFile, textureUVSetIndex, textureUVscaling, addressModeU, addressModeV, Logger); - - var attachedReference = AttachedReferenceManager.GetAttachedReference(textureValue.Texture); - var referenceName = attachedReference.Url; - - // find a new and correctName - if (!textureNameCount.ContainsKey(referenceName)) - textureNameCount.Add(referenceName, 1); - else - { - int count = textureNameCount[referenceName]; - textureNameCount[referenceName] = count + 1; - referenceName = string.Concat(referenceName, "_", count); - } - - return textureValue; - } - - private unsafe List ExtractModels(Scene* scene, Dictionary meshNames, Dictionary materialNames, Dictionary nodeNames) - { - GenerateMeshNames(scene, meshNames); - - var meshList = new List(); - for (uint i = 0; i < scene->MNumMeshes; ++i) - { - var mesh = scene->MMeshes[i]; - var lMaterial = scene->MMaterials[mesh->MMaterialIndex]; - - var meshParams = new MeshParameters - { - MeshName = meshNames[(IntPtr)mesh], - MaterialName = materialNames[(IntPtr)lMaterial], - NodeName = SearchMeshNode(scene->MRootNode, i, nodeNames) - }; - - meshList.Add(meshParams); - } - - return meshList; - } - - private unsafe string SearchMeshNode(Node* node, uint meshIndex, Dictionary nodeNames) - { - for (uint i = 0; i < node->MNumMeshes; ++i) - { - if (node->MMeshes[i] == meshIndex) - return nodeNames[(IntPtr)node]; - } - - for (uint i = 0; i < node->MNumChildren; ++i) - { - var res = SearchMeshNode(node->MChildren[i], meshIndex, nodeNames); - if (res != null) - return res; - } - - return null; - } - - private unsafe List ExtractNodeHierarchy(Scene* scene, Dictionary nodeNames) - { - var allNodes = new List(); - GetNodes(scene->MRootNode, 0, nodeNames, allNodes); - return allNodes; - } - - private unsafe void GetNodes(Node* node, int depth, Dictionary nodeNames, List allNodes) - { - var newNodeInfo = new NodeInfo - { - Name = nodeNames[(IntPtr)node], - Depth = depth, - Preserve = true - }; - - allNodes.Add(newNodeInfo); - for (uint i = 0; i < node->MNumChildren; ++i) - GetNodes(node->MChildren[i], depth + 1, nodeNames, allNodes); - } - - private unsafe List ExtractAnimations(Scene* scene, Dictionary animationNames) - { - if (scene->MNumAnimations == 0) - return null; - - GenerateAnimationNames(scene, animationNames); - - var animationList = new List(); - foreach (var animationName in animationNames) - { - animationList.Add(animationName.Value); - } - - return animationList; - } - - private unsafe List ExtractTextureDependencies(Scene* scene) - { - var textureNames = new List(); - - // texture search is done by type so we need to loop on them - var allTextureTypes = new TextureType[] - { - TextureType.TextureTypeDiffuse, - TextureType.TextureTypeSpecular, - TextureType.TextureTypeAmbient, - TextureType.TextureTypeEmissive, - TextureType.TextureTypeHeight, - TextureType.TextureTypeNormals, - TextureType.TextureTypeShininess, - TextureType.TextureTypeOpacity, - TextureType.TextureTypeDisplacement, - TextureType.TextureTypeLightmap, - TextureType.TextureTypeReflection - }; - - for (uint i = 0; i < scene->MNumMaterials; i++) - { - foreach (var textureType in allTextureTypes) - { - var lMaterial = scene->MMaterials[i]; - var nbTextures = assimp.GetMaterialTextureCount(lMaterial, textureType); - - for (uint j = 0; j < nbTextures; ++j) - { - var path = new AssimpString(); - var mapping = TextureMapping.TextureMappingUV; - uint uvIndex = 0; - var blend = 0.0f; - var textureOp = TextureOp.TextureOpMultiply; - var mapMode = TextureMapMode.TextureMapModeWrap; - uint flags = 0; - - if (assimp.GetMaterialTexture(lMaterial, textureType, j, ref path, ref mapping, ref uvIndex, ref blend, ref textureOp, ref mapMode, ref flags) == Return.ReturnSuccess) - { - var relFileName = path.AsString; - var fileNameToUse = Path.Combine(vfsInputPath, relFileName); - textureNames.Add(fileNameToUse); - break; - } - } - } - } - - return textureNames; - } - } - - public class MeshInfo - { - public MeshDraw Draw; - public List Bones; - public string Name; - public int MaterialIndex; - public bool HasSkinningPosition = false; - public bool HasSkinningNormal = false; - public int TotalClusterCount = 0; - } - - public class MaterialInstantiation - { - public List Parameters; - public MaterialAsset Material; - public string MaterialName; - } - - public unsafe class MaterialInstances - { - public Silk.NET.Assimp.Material* SourceMaterial; - public List Instances = new(); - public string MaterialsName; - } + { + Logger.Warning("The input file contains embeded textures. Embeded textures are not currently supported. This texture will be ignored", + new NotImplementedException("Embeded textures extraction")); + } + + private unsafe Dictionary ExtractMaterials(Scene* scene, Dictionary materialNames) + { + GenerateMaterialNames(scene, materialNames); + + var materials = new Dictionary(); + for (uint i = 0; i < scene->MNumMaterials; i++) + { + var lMaterial = scene->MMaterials[i]; + var materialName = materialNames[(IntPtr)lMaterial]; + materials.Add(materialName, ProcessMeshMaterial(lMaterial)); + } + return materials; + } + + private unsafe void GenerateMaterialNames(Scene* scene, Dictionary materialNames) + { + var baseNames = new List(); + for (uint i = 0; i < scene->MNumMaterials; i++) + { + var lMaterial = scene->MMaterials[i]; + + var aiMaterial = new AssimpString(); + var materialName = assimp.GetMaterialString(lMaterial, Silk.NET.Assimp.Assimp.MaterialNameBase, 0, 0, ref aiMaterial) == Return.Success ? aiMaterial.AsString : "Material"; + baseNames.Add(materialName); + } + + GenerateUniqueNames(materialNames, baseNames, i => (IntPtr)scene->MMaterials[i]); + } + + private unsafe MaterialAsset ProcessMeshMaterial(Silk.NET.Assimp.Material* pMaterial) + { + var finalMaterial = new MaterialAsset(); + + float specPower = 0; + float opacity = 0; + + bool hasDiffColor = false; + bool hasSpecColor = false; + bool hasAmbientColor = false; + bool hasEmissiveColor = false; + bool hasReflectiveColor = false; + bool hasSpecPower = false; + bool hasOpacity = false; + + var diffColor = System.Numerics.Vector4.Zero; + var specColor = System.Numerics.Vector4.Zero; + var ambientColor = System.Numerics.Vector4.Zero; + var emissiveColor = System.Numerics.Vector4.Zero; + var reflectiveColor = System.Numerics.Vector4.Zero; + var dummyColor = System.Numerics.Vector4.Zero; + + SetMaterialColorFlag(pMaterial, Silk.NET.Assimp.Assimp.MaterialColorDiffuseBase, ref hasDiffColor, ref diffColor, true);// always keep black color for diffuse + SetMaterialColorFlag(pMaterial, Silk.NET.Assimp.Assimp.MaterialColorSpecularBase, ref hasSpecColor, ref specColor, IsNotBlackColor(specColor)); + SetMaterialColorFlag(pMaterial, Silk.NET.Assimp.Assimp.MaterialColorAmbientBase, ref hasAmbientColor, ref ambientColor, IsNotBlackColor(specColor)); + SetMaterialColorFlag(pMaterial, Silk.NET.Assimp.Assimp.MaterialColorEmissiveBase, ref hasEmissiveColor, ref emissiveColor, IsNotBlackColor(emissiveColor)); + SetMaterialColorFlag(pMaterial, Silk.NET.Assimp.Assimp.MaterialColorReflectiveBase, ref hasReflectiveColor, ref reflectiveColor, IsNotBlackColor(reflectiveColor)); + SetMaterialFloatArrayFlag(pMaterial, Silk.NET.Assimp.Assimp.MaterialShininessBase, ref hasSpecPower, specPower, specPower > 0); + SetMaterialFloatArrayFlag(pMaterial, Silk.NET.Assimp.Assimp.MaterialOpacityBase, ref hasOpacity, opacity, opacity < 1.0); + + BuildLayeredSurface(pMaterial, hasDiffColor, false, diffColor.ToStrideColor(), 0.0f, TextureType.Diffuse, finalMaterial); + BuildLayeredSurface(pMaterial, hasSpecColor, false, specColor.ToStrideColor(), 0.0f, TextureType.Specular, finalMaterial); + BuildLayeredSurface(pMaterial, false, false, dummyColor.ToStrideColor(), 0.0f, TextureType.Normals, finalMaterial); + BuildLayeredSurface(pMaterial, false, false, dummyColor.ToStrideColor(), 0.0f, TextureType.Displacement, finalMaterial); + BuildLayeredSurface(pMaterial, hasAmbientColor, false, ambientColor.ToStrideColor(), 0.0f, TextureType.Ambient, finalMaterial); + BuildLayeredSurface(pMaterial, false, hasOpacity, dummyColor.ToStrideColor(), opacity, TextureType.Opacity, finalMaterial); + BuildLayeredSurface(pMaterial, false, hasSpecPower, dummyColor.ToStrideColor(), specPower, TextureType.Shininess, finalMaterial); + BuildLayeredSurface(pMaterial, hasEmissiveColor, false, emissiveColor.ToStrideColor(), 0.0f, TextureType.Emissive, finalMaterial); + BuildLayeredSurface(pMaterial, false, false, dummyColor.ToStrideColor(), 0.0f, TextureType.Height, finalMaterial); + BuildLayeredSurface(pMaterial, hasReflectiveColor, false, reflectiveColor.ToStrideColor(), 0.0f, TextureType.Reflection, finalMaterial); + + return finalMaterial; + } + + private unsafe void SetMaterialColorFlag(Silk.NET.Assimp.Material* pMaterial, string materialColorBase, ref bool hasMatColor, ref System.Numerics.Vector4 matColor, bool condition) + { + if (assimp.GetMaterialColor(pMaterial, materialColorBase, 0, 0, ref matColor) == Return.Success && condition) + { + hasMatColor = true; + } + } + private unsafe void SetMaterialFloatArrayFlag(Silk.NET.Assimp.Material* pMaterial, string materialBase, ref bool hasMatProperty, float matColor, bool condition) + { + if(assimp.GetMaterialFloatArray(pMaterial, materialBase, 0, 0, &matColor, (uint*)0x0) == Return.Success && condition) + { + hasMatProperty = true; + } + } + + private bool IsNotBlackColor(System.Numerics.Vector4 diffColor) + { + return diffColor != System.Numerics.Vector4.Zero; + } + + private unsafe void BuildLayeredSurface(Silk.NET.Assimp.Material* pMat, bool hasBaseColor, bool hasBaseValue, Color4 baseColor, float baseValue, TextureType textureType, MaterialAsset finalMaterial) + { + var nbTextures = assimp.GetMaterialTextureCount(pMat, textureType); + + IComputeColor computeColorNode = null; + int textureCount = 0; + if (nbTextures == 0) + { + if (hasBaseColor) + { + computeColorNode = new ComputeColor(baseColor); + } + //else if (hasBaseValue) + //{ + // computeColorNode = gcnew MaterialFloatComputeNode(baseValue); + //} + } + else + { + computeColorNode = GenerateOneTextureTypeLayers(pMat, textureType, textureCount, finalMaterial); + } + + if (computeColorNode == null) + { + return; + } + + if (textureType == TextureType.Diffuse) + { + if (assimp.GetMaterialTextureCount(pMat, TextureType.Lightmap) > 0) + { + var lightMap = GenerateOneTextureTypeLayers(pMat, TextureType.Lightmap, textureCount, finalMaterial); + if (lightMap != null) + computeColorNode = new ComputeBinaryColor(computeColorNode, lightMap, BinaryOperator.Add); + } + + finalMaterial.Attributes.Diffuse = new MaterialDiffuseMapFeature(computeColorNode); + + // TODO TEMP: Set a default diffuse model + finalMaterial.Attributes.DiffuseModel = new MaterialDiffuseLambertModelFeature(); + } + else if (textureType == TextureType.Specular) + { + var specularFeature = new MaterialSpecularMapFeature + { + SpecularMap = computeColorNode + }; + finalMaterial.Attributes.Specular = specularFeature; + + // TODO TEMP: Set a default specular model + var specularModel = new MaterialSpecularMicrofacetModelFeature + { + Fresnel = new MaterialSpecularMicrofacetFresnelSchlick(), + Visibility = new MaterialSpecularMicrofacetVisibilityImplicit(), + NormalDistribution = new MaterialSpecularMicrofacetNormalDistributionBlinnPhong() + }; + finalMaterial.Attributes.SpecularModel = specularModel; + } + else if (textureType == TextureType.Emissive) + { + // TODO: Add support + } + else if (textureType == TextureType.Ambient) + { + // TODO: Add support + } + else if (textureType == TextureType.Reflection) + { + // TODO: Add support + } + if (textureType == TextureType.Opacity) + { + // TODO: Add support + } + else if (textureType == TextureType.Shininess) + { + // TODO: Add support + } + if (textureType == TextureType.Specular) + { + // TODO: Add support + } + else if (textureType == TextureType.Normals) + { + finalMaterial.Attributes.Surface = new MaterialNormalMapFeature(computeColorNode); + } + else if (textureType == TextureType.Displacement) + { + // TODO: Add support + } + else if (textureType == TextureType.Height) + { + // TODO: Add support + } + } + + private unsafe IComputeColor GenerateOneTextureTypeLayers(Silk.NET.Assimp.Material* pMat, TextureType textureType, int textureCount, MaterialAsset finalMaterial) + { + var stack = Material.Materials.ConvertAssimpStackCppToCs(assimp, pMat, textureType); + + var compositionFathers = new Stack(); + + var sets = new Stack(); + sets.Push(0); + + var nbTextures = assimp.GetMaterialTextureCount(pMat, textureType); + + IComputeColor curComposition = null, newCompositionFather = null; + + var isRootElement = true; + IComputeColor rootMaterial = null; + + while (!stack.IsEmpty) + { + var top = stack.Pop(); + + IComputeColor curCompositionFather = null; + if (!isRootElement) + { + if (compositionFathers.Count == 0) + Logger.Error("Texture Stack Invalid : Operand without Operation."); + + curCompositionFather = compositionFathers.Pop(); + } + + var type = top.Type; + var strength = top.Blend; + var alpha = top.Alpha; + int set = sets.Peek(); + + if (type == Material.StackType.Operation) + { + set = sets.Pop(); + var realTop = (Material.StackOperation)top; + var op = realTop.Operation; + var binNode = new ComputeBinaryColor(null, null, BinaryOperator.Add); + + binNode.Operator = op switch + { + Material.Operation.Add3ds or Material.Operation.AddMaya => BinaryOperator.Add, + Material.Operation.Multiply3ds or Material.Operation.MultiplyMaya => BinaryOperator.Multiply, + _ => BinaryOperator.Add, + }; + curComposition = binNode; + } + else if (type == Material.StackType.Color) + { + var realTop = (Material.StackColor)top; + var ol = realTop.Color; + curComposition = new ComputeColor(new Color4(ol.R, ol.G, ol.B, alpha)); + } + else if (type == Material.StackType.Texture) + { + var realTop = (Material.StackTexture)top; + var texPath = realTop.TexturePath; + var indexUV = realTop.Channel; + curComposition = GetTextureReferenceNode(vfsOutputFilename, texPath, (uint)indexUV, Vector2.One, ConvertTextureMode(realTop.MappingModeU), ConvertTextureMode(realTop.MappingModeV), finalMaterial); + } + + newCompositionFather = curComposition; + + if (strength != 1.0f) + { + var strengthAlpha = strength; + if (type != Material.StackType.Color) + strengthAlpha *= alpha; + + var factorComposition = new ComputeFloat4(new Vector4(strength, strength, strength, strengthAlpha)); + curComposition = new ComputeBinaryColor(curComposition, factorComposition, BinaryOperator.Multiply); + } + else if (alpha != 1.0f && type != Material.StackType.Color) + { + var factorComposition = new ComputeFloat4(new Vector4(1.0f, 1.0f, 1.0f, alpha)); + curComposition = new ComputeBinaryColor(curComposition, factorComposition, BinaryOperator.Multiply); + } + + if (isRootElement) + { + rootMaterial = curComposition; + isRootElement = false; + compositionFathers.Push(curCompositionFather); + } + else + { + if (set == 0) + { + ((ComputeBinaryColor)curCompositionFather).LeftChild = curComposition; + compositionFathers.Push(curCompositionFather); + sets.Push(1); + } + else if (set == 1) + { + ((ComputeBinaryColor)curCompositionFather).RightChild = curComposition; + } + else + { + Logger.Error($"Texture Stack Invalid : Invalid Operand Number {set}."); + } + } + + if (type == Material.StackType.Operation) + { + compositionFathers.Push(newCompositionFather); + sets.Push(0); + } + } + + return rootMaterial; + } + + private static TextureAddressMode ConvertTextureMode(Material.MappingMode mappingMode) + { + return mappingMode switch + { + Material.MappingMode.Clamp => TextureAddressMode.Clamp, + Material.MappingMode.Decal => TextureAddressMode.Border, + Material.MappingMode.Mirror => TextureAddressMode.Mirror, + _ => TextureAddressMode.Wrap, + }; + } + + private ComputeTextureColor GetTextureReferenceNode(string vfsOutputPath, string sourceTextureFile, uint textureUVSetIndex, Vector2 textureUVscaling, TextureAddressMode addressModeU, TextureAddressMode addressModeV, MaterialAsset finalMaterial) + { + // TODO: compare with FBX importer - see if there could be some conflict between texture names + var textureValue = TextureLayerGenerator.GenerateMaterialTextureNode(vfsOutputPath, sourceTextureFile, textureUVSetIndex, textureUVscaling, addressModeU, addressModeV, Logger); + + var attachedReference = AttachedReferenceManager.GetAttachedReference(textureValue.Texture); + var referenceName = attachedReference.Url; + + // find a new and correctName + if (!textureNameCount.ContainsKey(referenceName)) + textureNameCount.Add(referenceName, 1); + else + { + int count = textureNameCount[referenceName]; + textureNameCount[referenceName] = count + 1; + referenceName = string.Concat(referenceName, "_", count); + } + + return textureValue; + } + + private unsafe List ExtractModels(Scene* scene, Dictionary meshNames, Dictionary materialNames, Dictionary nodeNames) + { + GenerateMeshNames(scene, meshNames); + + var meshList = new List(); + for (uint i = 0; i < scene->MNumMeshes; ++i) + { + var mesh = scene->MMeshes[i]; + var lMaterial = scene->MMaterials[mesh->MMaterialIndex]; + + var meshParams = new MeshParameters + { + MeshName = meshNames[(IntPtr)mesh], + MaterialName = materialNames[(IntPtr)lMaterial], + NodeName = SearchMeshNode(scene->MRootNode, i, nodeNames) + }; + + meshList.Add(meshParams); + } + + return meshList; + } + + private unsafe string SearchMeshNode(Node* node, uint meshIndex, Dictionary nodeNames) + { + for (uint i = 0; i < node->MNumMeshes; ++i) + { + if (node->MMeshes[i] == meshIndex) + return nodeNames[(IntPtr)node]; + } + + for (uint i = 0; i < node->MNumChildren; ++i) + { + var res = SearchMeshNode(node->MChildren[i], meshIndex, nodeNames); + if (res != null) + return res; + } + + return null; + } + + private unsafe List ExtractNodeHierarchy(Scene* scene, Dictionary nodeNames) + { + var allNodes = new List(); + GetNodes(scene->MRootNode, 0, nodeNames, allNodes); + return allNodes; + } + + private unsafe void GetNodes(Node* node, int depth, Dictionary nodeNames, List allNodes) + { + var newNodeInfo = new NodeInfo + { + Name = nodeNames[(IntPtr)node], + Depth = depth, + Preserve = true + }; + + allNodes.Add(newNodeInfo); + for (uint i = 0; i < node->MNumChildren; ++i) + GetNodes(node->MChildren[i], depth + 1, nodeNames, allNodes); + } + + private unsafe List ExtractAnimations(Scene* scene, Dictionary animationNames) + { + if (scene->MNumAnimations == 0) + return null; + + GenerateAnimationNames(scene, animationNames); + + var animationList = new List(); + foreach (var animationName in animationNames) + { + animationList.Add(animationName.Value); + } + + return animationList; + } + + private unsafe List ExtractTextureDependencies(Scene* scene) + { + var textureNames = new List(); + + // texture search is done by type so we need to loop on them + var allTextureTypes = new TextureType[] + { + TextureType.Diffuse, + TextureType.Specular, + TextureType.Ambient, + TextureType.Emissive, + TextureType.Height, + TextureType.Normals, + TextureType.Shininess, + TextureType.Opacity, + TextureType.Displacement, + TextureType.Lightmap, + TextureType.Reflection + }; + + for (uint i = 0; i < scene->MNumMaterials; i++) + { + foreach (var textureType in allTextureTypes) + { + var lMaterial = scene->MMaterials[i]; + var nbTextures = assimp.GetMaterialTextureCount(lMaterial, textureType); + + for (uint j = 0; j < nbTextures; ++j) + { + var path = new AssimpString(); + var mapping = TextureMapping.UV; + uint uvIndex = 0; + var blend = 0.0f; + var textureOp = TextureOp.Multiply; + var mapMode = TextureMapMode.Wrap; + uint flags = 0; + + if (assimp.GetMaterialTexture(lMaterial, textureType, j, ref path, ref mapping, ref uvIndex, ref blend, ref textureOp, ref mapMode, ref flags) == Return.Success) + { + var relFileName = path.AsString; + var fileNameToUse = Path.Combine(vfsInputPath, relFileName); + textureNames.Add(fileNameToUse); + break; + } + } + } + } + + return textureNames; + } + } + + public class MeshInfo + { + public MeshDraw Draw; + public List Bones; + public string Name; + public int MaterialIndex; + public bool HasSkinningPosition = false; + public bool HasSkinningNormal = false; + public int TotalClusterCount = 0; + } + + public class MaterialInstantiation + { + public List Parameters; + public MaterialAsset Material; + public string MaterialName; + } + + public unsafe class MaterialInstances + { + public Silk.NET.Assimp.Material* SourceMaterial; + public List Instances = new(); + public string MaterialsName; + } } diff --git a/sources/tools/Stride.Importer.Assimp/Stride.Importer.Assimp.csproj b/sources/tools/Stride.Importer.Assimp/Stride.Importer.Assimp.csproj index 8ecabe8fba..7349f776fa 100644 --- a/sources/tools/Stride.Importer.Assimp/Stride.Importer.Assimp.csproj +++ b/sources/tools/Stride.Importer.Assimp/Stride.Importer.Assimp.csproj @@ -16,7 +16,7 @@ - + diff --git a/sources/tools/Stride.Importer.Assimp/Utils.cs b/sources/tools/Stride.Importer.Assimp/Utils.cs index 0dc626ba30..5a0f457d6b 100644 --- a/sources/tools/Stride.Importer.Assimp/Utils.cs +++ b/sources/tools/Stride.Importer.Assimp/Utils.cs @@ -1,11 +1,10 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using Silk.NET.Assimp; using Stride.Animations; using Stride.Core.Mathematics; -using System; -using System.Collections.Generic; -using System.Linq; using System.Numerics; -using System.Text; -using System.Threading.Tasks; namespace Stride.Importer.Assimp { @@ -30,10 +29,8 @@ public static Core.Mathematics.Vector3 ToStrideVector3(this System.Numerics.Vect public static Color ToStrideColor(this System.Numerics.Vector4 v) => new Color(v.X, v.Y, v.Z, v.W); - public static Core.Mathematics.Quaternion ToStrideQuaternion(this System.Numerics.Quaternion q) - // TODO: Not sure why I have to do this Assimp & System.Numerics.Quaternion seems to have the same memory layout (W, X, Y, Z) - // so just passing X, Y, Z, W without swizzling should work ... - => new Core.Mathematics.Quaternion(q.Y, q.Z, q.W, q.X); + public static Core.Mathematics.Quaternion ToStrideQuaternion(this AssimpQuaternion q) + => new Core.Mathematics.Quaternion(q.X, q.Y, q.Z, q.W); public static unsafe uint GetNumUVChannels(Silk.NET.Assimp.Mesh* mesh) { @@ -57,7 +54,7 @@ public static unsafe uint GetNumColorChannels(Silk.NET.Assimp.Mesh* mesh) return (uint)n; } - public static CompressedTimeSpan AiTimeToXkTimeSpan(double time, double aiTickPerSecond) + public static CompressedTimeSpan AiTimeToStrideTimeSpan(double time, double aiTickPerSecond) { var sdTime = CompressedTimeSpan.TicksPerSecond / aiTickPerSecond * time; return new CompressedTimeSpan((int)sdTime); diff --git a/sources/tools/Stride.Importer.Common/MeshMaterials.cs b/sources/tools/Stride.Importer.Common/MeshMaterials.cs index 02ccbbe4fe..751575cf98 100644 --- a/sources/tools/Stride.Importer.Common/MeshMaterials.cs +++ b/sources/tools/Stride.Importer.Common/MeshMaterials.cs @@ -1,12 +1,14 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System.Collections.Generic; using Stride.Assets.Materials; - namespace Stride.Importer.Common { public class MeshMaterials { public Dictionary Materials; - public List Models; - public List BoneNodes; + public List Models; + public List BoneNodes; } } diff --git a/sources/tools/Stride.Importer.Common/MeshParameters.cs b/sources/tools/Stride.Importer.Common/MeshParameters.cs index 160f321431..575386dc9e 100644 --- a/sources/tools/Stride.Importer.Common/MeshParameters.cs +++ b/sources/tools/Stride.Importer.Common/MeshParameters.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System.Collections.Generic; namespace Stride.Importer.Common @@ -5,8 +8,8 @@ namespace Stride.Importer.Common public class MeshParameters { public string MaterialName; - public string MeshName; - public string NodeName; + public string MeshName; + public string NodeName; public HashSet BoneNodes; } } diff --git a/sources/tools/Stride.Importer.Common/TextureLayerGenerator.cs b/sources/tools/Stride.Importer.Common/TextureLayerGenerator.cs index 202279bc65..6e25bc3bc4 100644 --- a/sources/tools/Stride.Importer.Common/TextureLayerGenerator.cs +++ b/sources/tools/Stride.Importer.Common/TextureLayerGenerator.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System.IO; using Stride.Core.Assets; using Stride.Core.Diagnostics; @@ -26,7 +29,7 @@ public static ShaderClassSource GenerateTextureLayer(string vfsOutputPath, strin var uvSetName = "TEXCOORD"; if (textureUVSetIndex != 0) uvSetName += textureUVSetIndex; - //albedoMaterial->Add(gcnew ShaderClassSource("TextureStream", uvSetName, "TEXTEST" + uvSetIndex)); + var uvScaling = textureUVscaling; var textureName = parameterKey.Name; var needScaling = uvScaling != Vector2.One;