// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #pragma once #if COMPILE_WITH_MODEL_TOOL #include "Engine/Core/Config.h" #include "Engine/Content/Assets/ModelBase.h" #if USE_EDITOR #include "Engine/Core/ISerializable.h" #include "Engine/Graphics/Models/ModelData.h" #include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Animations/AnimationData.h" class JsonWriter; /// /// The model file import data types (used as flags). /// enum class ImportDataTypes : int32 { None = 0, /// /// Imports materials and meshes. /// Geometry = 1 << 0, /// /// Imports the skeleton bones hierarchy. /// Skeleton = 1 << 1, /// /// Imports the animations. /// Animations = 1 << 2, /// /// Imports the scene nodes hierarchy. /// Nodes = 1 << 3, /// /// Imports the materials. /// Materials = 1 << 4, /// /// Imports the textures. /// Textures = 1 << 5, }; DECLARE_ENUM_OPERATORS(ImportDataTypes); /// /// Imported model data container. Represents unified model source file data (meshes, animations, skeleton, materials). /// class ImportedModelData { public: struct LOD { Array Meshes; BoundingBox GetBox() const; }; struct Node { /// /// The parent node index. The root node uses value -1. /// int32 ParentIndex; /// /// The local transformation of the node, relative to the parent node. /// Transform LocalTransform; /// /// The name of this node. /// String Name; }; public: /// /// The import data types types. /// ImportDataTypes Types; /// /// The textures slots. /// Array Textures; /// /// The material slots. /// Array Materials; /// /// The level of details data. /// Array LODs; /// /// The skeleton data. /// SkeletonData Skeleton; /// /// The scene nodes. /// Array Nodes; /// /// The node animations. /// AnimationData Animation; public: /// /// Initializes a new instance of the class. /// /// The types. ImportedModelData(ImportDataTypes types) { Types = types; } /// /// Finalizes an instance of the class. /// ~ImportedModelData() { // Ensure to cleanup data for (int32 i = 0; i < LODs.Count(); i++) LODs[i].Meshes.ClearDelete(); } }; #endif struct ModelSDFHeader { Float3 LocalToUVWMul; float WorldUnitsPerVoxel; Float3 LocalToUVWAdd; float MaxDistance; Float3 LocalBoundsMin; int32 MipLevels; Float3 LocalBoundsMax; int32 Width; int32 Height; int32 Depth; PixelFormat Format; float ResolutionScale; int32 LOD; ModelSDFHeader() = default; ModelSDFHeader(const ModelBase::SDFData& sdf, const struct GPUTextureDescription& desc); }; struct ModelSDFMip { int32 MipIndex; uint32 RowPitch; uint32 SlicePitch; ModelSDFMip() = default; ModelSDFMip(int32 mipIndex, uint32 rowPitch, uint32 slicePitch); ModelSDFMip(int32 mipIndex, const TextureMipData& mip); }; /// /// Models data importing and processing utility. /// API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API ModelTool { DECLARE_SCRIPTING_TYPE_MINIMAL(ModelTool); // Optional: inputModel or modelData // Optional: outputSDF or null, outputStream or null static bool GenerateModelSDF(class Model* inputModel, class ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, class MemoryWriteStream* outputStream, const StringView& assetName, float backfacesThreshold = 0.6f); #if USE_EDITOR public: /// /// Declares the imported data type. /// API_ENUM(Attributes="HideInEditor") enum class ModelType : int32 { // The model asset. Model = 0, // The skinned model asset. SkinnedModel = 1, // The animation asset. Animation = 2, }; /// /// Declares the imported animation clip duration. /// API_ENUM(Attributes="HideInEditor") enum class AnimationDuration : int32 { // The imported duration. Imported = 0, // The custom duration specified via keyframes range. Custom = 1, }; /// /// Model import options. /// API_STRUCT(Attributes="HideInEditor") struct FLAXENGINE_API Options : public ISerializable { DECLARE_SCRIPTING_TYPE_MINIMAL(Options); // Type of the imported asset. API_FIELD(Attributes="EditorOrder(0)") ModelType Type = ModelType::Model; public: // Geometry // Enable model normal vectors recalculating. API_FIELD(Attributes="EditorOrder(20), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))") bool CalculateNormals = 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. API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSmoothingNormalsAngle)), Limit(0, 175, 0.1f)") float SmoothingNormalsAngle = 175.0f; // If checked, the imported normal vectors of the mesh will be flipped (scaled by -1). API_FIELD(Attributes="EditorOrder(35), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))") bool FlipNormals = false; // Enable model tangent vectors recalculating. API_FIELD(Attributes="EditorOrder(40), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))") bool CalculateTangents = 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. API_FIELD(Attributes="EditorOrder(45), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSmoothingTangentsAngle)), Limit(0, 45, 0.1f)") float SmoothingTangentsAngle = 45.0f; // Enable/disable meshes geometry optimization. API_FIELD(Attributes="EditorOrder(50), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))") bool OptimizeMeshes = true; // Enable/disable geometry merge for meshes with the same materials. API_FIELD(Attributes="EditorOrder(60), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))") bool MergeMeshes = true; // Enable/disable importing meshes Level of Details. API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Geometry\", \"Import LODs\"), VisibleIf(nameof(ShowGeometry))") bool ImportLODs = true; // Enable/disable importing vertex colors (channel 0 only). API_FIELD(Attributes="EditorOrder(80), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowModel))") bool ImportVertexColors = true; // Enable/disable importing blend shapes (morph targets). API_FIELD(Attributes="EditorOrder(85), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowSkinnedModel))") bool ImportBlendShapes = false; // The lightmap UVs source. API_FIELD(Attributes="EditorOrder(90), EditorDisplay(\"Geometry\", \"Lightmap UVs Source\"), VisibleIf(nameof(ShowModel))") ModelLightmapUVsSource LightmapUVsSource = ModelLightmapUVsSource::Disable; // If specified, all meshes which name starts with this prefix will be imported as a separate collision data (excluded used for rendering). API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))") String CollisionMeshesPrefix = TEXT(""); public: // Transform // Custom uniform import scale. API_FIELD(Attributes="EditorOrder(500), EditorDisplay(\"Transform\")") float Scale = 1.0f; // Custom import geometry rotation. API_FIELD(Attributes="EditorOrder(510), EditorDisplay(\"Transform\")") Quaternion Rotation = Quaternion::Identity; // Custom import geometry offset. API_FIELD(Attributes="EditorOrder(520), EditorDisplay(\"Transform\")") Float3 Translation = Float3::Zero; // If checked, the imported geometry will be shifted to the center of mass. API_FIELD(Attributes="EditorOrder(530), EditorDisplay(\"Transform\")") bool CenterGeometry = false; public: // Animation // Imported animation duration mode. Can use the original value or overriden by settings. API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))") AnimationDuration Duration = AnimationDuration::Imported; // Imported animation first/last frame index. Used only if Duration mode is set to Custom. API_FIELD(Attributes="EditorOrder(1010), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowFramesRange)), Limit(0)") Float2 FramesRange = Float2::Zero; // 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. API_FIELD(Attributes="EditorOrder(1020), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation)), Limit(0, 1000, 0.01f)") float DefaultFrameRate = 0.0f; // The imported animation sampling rate. If value is 0 then the original animation speed will be used. API_FIELD(Attributes="EditorOrder(1030), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation)), Limit(0, 1000, 0.01f)") float SamplingRate = 0.0f; // The imported animation will have removed tracks with no keyframes or unspecified data. API_FIELD(Attributes="EditorOrder(1040), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))") bool SkipEmptyCurves = true; // The imported animation channels will be optimized to remove redundant keyframes. API_FIELD(Attributes="EditorOrder(1050), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))") bool OptimizeKeyframes = true; // If checked, the importer will import scale animation tracks (otherwise scale animation will be ignored). API_FIELD(Attributes="EditorOrder(1055), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))") bool ImportScaleTracks = false; // Enables root motion extraction support from this animation. API_FIELD(Attributes="EditorOrder(1060), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))") bool EnableRootMotion = false; // The custom node name to be used as a root motion source. If not specified the actual root node will be used. API_FIELD(Attributes="EditorOrder(1070), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))") String RootNodeName = TEXT(""); public: // Level Of Detail // If checked, the importer will generate a sequence of LODs based on the base LOD index. API_FIELD(Attributes="EditorOrder(1100), EditorDisplay(\"Level Of Detail\", \"Generate LODs\"), VisibleIf(nameof(ShowGeometry))") bool GenerateLODs = false; // The index of the LOD from the source model data to use as a reference for following LODs generation. API_FIELD(Attributes="EditorOrder(1110), EditorDisplay(\"Level Of Detail\", \"Base LOD\"), VisibleIf(nameof(ShowGeometry)), Limit(0, 5)") int32 BaseLOD = 0; // The amount of LODs to include in the model (all remaining ones starting from Base LOD will be generated). API_FIELD(Attributes="EditorOrder(1120), EditorDisplay(\"Level Of Detail\", \"LOD Count\"), VisibleIf(nameof(ShowGeometry)), Limit(1, 6)") int32 LODCount = 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%. API_FIELD(Attributes="EditorOrder(1130), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGeometry)), Limit(0, 1, 0.001f)") float TriangleReduction = 0.5f; // Whether to do a sloppy mesh optimization. This is faster and does not follow the topology of the original mesh. API_FIELD(Attributes="EditorOrder(1140), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGeometry))") bool Sloppy = true; // Only used if Sloppy is false. Target error is an approximate measure of the deviation from the original mesh using distance normalized to [0..1] range (e.g. 1e-2f means that simplifier will try to maintain the error to be below 1% of the mesh extents). API_FIELD(Attributes="EditorOrder(1150), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGeometry)), Limit(0.01f, 1, 0.001f)") float LODTargetError = 0.1f; public: // Materials // If checked, the importer will create materials for model meshes as specified in the file. API_FIELD(Attributes="EditorOrder(400), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))") bool ImportMaterials = true; // If checked, the importer will import texture files used by the model and any embedded texture resources. API_FIELD(Attributes="EditorOrder(410), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))") bool ImportTextures = true; // If checked, the importer will try to restore the model material slots. API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Materials\", \"Restore Materials On Reimport\"), VisibleIf(nameof(ShowGeometry))") bool RestoreMaterialsOnReimport = true; public: // SDF // If checked, enables generation of Signed Distance Field (SDF). API_FIELD(Attributes="EditorOrder(1500), EditorDisplay(\"SDF\"), VisibleIf(nameof(ShowModel))") bool GenerateSDF = false; // Resolution scale for generated Signed Distance Field (SDF) texture. Higher values improve accuracy but increase memory usage and reduce performance. API_FIELD(Attributes="EditorOrder(1510), EditorDisplay(\"SDF\"), VisibleIf(nameof(ShowModel)), Limit(0.0001f, 100.0f)") float SDFResolution = 1.0f; public: // Splitting // If checked, the imported mesh/animations are splitted into separate assets. Used if ObjectIndex is set to -1. API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Splitting\")") bool SplitObjects = 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. API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Splitting\")") int32 ObjectIndex = -1; // Runtime data for objects splitting during import (used internally) void* SplitContext = nullptr; Function OnSplitImport; public: // [ISerializable] void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; }; public: /// /// Imports the model source file data. /// /// The file path. /// The output data. /// The import options. /// The error message container. /// True if fails, otherwise false. static bool ImportData(const String& path, ImportedModelData& data, Options& options, String& errorMsg); /// /// Imports the model. /// /// The file path. /// The output data. /// The import options. /// The error message container. /// The output folder for the additional imported data - optional. Used to auto-import textures and material assets. /// True if fails, otherwise false. static bool ImportModel(const String& path, ModelData& meshData, Options& options, String& errorMsg, const String& autoImportOutput = String::Empty); public: static int32 DetectLodIndex(const String& nodeName); static bool FindTexture(const String& sourcePath, const String& file, String& path); /// /// Gets the local transformations to go from rootIndex to index. /// /// The nodes containing the local transformations. /// The root index. /// The current index. /// The transformation at this index. template static Transform CombineTransformsFromNodeIndices(Array& nodes, int32 rootIndex, int32 index) { if (index == -1 || index == rootIndex) return Transform::Identity; auto result = nodes[index].LocalTransform; if (index != rootIndex) { const auto parentTransform = CombineTransformsFromNodeIndices(nodes, rootIndex, nodes[index].ParentIndex); result = parentTransform.LocalToWorld(result); } return result; } private: #if USE_ASSIMP static bool ImportDataAssimp(const char* path, ImportedModelData& data, Options& options, String& errorMsg); #endif #if USE_AUTODESK_FBX_SDK static bool ImportDataAutodeskFbxSdk(const char* path, ImportedModelData& data, Options& options, String& errorMsg); #endif #if USE_OPEN_FBX static bool ImportDataOpenFBX(const char* path, ImportedModelData& data, Options& options, String& errorMsg); #endif #endif }; #endif