// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using FlaxEngine; namespace FlaxEditor.Content.Import { /// /// Importing model lightmap UVs source /// [HideInEditor] public enum ModelLightmapUVsSource : int { /// /// No lightmap UVs. /// Disable = 0, /// /// Generate lightmap UVs from model geometry. /// Generate = 1, /// /// The texcoords channel 0. /// Channel0 = 2, /// /// The texcoords channel 1. /// Channel1 = 3, /// /// The texcoords channel 2. /// Channel2 = 4, /// /// The texcoords channel 3. /// Channel3 = 5 } /// /// Declares the imported data type. /// [HideInEditor] public enum ModelType : int { /// /// The model asset. /// Model = 0, /// /// The skinned model asset. /// SkinnedModel = 1, /// /// The animation asset. /// Animation = 2, } /// /// Declares the imported animation clip duration. /// [HideInEditor] public enum AnimationDuration : int { /// /// The imported duration. /// Imported = 0, /// /// The custom duration specified via keyframes range. /// Custom = 1, } /// /// Proxy object to present model import settings in . /// [HideInEditor] public class ModelImportSettings { /// /// Type of the imported asset. /// [EditorOrder(0)] public ModelType Type { get; set; } = ModelType.Model; /// /// Enable model normal vectors recalculating. /// [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] [EditorOrder(20), DefaultValue(false)] public bool CalculateNormals { get; set; } = false; /// /// Specifies the maximum angle (in degrees) that may be between two face normals at the same vertex position that their are smoothed together. The default value is 175. /// [EditorDisplay("Geometry"), VisibleIf(nameof(ShowSmoothingNormalsAngle))] [EditorOrder(30), DefaultValue(175.0f), Limit(0, 175, 0.1f)] public float SmoothingNormalsAngle { get; set; } = 175.0f; private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals; /// /// If checked, the imported normal vectors of the mesh will be flipped (scaled by -1). /// [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] [EditorOrder(35), DefaultValue(false)] public bool FlipNormals { get; set; } = false; /// /// Enable model tangent vectors recalculating. /// [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] [EditorOrder(40), DefaultValue(false)] public bool CalculateTangents { get; set; } = false; /// /// Specifies the maximum angle (in degrees) that may be between two vertex tangents that their tangents and bi-tangents are smoothed. The default value is 45. /// [EditorDisplay("Geometry"), VisibleIf(nameof(ShowSmoothingTangentsAngle))] [EditorOrder(45), DefaultValue(45.0f), Limit(0, 45, 0.1f)] public float SmoothingTangentsAngle { get; set; } = 45.0f; private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents; /// /// Enable/disable meshes geometry optimization. /// [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] [EditorOrder(50), DefaultValue(true)] public bool OptimizeMeshes { get; set; } = true; /// /// Enable/disable geometry merge for meshes with the same materials. /// [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] [EditorOrder(60), DefaultValue(true)] public bool MergeMeshes { get; set; } = true; /// /// Enable/disable importing meshes Level of Details. /// [EditorDisplay("Geometry", "Import LODs"), VisibleIf(nameof(ShowGeometry))] [EditorOrder(70), DefaultValue(true)] public bool ImportLODs { get; set; } = true; /// /// Enable/disable importing vertex colors (channel 0 only). /// [EditorDisplay("Geometry"), VisibleIf(nameof(ShowModel))] [EditorOrder(80), DefaultValue(true)] public bool ImportVertexColors { get; set; } = true; /// /// Enable/disable importing blend shapes (morph targets). /// [EditorDisplay("Geometry"), VisibleIf(nameof(ShowSkinnedModel))] [EditorOrder(85), DefaultValue(false)] public bool ImportBlendShapes { get; set; } = false; /// /// The lightmap UVs source. /// [EditorDisplay("Geometry", "Lightmap UVs Source"), VisibleIf(nameof(ShowModel))] [EditorOrder(90), DefaultValue(ModelLightmapUVsSource.Disable)] public ModelLightmapUVsSource LightmapUVsSource { get; set; } = ModelLightmapUVsSource.Disable; /// /// If specified, all meshes which name starts with this prefix will be imported as a separate collision data (excluded used for rendering). /// [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))] [EditorOrder(100), DefaultValue("")] public string CollisionMeshesPrefix { get; set; } /// /// Custom uniform import scale. /// [EditorOrder(500), DefaultValue(1.0f), EditorDisplay("Transform")] public float Scale { get; set; } = 1.0f; /// /// Custom import geometry rotation. /// [DefaultValue(typeof(Quaternion), "0,0,0,1")] [EditorOrder(510), EditorDisplay("Transform")] public Quaternion Rotation { get; set; } = Quaternion.Identity; /// /// Custom import geometry offset. /// [DefaultValue(typeof(Float3), "0,0,0")] [EditorOrder(520), EditorDisplay("Transform")] public Float3 Translation { get; set; } = Float3.Zero; /// /// If checked, the imported geometry will be shifted to the center of mass. /// [EditorOrder(530), DefaultValue(false), EditorDisplay("Transform")] public bool CenterGeometry { get; set; } = false; /// /// Imported animation duration mode. Can use the original value or overriden by settings. /// [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] [EditorOrder(1000), DefaultValue(AnimationDuration.Imported)] public AnimationDuration Duration { get; set; } = AnimationDuration.Imported; /// /// Imported animation first frame index. Used only if Duration mode is set to Custom. /// [EditorDisplay("Animation"), VisibleIf(nameof(ShowFramesRange))] [EditorOrder(1010), DefaultValue(0.0f), Limit(0)] public float FramesRangeStart { get; set; } = 0; /// /// Imported animation last frame index. Used only if Duration mode is set to Custom. /// [EditorDisplay("Animation"), VisibleIf(nameof(ShowFramesRange))] [EditorOrder(1020), DefaultValue(0.0f), Limit(0)] public float FramesRangeEnd { get; set; } = 0; private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom; /// /// The imported animation default frame rate. Can specify the default frames per second amount for imported animation. If value is 0 then the original animation frame rate will be used. /// [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] [EditorOrder(1025), DefaultValue(0.0f), Limit(0, 1000, 0.01f)] public float DefaultFrameRate { get; set; } = 0.0f; /// /// The imported animation sampling rate. If value is 0 then the original animation speed will be used. /// [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] [EditorOrder(1030), DefaultValue(0.0f), Limit(0, 1000, 0.01f)] public float SamplingRate { get; set; } = 0.0f; /// /// The imported animation will have removed tracks with no keyframes or unspecified data. /// [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] [EditorOrder(1040), DefaultValue(true)] public bool SkipEmptyCurves { get; set; } = true; /// /// The imported animation channels will be optimized to remove redundant keyframes. /// [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] [EditorOrder(1050), DefaultValue(true)] public bool OptimizeKeyframes { get; set; } = true; /// /// If checked, the importer will import scale animation tracks (otherwise scale animation will be ignored). /// [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] [EditorOrder(1055), DefaultValue(false)] public bool ImportScaleTracks { get; set; } = false; /// /// Enables root motion extraction support from this animation. /// [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] [EditorOrder(1060), DefaultValue(false)] public bool EnableRootMotion { get; set; } = false; /// /// The custom node name to be used as a root motion source. If not specified the actual root node will be used. /// [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))] [EditorOrder(1070), DefaultValue(typeof(string), "")] public string RootNodeName { get; set; } /// /// If checked, the importer will generate a sequence of LODs based on the base LOD index. /// [EditorDisplay("Level Of Detail", "Generate LODs"), VisibleIf(nameof(ShowGeometry))] [EditorOrder(1100), DefaultValue(false)] public bool GenerateLODs { get; set; } = false; /// /// The index of the LOD from the source model data to use as a reference for following LODs generation. /// [EditorDisplay("Level Of Detail", "Base LOD"), VisibleIf(nameof(ShowGeometry))] [EditorOrder(1110), DefaultValue(0), Limit(0, Model.MaxLODs - 1)] public int BaseLOD { get; set; } = 0; /// /// The amount of LODs to include in the model (all remaining ones starting from Base LOD will be generated). /// [EditorDisplay("Level Of Detail", "LOD Count"), VisibleIf(nameof(ShowGeometry))] [EditorOrder(1120), DefaultValue(4), Limit(1, Model.MaxLODs)] public int LODCount { get; set; } = 4; /// /// The target amount of triangles for the generated LOD (based on the higher LOD). Normalized to range 0-1. For instance 0.4 cuts the triangle count to 40%. /// [EditorDisplay("Level Of Detail"), VisibleIf(nameof(ShowGeometry))] [EditorOrder(1130), DefaultValue(0.5f), Limit(0, 1, 0.001f)] public float TriangleReduction { get; set; } = 0.5f; /// /// If checked, the importer will create materials for model meshes as specified in the file. /// [EditorDisplay("Materials"), VisibleIf(nameof(ShowGeometry))] [EditorOrder(400), DefaultValue(true)] public bool ImportMaterials { get; set; } = true; /// /// If checked, the importer will import texture files used by the model and any embedded texture resources. /// [EditorDisplay("Materials"), VisibleIf(nameof(ShowGeometry))] [EditorOrder(410), DefaultValue(true)] public bool ImportTextures { get; set; } = true; /// /// If checked, the importer will try to restore the model material slots. /// [EditorDisplay("Materials", "Restore Materials On Reimport"), VisibleIf(nameof(ShowGeometry))] [EditorOrder(420), DefaultValue(true)] public bool RestoreMaterialsOnReimport { get; set; } = true; /// /// If checked, enables generation of Signed Distance Field (SDF). /// [EditorDisplay("SDF"), VisibleIf(nameof(ShowModel))] [EditorOrder(1500), DefaultValue(false)] public bool GenerateSDF { get; set; } = false; /// /// Resolution scale for generated Signed Distance Field (SDF) texture. Higher values improve accuracy but increase memory usage and reduce performance. /// [EditorDisplay("SDF"), VisibleIf(nameof(ShowModel))] [EditorOrder(1510), DefaultValue(1.0f), Limit(0.0001f, 100.0f)] public float SDFResolution { get; set; } = 1.0f; /// /// If checked, the imported mesh/animations are splitted into separate assets. Used if ObjectIndex is set to -1. /// [EditorOrder(2000), DefaultValue(false), EditorDisplay("Splitting")] public bool SplitObjects { get; set; } = false; /// /// The zero-based index for the mesh/animation clip to import. If the source file has more than one mesh/animation it can be used to pick a desire object. Default -1 imports all objects. /// [EditorOrder(2010), DefaultValue(-1), EditorDisplay("Splitting")] public int ObjectIndex { get; set; } = -1; private bool ShowGeometry => Type == ModelType.Model || Type == ModelType.SkinnedModel; private bool ShowModel => Type == ModelType.Model; private bool ShowSkinnedModel => Type == ModelType.SkinnedModel; private bool ShowAnimation => Type == ModelType.Animation; [StructLayout(LayoutKind.Sequential)] [NativeMarshalling(typeof(InternalOptionsMarshaler))] internal struct InternalOptions { public ModelType Type; // Geometry public byte CalculateNormals; public float SmoothingNormalsAngle; public byte FlipNormals; public float SmoothingTangentsAngle; public byte CalculateTangents; public byte OptimizeMeshes; public byte MergeMeshes; public byte ImportLODs; public byte ImportVertexColors; public byte ImportBlendShapes; public ModelLightmapUVsSource LightmapUVsSource; public string CollisionMeshesPrefix; // Transform public float Scale; public Quaternion Rotation; public Float3 Translation; public byte CenterGeometry; // Animation public AnimationDuration Duration; public float FramesRangeStart; public float FramesRangeEnd; public float DefaultFrameRate; public float SamplingRate; public byte SkipEmptyCurves; public byte OptimizeKeyframes; public byte ImportScaleTracks; public byte EnableRootMotion; public string RootNodeName; // Level Of Detail public byte GenerateLODs; public int BaseLOD; public int LODCount; public float TriangleReduction; // Misc public byte ImportMaterials; public byte ImportTextures; public byte RestoreMaterialsOnReimport; // SDF public byte GenerateSDF; public float SDFResolution; // Splitting public byte SplitObjects; public int ObjectIndex; } [CustomMarshaller(typeof(InternalOptions), MarshalMode.Default, typeof(InternalOptionsMarshaler))] internal static class InternalOptionsMarshaler { [Unmanaged] [StructLayout(LayoutKind.Sequential)] internal struct InternalOptionsNative { public int Type; // Geometry public byte CalculateNormals; public float SmoothingNormalsAngle; public byte FlipNormals; public float SmoothingTangentsAngle; public byte CalculateTangents; public byte OptimizeMeshes; public byte MergeMeshes; public byte ImportLODs; public byte ImportVertexColors; public byte ImportBlendShapes; public int LightmapUVsSource; //[MarshalAs(UnmanagedType.LPWStr)] public IntPtr CollisionMeshesPrefix; // Transform public float Scale; public Quaternion Rotation; public Float3 Translation; public byte CenterGeometry; // Animation public int Duration; public float FramesRangeStart; public float FramesRangeEnd; public float DefaultFrameRate; public float SamplingRate; public byte SkipEmptyCurves; public byte OptimizeKeyframes; public byte EnableRootMotion; //[MarshalAs(UnmanagedType.LPWStr)] public IntPtr RootNodeName; // Level Of Detail public byte GenerateLODs; public int BaseLOD; public int LODCount; public float TriangleReduction; // Misc public byte ImportMaterials; public byte ImportTextures; public byte RestoreMaterialsOnReimport; // SDF public byte GenerateSDF; public float SDFResolution; // Splitting public byte SplitObjects; public int ObjectIndex; } internal static InternalOptions ConvertToManaged(InternalOptionsNative unmanaged) => ToManaged(unmanaged); internal static InternalOptionsNative ConvertToUnmanaged(InternalOptions managed) => ToNative(managed); internal static InternalOptions ToManaged(InternalOptionsNative managed) { return new InternalOptions() { Type = (ModelType)managed.Type, CalculateNormals = managed.CalculateNormals, SmoothingNormalsAngle = managed.SmoothingNormalsAngle, FlipNormals = managed.FlipNormals, SmoothingTangentsAngle = managed.SmoothingTangentsAngle, CalculateTangents = managed.CalculateTangents, OptimizeMeshes = managed.OptimizeMeshes, MergeMeshes = managed.MergeMeshes, ImportLODs = managed.ImportLODs, ImportVertexColors = managed.ImportVertexColors, ImportBlendShapes = managed.ImportBlendShapes, LightmapUVsSource = (ModelLightmapUVsSource)managed.LightmapUVsSource, CollisionMeshesPrefix = ManagedString.ToManaged(managed.CollisionMeshesPrefix), Scale = managed.Scale, Rotation = managed.Rotation, Translation = managed.Translation, CenterGeometry = managed.CenterGeometry, Duration = (AnimationDuration)managed.Duration, FramesRangeStart = managed.FramesRangeStart, FramesRangeEnd = managed.FramesRangeEnd, DefaultFrameRate = managed.DefaultFrameRate, SamplingRate = managed.SamplingRate, SkipEmptyCurves = managed.SkipEmptyCurves, OptimizeKeyframes = managed.OptimizeKeyframes, EnableRootMotion = managed.EnableRootMotion, RootNodeName = ManagedString.ToManaged(managed.RootNodeName), GenerateLODs = managed.GenerateLODs, BaseLOD = managed.BaseLOD, LODCount = managed.LODCount, TriangleReduction = managed.TriangleReduction, ImportMaterials = managed.ImportMaterials, ImportTextures = managed.ImportTextures, RestoreMaterialsOnReimport = managed.RestoreMaterialsOnReimport, GenerateSDF = managed.GenerateSDF, SDFResolution = managed.SDFResolution, SplitObjects = managed.SplitObjects, ObjectIndex = managed.ObjectIndex, }; } internal static InternalOptionsNative ToNative(InternalOptions managed) { return new InternalOptionsNative() { Type = (int)managed.Type, CalculateNormals = managed.CalculateNormals, SmoothingNormalsAngle = managed.SmoothingNormalsAngle, FlipNormals = managed.FlipNormals, SmoothingTangentsAngle = managed.SmoothingTangentsAngle, CalculateTangents = managed.CalculateTangents, OptimizeMeshes = managed.OptimizeMeshes, MergeMeshes = managed.MergeMeshes, ImportLODs = managed.ImportLODs, ImportVertexColors = managed.ImportVertexColors, ImportBlendShapes = managed.ImportBlendShapes, LightmapUVsSource = (int)managed.LightmapUVsSource, CollisionMeshesPrefix = ManagedString.ToNative(managed.CollisionMeshesPrefix), Scale = managed.Scale, Rotation = managed.Rotation, Translation = managed.Translation, CenterGeometry = managed.CenterGeometry, Duration = (int)managed.Duration, FramesRangeStart = managed.FramesRangeStart, FramesRangeEnd = managed.FramesRangeEnd, DefaultFrameRate = managed.DefaultFrameRate, SamplingRate = managed.SamplingRate, SkipEmptyCurves = managed.SkipEmptyCurves, OptimizeKeyframes = managed.OptimizeKeyframes, EnableRootMotion = managed.EnableRootMotion, RootNodeName = ManagedString.ToNative(managed.RootNodeName), GenerateLODs = managed.GenerateLODs, BaseLOD = managed.BaseLOD, LODCount = managed.LODCount, TriangleReduction = managed.TriangleReduction, ImportMaterials = managed.ImportMaterials, ImportTextures = managed.ImportTextures, RestoreMaterialsOnReimport = managed.RestoreMaterialsOnReimport, GenerateSDF = managed.GenerateSDF, SDFResolution = managed.SDFResolution, SplitObjects = managed.SplitObjects, ObjectIndex = managed.ObjectIndex, }; } internal static void Free(InternalOptionsNative unmanaged) { ManagedString.Free(unmanaged.CollisionMeshesPrefix); ManagedString.Free(unmanaged.RootNodeName); } } internal void ToInternal(out InternalOptions options) { options = new InternalOptions { Type = Type, CalculateNormals = (byte)(CalculateNormals ? 1 : 0), SmoothingNormalsAngle = SmoothingNormalsAngle, FlipNormals = (byte)(FlipNormals ? 1 : 0), SmoothingTangentsAngle = SmoothingTangentsAngle, CalculateTangents = (byte)(CalculateTangents ? 1 : 0), OptimizeMeshes = (byte)(OptimizeMeshes ? 1 : 0), MergeMeshes = (byte)(MergeMeshes ? 1 : 0), ImportLODs = (byte)(ImportLODs ? 1 : 0), ImportVertexColors = (byte)(ImportVertexColors ? 1 : 0), ImportBlendShapes = (byte)(ImportBlendShapes ? 1 : 0), LightmapUVsSource = LightmapUVsSource, CollisionMeshesPrefix = CollisionMeshesPrefix, Scale = Scale, Rotation = Rotation, Translation = Translation, CenterGeometry = (byte)(CenterGeometry ? 1 : 0), Duration = Duration, FramesRangeStart = FramesRangeStart, FramesRangeEnd = FramesRangeEnd, DefaultFrameRate = DefaultFrameRate, SamplingRate = SamplingRate, SkipEmptyCurves = (byte)(SkipEmptyCurves ? 1 : 0), OptimizeKeyframes = (byte)(OptimizeKeyframes ? 1 : 0), ImportScaleTracks = (byte)(ImportScaleTracks ? 1 : 0), EnableRootMotion = (byte)(EnableRootMotion ? 1 : 0), RootNodeName = RootNodeName, GenerateLODs = (byte)(GenerateLODs ? 1 : 0), BaseLOD = BaseLOD, LODCount = LODCount, TriangleReduction = TriangleReduction, ImportMaterials = (byte)(ImportMaterials ? 1 : 0), ImportTextures = (byte)(ImportTextures ? 1 : 0), RestoreMaterialsOnReimport = (byte)(RestoreMaterialsOnReimport ? 1 : 0), GenerateSDF = (byte)(GenerateSDF ? 1 : 0), SDFResolution = SDFResolution, SplitObjects = (byte)(SplitObjects ? 1 : 0), ObjectIndex = ObjectIndex, }; } internal void FromInternal(ref InternalOptions options) { Type = options.Type; CalculateNormals = options.CalculateNormals != 0; SmoothingNormalsAngle = options.SmoothingNormalsAngle; FlipNormals = options.FlipNormals != 0; SmoothingTangentsAngle = options.SmoothingTangentsAngle; CalculateTangents = options.CalculateTangents != 0; OptimizeMeshes = options.OptimizeMeshes != 0; MergeMeshes = options.MergeMeshes != 0; ImportLODs = options.ImportLODs != 0; ImportVertexColors = options.ImportVertexColors != 0; ImportBlendShapes = options.ImportBlendShapes != 0; LightmapUVsSource = options.LightmapUVsSource; CollisionMeshesPrefix = options.CollisionMeshesPrefix; Scale = options.Scale; Rotation = options.Rotation; Translation = options.Translation; CenterGeometry = options.CenterGeometry != 0; FramesRangeStart = options.FramesRangeStart; FramesRangeEnd = options.FramesRangeEnd; DefaultFrameRate = options.DefaultFrameRate; SamplingRate = options.SamplingRate; SkipEmptyCurves = options.SkipEmptyCurves != 0; OptimizeKeyframes = options.OptimizeKeyframes != 0; ImportScaleTracks = options.ImportScaleTracks != 0; EnableRootMotion = options.EnableRootMotion != 0; RootNodeName = options.RootNodeName; GenerateLODs = options.GenerateLODs != 0; BaseLOD = options.BaseLOD; LODCount = options.LODCount; TriangleReduction = options.TriangleReduction; ImportMaterials = options.ImportMaterials != 0; ImportTextures = options.ImportTextures != 0; RestoreMaterialsOnReimport = options.RestoreMaterialsOnReimport != 0; GenerateSDF = options.GenerateSDF != 0; SDFResolution = options.SDFResolution; SplitObjects = options.SplitObjects != 0; ObjectIndex = options.ObjectIndex; } /// /// Tries the restore the asset import options from the target resource file. Applies the project default options too. /// /// The options. /// The asset path. /// True settings has been restored, otherwise false. public static void TryRestore(ref ModelImportSettings options, string assetPath) { ModelImportEntry.Internal_GetModelImportOptions(assetPath, out var internalOptions); options.FromInternal(ref internalOptions); } } /// /// Model asset import entry. /// /// public partial class ModelImportEntry : AssetImportEntry { private ModelImportSettings _settings = new ModelImportSettings(); /// /// Initializes a new instance of the class. /// /// The import request. public ModelImportEntry(ref Request request) : base(ref request) { // Try to restore target asset model import options (useful for fast reimport) ModelImportSettings.TryRestore(ref _settings, ResultUrl); } /// public override object Settings => _settings; /// public override bool TryOverrideSettings(object settings) { if (settings is ModelImportSettings o) { _settings = o; return true; } return false; } /// public override bool Import() { return Editor.Import(SourceUrl, ResultUrl, _settings); } #region Internal Calls [LibraryImport("FlaxEngine", EntryPoint = "ModelImportEntryInternal_GetModelImportOptions", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_GetModelImportOptions(string path, out ModelImportSettings.InternalOptions result); #endregion } }