// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. using System.ComponentModel; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; 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 { /// /// Gets or sets the type of the imported asset. /// [EditorOrder(0), Tooltip("Type of the imported asset")] public ModelType Type { get; set; } = ModelType.Model; /// /// True if calculate model normals, otherwise will import them. /// [EditorOrder(20), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("Enable model normal vectors recalculating")] public bool CalculateNormals { get; set; } = false; /// /// Calculated normals smoothing angle. /// [VisibleIf("CalculateNormals")] [EditorOrder(30), DefaultValue(175.0f), Limit(0, 175, 0.1f), EditorDisplay("Geometry"), Tooltip("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.")] public float SmoothingNormalsAngle { get; set; } = 175.0f; /// /// If checked, the imported normal vectors of the mesh will be flipped (scaled by -1). /// [EditorOrder(35), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("If checked, the imported normal vectors of the mesh will be flipped (scaled by -1).")] public bool FlipNormals { get; set; } = false; /// /// True if calculate model tangents, otherwise will import them. /// [EditorOrder(40), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("Enable model tangent vectors recalculating")] public bool CalculateTangents { get; set; } = false; /// /// Calculated normals smoothing angle. /// [VisibleIf("CalculateTangents")] [EditorOrder(45), DefaultValue(45.0f), Limit(0, 45, 0.1f), EditorDisplay("Geometry"), Tooltip("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.")] public float SmoothingTangentsAngle { get; set; } = 45.0f; /// /// Enable/disable meshes geometry optimization. /// [EditorOrder(50), DefaultValue(true), EditorDisplay("Geometry"), Tooltip("Enable/disable meshes geometry optimization")] public bool OptimizeMeshes { get; set; } = true; /// /// Enable/disable geometry merge for meshes with the same materials. /// [EditorOrder(60), DefaultValue(true), EditorDisplay("Geometry"), Tooltip("Enable/disable geometry merge for meshes with the same materials")] public bool MergeMeshes { get; set; } = true; /// /// Enable/disable importing meshes Level of Details. /// [EditorOrder(70), DefaultValue(true), EditorDisplay("Geometry", "Import LODs"), Tooltip("Enable/disable importing meshes Level of Details")] public bool ImportLODs { get; set; } = true; /// /// Enable/disable importing vertex colors (channel 0 only). /// [EditorOrder(80), DefaultValue(true), EditorDisplay("Geometry"), Tooltip("Enable/disable importing vertex colors (channel 0 only)")] public bool ImportVertexColors { get; set; } = true; /// /// Enable/disable importing blend shapes (morph targets). /// [EditorOrder(85), DefaultValue(false), EditorDisplay("Geometry"), Tooltip("Enable/disable importing blend shapes (morph targets).")] public bool ImportBlendShapes { get; set; } = false; /// /// The lightmap UVs source. /// [EditorOrder(90), DefaultValue(ModelLightmapUVsSource.Disable), EditorDisplay("Geometry", "Lightmap UVs Source"), Tooltip("Model lightmap UVs source")] 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). /// [EditorOrder(100), DefaultValue(""), EditorDisplay("Geometry")] public string CollisionMeshesPrefix { get; set; } /// /// Custom uniform import scale. /// [EditorOrder(500), DefaultValue(1.0f), EditorDisplay("Transform"), Tooltip("Custom uniform import scale")] public float Scale { get; set; } = 1.0f; /// /// Custom import geometry rotation. /// [DefaultValue(typeof(Quaternion), "0,0,0,1")] [EditorOrder(510), EditorDisplay("Transform"), Tooltip("Custom import geometry rotation")] public Quaternion Rotation { get; set; } = Quaternion.Identity; /// /// Custom import geometry offset. /// [DefaultValue(typeof(Vector3), "0,0,0")] [EditorOrder(520), EditorDisplay("Transform"), Tooltip("Custom import geometry offset")] public Vector3 Translation { get; set; } = Vector3.Zero; /// /// If checked, the imported geometry will be shifted to the center of mass. /// [EditorOrder(530), DefaultValue(false), EditorDisplay("Transform"), Tooltip("If checked, the imported geometry will be shifted to the center of mass.")] public bool CenterGeometry { get; set; } = false; /// /// The imported animation duration mode. /// [EditorOrder(1000), DefaultValue(AnimationDuration.Imported), EditorDisplay("Animation"), Tooltip("Imported animation duration mode. Can use the original value or overriden by settings.")] public AnimationDuration Duration { get; set; } = AnimationDuration.Imported; /// /// The imported animation first frame index. Used only if Duration mode is set to Custom. /// [EditorOrder(1010), DefaultValue(0.0f), Limit(0), EditorDisplay("Animation"), Tooltip("Imported animation first frame index. Used only if Duration mode is set to Custom.")] public float FramesRangeStart { get; set; } = 0; /// /// The imported animation end frame index. Used only if Duration mode is set to Custom. /// [EditorOrder(1020), DefaultValue(0.0f), Limit(0), EditorDisplay("Animation"), Tooltip("Imported animation last frame index. Used only if Duration mode is set to Custom.")] public float FramesRangeEnd { get; set; } = 0; /// /// 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. /// [EditorOrder(1025), DefaultValue(0.0f), Limit(0, 1000, 0.01f), EditorDisplay("Animation"), Tooltip("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.")] public float DefaultFrameRate { get; set; } = 0.0f; /// /// The imported animation sampling rate. If value is 0 then the original animation speed will be used. /// [EditorOrder(1030), DefaultValue(0.0f), Limit(0, 1000, 0.01f), EditorDisplay("Animation"), Tooltip("The imported animation sampling rate. If value is 0 then the original animation speed will be used.")] public float SamplingRate { get; set; } = 0.0f; /// /// The imported animation will have removed tracks with no keyframes or unspecified data. /// [EditorOrder(1040), DefaultValue(true), EditorDisplay("Animation"), Tooltip("The imported animation will have removed tracks with no keyframes or unspecified data.")] public bool SkipEmptyCurves { get; set; } = true; /// /// The imported animation channels will be optimized to remove redundant keyframes. /// [EditorOrder(1050), DefaultValue(true), EditorDisplay("Animation"), Tooltip("The imported animation channels will be optimized to remove redundant keyframes.")] public bool OptimizeKeyframes { get; set; } = true; /// /// Enables root motion extraction support from this animation. /// [EditorOrder(1060), DefaultValue(false), EditorDisplay("Animation"), Tooltip("Enables root motion extraction support from this animation.")] 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. /// [EditorOrder(1070), DefaultValue(typeof(string), ""), EditorDisplay("Animation"), Tooltip("The custom node name to be used as a root motion source. If not specified the actual root node will be used.")] public string RootNodeName { get; set; } /// /// If checked, the importer will generate a sequence of LODs based on the base LOD index. /// [EditorOrder(1100), DefaultValue(false), EditorDisplay("Level Of Detail", "Generate LODs"), Tooltip("If checked, the importer will generate a sequence of LODs based on the base LOD index.")] 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. /// [EditorOrder(1110), DefaultValue(0), Limit(0, Model.MaxLODs - 1), EditorDisplay("Level Of Detail", "Base LOD"), Tooltip("The index of the LOD from the source model data to use as a reference for following LODs generation.")] 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). /// [EditorOrder(1120), DefaultValue(4), Limit(1, Model.MaxLODs), EditorDisplay("Level Of Detail", "LOD Count"), Tooltip("The amount of LODs to include in the model (all remaining ones starting from Base LOD will be generated).")] 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%. /// [EditorOrder(1130), DefaultValue(0.5f), Limit(0, 1, 0.001f), EditorDisplay("Level Of Detail"), Tooltip("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%.")] public float TriangleReduction { get; set; } = 0.5f; /// /// If checked, the importer will create materials for model meshes as specified in the file. /// [EditorOrder(400), DefaultValue(true), EditorDisplay("Materials"), Tooltip("If checked, the importer will create materials for model meshes as specified in the file.")] public bool ImportMaterials { get; set; } = true; /// /// If checked, the importer will import texture files used by the model and any embedded texture resources. /// [EditorOrder(410), DefaultValue(true), EditorDisplay("Materials"), Tooltip("If checked, the importer will import texture files used by the model and any embedded texture resources.")] public bool ImportTextures { get; set; } = true; /// /// If checked, the importer will try to restore the model material slots. /// [EditorOrder(420), DefaultValue(true), EditorDisplay("Materials", "Restore Materials On Reimport"), Tooltip("If checked, the importer will try to restore the assigned materials to the model slots.")] public bool RestoreMaterialsOnReimport { get; set; } = true; /// /// If checked, enables generation of Signed Distance Field (SDF). /// [EditorOrder(1500), DefaultValue(false), EditorDisplay("SDF"), VisibleIf(nameof(Type_Model))] 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. /// [EditorOrder(1510), DefaultValue(1.0f), Limit(0.0001f, 100.0f), EditorDisplay("SDF"), VisibleIf(nameof(Type_Model))] 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"), Tooltip("If checked, the imported mesh/animations are splitted into separate assets. Used if ObjectIndex is set to -1.")] 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"), Tooltip("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.")] public int ObjectIndex { get; set; } = -1; private bool Type_Model => Type == ModelType.Model; [StructLayout(LayoutKind.Sequential)] 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 Vector3 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 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; } 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), 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; 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 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 [MethodImpl(MethodImplOptions.InternalCall)] internal static extern void Internal_GetModelImportOptions(string path, out ModelImportSettings.InternalOptions result); #endregion } }