From 2dcba9225a58e499ba12d9a6eb17482c592e851e Mon Sep 17 00:00:00 2001 From: Doprez <73259914+Doprez@users.noreply.github.com> Date: Mon, 27 May 2024 13:40:54 -0600 Subject: [PATCH 01/10] ported https://github.com/assimp/assimp/pull/5494/files --- .../tools/Stride.Importer.3D/MeshConverter.cs | 87 ++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/sources/tools/Stride.Importer.3D/MeshConverter.cs b/sources/tools/Stride.Importer.3D/MeshConverter.cs index 7615a73f09..42d3e27e25 100644 --- a/sources/tools/Stride.Importer.3D/MeshConverter.cs +++ b/sources/tools/Stride.Importer.3D/MeshConverter.cs @@ -1305,10 +1305,95 @@ private ComputeTextureColor GetTextureReferenceNode(string vfsOutputPath, string return textureValue; } + private unsafe void CorrectRootTransform(ref Scene* scene) + { + if(scene == null && scene->MMetaData == null) + { + return; + } + + int upAxis = 1, upAxisSign = 1, frontAxis = 2, frontAxisSign = 1, coordAxis = 0, coordAxisSign = 1; + double unitScaleFactor = 1.0; + + for(uint i = 0; i < scene->MMetaData->MNumProperties; i++) + { + if (scene->MMetaData->MKeys[i].AsString == "UpAxis") + { + GetMetaDataValue(i, scene->MMetaData, ref upAxis); + } + if (scene->MMetaData->MKeys[i].AsString == "UpAxisSign") + { + GetMetaDataValue(i, scene->MMetaData, ref upAxisSign); + } + if (scene->MMetaData->MKeys[i].AsString == "FrontAxis") + { + GetMetaDataValue(i, scene->MMetaData, ref frontAxis); + } + if (scene->MMetaData->MKeys[i].AsString == "FrontAxisSign") + { + GetMetaDataValue(i, scene->MMetaData, ref frontAxisSign); + } + if (scene->MMetaData->MKeys[i].AsString == "CoordAxis") + { + GetMetaDataValue(i, scene->MMetaData, ref coordAxis); + } + if (scene->MMetaData->MKeys[i].AsString == "CoordAxisSign") + { + GetMetaDataValue(i, scene->MMetaData, ref coordAxisSign); + } + if (scene->MMetaData->MKeys[i].AsString == "UnitScaleFactor") + { + GetMetaDataValue(i, scene->MMetaData, ref unitScaleFactor); + } + } + + var upVec = upAxis switch + { + 0 => new Vector3(upAxisSign * (float)unitScaleFactor, 0, 0), + 1 => new Vector3(0, upAxisSign * (float)unitScaleFactor, 0), + 2 => new Vector3(0, 0, upAxisSign * (float)unitScaleFactor), + _ => new Vector3(0, 1, 0), + }; + var forwardVec = frontAxis switch + { + 0 => new Vector3(frontAxisSign * (float)unitScaleFactor, 0, 0), + 1 => new Vector3(0, frontAxisSign * (float)unitScaleFactor, 0), + 2 => new Vector3(0, 0, frontAxisSign * (float)unitScaleFactor), + _ => new Vector3(0, 1, 0), + }; + var rightVec = coordAxis switch + { + 0 => new Vector3(coordAxisSign * (float)unitScaleFactor, 0, 0), + 1 => new Vector3(0, coordAxisSign * (float)unitScaleFactor, 0), + 2 => new Vector3(0, 0, coordAxisSign * (float)unitScaleFactor), + _ => new Vector3(0, 1, 0), + }; + + System.Numerics.Matrix4x4 matrix = new System.Numerics.Matrix4x4 + (rightVec.X, rightVec.Y, rightVec.Z, 0, + upVec.X, upVec.Y, upVec.Z, 0, + forwardVec.X, forwardVec.Y, forwardVec.Z, 0, + 0, 0, 0, 1); + + scene->MRootNode->MTransformation *= matrix; + } + + private unsafe bool GetMetaDataValue(uint index, Metadata* metaData, ref T value) + { + if(index >= metaData->MNumProperties) + { + return false; + } + + value = *(T*)metaData->MValues[index].MData; + + return true; + } + private unsafe List ExtractModels(Scene* scene, Dictionary meshNames, Dictionary materialNames, Dictionary nodeNames) { GenerateMeshNames(scene, meshNames); - + CorrectRootTransform(ref scene); var meshList = new List(); for (uint i = 0; i < scene->MNumMeshes; ++i) { From 7e5362a7e388b297b4b83a3e5a5cd3f2adfe70a2 Mon Sep 17 00:00:00 2001 From: Eideren Date: Tue, 4 Jun 2024 02:06:56 +0200 Subject: [PATCH 02/10] Cleanup --- .../tools/Stride.Importer.3D/MeshConverter.cs | 135 +++--- .../Stride.Importer.3D/aiPostProcessSteps.cs | 439 ++++++++++++++++++ 2 files changed, 495 insertions(+), 79 deletions(-) create mode 100644 sources/tools/Stride.Importer.3D/aiPostProcessSteps.cs diff --git a/sources/tools/Stride.Importer.3D/MeshConverter.cs b/sources/tools/Stride.Importer.3D/MeshConverter.cs index 42d3e27e25..964c806132 100644 --- a/sources/tools/Stride.Importer.3D/MeshConverter.cs +++ b/sources/tools/Stride.Importer.3D/MeshConverter.cs @@ -29,26 +29,6 @@ namespace Stride.Importer.ThreeD { public class MeshConverter { - - static class PostProcessActions - { - public const uint CalculateTangentSpace = 1; - public const uint Triangulate = 8; - public const uint GenerateNormals = 32; - public const uint JoinIdenticalVertices = 2; - public const uint LimitBoneWeights = 512; - public const uint SortByPrimitiveType = 32768; - public const uint FlipWindingOrder = 16777216; - public const uint FlipUVs = 8388608; - public const uint GenSmoothNormals = 64; - public const uint ImproveCacheLocality = 2048; - public const uint RemoveRedundantMaterials = 4096; - public const uint SplitLargeMeshes = 128; - public const uint GenUVCoords = 262144; - public const uint GlobalScaling = 134217728; - public const uint OptimizeGraph = 4194304; - } - static MeshConverter() { if (Platform.Type == PlatformType.Windows) @@ -61,7 +41,7 @@ static MeshConverter() public Logger Logger { get; set; } - private readonly Silk.NET.Assimp.Assimp assimp = Silk.NET.Assimp.Assimp.GetApi(); + private readonly Assimp assimp = Assimp.GetApi(); public bool AllowUnsignedBlendIndices { get; set; } @@ -94,14 +74,14 @@ public unsafe EntityInfo ExtractEntity(string inputFilename, string outputFilena try { uint importFlags = 0; - var postProcessFlags = PostProcessSteps.SortByPrimitiveType; + var postProcessFlags = aiPostProcessSteps.aiProcess_SortByPType; if (deduplicateMaterials) { - postProcessFlags |= PostProcessSteps.RemoveRedundantMaterials; + postProcessFlags |= aiPostProcessSteps.aiProcess_RemoveRedundantMaterials; } - var scene = Initialize(inputFilename, outputFilename, importFlags, 0); + var scene = Initialize(inputFilename, outputFilename, importFlags, postProcessFlags); // If scene is null, something went wrong inside Assimp if (scene == null) { @@ -147,7 +127,13 @@ public unsafe Model Convert(string inputFilename, string outputFilename, bool de { uint importFlags = 0; - var scene = Initialize(inputFilename, outputFilename, importFlags, 0); + aiPostProcessSteps postProcessFlags = 0; + if (deduplicateMaterials) + { + postProcessFlags |= aiPostProcessSteps.aiProcess_RemoveRedundantMaterials; + } + + var scene = Initialize(inputFilename, outputFilename, importFlags, postProcessFlags); return ConvertAssimpScene(scene); } @@ -162,14 +148,14 @@ public unsafe AnimationInfo ConvertAnimation(string inputFilename, string output public unsafe Rendering.Skeleton ConvertSkeleton(string inputFilename, string outputFilename) { uint importFlags = 0; - var postProcessFlags = PostProcessSteps.None; + aiPostProcessSteps postProcessFlags = 0; - var scene = Initialize(inputFilename, outputFilename, importFlags, 0); + var scene = Initialize(inputFilename, outputFilename, importFlags, postProcessFlags); return ProcessSkeleton(scene); } - private unsafe Scene* Initialize(string inputFilename, string outputFilename, uint importFlags, uint postProcessFlags) + private unsafe Scene* Initialize(string inputFilename, string outputFilename, uint importFlags, aiPostProcessSteps postProcessFlags) { ResetConversionData(); @@ -182,15 +168,15 @@ public unsafe Rendering.Skeleton ConvertSkeleton(string inputFilename, string ou assimp.SetImportPropertyFloat(propStore, "APP_SCALE_FACTOR", .01f); var scene = assimp.ImportFileExWithProperties(inputFilename, importFlags, null, propStore); - var postProcessFlags1 = PostProcessActions.CalculateTangentSpace - | PostProcessActions.Triangulate - | PostProcessActions.GenerateNormals - | PostProcessActions.SortByPrimitiveType - | PostProcessActions.FlipWindingOrder - | PostProcessActions.FlipUVs - | PostProcessActions.GlobalScaling; + postProcessFlags |= aiPostProcessSteps.aiProcess_CalcTangentSpace + | aiPostProcessSteps.aiProcess_Triangulate + | aiPostProcessSteps.aiProcess_GenNormals + | aiPostProcessSteps.aiProcess_SortByPType + | aiPostProcessSteps.aiProcess_FlipWindingOrder + | aiPostProcessSteps.aiProcess_FlipUVs + | aiPostProcessSteps.aiProcess_GlobalScale; - scene = assimp.ApplyPostProcessing(scene, postProcessFlags1); + scene = assimp.ApplyPostProcessing(scene, (uint)postProcessFlags); assimp.ReleasePropertyStore(propStore); return scene; } @@ -212,42 +198,44 @@ private unsafe Model ConvertAssimpScene(Scene* scene) // meshes for (var i = 0; i < scene->MNumMeshes; ++i) { - if (meshIndexToNodeIndex.ContainsKey(i)) + if (!meshIndexToNodeIndex.TryGetValue(i, out var value)) { - var meshInfo = ProcessMesh(scene, scene->MMeshes[i], meshNames); - - if (meshInfo == null) + continue; + } + + var meshInfo = ProcessMesh(scene, scene->MMeshes[i], meshNames); + + if (meshInfo == null) + { + continue; + } + + foreach (var nodeIndex in value) + { + var nodeMeshData = new Mesh { - continue; - } + Draw = meshInfo.Draw, + Name = meshInfo.Name, + MaterialIndex = meshInfo.MaterialIndex, + NodeIndex = nodeIndex, + }; - foreach (var nodeIndex in meshIndexToNodeIndex[i]) + if (meshInfo.Bones != null) { - var nodeMeshData = new Mesh + nodeMeshData.Skinning = new MeshSkinningDefinition { - Draw = meshInfo.Draw, - Name = meshInfo.Name, - MaterialIndex = meshInfo.MaterialIndex, - NodeIndex = nodeIndex, + Bones = meshInfo.Bones.ToArray() }; + } - 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.HasSkinningPosition && meshInfo.TotalClusterCount > 0) + nodeMeshData.Parameters.Set(MaterialKeys.HasSkinningPosition, true); - if (meshInfo.HasSkinningNormal && meshInfo.TotalClusterCount > 0) - nodeMeshData.Parameters.Set(MaterialKeys.HasSkinningNormal, true); + if (meshInfo.HasSkinningNormal && meshInfo.TotalClusterCount > 0) + nodeMeshData.Parameters.Set(MaterialKeys.HasSkinningNormal, true); - modelData.Meshes.Add(nodeMeshData); - } + modelData.Meshes.Add(nodeMeshData); } } @@ -471,7 +459,7 @@ private unsafe void GenerateUniqueNames(Dictionary finalNames, L private unsafe HashSet GetBoneList(Scene* scene) { - HashSet bones = new HashSet(); + HashSet bones = new HashSet(); for (uint i = 0; i < scene->MNumMeshes; i++) { var lMesh = scene->MMeshes[i]; @@ -479,10 +467,7 @@ private unsafe HashSet GetBoneList(Scene* scene) for (int j = 0; j < boneCount; j++) { string boneName = lMesh->MBones[j]->MName.AsString; - if(!bones.Contains(boneName)) - { - bones.Add(boneName); - } + bones.Add(boneName); } } return bones; @@ -495,7 +480,6 @@ private unsafe void GenerateMeshNames(Scene* scene, Dictionary m { var lMesh = scene->MMeshes[i]; baseNames.Add(lMesh->MName.AsString.CleanNodeName()); - } GenerateUniqueNames(meshNames, baseNames, i => (IntPtr)scene->MMeshes[i]); } @@ -533,7 +517,7 @@ private unsafe void GetNodeNames(Node* node, List nodeNames, List nodeNames, Dictionary> meshIndexToNodeIndex, HashSet filterInNodes) + private unsafe void RegisterNodes(Node* fromNode, int parentIndex, Dictionary nodeNames, Dictionary> meshIndexToNodeIndex, HashSet storeTransformationsForNodes) { var nodeIndex = nodes.Count; @@ -559,7 +543,6 @@ private unsafe void RegisterNodes(Node* fromNode, int parentIndex, DictionaryMMeshes; // Extract scene scaling and rotation from the root node. // Bake scaling into all node's positions and rotation into the 1st-level nodes. @@ -597,7 +580,7 @@ private unsafe void RegisterNodes(Node* fromNode, int parentIndex, DictionaryMNumChildren; ++child) { - RegisterNodes(fromNode->MChildren[child], nodeIndex, nodeNames, meshIndexToNodeIndex, filterInNodes); + RegisterNodes(fromNode->MChildren[child], nodeIndex, nodeNames, meshIndexToNodeIndex, storeTransformationsForNodes); } } @@ -611,7 +594,7 @@ private unsafe MeshInfo ProcessMesh(Scene* scene, Silk.NET.Assimp.Mesh* mesh, Di // 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>(); + var vertexIndexToBoneIdWeight = new List>(); if (mesh->MNumBones > 0) { bones = new List(); @@ -625,7 +608,7 @@ private unsafe MeshInfo ProcessMesh(Scene* scene, Silk.NET.Assimp.Mesh* mesh, Di vertexIndexToBoneIdWeight.Add(new List<(short, float)>()); } - // Build skinning clusters and fill controls points data stutcture + // Build skinning clusters and fill controls points data structure for (uint boneId = 0; boneId < mesh->MNumBones; ++boneId) { var bone = mesh->MBones[boneId]; @@ -870,8 +853,6 @@ private unsafe MeshInfo ProcessMesh(Scene* scene, Silk.NET.Assimp.Mesh* mesh, Di for (int j = 0; j < 3; ++j) { *((uint*)ibPointer) = mesh->MFaces[(int)i].MIndices[j]; - - var _index = (ushort)(mesh->MFaces[(int)i].MIndices[j]); ibPointer += sizeof(uint); } } @@ -880,7 +861,6 @@ private unsafe MeshInfo ProcessMesh(Scene* scene, Silk.NET.Assimp.Mesh* mesh, Di for (int j = 0; j < 3; ++j) { *((ushort*)ibPointer) = (ushort)(mesh->MFaces[(int)i].MIndices[j]); - var _index = (ushort)(mesh->MFaces[(int)i].MIndices[j]); ibPointer += sizeof(ushort); } } @@ -898,7 +878,6 @@ private unsafe MeshInfo ProcessMesh(Scene* scene, Silk.NET.Assimp.Mesh* mesh, Di drawData.PrimitiveType = PrimitiveType.TriangleList; drawData.DrawCount = (int)nbIndices; - return new MeshInfo { Draw = drawData, @@ -909,8 +888,6 @@ private unsafe MeshInfo ProcessMesh(Scene* scene, Silk.NET.Assimp.Mesh* mesh, Di HasSkinningNormal = hasSkinningNormal, TotalClusterCount = totalClusterCount }; - - } private void NormalizeVertexWeights(List> controlPts, int nbBoneByVertex) diff --git a/sources/tools/Stride.Importer.3D/aiPostProcessSteps.cs b/sources/tools/Stride.Importer.3D/aiPostProcessSteps.cs new file mode 100644 index 0000000000..fe022f2f39 --- /dev/null +++ b/sources/tools/Stride.Importer.3D/aiPostProcessSteps.cs @@ -0,0 +1,439 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; + +namespace Stride.Importer.ThreeD; + +/// +/// Extracted from assimp/postprocess.h, Silk does not yet provide all of those +/// +[Flags] +public enum aiPostProcessSteps : uint +{ + /// + /// Calculates the tangents and bitangents for the imported meshes./// Does nothing if a mesh does not have normals. You might want this post + /// processing step to be executed if you plan to use tangent space calculations + /// such as normal mapping applied to the meshes. There's an importer property, + /// #AI_CONFIG_PP_CT_MAX_SMOOTHING_ANGLE, which allows you to specify + /// a maximum smoothing angle for the algorithm. However, usually you'll + /// want to leave it at the default value. + /// + aiProcess_CalcTangentSpace = 0x1, + + /// + /// Identifies and joins identical vertex data sets within all + /// imported meshes./// After this step is run, each mesh contains unique vertices, + /// so a vertex may be used by multiple faces. You usually want + /// to use this post processing step. If your application deals with + /// indexed geometry, this step is compulsory or you'll just waste rendering + /// time. If this flag is not specified, no vertices are referenced by + /// more than one face and no index buffer is required for rendering. + /// Unless the importer (like ply) had to split vertices. Then you need one regardless. + /// + aiProcess_JoinIdenticalVertices = 0x2, + + /// + /// Converts all the imported data to a left-handed coordinate space./// By default the data is returned in a right-handed coordinate space (which + /// OpenGL prefers). In this space, +X points to the right, + /// +Z points towards the viewer, and +Y points upwards. In the DirectX + /// coordinate space +X points to the right, +Y points upwards, and +Z points + /// away from the viewer./// You'll probably want to consider this flag if you use Direct3D for + /// rendering. The #aiProcess_ConvertToLeftHanded flag supersedes this + /// setting and bundles all conversions typically required for D3D-based + /// applications. + /// + aiProcess_MakeLeftHanded = 0x4, + + /// + /// Triangulates all faces of all meshes./// By default the imported mesh data might contain faces with more than 3 + /// indices. For rendering you'll usually want all faces to be triangles. + /// This post processing step splits up faces with more than 3 indices into + /// triangles. Line and point primitives are *not* modified! If you want + /// 'triangles only' with no other kinds of primitives, try the following + /// solution: + ///
    + ///
  • Specify both #aiProcess_Triangulate and #aiProcess_SortByPType
  • + ///
  • Ignore all point and line meshes when you process assimp's output
  • + ///
+ ///
+ aiProcess_Triangulate = 0x8, + + /// + /// Removes some parts of the data structure (animations, materials, + /// light sources, cameras, textures, vertex components)./// The components to be removed are specified in a separate + /// importer property, #AI_CONFIG_PP_RVC_FLAGS. This is quite useful + /// if you don't need all parts of the output structure. Vertex colors + /// are rarely used today for example... Calling this step to remove unneeded + /// data from the pipeline as early as possible results in increased + /// performance and a more optimized output data structure. + /// This step is also useful if you want to force Assimp to recompute + /// normals or tangents. The corresponding steps don't recompute them if + /// they're already there (loaded from the source asset). By using this + /// step you can make sure they are NOT there./// This flag is a poor one, mainly because its purpose is usually + /// misunderstood. Consider the following case: a 3D model has been exported + /// from a CAD app, and it has per-face vertex colors. Vertex positions can't be + /// shared, thus the #aiProcess_JoinIdenticalVertices step fails to + /// optimize the data because of these nasty little vertex colors. + /// Most apps don't even process them, so it's all for nothing. By using + /// this step, unneeded components are excluded as early as possible + /// thus opening more room for internal optimizations. + /// + aiProcess_RemoveComponent = 0x10, + + /// + /// Generates normals for all faces of all meshes./// This is ignored if normals are already there at the time this flag + /// is evaluated. Model importers try to load them from the source file, so + /// they're usually already there. Face normals are shared between all points + /// of a single face, so a single point can have multiple normals, which + /// forces the library to duplicate vertices in some cases. + /// #aiProcess_JoinIdenticalVertices is *senseless* then./// This flag may not be specified together with #aiProcess_GenSmoothNormals. + /// + aiProcess_GenNormals = 0x20, + + /// + /// Generates smooth normals for all vertices in the mesh./// This is ignored if normals are already there at the time this flag + /// is evaluated. Model importers try to load them from the source file, so + /// they're usually already there./// This flag may not be specified together with + /// #aiProcess_GenNormals. There's a importer property, + /// #AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE which allows you to specify + /// an angle maximum for the normal smoothing algorithm. Normals exceeding + /// this limit are not smoothed, resulting in a 'hard' seam between two faces. + /// Using a decent angle here (e.g. 80 degrees) results in very good visual + /// appearance. + /// + aiProcess_GenSmoothNormals = 0x40, + + /// + /// Splits large meshes into smaller sub-meshes./// This is quite useful for real-time rendering, where the number of triangles + /// which can be maximally processed in a single draw-call is limited + /// by the video driver/hardware. The maximum vertex buffer is usually limited + /// too. Both requirements can be met with this step: you may specify both a + /// triangle and vertex limit for a single mesh./// The split limits can (and should!) be set through the + /// #AI_CONFIG_PP_SLM_VERTEX_LIMIT and #AI_CONFIG_PP_SLM_TRIANGLE_LIMIT + /// importer properties. The default values are #AI_SLM_DEFAULT_MAX_VERTICES and + /// #AI_SLM_DEFAULT_MAX_TRIANGLES./// Note that splitting is generally a time-consuming task, but only if there's + /// something to split. The use of this step is recommended for most users. + /// + aiProcess_SplitLargeMeshes = 0x80, + + /// + /// Removes the node graph and pre-transforms all vertices with + /// the local transformation matrices of their nodes./// If the resulting scene can be reduced to a single mesh, with a single + /// material, no lights, and no cameras, then the output scene will contain + /// only a root node (with no children) that references the single mesh. + /// Otherwise, the output scene will be reduced to a root node with a single + /// level of child nodes, each one referencing one mesh, and each mesh + /// referencing one material./// In either case, for rendering, you can + /// simply render all meshes in order - you don't need to pay + /// attention to local transformations and the node hierarchy. + /// Animations are removed during this step. + /// This step is intended for applications without a scenegraph. + /// The step CAN cause some problems: if e.g. a mesh of the asset + /// contains normals and another, using the same material index, does not, + /// they will be brought together, but the first meshes's part of + /// the normal list is zeroed. However, these artifacts are rare. + /// @note The #AI_CONFIG_PP_PTV_NORMALIZE configuration property + /// can be set to normalize the scene's spatial dimension to the -1...1 + /// range. + /// + aiProcess_PreTransformVertices = 0x100, + + /// + /// Limits the number of bones simultaneously affecting a single vertex to a maximum value./// If any vertex is affected by more than the maximum number of bones, the least + /// important vertex weights are removed and the remaining vertex weights are + /// renormalized so that the weights still sum up to 1. + /// The default bone weight limit is 4 (defined as #AI_LMW_MAX_WEIGHTS in + /// config.h), but you can use the #AI_CONFIG_PP_LBW_MAX_WEIGHTS importer + /// property to supply your own limit to the post processing step./// If you intend to perform the skinning in hardware, this post processing + /// step might be of interest to you. + /// + aiProcess_LimitBoneWeights = 0x200, + + /// + /// Validates the imported scene data structure. + /// This makes sure that all indices are valid, all animations and + /// bones are linked correctly, all material references are correct .. etc./// It is recommended that you capture Assimp's log output if you use this flag, + /// so you can easily find out what's wrong if a file fails the + /// validation. The validator is quite strict and will find *all* + /// inconsistencies in the data structure... It is recommended that plugin + /// developers use it to debug their loaders. There are two types of + /// validation failures: + ///
    + ///
  • Error: There's something wrong with the imported data. Further + /// postprocessing is not possible and the data is not usable at all. + /// The import fails. #Importer::GetErrorString() or #aiGetErrorString() + /// carry the error message around.
  • + ///
  • Warning: There are some minor issues (e.g. 1000000 animation + /// keyframes with the same time), but further postprocessing and use + /// of the data structure is still safe. Warning details are written + /// to the log file, #AI_SCENE_FLAGS_VALIDATION_WARNING is set + /// in #aiScene::mFlags
  • + ///
/// This post-processing step is not time-consuming. Its use is not + /// compulsory, but recommended. + ///
+ aiProcess_ValidateDataStructure = 0x400, + + /// + /// Reorders triangles for better vertex cache locality./// The step tries to improve the ACMR (average post-transform vertex cache + /// miss ratio) for all meshes. The implementation runs in O(n) and is + /// roughly based on the 'tipsify' algorithm (see this + /// paper)./// If you intend to render huge models in hardware, this step might + /// be of interest to you. The #AI_CONFIG_PP_ICL_PTCACHE_SIZE + /// importer property can be used to fine-tune the cache optimization. + /// + aiProcess_ImproveCacheLocality = 0x800, + + /// + /// Searches for redundant/unreferenced materials and removes them./// This is especially useful in combination with the + /// #aiProcess_PreTransformVertices and #aiProcess_OptimizeMeshes flags. + /// Both join small meshes with equal characteristics, but they can't do + /// their work if two meshes have different materials. Because several + /// material settings are lost during Assimp's import filters, + /// (and because many exporters don't check for redundant materials), huge + /// models often have materials which are are defined several times with + /// exactly the same settings./// Several material settings not contributing to the final appearance of + /// a surface are ignored in all comparisons (e.g. the material name). + /// So, if you're passing additional information through the + /// content pipeline (probably using *magic* material names), don't + /// specify this flag. Alternatively take a look at the + /// #AI_CONFIG_PP_RRM_EXCLUDE_LIST importer property. + /// + aiProcess_RemoveRedundantMaterials = 0x1000, + + /// + /// This step tries to determine which meshes have normal vectors + /// that are facing inwards and inverts them./// The algorithm is simple but effective: + /// the bounding box of all vertices + their normals is compared against + /// the volume of the bounding box of all vertices without their normals. + /// This works well for most objects, problems might occur with planar + /// surfaces. However, the step tries to filter such cases. + /// The step inverts all in-facing normals. Generally it is recommended + /// to enable this step, although the result is not always correct. + /// + aiProcess_FixInfacingNormals = 0x2000, + + + + /// + /// This step generically populates aiBone->mArmature and aiBone->mNode generically + /// The point of these is it saves you later having to calculate these elements + /// This is useful when handling rest information or skin information + /// If you have multiple armatures on your models we strongly recommend enabling this + /// Instead of writing your own multi-root, multi-armature lookups we have done the + /// hard work for you :) + /// + aiProcess_PopulateArmatureData = 0x4000, + + /// + /// This step splits meshes with more than one primitive type in + /// homogeneous sub-meshes./// The step is executed after the triangulation step. After the step + /// returns, just one bit is set in aiMesh::mPrimitiveTypes. This is + /// especially useful for real-time rendering where point and line + /// primitives are often ignored or rendered separately. + /// You can use the #AI_CONFIG_PP_SBP_REMOVE importer property to + /// specify which primitive types you need. This can be used to easily + /// exclude lines and points, which are rarely used, from the import. + /// + aiProcess_SortByPType = 0x8000, + + /// + /// This step searches all meshes for degenerate primitives and + /// converts them to proper lines or points./// A face is 'degenerate' if one or more of its points are identical. + /// To have the degenerate stuff not only detected and collapsed but + /// removed, try one of the following procedures: + ///
1. (if you support lines and points for rendering but don't + /// want the degenerates)
+ ///
    + ///
  • Specify the #aiProcess_FindDegenerates flag. + ///
  • + ///
  • Set the #AI_CONFIG_PP_FD_REMOVE importer property to + /// 1. This will cause the step to remove degenerate triangles from the + /// import as soon as they're detected. They won't pass any further + /// pipeline steps. + ///
  • + ///
+ ///
2.(if you don't support lines and points at all)
+ ///
    + ///
  • Specify the #aiProcess_FindDegenerates flag. + ///
  • + ///
  • Specify the #aiProcess_SortByPType flag. This moves line and + /// point primitives to separate meshes. + ///
  • + ///
  • Set the #AI_CONFIG_PP_SBP_REMOVE importer property to + /// @code aiPrimitiveType_POINTS | aiPrimitiveType_LINES + /// @endcode to cause SortByPType to reject point + /// and line meshes from the scene. + ///
  • + ///
/// This step also removes very small triangles with a surface area smaller + /// than 10^-6. If you rely on having these small triangles, or notice holes + /// in your model, set the property #AI_CONFIG_PP_FD_CHECKAREA to + /// false. + /// @note Degenerate polygons are not necessarily evil and that's why + /// they're not removed by default. There are several file formats which + /// don't support lines or points, and some exporters bypass the + /// format specification and write them as degenerate triangles instead. + ///
+ aiProcess_FindDegenerates = 0x10000, + + /// + /// This step searches all meshes for invalid data, such as zeroed + /// normal vectors or invalid UV coords and removes/fixes them. This is + /// intended to get rid of some common exporter errors./// This is especially useful for normals. If they are invalid, and + /// the step recognizes this, they will be removed and can later + /// be recomputed, i.e. by the #aiProcess_GenSmoothNormals flag.
+ /// The step will also remove meshes that are infinitely small and reduce + /// animation tracks consisting of hundreds if redundant keys to a single + /// key. The AI_CONFIG_PP_FID_ANIM_ACCURACY config property decides + /// the accuracy of the check for duplicate animation tracks. + ///
+ aiProcess_FindInvalidData = 0x20000, + + /// + /// This step converts non-UV mappings (such as spherical or + /// cylindrical mapping) to proper texture coordinate channels./// Most applications will support UV mapping only, so you will + /// probably want to specify this step in every case. Note that Assimp is not + /// always able to match the original mapping implementation of the + /// 3D app which produced a model perfectly. It's always better to let the + /// modelling app compute the UV channels - 3ds max, Maya, Blender, + /// LightWave, and Modo do this for example./// @note If this step is not requested, you'll need to process the + /// #AI_MATKEY_MAPPING material property in order to display all assets + /// properly. + /// + aiProcess_GenUVCoords = 0x40000, + + /// + /// This step applies per-texture UV transformations and bakes + /// them into stand-alone vtexture coordinate channels./// UV transformations are specified per-texture - see the + /// #AI_MATKEY_UVTRANSFORM material key for more information. + /// This step processes all textures with + /// transformed input UV coordinates and generates a new (pre-transformed) UV channel + /// which replaces the old channel. Most applications won't support UV + /// transformations, so you will probably want to specify this step./// @note UV transformations are usually implemented in real-time apps by + /// transforming texture coordinates at vertex shader stage with a 3x3 + /// (homogeneous) transformation matrix. + /// + aiProcess_TransformUVCoords = 0x80000, + + /// + /// This step searches for duplicate meshes and replaces them + /// with references to the first mesh./// This step takes a while, so don't use it if speed is a concern. + /// Its main purpose is to workaround the fact that many export + /// file formats don't support instanced meshes, so exporters need to + /// duplicate meshes. This step removes the duplicates again. Please + /// note that Assimp does not currently support per-node material + /// assignment to meshes, which means that identical meshes with + /// different materials are currently *not* joined, although this is + /// planned for future versions. + /// + aiProcess_FindInstances = 0x100000, + + /// + /// A post-processing step to reduce the number of meshes./// This will, in fact, reduce the number of draw calls./// This is a very effective optimization and is recommended to be used + /// together with #aiProcess_OptimizeGraph, if possible. The flag is fully + /// compatible with both #aiProcess_SplitLargeMeshes and #aiProcess_SortByPType. + /// + aiProcess_OptimizeMeshes = 0x200000, + + + /// + /// A post-processing step to optimize the scene hierarchy./// Nodes without animations, bones, lights or cameras assigned are + /// collapsed and joined./// Node names can be lost during this step. If you use special 'tag nodes' + /// to pass additional information through your content pipeline, use the + /// #AI_CONFIG_PP_OG_EXCLUDE_LIST importer property to specify a + /// list of node names you want to be kept. Nodes matching one of the names + /// in this list won't be touched or modified./// Use this flag with caution. Most simple files will be collapsed to a + /// single node, so complex hierarchies are usually completely lost. This is not + /// useful for editor environments, but probably a very effective + /// optimization if you just want to get the model data, convert it to your + /// own format, and render it as fast as possible./// This flag is designed to be used with #aiProcess_OptimizeMeshes for best + /// results./// @note 'Crappy' scenes with thousands of extremely small meshes packed + /// in deeply nested nodes exist for almost all file formats. + /// #aiProcess_OptimizeMeshes in combination with #aiProcess_OptimizeGraph + /// usually fixes them all and makes them renderable. + /// + aiProcess_OptimizeGraph = 0x400000, + + /// + /// This step flips all UV coordinates along the y-axis and adjusts + /// material settings and bitangents accordingly./// Output UV coordinate system: + /// @code + /// 0y|0y ---------- 1x|0y + /// | | + /// | | + /// | | + /// 0x|1y ---------- 1x|1y + /// @endcode/// You'll probably want to consider this flag if you use Direct3D for + /// rendering. The #aiProcess_ConvertToLeftHanded flag supersedes this + /// setting and bundles all conversions typically required for D3D-based + /// applications. + /// + aiProcess_FlipUVs = 0x800000, + + /// + /// This step adjusts the output face winding order to be CW./// The default face winding order is counter clockwise (CCW)./// Output face order: + /// @code + /// x2/// x0 + /// x1 + /// @endcode + /// + aiProcess_FlipWindingOrder = 0x1000000, + + /// + /// This step splits meshes with many bones into sub-meshes so that each + /// sub-mesh has fewer or as many bones as a given limit. + /// + aiProcess_SplitByBoneCount = 0x2000000, + + /// + /// This step removes bones losslessly or according to some threshold./// In some cases (i.e. formats that require it) exporters are forced to + /// assign dummy bone weights to otherwise static meshes assigned to + /// animated meshes. Full, weight-based skinning is expensive while + /// animating nodes is extremely cheap, so this step is offered to clean up + /// the data in that regard./// Use #AI_CONFIG_PP_DB_THRESHOLD to control this. + /// Use #AI_CONFIG_PP_DB_ALL_OR_NONE if you want bones removed if and + /// only if all bones within the scene qualify for removal. + /// + aiProcess_Debone = 0x4000000, + + + + /// + /// This step will perform a global scale of the model./// Some importers are providing a mechanism to define a scaling unit for the + /// model. This post processing step can be used to do so. You need to get the + /// global scaling from your importer settings like in FBX. Use the flag + /// AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY from the global property table to configure this./// Use #AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY to setup the global scaling factor. + /// + aiProcess_GlobalScale = 0x8000000, + + /// + /// A postprocessing step to embed of textures./// This will remove external data dependencies for textures. + /// If a texture's file does not exist at the specified path + /// (due, for instance, to an absolute path generated on another system), + /// it will check if a file with the same name exists at the root folder + /// of the imported model. And if so, it uses that. + /// + aiProcess_EmbedTextures = 0x10000000, + + // aiProcess_GenEntityMeshes = 0x100000, + // aiProcess_OptimizeAnimations = 0x200000 + // aiProcess_FixTexturePaths = 0x200000 + + + aiProcess_ForceGenNormals = 0x20000000, + + /// + /// Drops normals for all faces of all meshes./// This is ignored if no normals are present. + /// Face normals are shared between all points of a single face, + /// so a single point can have multiple normals, which + /// forces the library to duplicate vertices in some cases. + /// #aiProcess_JoinIdenticalVertices is *senseless* then. + /// This process gives sense back to aiProcess_JoinIdenticalVertices + /// + aiProcess_DropNormals = 0x40000000, + + /// + /// + aiProcess_GenBoundingBoxes = 0x80000000, +}; From df679a5b7d85a123ddbe58aa9afe111dad133631 Mon Sep 17 00:00:00 2001 From: Eideren Date: Tue, 4 Jun 2024 02:07:30 +0200 Subject: [PATCH 03/10] Potential fix to investigate further --- .../tools/Stride.Importer.3D/MeshConverter.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/sources/tools/Stride.Importer.3D/MeshConverter.cs b/sources/tools/Stride.Importer.3D/MeshConverter.cs index 964c806132..dbcafd2a74 100644 --- a/sources/tools/Stride.Importer.3D/MeshConverter.cs +++ b/sources/tools/Stride.Importer.3D/MeshConverter.cs @@ -563,17 +563,13 @@ private unsafe void RegisterNodes(Node* fromNode, int parentIndex, DictionaryMTransformation.ToStrideMatrix(); transform.Decompose(out modelNodeDefinition.Transform.Scale, out modelNodeDefinition.Transform.Rotation, out modelNodeDefinition.Transform.Position); } - - if (filterInNodes!=null - && filterInNodes.Count>0) + + /*if (storeTransformationsForNodes.Contains(fromNode->MName.AsString) == false) { - if(!filterInNodes.Contains(fromNode->MName.AsString)) - { - modelNodeDefinition.Transform.Rotation = Quaternion.Identity; - modelNodeDefinition.Transform.Scale = Vector3.One; - modelNodeDefinition.Transform.Position = Vector3.Zero; - } - } + modelNodeDefinition.Transform.Rotation = Quaternion.Identity; + modelNodeDefinition.Transform.Scale = Vector3.One; + modelNodeDefinition.Transform.Position = Vector3.Zero; + }*/ nodes.Add(modelNodeDefinition); From 2a8bacf46e2f0571e911b75d5f6843cad3017007 Mon Sep 17 00:00:00 2001 From: Eideren Date: Tue, 4 Jun 2024 19:28:06 +0200 Subject: [PATCH 04/10] Fallback to BaseColor when diffuse is not available --- sources/tools/Stride.Importer.3D/MeshConverter.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sources/tools/Stride.Importer.3D/MeshConverter.cs b/sources/tools/Stride.Importer.3D/MeshConverter.cs index dbcafd2a74..dc92214dc4 100644 --- a/sources/tools/Stride.Importer.3D/MeshConverter.cs +++ b/sources/tools/Stride.Importer.3D/MeshConverter.cs @@ -1001,6 +1001,8 @@ private unsafe MaterialAsset ProcessMeshMaterial(Silk.NET.Assimp.Material* pMate 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); + if (hasDiffColor == false) + SetMaterialColorFlag(pMaterial, Silk.NET.Assimp.Assimp.MatkeyBaseColor, ref hasDiffColor, ref diffColor, true); BuildLayeredSurface(pMaterial, hasDiffColor, false, diffColor.ToStrideColor(), 0.0f, TextureType.Diffuse, finalMaterial); BuildLayeredSurface(pMaterial, hasSpecColor, false, specColor.ToStrideColor(), 0.0f, TextureType.Specular, finalMaterial); From e2636c40b6c33e800fa1d59083aeac6c1d8471e2 Mon Sep 17 00:00:00 2001 From: Eideren Date: Wed, 5 Jun 2024 01:55:23 +0200 Subject: [PATCH 05/10] Automatically set preview model on import for animations --- .../ModelAssetImporter.cs | 60 +++++++------------ 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/sources/engine/Stride.Assets.Models/ModelAssetImporter.cs b/sources/engine/Stride.Assets.Models/ModelAssetImporter.cs index 93f0c891a9..bc6accd05b 100644 --- a/sources/engine/Stride.Assets.Models/ModelAssetImporter.cs +++ b/sources/engine/Stride.Assets.Models/ModelAssetImporter.cs @@ -3,6 +3,7 @@ using System; using System.Linq; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Stride.Core; using Stride.Core.Assets; using Stride.Core.Assets.Analysis; @@ -97,33 +98,29 @@ public override IEnumerable Import(UFile localPath, AssetImporterPara skeletonAsset = ImportSkeleton(rawAssetReferences, localPath, localPath, entityInfo); } - // 3. Animation - if (importParameters.IsTypeSelectedForOutput()) - { - int _iAnimIndex = 0; - entityInfo?.AnimationNodes?.ForEach(c => - { - TimeSpan startTime, endTime; - GetAnimationDuration(localPath, importParameters.Logger, importParameters, _iAnimIndex, out startTime, out endTime); - - ImportAnimation(rawAssetReferences, localPath, entityInfo.AnimationNodes[_iAnimIndex], _iAnimIndex, skeletonAsset, startTime, endTime); - - _iAnimIndex++; - }); - - - } - - // 4. Materials + // 3. Materials if (isImportingMaterial) { ImportMaterials(rawAssetReferences, entityInfo.Materials); } - // 5. Model + ModelAsset modelAsset = null; + // 4. Model if (isImportingModel) { - ImportModel(rawAssetReferences, localPath, localPath, entityInfo, false, skeletonAsset); + modelAsset = ImportModel(rawAssetReferences, localPath, localPath, entityInfo, false, skeletonAsset); + } + + // 5. Animation + if (importParameters.IsTypeSelectedForOutput()) + { + for (int i = 0; i < entityInfo.AnimationNodes.Count; i++) + { + TimeSpan startTime, endTime; + GetAnimationDuration(localPath, importParameters.Logger, importParameters, i, out startTime, out endTime); + + ImportAnimation(rawAssetReferences, localPath, entityInfo.AnimationNodes[i], i, skeletonAsset, modelAsset, startTime, endTime); + } } return rawAssetReferences; @@ -151,23 +148,7 @@ private static AssetItem ImportSkeleton(List assetReferences, UFile a return assetItem; } - private static void ImportAnimation(List assetReferences, UFile localPath, List animationNodes, bool shouldPostFixName, AssetItem skeletonAsset, TimeSpan animationStartTime, TimeSpan animationEndTime) - { - if (animationNodes != null && animationNodes.Count > 0) - { - var assetSource = localPath; - - var asset = new AnimationAsset { Source = assetSource, AnimationTimeMaximum = animationEndTime, AnimationTimeMinimum = animationStartTime }; - var animUrl = localPath.GetFileNameWithoutExtension() + (shouldPostFixName ? " Animation" : ""); - - if (skeletonAsset != null) - asset.Skeleton = AttachedReferenceManager.CreateProxyObject(skeletonAsset.Id, skeletonAsset.Location); - - assetReferences.Add(new AssetItem(animUrl, asset)); - } - } - - private static void ImportAnimation(List assetReferences, UFile localPath, string animationNodeName, int animationNodeIndex, AssetItem skeletonAsset, TimeSpan animationStartTime, TimeSpan animationEndTime) + private static void ImportAnimation(List assetReferences, UFile localPath, string animationNodeName, int animationNodeIndex, [MaybeNull]AssetItem skeletonAsset, [MaybeNull]ModelAsset modelAsset, TimeSpan animationStartTime, TimeSpan animationEndTime) { var assetSource = localPath; var asset = new AnimationAsset { Source = assetSource, AnimationTimeMaximum = animationEndTime, AnimationTimeMinimum = animationStartTime }; @@ -189,11 +170,13 @@ private static void ImportAnimation(List assetReferences, UFile local asset.AnimationStack = animationNodeIndex; if (skeletonAsset != null) asset.Skeleton = AttachedReferenceManager.CreateProxyObject(skeletonAsset.Id, skeletonAsset.Location); + if (modelAsset != null) + asset.PreviewModel = AttachedReferenceManager.CreateProxyObject(modelAsset.Id, modelAsset.Source); assetReferences.Add(new AssetItem(animUrl, asset)); } - private static void ImportModel(List assetReferences, UFile assetSource, UFile localPath, EntityInfo entityInfo, bool shouldPostFixName, AssetItem skeletonAsset) + private static ModelAsset ImportModel(List assetReferences, UFile assetSource, UFile localPath, EntityInfo entityInfo, bool shouldPostFixName, AssetItem skeletonAsset) { var asset = new ModelAsset { Source = assetSource }; @@ -229,6 +212,7 @@ private static void ImportModel(List assetReferences, UFile assetSour var modelUrl = new UFile(localPath.GetFileNameWithoutExtension() + (shouldPostFixName?" Model": "")); var assetItem = new AssetItem(modelUrl, asset); assetReferences.Add(assetItem); + return asset; } private static void ImportMaterials(List assetReferences, Dictionary materials) From d766dd4847df93b32a2054aa2ccd3a8496d32a69 Mon Sep 17 00:00:00 2001 From: Eideren Date: Wed, 5 Jun 2024 01:58:22 +0200 Subject: [PATCH 06/10] Use BaseColor as Diffuse if Diffuse is not defined --- .../tools/Stride.Importer.3D/MeshConverter.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sources/tools/Stride.Importer.3D/MeshConverter.cs b/sources/tools/Stride.Importer.3D/MeshConverter.cs index dc92214dc4..6be958bd7e 100644 --- a/sources/tools/Stride.Importer.3D/MeshConverter.cs +++ b/sources/tools/Stride.Importer.3D/MeshConverter.cs @@ -965,7 +965,7 @@ private unsafe void GenerateMaterialNames(Scene* scene, DictionaryMMaterials[i]; var aiMaterial = new AssimpString(); - var materialName = assimp.GetMaterialString(lMaterial, Silk.NET.Assimp.Assimp.MaterialNameBase, 0, 0, ref aiMaterial) == Return.Success ? aiMaterial.AsString : "Material"; + var materialName = assimp.GetMaterialString(lMaterial, Assimp.MaterialNameBase, 0, 0, ref aiMaterial) == Return.Success ? aiMaterial.AsString : "Material"; baseNames.Add(materialName); } @@ -994,15 +994,15 @@ private unsafe MaterialAsset ProcessMeshMaterial(Silk.NET.Assimp.Material* pMate 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); + SetMaterialColorFlag(pMaterial, Assimp.MaterialColorDiffuseBase, ref hasDiffColor, ref diffColor, true);// always keep black color for diffuse + SetMaterialColorFlag(pMaterial, Assimp.MaterialColorSpecularBase, ref hasSpecColor, ref specColor, IsNotBlackColor(specColor)); + SetMaterialColorFlag(pMaterial, Assimp.MaterialColorAmbientBase, ref hasAmbientColor, ref ambientColor, IsNotBlackColor(specColor)); + SetMaterialColorFlag(pMaterial, Assimp.MaterialColorEmissiveBase, ref hasEmissiveColor, ref emissiveColor, IsNotBlackColor(emissiveColor)); + SetMaterialColorFlag(pMaterial, Assimp.MaterialColorReflectiveBase, ref hasReflectiveColor, ref reflectiveColor, IsNotBlackColor(reflectiveColor)); + SetMaterialFloatArrayFlag(pMaterial, Assimp.MaterialShininessBase, ref hasSpecPower, specPower, specPower > 0); + SetMaterialFloatArrayFlag(pMaterial, Assimp.MaterialOpacityBase, ref hasOpacity, opacity, opacity < 1.0); if (hasDiffColor == false) - SetMaterialColorFlag(pMaterial, Silk.NET.Assimp.Assimp.MatkeyBaseColor, ref hasDiffColor, ref diffColor, true); + SetMaterialColorFlag(pMaterial, Assimp.MatkeyBaseColor, ref hasDiffColor, ref diffColor, true); BuildLayeredSurface(pMaterial, hasDiffColor, false, diffColor.ToStrideColor(), 0.0f, TextureType.Diffuse, finalMaterial); BuildLayeredSurface(pMaterial, hasSpecColor, false, specColor.ToStrideColor(), 0.0f, TextureType.Specular, finalMaterial); From f3abffa1ff3bfee57e815bdec05b11df06a2040a Mon Sep 17 00:00:00 2001 From: Eideren Date: Wed, 5 Jun 2024 02:04:20 +0200 Subject: [PATCH 07/10] Document pivot todo --- sources/tools/Stride.Importer.3D/MeshConverter.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sources/tools/Stride.Importer.3D/MeshConverter.cs b/sources/tools/Stride.Importer.3D/MeshConverter.cs index 6be958bd7e..0b67d84832 100644 --- a/sources/tools/Stride.Importer.3D/MeshConverter.cs +++ b/sources/tools/Stride.Importer.3D/MeshConverter.cs @@ -164,7 +164,7 @@ public unsafe Rendering.Skeleton ConvertSkeleton(string inputFilename, string ou vfsInputPath = VirtualFileSystem.GetParentFolder(inputFilename); var propStore = assimp.CreatePropertyStore(); - assimp.SetImportPropertyInteger(propStore, "IMPORT_FBX_PRESERVE_PIVOTS", 0); + assimp.SetImportPropertyInteger(propStore, "IMPORT_FBX_PRESERVE_PIVOTS", 0); // Trade some issues for others, see: https://github.com/assimp/assimp/issues/894, https://github.com/assimp/assimp/issues/1974 assimp.SetImportPropertyFloat(propStore, "APP_SCALE_FACTOR", .01f); var scene = assimp.ImportFileExWithProperties(inputFilename, importFlags, null, propStore); @@ -310,7 +310,12 @@ private unsafe AnimationInfo ProcessAnimations(Scene* scene, int animationIndex) var nodeAnim = aiAnim->MChannels[nodeAnimId]; var nodeName = nodeAnim->MNodeName.AsString.CleanNodeName(); - if (!visitedNodeNames.Contains(nodeName)) + // TODO: Need to resample the animation created by the pivot chain into a single animation, have a look at the file hierarchy in Assimp's viewer to get a better clue + // See: 'IMPORT_FBX_PRESERVE_PIVOTS' above and https://github.com/assimp/assimp/discussions/4966 + if (nodeAnim->MNodeName.AsString.Contains("$AssimpFbx$")) + Logger.Error($"Animation '{animName}' contains a pivot bone ({nodeAnim->MNodeName.AsString}), we currently do not handle these. This animation may not resolve properly."); + + if (visitedNodeNames.Add(nodeName)) { visitedNodeNames.Add(nodeName); ProcessNodeAnimation(animationData.AnimationClips, nodeAnim, ticksPerSec); From 9d4e1bdd3413299a3968d1fd45db90e618d7ee6a Mon Sep 17 00:00:00 2001 From: Eideren Date: Wed, 5 Jun 2024 02:16:20 +0200 Subject: [PATCH 08/10] Rollback transformations related logic --- .../tools/Stride.Importer.3D/MeshConverter.cs | 122 +----------------- 1 file changed, 6 insertions(+), 116 deletions(-) diff --git a/sources/tools/Stride.Importer.3D/MeshConverter.cs b/sources/tools/Stride.Importer.3D/MeshConverter.cs index 0b67d84832..ee37ff509c 100644 --- a/sources/tools/Stride.Importer.3D/MeshConverter.cs +++ b/sources/tools/Stride.Importer.3D/MeshConverter.cs @@ -193,7 +193,7 @@ private unsafe Model ConvertAssimpScene(Scene* scene) // register the nodes and fill hierarchy var meshIndexToNodeIndex = new Dictionary>(); - RegisterNodes(scene->MRootNode, -1, nodeNames, meshIndexToNodeIndex, GetBoneList(scene)); + RegisterNodes(scene->MRootNode, -1, nodeNames, meshIndexToNodeIndex); // meshes for (var i = 0; i < scene->MNumMeshes; ++i) @@ -250,7 +250,7 @@ private unsafe Rendering.Skeleton ProcessSkeleton(Scene* scene) // register the nodes and fill hierarchy var meshIndexToNodeIndex = new Dictionary>(); - RegisterNodes(scene->MRootNode, -1, nodeNames, meshIndexToNodeIndex, GetBoneList(scene)); + RegisterNodes(scene->MRootNode, -1, nodeNames, meshIndexToNodeIndex); return new Rendering.Skeleton { @@ -268,7 +268,7 @@ private unsafe AnimationInfo ProcessAnimations(Scene* scene, int animationIndex) // register the nodes and fill hierarchy var meshIndexToNodeIndex = new Dictionary>(); - RegisterNodes(scene->MRootNode, -1, nodeNames, meshIndexToNodeIndex, GetBoneList(scene)); + RegisterNodes(scene->MRootNode, -1, nodeNames, meshIndexToNodeIndex); if (scene->MNumAnimations > 0) { @@ -462,22 +462,6 @@ private unsafe void GenerateUniqueNames(Dictionary finalNames, L } } - private unsafe HashSet GetBoneList(Scene* scene) - { - HashSet bones = new HashSet(); - for (uint i = 0; i < scene->MNumMeshes; i++) - { - var lMesh = scene->MMeshes[i]; - var boneCount = lMesh->MNumBones; - for (int j = 0; j < boneCount; j++) - { - string boneName = lMesh->MBones[j]->MName.AsString; - bones.Add(boneName); - } - } - return bones; - } - private unsafe void GenerateMeshNames(Scene* scene, Dictionary meshNames) { var baseNames = new List(); @@ -522,7 +506,7 @@ private unsafe void GetNodeNames(Node* node, List nodeNames, List nodeNames, Dictionary> meshIndexToNodeIndex, HashSet storeTransformationsForNodes) + private unsafe void RegisterNodes(Node* fromNode, int parentIndex, Dictionary nodeNames, Dictionary> meshIndexToNodeIndex) { var nodeIndex = nodes.Count; @@ -569,19 +553,12 @@ private unsafe void RegisterNodes(Node* fromNode, int parentIndex, DictionaryMName.AsString) == false) - { - modelNodeDefinition.Transform.Rotation = Quaternion.Identity; - modelNodeDefinition.Transform.Scale = Vector3.One; - modelNodeDefinition.Transform.Position = Vector3.Zero; - }*/ - nodes.Add(modelNodeDefinition); // register the children for (uint child = 0; child < fromNode->MNumChildren; ++child) { - RegisterNodes(fromNode->MChildren[child], nodeIndex, nodeNames, meshIndexToNodeIndex, storeTransformationsForNodes); + RegisterNodes(fromNode->MChildren[child], nodeIndex, nodeNames, meshIndexToNodeIndex); } } @@ -647,8 +624,7 @@ private unsafe MeshInfo ProcessMesh(Scene* scene, Silk.NET.Assimp.Mesh* mesh, Di bones.Add(new MeshBoneDefinition { NodeIndex = nodeIndex, - LinkToMeshMatrix = bone->MOffsetMatrix.ToStrideMatrix() - // LinkToMeshMatrix = rootTransformInverse * bone->MOffsetMatrix.ToStrideMatrix() * rootTransform + LinkToMeshMatrix = rootTransformInverse * bone->MOffsetMatrix.ToStrideMatrix() * rootTransform }); } @@ -1285,95 +1261,9 @@ private ComputeTextureColor GetTextureReferenceNode(string vfsOutputPath, string return textureValue; } - private unsafe void CorrectRootTransform(ref Scene* scene) - { - if(scene == null && scene->MMetaData == null) - { - return; - } - - int upAxis = 1, upAxisSign = 1, frontAxis = 2, frontAxisSign = 1, coordAxis = 0, coordAxisSign = 1; - double unitScaleFactor = 1.0; - - for(uint i = 0; i < scene->MMetaData->MNumProperties; i++) - { - if (scene->MMetaData->MKeys[i].AsString == "UpAxis") - { - GetMetaDataValue(i, scene->MMetaData, ref upAxis); - } - if (scene->MMetaData->MKeys[i].AsString == "UpAxisSign") - { - GetMetaDataValue(i, scene->MMetaData, ref upAxisSign); - } - if (scene->MMetaData->MKeys[i].AsString == "FrontAxis") - { - GetMetaDataValue(i, scene->MMetaData, ref frontAxis); - } - if (scene->MMetaData->MKeys[i].AsString == "FrontAxisSign") - { - GetMetaDataValue(i, scene->MMetaData, ref frontAxisSign); - } - if (scene->MMetaData->MKeys[i].AsString == "CoordAxis") - { - GetMetaDataValue(i, scene->MMetaData, ref coordAxis); - } - if (scene->MMetaData->MKeys[i].AsString == "CoordAxisSign") - { - GetMetaDataValue(i, scene->MMetaData, ref coordAxisSign); - } - if (scene->MMetaData->MKeys[i].AsString == "UnitScaleFactor") - { - GetMetaDataValue(i, scene->MMetaData, ref unitScaleFactor); - } - } - - var upVec = upAxis switch - { - 0 => new Vector3(upAxisSign * (float)unitScaleFactor, 0, 0), - 1 => new Vector3(0, upAxisSign * (float)unitScaleFactor, 0), - 2 => new Vector3(0, 0, upAxisSign * (float)unitScaleFactor), - _ => new Vector3(0, 1, 0), - }; - var forwardVec = frontAxis switch - { - 0 => new Vector3(frontAxisSign * (float)unitScaleFactor, 0, 0), - 1 => new Vector3(0, frontAxisSign * (float)unitScaleFactor, 0), - 2 => new Vector3(0, 0, frontAxisSign * (float)unitScaleFactor), - _ => new Vector3(0, 1, 0), - }; - var rightVec = coordAxis switch - { - 0 => new Vector3(coordAxisSign * (float)unitScaleFactor, 0, 0), - 1 => new Vector3(0, coordAxisSign * (float)unitScaleFactor, 0), - 2 => new Vector3(0, 0, coordAxisSign * (float)unitScaleFactor), - _ => new Vector3(0, 1, 0), - }; - - System.Numerics.Matrix4x4 matrix = new System.Numerics.Matrix4x4 - (rightVec.X, rightVec.Y, rightVec.Z, 0, - upVec.X, upVec.Y, upVec.Z, 0, - forwardVec.X, forwardVec.Y, forwardVec.Z, 0, - 0, 0, 0, 1); - - scene->MRootNode->MTransformation *= matrix; - } - - private unsafe bool GetMetaDataValue(uint index, Metadata* metaData, ref T value) - { - if(index >= metaData->MNumProperties) - { - return false; - } - - value = *(T*)metaData->MValues[index].MData; - - return true; - } - private unsafe List ExtractModels(Scene* scene, Dictionary meshNames, Dictionary materialNames, Dictionary nodeNames) { GenerateMeshNames(scene, meshNames); - CorrectRootTransform(ref scene); var meshList = new List(); for (uint i = 0; i < scene->MNumMeshes; ++i) { From 9ae194e3621490a8d8410228a4a53a74ef8dbfc2 Mon Sep 17 00:00:00 2001 From: Eideren Date: Wed, 5 Jun 2024 02:17:00 +0200 Subject: [PATCH 09/10] More cleanup --- .../tools/Stride.Importer.3D/MeshConverter.cs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/sources/tools/Stride.Importer.3D/MeshConverter.cs b/sources/tools/Stride.Importer.3D/MeshConverter.cs index ee37ff509c..4f80dcc77b 100644 --- a/sources/tools/Stride.Importer.3D/MeshConverter.cs +++ b/sources/tools/Stride.Importer.3D/MeshConverter.cs @@ -317,7 +317,6 @@ private unsafe AnimationInfo ProcessAnimations(Scene* scene, int animationIndex) if (visitedNodeNames.Add(nodeName)) { - visitedNodeNames.Add(nodeName); ProcessNodeAnimation(animationData.AnimationClips, nodeAnim, ticksPerSec); } else @@ -436,10 +435,8 @@ private unsafe void GenerateUniqueNames(Dictionary finalNames, L tempNames.Add(itemName); - // count the occurences of this name - if (!itemNameTotalCount.ContainsKey(itemName)) - itemNameTotalCount.Add(itemName, 1); - else + // count the occurrences of this name + if (!itemNameTotalCount.TryAdd(itemName, 1)) itemNameTotalCount[itemName]++; } @@ -450,9 +447,7 @@ private unsafe void GenerateUniqueNames(Dictionary finalNames, L if (itemNameTotalCount[itemName] > 1) { - if (!itemNameCurrentCount.ContainsKey(itemName)) - itemNameCurrentCount.Add(itemName, 1); - else + if (!itemNameCurrentCount.TryAdd(itemName, 1)) itemNameCurrentCount[itemName]++; itemName = itemName + "_" + itemNameCurrentCount[itemName].ToString(CultureInfo.InvariantCulture); @@ -906,9 +901,9 @@ private unsafe void ExtractEmbededTexture(Scene* scene, string importFieName) for (uint i = 0; i < scene->MNumTextures; ++i) { var texture=scene->MTextures[i]; - string fullName = Path.Combine(dir,Path.GetFileName(texture->MFilename)); + string fullName = Path.Combine(dir, Path.GetFileName(texture->MFilename)); CreateTextureFile(texture, fullName); - } + } } private unsafe void CreateTextureFile(Silk.NET.Assimp.Texture* texture, string path) @@ -1249,9 +1244,7 @@ private ComputeTextureColor GetTextureReferenceNode(string vfsOutputPath, string var referenceName = attachedReference.Url; // find a new and correctName - if (!textureNameCount.ContainsKey(referenceName)) - textureNameCount.Add(referenceName, 1); - else + if (!textureNameCount.TryAdd(referenceName, 1)) { int count = textureNameCount[referenceName]; textureNameCount[referenceName] = count + 1; From 6edb63d9780562cb9c1deb3cf412a1b4662bdda3 Mon Sep 17 00:00:00 2001 From: Eideren Date: Wed, 5 Jun 2024 02:17:50 +0200 Subject: [PATCH 10/10] Fix issue when trying to import animation from an fbx without animations --- sources/tools/Stride.Importer.3D/MeshConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/tools/Stride.Importer.3D/MeshConverter.cs b/sources/tools/Stride.Importer.3D/MeshConverter.cs index 4f80dcc77b..fe39fb10f1 100644 --- a/sources/tools/Stride.Importer.3D/MeshConverter.cs +++ b/sources/tools/Stride.Importer.3D/MeshConverter.cs @@ -1318,7 +1318,7 @@ private unsafe void GetNodes(Node* node, int depth, Dictionary n private unsafe List ExtractAnimations(Scene* scene, Dictionary animationNames) { if (scene->MNumAnimations == 0) - return null; + return new(); GenerateAnimationNames(scene, animationNames);