Add Split Objects object to split imported meshes/animations into separate assets
This commit is contained in:
@@ -243,12 +243,6 @@ namespace FlaxEditor.Content.Import
|
||||
[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; }
|
||||
|
||||
/// <summary>
|
||||
/// The zero-based index for the animation clip to import. If the source file has more than one animation it can be used to pick a desire clip.
|
||||
/// </summary>
|
||||
[EditorOrder(1080), DefaultValue(-1), EditorDisplay("Animation"), Tooltip("The zero-based index for the animation clip to import. If the source file has more than one animation it can be used to pick a desire clip.")]
|
||||
public int AnimationIndex { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// If checked, the importer will generate a sequence of LODs based on the base LOD index.
|
||||
/// </summary>
|
||||
@@ -290,6 +284,18 @@ namespace FlaxEditor.Content.Import
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// If checked, the imported mesh/animations are splitted into separate assets. Used if ObjectIndex is set to -1.
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[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.")]
|
||||
public int ObjectIndex { get; set; } = -1;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct InternalOptions
|
||||
@@ -325,7 +331,6 @@ namespace FlaxEditor.Content.Import
|
||||
public byte OptimizeKeyframes;
|
||||
public byte EnableRootMotion;
|
||||
public string RootNodeName;
|
||||
public int AnimationIndex;
|
||||
|
||||
// Level Of Detail
|
||||
public byte GenerateLODs;
|
||||
@@ -337,6 +342,10 @@ namespace FlaxEditor.Content.Import
|
||||
public byte ImportMaterials;
|
||||
public byte ImportTextures;
|
||||
public byte RestoreMaterialsOnReimport;
|
||||
|
||||
// Splitting
|
||||
public byte SplitObjects;
|
||||
public int ObjectIndex;
|
||||
}
|
||||
|
||||
internal void ToInternal(out InternalOptions options)
|
||||
@@ -368,7 +377,6 @@ namespace FlaxEditor.Content.Import
|
||||
OptimizeKeyframes = (byte)(OptimizeKeyframes ? 1 : 0),
|
||||
EnableRootMotion = (byte)(EnableRootMotion ? 1 : 0),
|
||||
RootNodeName = RootNodeName,
|
||||
AnimationIndex = AnimationIndex,
|
||||
GenerateLODs = (byte)(GenerateLODs ? 1 : 0),
|
||||
BaseLOD = BaseLOD,
|
||||
LODCount = LODCount,
|
||||
@@ -376,6 +384,8 @@ namespace FlaxEditor.Content.Import
|
||||
ImportMaterials = (byte)(ImportMaterials ? 1 : 0),
|
||||
ImportTextures = (byte)(ImportTextures ? 1 : 0),
|
||||
RestoreMaterialsOnReimport = (byte)(RestoreMaterialsOnReimport ? 1 : 0),
|
||||
SplitObjects = (byte)(SplitObjects ? 1 : 0),
|
||||
ObjectIndex = ObjectIndex,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -405,7 +415,6 @@ namespace FlaxEditor.Content.Import
|
||||
OptimizeKeyframes = options.OptimizeKeyframes != 0;
|
||||
EnableRootMotion = options.EnableRootMotion != 0;
|
||||
RootNodeName = options.RootNodeName;
|
||||
AnimationIndex = options.AnimationIndex;
|
||||
GenerateLODs = options.GenerateLODs != 0;
|
||||
BaseLOD = options.BaseLOD;
|
||||
LODCount = options.LODCount;
|
||||
@@ -413,6 +422,8 @@ namespace FlaxEditor.Content.Import
|
||||
ImportMaterials = options.ImportMaterials != 0;
|
||||
ImportTextures = options.ImportTextures != 0;
|
||||
RestoreMaterialsOnReimport = options.RestoreMaterialsOnReimport != 0;
|
||||
SplitObjects = options.SplitObjects != 0;
|
||||
ObjectIndex = options.ObjectIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -183,7 +183,6 @@ struct InternalModelOptions
|
||||
byte OptimizeKeyframes;
|
||||
byte EnableRootMotion;
|
||||
MonoString* RootNodeName;
|
||||
int32 AnimationIndex;
|
||||
|
||||
// Level Of Detail
|
||||
byte GenerateLODs;
|
||||
@@ -196,6 +195,10 @@ struct InternalModelOptions
|
||||
byte ImportTextures;
|
||||
byte RestoreMaterialsOnReimport;
|
||||
|
||||
// Splitting
|
||||
byte SplitObjects;
|
||||
int32 ObjectIndex;
|
||||
|
||||
static void Convert(InternalModelOptions* from, ImportModelFile::Options* to)
|
||||
{
|
||||
to->Type = from->Type;
|
||||
@@ -223,7 +226,6 @@ struct InternalModelOptions
|
||||
to->OptimizeKeyframes = from->OptimizeKeyframes;
|
||||
to->EnableRootMotion = from->EnableRootMotion;
|
||||
to->RootNodeName = MUtils::ToString(from->RootNodeName);
|
||||
to->AnimationIndex = from->AnimationIndex;
|
||||
to->GenerateLODs = from->GenerateLODs;
|
||||
to->BaseLOD = from->BaseLOD;
|
||||
to->LODCount = from->LODCount;
|
||||
@@ -231,6 +233,8 @@ struct InternalModelOptions
|
||||
to->ImportMaterials = from->ImportMaterials;
|
||||
to->ImportTextures = from->ImportTextures;
|
||||
to->RestoreMaterialsOnReimport = from->RestoreMaterialsOnReimport;
|
||||
to->SplitObjects = from->SplitObjects;
|
||||
to->ObjectIndex = from->ObjectIndex;
|
||||
}
|
||||
|
||||
static void Convert(ImportModelFile::Options* from, InternalModelOptions* to)
|
||||
@@ -260,7 +264,6 @@ struct InternalModelOptions
|
||||
to->OptimizeKeyframes = from->OptimizeKeyframes;
|
||||
to->EnableRootMotion = from->EnableRootMotion;
|
||||
to->RootNodeName = MUtils::ToString(from->RootNodeName);
|
||||
to->AnimationIndex = from->AnimationIndex;
|
||||
to->GenerateLODs = from->GenerateLODs;
|
||||
to->BaseLOD = from->BaseLOD;
|
||||
to->LODCount = from->LODCount;
|
||||
@@ -268,6 +271,8 @@ struct InternalModelOptions
|
||||
to->ImportMaterials = from->ImportMaterials;
|
||||
to->ImportTextures = from->ImportTextures;
|
||||
to->RestoreMaterialsOnReimport = from->RestoreMaterialsOnReimport;
|
||||
to->SplitObjects = from->SplitObjects;
|
||||
to->ObjectIndex = from->ObjectIndex;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "Engine/Content/Assets/Animation.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "AssetsImportingManager.h"
|
||||
|
||||
bool ImportModelFile::TryGetImportOptions(String path, Options& options)
|
||||
{
|
||||
@@ -111,6 +112,19 @@ CreateAssetResult ImportModelFile::Import(CreateAssetContext& context)
|
||||
LOG(Warning, "Missing model import options. Using default values.");
|
||||
}
|
||||
}
|
||||
if (options.SplitObjects)
|
||||
{
|
||||
options.OnSplitImport.Bind([&context](Options& splitOptions, const String& objectName)
|
||||
{
|
||||
// Recursive importing of the split object
|
||||
String postFix = objectName;
|
||||
const int32 splitPos = postFix.FindLast(TEXT('|'));
|
||||
if (splitPos != -1)
|
||||
postFix = postFix.Substring(splitPos + 1);
|
||||
const String outputPath = String(StringUtils::GetPathWithoutExtension(context.TargetAssetPath)) + TEXT(" ") + postFix + TEXT(".flax");
|
||||
return AssetsImportingManager::Import(context.InputPath, outputPath, &splitOptions);
|
||||
});
|
||||
}
|
||||
|
||||
// Import model file
|
||||
ModelData modelData;
|
||||
@@ -149,7 +163,6 @@ CreateAssetResult ImportModelFile::Import(CreateAssetContext& context)
|
||||
return result;
|
||||
|
||||
#if IMPORT_MODEL_CACHE_OPTIONS
|
||||
|
||||
// Create json with import context
|
||||
rapidjson_flax::StringBuffer importOptionsMetaBuffer;
|
||||
importOptionsMetaBuffer.Reserve(256);
|
||||
@@ -162,7 +175,6 @@ CreateAssetResult ImportModelFile::Import(CreateAssetContext& context)
|
||||
}
|
||||
importOptionsMeta.EndObject();
|
||||
context.Data.Metadata.Copy((const byte*)importOptionsMetaBuffer.GetString(), (uint32)importOptionsMetaBuffer.GetSize());
|
||||
|
||||
#endif
|
||||
|
||||
return CreateAssetResult::Ok;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "ModelTool.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/DeleteMe.h"
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Tools/TextureTool/TextureTool.h"
|
||||
@@ -38,8 +39,6 @@ public:
|
||||
DefaultLogger::kill();
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void write(const char* message) override
|
||||
{
|
||||
String s(message);
|
||||
@@ -139,22 +138,19 @@ struct AssimpBone
|
||||
|
||||
struct AssimpImporterData
|
||||
{
|
||||
ImportedModelData& Model;
|
||||
Importer AssimpImporter;
|
||||
AssimpLogStream AssimpLogStream;
|
||||
const String Path;
|
||||
const aiScene* Scene;
|
||||
const aiScene* Scene = nullptr;
|
||||
const ModelTool::Options& Options;
|
||||
|
||||
Array<AssimpNode> Nodes;
|
||||
Array<AssimpBone> Bones;
|
||||
Dictionary<int32, Array<int32>> MeshIndexToNodeIndex;
|
||||
|
||||
AssimpImporterData(const char* path, ImportedModelData& model, const ModelTool::Options& options, const aiScene* scene)
|
||||
: Model(model)
|
||||
, Path(path)
|
||||
, Scene(scene)
|
||||
AssimpImporterData(const char* path, const ModelTool::Options& options)
|
||||
: Path(path)
|
||||
, Options(options)
|
||||
, Nodes(static_cast<int32>(scene->mNumMeshes * 4.0f))
|
||||
, MeshIndexToNodeIndex(static_cast<int32>(scene->mNumMeshes * 8.0f))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -231,7 +227,7 @@ void ProcessNodes(AssimpImporterData& data, aiNode* aNode, int32 parentIndex)
|
||||
}
|
||||
}
|
||||
|
||||
bool ProcessMesh(AssimpImporterData& data, const aiMesh* aMesh, MeshData& mesh, String& errorMsg)
|
||||
bool ProcessMesh(ImportedModelData& result, AssimpImporterData& data, const aiMesh* aMesh, MeshData& mesh, String& errorMsg)
|
||||
{
|
||||
// Properties
|
||||
mesh.Name = aMesh->mName.C_Str();
|
||||
@@ -330,7 +326,7 @@ bool ProcessMesh(AssimpImporterData& data, const aiMesh* aMesh, MeshData& mesh,
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(Warning, "Cannot import model lightmap uvs. Missing texcoords channel {0}.", inputChannelIndex);
|
||||
LOG(Warning, "Cannot import result lightmap uvs. Missing texcoords channel {0}.", inputChannelIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +343,7 @@ bool ProcessMesh(AssimpImporterData& data, const aiMesh* aMesh, MeshData& mesh,
|
||||
}
|
||||
|
||||
// Blend Indices and Blend Weights
|
||||
if (aMesh->mNumBones > 0 && aMesh->mBones && data.Model.Types & ImportDataTypes::Skeleton)
|
||||
if (aMesh->mNumBones > 0 && aMesh->mBones && result.Types & ImportDataTypes::Skeleton)
|
||||
{
|
||||
const int32 vertexCount = mesh.Positions.Count();
|
||||
mesh.BlendIndices.Resize(vertexCount);
|
||||
@@ -428,7 +424,7 @@ bool ProcessMesh(AssimpImporterData& data, const aiMesh* aMesh, MeshData& mesh,
|
||||
}
|
||||
|
||||
// Blend Shapes
|
||||
if (aMesh->mNumAnimMeshes > 0 && data.Model.Types & ImportDataTypes::Skeleton && data.Options.ImportBlendShapes)
|
||||
if (aMesh->mNumAnimMeshes > 0 && result.Types & ImportDataTypes::Skeleton && data.Options.ImportBlendShapes)
|
||||
{
|
||||
mesh.BlendShapes.EnsureCapacity(aMesh->mNumAnimMeshes);
|
||||
for (unsigned int animMeshIndex = 0; animMeshIndex < aMesh->mNumAnimMeshes; animMeshIndex++)
|
||||
@@ -473,7 +469,7 @@ bool ProcessMesh(AssimpImporterData& data, const aiMesh* aMesh, MeshData& mesh,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImportTexture(AssimpImporterData& data, aiString& aFilename, int32& textureIndex, TextureEntry::TypeHint type)
|
||||
bool ImportTexture(ImportedModelData& result, AssimpImporterData& data, aiString& aFilename, int32& textureIndex, TextureEntry::TypeHint type)
|
||||
{
|
||||
// Find texture file path
|
||||
const String filename = String(aFilename.C_Str()).TrimTrailing();
|
||||
@@ -483,35 +479,35 @@ bool ImportTexture(AssimpImporterData& data, aiString& aFilename, int32& texture
|
||||
|
||||
// Check if already used
|
||||
textureIndex = 0;
|
||||
while (textureIndex < data.Model.Textures.Count())
|
||||
while (textureIndex < result.Textures.Count())
|
||||
{
|
||||
if (data.Model.Textures[textureIndex].FilePath == path)
|
||||
if (result.Textures[textureIndex].FilePath == path)
|
||||
return true;
|
||||
textureIndex++;
|
||||
}
|
||||
|
||||
// Import texture
|
||||
auto& texture = data.Model.Textures.AddOne();
|
||||
auto& texture = result.Textures.AddOne();
|
||||
texture.FilePath = path;
|
||||
texture.Type = type;
|
||||
texture.AssetID = Guid::Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImportMaterialTexture(AssimpImporterData& data, const aiMaterial* aMaterial, aiTextureType aTextureType, int32& textureIndex, TextureEntry::TypeHint type)
|
||||
bool ImportMaterialTexture(ImportedModelData& result, AssimpImporterData& data, const aiMaterial* aMaterial, aiTextureType aTextureType, int32& textureIndex, TextureEntry::TypeHint type)
|
||||
{
|
||||
aiString aFilename;
|
||||
return aMaterial->GetTexture(aTextureType, 0, &aFilename, nullptr, nullptr, nullptr, nullptr) == AI_SUCCESS &&
|
||||
ImportTexture(data, aFilename, textureIndex, type);
|
||||
ImportTexture(result, data, aFilename, textureIndex, type);
|
||||
}
|
||||
|
||||
bool ImportMaterials(AssimpImporterData& data, String& errorMsg)
|
||||
bool ImportMaterials(ImportedModelData& result, AssimpImporterData& data, String& errorMsg)
|
||||
{
|
||||
const uint32 materialsCount = (uint32)data.Scene->mNumMaterials;
|
||||
data.Model.Materials.Resize(materialsCount, false);
|
||||
const uint32 materialsCount = data.Scene->mNumMaterials;
|
||||
result.Materials.Resize(materialsCount, false);
|
||||
for (uint32 i = 0; i < materialsCount; i++)
|
||||
{
|
||||
auto& materialSlot = data.Model.Materials[i];
|
||||
auto& materialSlot = result.Materials[i];
|
||||
const aiMaterial* aMaterial = data.Scene->mMaterials[i];
|
||||
|
||||
aiString aName;
|
||||
@@ -519,7 +515,7 @@ bool ImportMaterials(AssimpImporterData& data, String& errorMsg)
|
||||
materialSlot.Name = String(aName.C_Str()).TrimTrailing();
|
||||
materialSlot.AssetID = Guid::Empty;
|
||||
|
||||
if (data.Model.Types & ImportDataTypes::Materials)
|
||||
if (result.Types & ImportDataTypes::Materials)
|
||||
{
|
||||
aiColor3D aColor;
|
||||
if (aMaterial->Get(AI_MATKEY_COLOR_DIFFUSE, aColor) == AI_SUCCESS)
|
||||
@@ -531,19 +527,19 @@ bool ImportMaterials(AssimpImporterData& data, String& errorMsg)
|
||||
if (aMaterial->Get(AI_MATKEY_OPACITY, aFloat) == AI_SUCCESS)
|
||||
materialSlot.Opacity.Value = aFloat;
|
||||
|
||||
if (data.Model.Types & ImportDataTypes::Textures)
|
||||
if (result.Types & ImportDataTypes::Textures)
|
||||
{
|
||||
ImportMaterialTexture(data, aMaterial, aiTextureType_DIFFUSE, materialSlot.Diffuse.TextureIndex, TextureEntry::TypeHint::ColorRGB);
|
||||
ImportMaterialTexture(data, aMaterial, aiTextureType_EMISSIVE, materialSlot.Emissive.TextureIndex, TextureEntry::TypeHint::ColorRGB);
|
||||
ImportMaterialTexture(data, aMaterial, aiTextureType_NORMALS, materialSlot.Normals.TextureIndex, TextureEntry::TypeHint::Normals);
|
||||
ImportMaterialTexture(data, aMaterial, aiTextureType_OPACITY, materialSlot.Opacity.TextureIndex, TextureEntry::TypeHint::ColorRGBA);
|
||||
ImportMaterialTexture(result, data, aMaterial, aiTextureType_DIFFUSE, materialSlot.Diffuse.TextureIndex, TextureEntry::TypeHint::ColorRGB);
|
||||
ImportMaterialTexture(result, data, aMaterial, aiTextureType_EMISSIVE, materialSlot.Emissive.TextureIndex, TextureEntry::TypeHint::ColorRGB);
|
||||
ImportMaterialTexture(result, data, aMaterial, aiTextureType_NORMALS, materialSlot.Normals.TextureIndex, TextureEntry::TypeHint::Normals);
|
||||
ImportMaterialTexture(result, data, aMaterial, aiTextureType_OPACITY, materialSlot.Opacity.TextureIndex, TextureEntry::TypeHint::ColorRGBA);
|
||||
|
||||
if (materialSlot.Diffuse.TextureIndex != -1)
|
||||
{
|
||||
// Detect using alpha mask in diffuse texture
|
||||
materialSlot.Diffuse.HasAlphaMask = TextureTool::HasAlpha(data.Model.Textures[materialSlot.Diffuse.TextureIndex].FilePath);
|
||||
materialSlot.Diffuse.HasAlphaMask = TextureTool::HasAlpha(result.Textures[materialSlot.Diffuse.TextureIndex].FilePath);
|
||||
if (materialSlot.Diffuse.HasAlphaMask)
|
||||
data.Model.Textures[materialSlot.Diffuse.TextureIndex].Type = TextureEntry::TypeHint::ColorRGBA;
|
||||
result.Textures[materialSlot.Diffuse.TextureIndex].Type = TextureEntry::TypeHint::ColorRGBA;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -552,46 +548,42 @@ bool ImportMaterials(AssimpImporterData& data, String& errorMsg)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImportMeshes(AssimpImporterData& data, String& errorMsg)
|
||||
bool ImportMesh(int32 i, ImportedModelData& result, AssimpImporterData& data, String& errorMsg)
|
||||
{
|
||||
for (unsigned i = 0; i < data.Scene->mNumMeshes; i++)
|
||||
const auto aMesh = data.Scene->mMeshes[i];
|
||||
|
||||
// Skip invalid meshes
|
||||
if (aMesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE || aMesh->mNumVertices == 0 || aMesh->mNumFaces == 0 || aMesh->mFaces[0].mNumIndices != 3)
|
||||
return false;
|
||||
|
||||
// Skip unused meshes
|
||||
if (!data.MeshIndexToNodeIndex.ContainsKey(i))
|
||||
return false;
|
||||
|
||||
// Import mesh data
|
||||
MeshData* meshData = New<MeshData>();
|
||||
if (ProcessMesh(result, data, aMesh, *meshData, errorMsg))
|
||||
return true;
|
||||
|
||||
auto& nodesWithMesh = data.MeshIndexToNodeIndex[i];
|
||||
for (int32 j = 0; j < nodesWithMesh.Count(); j++)
|
||||
{
|
||||
const auto aMesh = data.Scene->mMeshes[i];
|
||||
const auto nodeIndex = nodesWithMesh[j];
|
||||
auto& node = data.Nodes[nodeIndex];
|
||||
const int32 lodIndex = node.LodIndex;
|
||||
|
||||
// Skip invalid meshes
|
||||
if (aMesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE || aMesh->mNumVertices == 0 || aMesh->mNumFaces == 0 || aMesh->mFaces[0].mNumIndices != 3)
|
||||
continue;
|
||||
|
||||
// Skip unused meshes
|
||||
if (!data.MeshIndexToNodeIndex.ContainsKey(i))
|
||||
continue;
|
||||
|
||||
// Import mesh data
|
||||
MeshData* meshData = New<MeshData>();
|
||||
if (ProcessMesh(data, aMesh, *meshData, errorMsg))
|
||||
return true;
|
||||
|
||||
auto& nodesWithMesh = data.MeshIndexToNodeIndex[i];
|
||||
for (int32 j = 0; j < nodesWithMesh.Count(); j++)
|
||||
// The first mesh instance uses meshData directly while others have to clone it
|
||||
if (j != 0)
|
||||
{
|
||||
const auto nodeIndex = nodesWithMesh[j];
|
||||
auto& node = data.Nodes[nodeIndex];
|
||||
const int32 lodIndex = node.LodIndex;
|
||||
|
||||
// The first mesh instance uses meshData directly while others have to clone it
|
||||
if (j != 0)
|
||||
{
|
||||
meshData = New<MeshData>(*meshData);
|
||||
}
|
||||
|
||||
// Link mesh
|
||||
meshData->NodeIndex = nodeIndex;
|
||||
if (data.Model.LODs.Count() <= lodIndex)
|
||||
data.Model.LODs.Resize(lodIndex + 1);
|
||||
data.Model.LODs[lodIndex].Meshes.Add(meshData);
|
||||
meshData = New<MeshData>(*meshData);
|
||||
}
|
||||
}
|
||||
|
||||
// Link mesh
|
||||
meshData->NodeIndex = nodeIndex;
|
||||
if (result.LODs.Count() <= lodIndex)
|
||||
result.LODs.Resize(lodIndex + 1);
|
||||
result.LODs[lodIndex].Meshes.Add(meshData);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -629,125 +621,181 @@ void ImportCurve(aiQuatKey* keys, uint32 keysCount, LinearCurve<Quaternion>& cur
|
||||
}
|
||||
}
|
||||
|
||||
static bool AssimpInited = false;
|
||||
|
||||
bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, const Options& options, String& errorMsg)
|
||||
bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, Options& options, String& errorMsg)
|
||||
{
|
||||
// Prepare
|
||||
if (!AssimpInited)
|
||||
auto context = (AssimpImporterData*)options.SplitContext;
|
||||
if (!context)
|
||||
{
|
||||
AssimpInited = true;
|
||||
static bool AssimpInited = false;
|
||||
if (!AssimpInited)
|
||||
{
|
||||
AssimpInited = true;
|
||||
LOG(Info, "Assimp {0}.{1}.{2}", aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision());
|
||||
}
|
||||
bool importMeshes = (data.Types & ImportDataTypes::Geometry) != 0;
|
||||
bool importAnimations = (data.Types & ImportDataTypes::Animations) != 0;
|
||||
context = New<AssimpImporterData>(path, options);
|
||||
|
||||
// Log Assimp version
|
||||
LOG(Info, "Assimp {0}.{1}.{2}", aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision());
|
||||
// Setup import flags
|
||||
unsigned int flags =
|
||||
aiProcess_JoinIdenticalVertices |
|
||||
aiProcess_LimitBoneWeights |
|
||||
aiProcess_Triangulate |
|
||||
aiProcess_GenUVCoords |
|
||||
aiProcess_FindDegenerates |
|
||||
aiProcess_FindInvalidData |
|
||||
//aiProcess_ValidateDataStructure |
|
||||
aiProcess_ConvertToLeftHanded;
|
||||
if (importMeshes)
|
||||
{
|
||||
if (options.CalculateNormals)
|
||||
flags |= aiProcess_FixInfacingNormals | aiProcess_GenSmoothNormals;
|
||||
if (options.CalculateTangents)
|
||||
flags |= aiProcess_CalcTangentSpace;
|
||||
if (options.OptimizeMeshes)
|
||||
flags |= aiProcess_OptimizeMeshes | aiProcess_SplitLargeMeshes | aiProcess_ImproveCacheLocality;
|
||||
if (options.MergeMeshes)
|
||||
flags |= aiProcess_RemoveRedundantMaterials;
|
||||
}
|
||||
|
||||
// Setup import options
|
||||
context->AssimpImporter.SetPropertyFloat(AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE, options.SmoothingNormalsAngle);
|
||||
context->AssimpImporter.SetPropertyFloat(AI_CONFIG_PP_CT_MAX_SMOOTHING_ANGLE, options.SmoothingTangentsAngle);
|
||||
//context->AssimpImporter.SetPropertyInteger(AI_CONFIG_PP_SLM_TRIANGLE_LIMIT, MAX_uint16);
|
||||
context->AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, false);
|
||||
context->AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, false);
|
||||
context->AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, false);
|
||||
context->AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, importAnimations);
|
||||
//context->AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false); // TODO: optimize pivots when https://github.com/assimp/assimp/issues/1068 gets fixed
|
||||
context->AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
|
||||
|
||||
// Import file
|
||||
context->Scene = context->AssimpImporter.ReadFile(path, flags);
|
||||
if (context->Scene == nullptr)
|
||||
{
|
||||
LOG_STR(Warning, String(context->AssimpImporter.GetErrorString()));
|
||||
LOG_STR(Warning, String(path));
|
||||
LOG_STR(Warning, StringUtils::ToString(flags));
|
||||
errorMsg = context->AssimpImporter.GetErrorString();
|
||||
Delete(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process imported scene nodes
|
||||
ProcessNodes(*context, context->Scene->mRootNode, -1);
|
||||
}
|
||||
Importer importer;
|
||||
AssimpLogStream assimpLogStream;
|
||||
bool importMeshes = (data.Types & ImportDataTypes::Geometry) != 0;
|
||||
bool importAnimations = (data.Types & ImportDataTypes::Animations) != 0;
|
||||
|
||||
// Setup import flags
|
||||
unsigned int flags =
|
||||
aiProcess_JoinIdenticalVertices |
|
||||
aiProcess_LimitBoneWeights |
|
||||
aiProcess_Triangulate |
|
||||
aiProcess_GenUVCoords |
|
||||
aiProcess_FindDegenerates |
|
||||
aiProcess_FindInvalidData |
|
||||
//aiProcess_ValidateDataStructure |
|
||||
aiProcess_ConvertToLeftHanded;
|
||||
if (importMeshes)
|
||||
{
|
||||
if (options.CalculateNormals)
|
||||
flags |= aiProcess_FixInfacingNormals | aiProcess_GenSmoothNormals;
|
||||
if (options.CalculateTangents)
|
||||
flags |= aiProcess_CalcTangentSpace;
|
||||
if (options.OptimizeMeshes)
|
||||
flags |= aiProcess_OptimizeMeshes | aiProcess_SplitLargeMeshes | aiProcess_ImproveCacheLocality;
|
||||
if (options.MergeMeshes)
|
||||
flags |= aiProcess_RemoveRedundantMaterials;
|
||||
}
|
||||
|
||||
// Setup import options
|
||||
importer.SetPropertyFloat(AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE, options.SmoothingNormalsAngle);
|
||||
importer.SetPropertyFloat(AI_CONFIG_PP_CT_MAX_SMOOTHING_ANGLE, options.SmoothingTangentsAngle);
|
||||
//importer.SetPropertyInteger(AI_CONFIG_PP_SLM_TRIANGLE_LIMIT, MAX_uint16);
|
||||
importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, false);
|
||||
importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, false);
|
||||
importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, false);
|
||||
importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, importAnimations);
|
||||
//importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false); // TODO: optimize pivots when https://github.com/assimp/assimp/issues/1068 gets fixed
|
||||
importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
|
||||
|
||||
// Import file
|
||||
const auto scene = importer.ReadFile(path, flags);
|
||||
if (scene == nullptr)
|
||||
{
|
||||
LOG_STR(Warning, String(importer.GetErrorString()));
|
||||
LOG_STR(Warning, String(path));
|
||||
LOG_STR(Warning, StringUtils::ToString(flags));
|
||||
errorMsg = importer.GetErrorString();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process imported scene nodes
|
||||
AssimpImporterData assimpData(path, data, options, scene);
|
||||
ProcessNodes(assimpData, scene->mRootNode, -1);
|
||||
DeleteMe<AssimpImporterData> contextCleanup(options.SplitContext ? nullptr : context);
|
||||
|
||||
// Import materials
|
||||
if (ImportMaterials(assimpData, errorMsg))
|
||||
if (ImportMaterials(data, *context, errorMsg))
|
||||
{
|
||||
LOG(Warning, "Failed to import materials.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Import geometry
|
||||
if (data.Types & ImportDataTypes::Geometry)
|
||||
if (data.Types & ImportDataTypes::Geometry && context->Scene->HasMeshes())
|
||||
{
|
||||
if (ImportMeshes(assimpData, errorMsg))
|
||||
const int meshCount = context->Scene->mNumMeshes;
|
||||
if (options.SplitObjects && options.ObjectIndex == -1)
|
||||
{
|
||||
LOG(Warning, "Failed to import meshes.");
|
||||
return true;
|
||||
// Import the first object within this call
|
||||
options.SplitObjects = false;
|
||||
options.ObjectIndex = 0;
|
||||
|
||||
if (meshCount > 1 && options.OnSplitImport.IsBinded())
|
||||
{
|
||||
// Split all animations into separate assets
|
||||
LOG(Info, "Splitting imported {0} meshes", meshCount);
|
||||
for (int32 i = 1; i < meshCount; i++)
|
||||
{
|
||||
auto splitOptions = options;
|
||||
splitOptions.ObjectIndex = i;
|
||||
splitOptions.SplitContext = context;
|
||||
const auto aMesh = context->Scene->mMeshes[i];
|
||||
const String objectName(aMesh->mName.C_Str());
|
||||
options.OnSplitImport(splitOptions, objectName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.ObjectIndex != -1)
|
||||
{
|
||||
// Import the selected mesh
|
||||
const auto meshIndex = Math::Clamp<int32>(options.ObjectIndex, 0, meshCount - 1);
|
||||
if (ImportMesh(meshIndex, data, *context, errorMsg))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Import all meshes
|
||||
for (int32 meshIndex = 0; meshIndex < meshCount; meshIndex++)
|
||||
{
|
||||
if (ImportMesh(meshIndex, data, *context, errorMsg))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import skeleton
|
||||
if (data.Types & ImportDataTypes::Skeleton)
|
||||
{
|
||||
data.Skeleton.Nodes.Resize(assimpData.Nodes.Count(), false);
|
||||
for (int32 i = 0; i < assimpData.Nodes.Count(); i++)
|
||||
data.Skeleton.Nodes.Resize(context->Nodes.Count(), false);
|
||||
for (int32 i = 0; i < context->Nodes.Count(); i++)
|
||||
{
|
||||
auto& node = data.Skeleton.Nodes[i];
|
||||
auto& aNode = assimpData.Nodes[i];
|
||||
auto& aNode = context->Nodes[i];
|
||||
|
||||
node.Name = aNode.Name;
|
||||
node.ParentIndex = aNode.ParentIndex;
|
||||
node.LocalTransform = aNode.LocalTransform;
|
||||
}
|
||||
|
||||
data.Skeleton.Bones.Resize(assimpData.Bones.Count(), false);
|
||||
for (int32 i = 0; i < assimpData.Bones.Count(); i++)
|
||||
data.Skeleton.Bones.Resize(context->Bones.Count(), false);
|
||||
for (int32 i = 0; i < context->Bones.Count(); i++)
|
||||
{
|
||||
auto& bone = data.Skeleton.Bones[i];
|
||||
auto& aBone = assimpData.Bones[i];
|
||||
auto& aBone = context->Bones[i];
|
||||
|
||||
const auto boneNodeIndex = aBone.NodeIndex;
|
||||
const auto parentBoneNodeIndex = aBone.ParentBoneIndex == -1 ? -1 : assimpData.Bones[aBone.ParentBoneIndex].NodeIndex;
|
||||
const auto parentBoneNodeIndex = aBone.ParentBoneIndex == -1 ? -1 : context->Bones[aBone.ParentBoneIndex].NodeIndex;
|
||||
|
||||
bone.ParentIndex = aBone.ParentBoneIndex;
|
||||
bone.NodeIndex = aBone.NodeIndex;
|
||||
bone.LocalTransform = CombineTransformsFromNodeIndices(assimpData.Nodes, parentBoneNodeIndex, boneNodeIndex);
|
||||
bone.LocalTransform = CombineTransformsFromNodeIndices(context->Nodes, parentBoneNodeIndex, boneNodeIndex);
|
||||
bone.OffsetMatrix = aBone.OffsetMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
// Import animations
|
||||
if (data.Types & ImportDataTypes::Animations)
|
||||
if (data.Types & ImportDataTypes::Animations && context->Scene->HasAnimations())
|
||||
{
|
||||
if (scene->HasAnimations())
|
||||
const int32 animCount = (int32)context->Scene->mNumAnimations;
|
||||
if (options.SplitObjects && options.ObjectIndex == -1)
|
||||
{
|
||||
const auto animIndex = Math::Clamp<int32>(options.AnimationIndex, 0, scene->mNumAnimations - 1);
|
||||
const auto animations = scene->mAnimations[animIndex];
|
||||
// Import the first object within this call
|
||||
options.SplitObjects = false;
|
||||
options.ObjectIndex = 0;
|
||||
|
||||
if (animCount > 1 && options.OnSplitImport.IsBinded())
|
||||
{
|
||||
// Split all animations into separate assets
|
||||
LOG(Info, "Splitting imported {0} animations", animCount);
|
||||
for (int32 i = 1; i < animCount; i++)
|
||||
{
|
||||
auto splitOptions = options;
|
||||
splitOptions.ObjectIndex = i;
|
||||
splitOptions.SplitContext = context;
|
||||
const auto animations = context->Scene->mAnimations[i];
|
||||
const String objectName(animations->mName.C_Str());
|
||||
options.OnSplitImport(splitOptions, objectName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import the animation
|
||||
{
|
||||
const auto animIndex = Math::Clamp<int32>(options.ObjectIndex, 0, context->Scene->mNumAnimations - 1);
|
||||
const auto animations = context->Scene->mAnimations[animIndex];
|
||||
data.Animation.Channels.Resize(animations->mNumChannels, false);
|
||||
data.Animation.Duration = animations->mDuration;
|
||||
data.Animation.FramesPerSecond = animations->mTicksPerSecond != 0.0 ? animations->mTicksPerSecond : 25.0;
|
||||
@@ -764,20 +812,16 @@ bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, cons
|
||||
ImportCurve(aAnim->mScalingKeys, aAnim->mNumScalingKeys, anim.Scale);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(Warning, "Loaded scene has no animations");
|
||||
}
|
||||
}
|
||||
|
||||
// Import nodes
|
||||
if (data.Types & ImportDataTypes::Nodes)
|
||||
{
|
||||
data.Nodes.Resize(assimpData.Nodes.Count());
|
||||
for (int32 i = 0; i < assimpData.Nodes.Count(); i++)
|
||||
data.Nodes.Resize(context->Nodes.Count());
|
||||
for (int32 i = 0; i < context->Nodes.Count(); i++)
|
||||
{
|
||||
auto& node = data.Nodes[i];
|
||||
auto& aNode = assimpData.Nodes[i];
|
||||
auto& aNode = context->Nodes[i];
|
||||
|
||||
node.Name = aNode.Name;
|
||||
node.ParentIndex = aNode.ParentIndex;
|
||||
|
||||
@@ -821,7 +821,7 @@ void BakeTransforms(FbxScene* scene)
|
||||
scene->GetRootNode()->ConvertPivotAnimationRecursive(nullptr, FbxNode::eDestinationPivot, frameRate, false);
|
||||
}
|
||||
|
||||
bool ModelTool::ImportDataAutodeskFbxSdk(const char* path, ImportedModelData& data, const Options& options, String& errorMsg)
|
||||
bool ModelTool::ImportDataAutodeskFbxSdk(const char* path, ImportedModelData& data, Options& options, String& errorMsg)
|
||||
{
|
||||
ScopeLock lock(FbxSdkManager::Locker);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "ModelTool.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/DeleteMe.h"
|
||||
#include "Engine/Core/Math/Mathd.h"
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
#include "Engine/Core/Collections/Sorting.h"
|
||||
@@ -78,8 +79,8 @@ struct FbxBone
|
||||
|
||||
struct OpenFbxImporterData
|
||||
{
|
||||
ImportedModelData& Model;
|
||||
const ofbx::IScene* Scene;
|
||||
std::unique_ptr<ofbx::IScene> ScenePtr;
|
||||
const String Path;
|
||||
const ModelTool::Options& Options;
|
||||
|
||||
@@ -94,10 +95,11 @@ struct OpenFbxImporterData
|
||||
Array<FbxNode> Nodes;
|
||||
Array<FbxBone> Bones;
|
||||
Array<const ofbx::Material*> Materials;
|
||||
Array<MaterialSlotEntry> ImportedMaterials;
|
||||
|
||||
OpenFbxImporterData(const char* path, ImportedModelData& model, const ModelTool::Options& options, const ofbx::IScene* scene)
|
||||
: Model(model)
|
||||
, Scene(scene)
|
||||
OpenFbxImporterData(const char* path, const ModelTool::Options& options, ofbx::IScene* scene)
|
||||
: Scene(scene)
|
||||
, ScenePtr(scene)
|
||||
, Path(path)
|
||||
, Options(options)
|
||||
, GlobalSettings(*scene->getGlobalSettings())
|
||||
@@ -170,7 +172,7 @@ struct OpenFbxImporterData
|
||||
}
|
||||
}
|
||||
|
||||
bool ImportMaterialTexture(const ofbx::Material* mat, ofbx::Texture::TextureType textureType, int32& textureIndex, TextureEntry::TypeHint type) const
|
||||
bool ImportMaterialTexture(ImportedModelData& result, const ofbx::Material* mat, ofbx::Texture::TextureType textureType, int32& textureIndex, TextureEntry::TypeHint type) const
|
||||
{
|
||||
const ofbx::Texture* tex = mat->getTexture(textureType);
|
||||
if (tex)
|
||||
@@ -188,15 +190,15 @@ struct OpenFbxImporterData
|
||||
|
||||
// Check if already used
|
||||
textureIndex = 0;
|
||||
while (textureIndex < Model.Textures.Count())
|
||||
while (textureIndex < result.Textures.Count())
|
||||
{
|
||||
if (Model.Textures[textureIndex].FilePath == path)
|
||||
if (result.Textures[textureIndex].FilePath == path)
|
||||
return true;
|
||||
textureIndex++;
|
||||
}
|
||||
|
||||
// Import texture
|
||||
auto& texture = Model.Textures.AddOne();
|
||||
auto& texture = result.Textures.AddOne();
|
||||
texture.FilePath = path;
|
||||
texture.Type = type;
|
||||
texture.AssetID = Guid::Empty;
|
||||
@@ -205,39 +207,46 @@ struct OpenFbxImporterData
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 AddMaterial(const ofbx::Material* mat)
|
||||
int32 AddMaterial(ImportedModelData& result, const ofbx::Material* mat)
|
||||
{
|
||||
int32 index = Materials.Find(mat);
|
||||
if (index == INVALID_INDEX)
|
||||
if (index == -1)
|
||||
{
|
||||
index = Materials.Count();
|
||||
Materials.Add(mat);
|
||||
auto& material = Model.Materials.AddOne();
|
||||
auto& material = ImportedMaterials.AddOne();
|
||||
material.AssetID = Guid::Empty;
|
||||
if (mat)
|
||||
material.Name = String(mat->name).TrimTrailing();
|
||||
|
||||
if (mat && Model.Types & ImportDataTypes::Materials)
|
||||
if (mat && result.Types & ImportDataTypes::Materials)
|
||||
{
|
||||
material.Diffuse.Color = ToColor(mat->getDiffuseColor());
|
||||
|
||||
if (Model.Types & ImportDataTypes::Textures)
|
||||
if (result.Types & ImportDataTypes::Textures)
|
||||
{
|
||||
ImportMaterialTexture(mat, ofbx::Texture::DIFFUSE, material.Diffuse.TextureIndex, TextureEntry::TypeHint::ColorRGB);
|
||||
ImportMaterialTexture(mat, ofbx::Texture::EMISSIVE, material.Emissive.TextureIndex, TextureEntry::TypeHint::ColorRGB);
|
||||
ImportMaterialTexture(mat, ofbx::Texture::NORMAL, material.Normals.TextureIndex, TextureEntry::TypeHint::Normals);
|
||||
ImportMaterialTexture(result, mat, ofbx::Texture::DIFFUSE, material.Diffuse.TextureIndex, TextureEntry::TypeHint::ColorRGB);
|
||||
ImportMaterialTexture(result, mat, ofbx::Texture::EMISSIVE, material.Emissive.TextureIndex, TextureEntry::TypeHint::ColorRGB);
|
||||
ImportMaterialTexture(result, mat, ofbx::Texture::NORMAL, material.Normals.TextureIndex, TextureEntry::TypeHint::Normals);
|
||||
|
||||
if (material.Diffuse.TextureIndex != -1)
|
||||
{
|
||||
// Detect using alpha mask in diffuse texture
|
||||
material.Diffuse.HasAlphaMask = TextureTool::HasAlpha(Model.Textures[material.Diffuse.TextureIndex].FilePath);
|
||||
material.Diffuse.HasAlphaMask = TextureTool::HasAlpha(result.Textures[material.Diffuse.TextureIndex].FilePath);
|
||||
if (material.Diffuse.HasAlphaMask)
|
||||
Model.Textures[material.Diffuse.TextureIndex].Type = TextureEntry::TypeHint::ColorRGBA;
|
||||
result.Textures[material.Diffuse.TextureIndex].Type = TextureEntry::TypeHint::ColorRGBA;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return index;
|
||||
const auto& importedMaterial = ImportedMaterials[index];
|
||||
for (int32 i = 0; i < result.Materials.Count(); i++)
|
||||
{
|
||||
if (result.Materials[i].Name == importedMaterial.Name)
|
||||
return i;
|
||||
}
|
||||
result.Materials.Add(importedMaterial);
|
||||
return result.Materials.Count() - 1;
|
||||
}
|
||||
|
||||
int32 FindNode(const ofbx::Object* link)
|
||||
@@ -450,7 +459,7 @@ bool ImportBones(OpenFbxImporterData& data, String& errorMsg)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProcessMesh(OpenFbxImporterData& data, const ofbx::Mesh* aMesh, MeshData& mesh, String& errorMsg, int32 triangleStart, int32 triangleEnd)
|
||||
bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* aMesh, MeshData& mesh, String& errorMsg, int32 triangleStart, int32 triangleEnd)
|
||||
{
|
||||
// Prepare
|
||||
const int32 firstVertexOffset = triangleStart * 3;
|
||||
@@ -476,7 +485,7 @@ bool ProcessMesh(OpenFbxImporterData& data, const ofbx::Mesh* aMesh, MeshData& m
|
||||
else
|
||||
aMaterial = aMesh->getMaterial(0);
|
||||
}
|
||||
mesh.MaterialSlotIndex = data.AddMaterial(aMaterial);
|
||||
mesh.MaterialSlotIndex = data.AddMaterial(result, aMaterial);
|
||||
|
||||
// Vertex positions
|
||||
mesh.Positions.Resize(vertexCount, false);
|
||||
@@ -632,7 +641,7 @@ bool ProcessMesh(OpenFbxImporterData& data, const ofbx::Mesh* aMesh, MeshData& m
|
||||
}
|
||||
|
||||
// Blend Indices and Blend Weights
|
||||
if (skin && skin->getClusterCount() > 0 && data.Model.Types & ImportDataTypes::Skeleton)
|
||||
if (skin && skin->getClusterCount() > 0 && result.Types & ImportDataTypes::Skeleton)
|
||||
{
|
||||
mesh.BlendIndices.Resize(vertexCount);
|
||||
mesh.BlendWeights.Resize(vertexCount);
|
||||
@@ -698,7 +707,7 @@ bool ProcessMesh(OpenFbxImporterData& data, const ofbx::Mesh* aMesh, MeshData& m
|
||||
}
|
||||
|
||||
// Blend Shapes
|
||||
if (blendShape && blendShape->getBlendShapeChannelCount() > 0 && data.Model.Types & ImportDataTypes::Skeleton && data.Options.ImportBlendShapes)
|
||||
if (blendShape && blendShape->getBlendShapeChannelCount() > 0 && result.Types & ImportDataTypes::Skeleton && data.Options.ImportBlendShapes)
|
||||
{
|
||||
mesh.BlendShapes.EnsureCapacity(blendShape->getBlendShapeChannelCount());
|
||||
for (int32 channelIndex = 0; channelIndex < blendShape->getBlendShapeChannelCount(); channelIndex++)
|
||||
@@ -799,7 +808,7 @@ bool ProcessMesh(OpenFbxImporterData& data, const ofbx::Mesh* aMesh, MeshData& m
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImportMesh(OpenFbxImporterData& data, const ofbx::Mesh* aMesh, String& errorMsg, int32 triangleStart, int32 triangleEnd)
|
||||
bool ImportMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* aMesh, String& errorMsg, int32 triangleStart, int32 triangleEnd)
|
||||
{
|
||||
// Find the parent node
|
||||
int32 nodeIndex = data.FindNode(aMesh);
|
||||
@@ -840,62 +849,57 @@ bool ImportMesh(OpenFbxImporterData& data, const ofbx::Mesh* aMesh, String& erro
|
||||
|
||||
// Import mesh data
|
||||
MeshData* meshData = New<MeshData>();
|
||||
if (ProcessMesh(data, aMesh, *meshData, errorMsg, triangleStart, triangleEnd))
|
||||
if (ProcessMesh(result, data, aMesh, *meshData, errorMsg, triangleStart, triangleEnd))
|
||||
return true;
|
||||
|
||||
// Link mesh
|
||||
auto& node = data.Nodes[nodeIndex];
|
||||
const int32 lodIndex = node.LodIndex;
|
||||
meshData->NodeIndex = nodeIndex;
|
||||
if (data.Model.LODs.Count() <= lodIndex)
|
||||
data.Model.LODs.Resize(lodIndex + 1);
|
||||
data.Model.LODs[lodIndex].Meshes.Add(meshData);
|
||||
if (result.LODs.Count() <= lodIndex)
|
||||
result.LODs.Resize(lodIndex + 1);
|
||||
result.LODs[lodIndex].Meshes.Add(meshData);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImportMeshes(OpenFbxImporterData& data, String& errorMsg)
|
||||
bool ImportMesh(int32 index, ImportedModelData& result, OpenFbxImporterData& data, String& errorMsg)
|
||||
{
|
||||
const int meshCount = data.Scene->getMeshCount();
|
||||
for (int i = 0; i < meshCount; i++)
|
||||
const auto aMesh = data.Scene->getMesh(index);
|
||||
const auto aGeometry = aMesh->getGeometry();
|
||||
const auto trianglesCount = aGeometry->getVertexCount() / 3;
|
||||
|
||||
// Skip invalid meshes
|
||||
if (IsMeshInvalid(aMesh))
|
||||
return false;
|
||||
|
||||
if (aMesh->getMaterialCount() < 2 || !aGeometry->getMaterials())
|
||||
{
|
||||
const auto aMesh = data.Scene->getMesh(i);
|
||||
const auto aGeometry = aMesh->getGeometry();
|
||||
const auto trianglesCount = aGeometry->getVertexCount() / 3;
|
||||
|
||||
// Skip invalid meshes
|
||||
if (IsMeshInvalid(aMesh))
|
||||
continue;
|
||||
|
||||
if (aMesh->getMaterialCount() < 2 || !aGeometry->getMaterials())
|
||||
{
|
||||
// Fast path if mesh is using single material for all triangles
|
||||
if (ImportMesh(data, aMesh, errorMsg, 0, trianglesCount - 1))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create mesh for each sequence of triangles that share the same material
|
||||
const auto materials = aGeometry->getMaterials();
|
||||
int32 rangeStart = 0;
|
||||
int32 rangeStartVal = materials[rangeStart];
|
||||
for (int32 triangleIndex = 1; triangleIndex < trianglesCount; triangleIndex++)
|
||||
{
|
||||
if (rangeStartVal != materials[triangleIndex])
|
||||
{
|
||||
if (ImportMesh(data, aMesh, errorMsg, rangeStart, triangleIndex - 1))
|
||||
return true;
|
||||
|
||||
// Start a new range
|
||||
rangeStart = triangleIndex;
|
||||
rangeStartVal = materials[triangleIndex];
|
||||
}
|
||||
}
|
||||
if (ImportMesh(data, aMesh, errorMsg, rangeStart, trianglesCount - 1))
|
||||
return true;
|
||||
}
|
||||
// Fast path if mesh is using single material for all triangles
|
||||
if (ImportMesh(result, data, aMesh, errorMsg, 0, trianglesCount - 1))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create mesh for each sequence of triangles that share the same material
|
||||
const auto materials = aGeometry->getMaterials();
|
||||
int32 rangeStart = 0;
|
||||
int32 rangeStartVal = materials[rangeStart];
|
||||
for (int32 triangleIndex = 1; triangleIndex < trianglesCount; triangleIndex++)
|
||||
{
|
||||
if (rangeStartVal != materials[triangleIndex])
|
||||
{
|
||||
if (ImportMesh(result, data, aMesh, errorMsg, rangeStart, triangleIndex - 1))
|
||||
return true;
|
||||
|
||||
// Start a new range
|
||||
rangeStart = triangleIndex;
|
||||
rangeStartVal = materials[triangleIndex];
|
||||
}
|
||||
}
|
||||
if (ImportMesh(result, data, aMesh, errorMsg, rangeStart, trianglesCount - 1))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -962,11 +966,11 @@ void ImportCurve(const ofbx::AnimationCurveNode* curveNode, LinearCurve<T>& curv
|
||||
}
|
||||
}
|
||||
|
||||
bool ImportAnimation(int32 index, ofbx::IScene* scene, ImportedModelData& data, OpenFbxImporterData& importerData)
|
||||
bool ImportAnimation(int32 index, ImportedModelData& data, OpenFbxImporterData& importerData)
|
||||
{
|
||||
const ofbx::AnimationStack* stack = scene->getAnimationStack(index);
|
||||
const ofbx::AnimationStack* stack = importerData.Scene->getAnimationStack(index);
|
||||
const ofbx::AnimationLayer* layer = stack->getLayer(0);
|
||||
const ofbx::TakeInfo* takeInfo = scene->getTakeInfo(stack->name);
|
||||
const ofbx::TakeInfo* takeInfo = importerData.Scene->getTakeInfo(stack->name);
|
||||
if (takeInfo == nullptr)
|
||||
return true;
|
||||
|
||||
@@ -1019,6 +1023,27 @@ bool ImportAnimation(int32 index, ofbx::IScene* scene, ImportedModelData& data,
|
||||
//ImportCurve(scalingNode, anim.Scale, info, ExtractKeyframeScale);
|
||||
}
|
||||
|
||||
if (importerData.ConvertRH)
|
||||
{
|
||||
for (int32 i = 0; i < data.Animation.Channels.Count(); i++)
|
||||
{
|
||||
auto& anim = data.Animation.Channels[i];
|
||||
auto& posKeys = anim.Position.GetKeyframes();
|
||||
auto& rotKeys = anim.Rotation.GetKeyframes();
|
||||
|
||||
for (int32 k = 0; k < posKeys.Count(); k++)
|
||||
{
|
||||
posKeys[k].Value.Z *= -1.0f;
|
||||
}
|
||||
|
||||
for (int32 k = 0; k < rotKeys.Count(); k++)
|
||||
{
|
||||
rotKeys[k].Value.X *= -1.0f;
|
||||
rotKeys[k].Value.Y *= -1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1037,171 +1062,208 @@ static Vector3 FbxVectorFromAxisAndSign(int axis, int sign)
|
||||
return { 0.f, 0.f, 0.f };
|
||||
}
|
||||
|
||||
bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, const Options& options, String& errorMsg)
|
||||
bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, Options& options, String& errorMsg)
|
||||
{
|
||||
// Import file
|
||||
Array<byte> fileData;
|
||||
if (File::ReadAllBytes(String(path), fileData))
|
||||
auto context = (OpenFbxImporterData*)options.SplitContext;
|
||||
if (!context)
|
||||
{
|
||||
errorMsg = TEXT("Cannot load file.");
|
||||
return true;
|
||||
}
|
||||
ofbx::u64 loadFlags = 0;
|
||||
if (data.Types & ImportDataTypes::Geometry)
|
||||
loadFlags |= (ofbx::u64)ofbx::LoadFlags::TRIANGULATE;
|
||||
else
|
||||
loadFlags |= (ofbx::u64)ofbx::LoadFlags::IGNORE_GEOMETRY;
|
||||
if (!options.ImportBlendShapes)
|
||||
loadFlags |= (ofbx::u64)ofbx::LoadFlags::IGNORE_BLEND_SHAPES;
|
||||
ofbx::IScene* scene = ofbx::load(fileData.Get(), fileData.Count(), loadFlags);
|
||||
if (!scene)
|
||||
{
|
||||
errorMsg = ofbx::getError();
|
||||
return true;
|
||||
}
|
||||
std::unique_ptr<ofbx::IScene> scenePtr(scene);
|
||||
fileData.Resize(0);
|
||||
|
||||
// Process imported scene
|
||||
OpenFbxImporterData importerData(path, data, options, scene);
|
||||
auto& globalSettings = importerData.GlobalSettings;
|
||||
ProcessNodes(importerData, scene->getRoot(), -1);
|
||||
|
||||
// Apply model scene global scale factor
|
||||
importerData.Nodes[0].LocalTransform = Transform(Vector3::Zero, Quaternion::Identity, globalSettings.UnitScaleFactor) * importerData.Nodes[0].LocalTransform;
|
||||
|
||||
// Log scene info
|
||||
LOG(Info, "Loaded FBX model, Frame Rate: {0}, Unit Scale Factor: {1}", importerData.FrameRate, globalSettings.UnitScaleFactor);
|
||||
LOG(Info, "Up: {1}{0}", globalSettings.UpAxis == ofbx::UpVector_AxisX ? TEXT("X") : globalSettings.UpAxis == ofbx::UpVector_AxisY ? TEXT("Y") : TEXT("Z"), globalSettings.UpAxisSign == 1 ? TEXT("+") : TEXT("-"));
|
||||
LOG(Info, "Front: {1}{0}", globalSettings.FrontAxis == ofbx::FrontVector_ParityEven ? TEXT("ParityEven") : TEXT("ParityOdd"), globalSettings.FrontAxisSign == 1 ? TEXT("+") : TEXT("-"));
|
||||
LOG(Info, "{0} Handed{1}", globalSettings.CoordAxis == ofbx::CoordSystem_RightHanded ? TEXT("Right") : TEXT("Left"), globalSettings.CoordAxisSign == 1 ? TEXT("") : TEXT(" (negative)"));
|
||||
LOG(Info, "Imported scene: Up={0}, Front={1}, Right={2}", importerData.Up, importerData.Front, importerData.Right);
|
||||
|
||||
// Extract embedded textures
|
||||
if (data.Types & ImportDataTypes::Textures)
|
||||
{
|
||||
String outputPath;
|
||||
for (int i = 0, c = scene->getEmbeddedDataCount(); i < c; i++)
|
||||
// Import file
|
||||
Array<byte> fileData;
|
||||
if (File::ReadAllBytes(String(path), fileData))
|
||||
{
|
||||
const ofbx::DataView aEmbedded = scene->getEmbeddedData(i);
|
||||
ofbx::DataView aFilename = scene->getEmbeddedFilename(i);
|
||||
char filenameData[256];
|
||||
aFilename.toString(filenameData);
|
||||
if (outputPath.IsEmpty())
|
||||
errorMsg = TEXT("Cannot load file.");
|
||||
return true;
|
||||
}
|
||||
ofbx::u64 loadFlags = 0;
|
||||
if (data.Types & ImportDataTypes::Geometry)
|
||||
loadFlags |= (ofbx::u64)ofbx::LoadFlags::TRIANGULATE;
|
||||
else
|
||||
loadFlags |= (ofbx::u64)ofbx::LoadFlags::IGNORE_GEOMETRY;
|
||||
if (!options.ImportBlendShapes)
|
||||
loadFlags |= (ofbx::u64)ofbx::LoadFlags::IGNORE_BLEND_SHAPES;
|
||||
ofbx::IScene* scene = ofbx::load(fileData.Get(), fileData.Count(), loadFlags);
|
||||
if (!scene)
|
||||
{
|
||||
errorMsg = ofbx::getError();
|
||||
return true;
|
||||
}
|
||||
fileData.Resize(0);
|
||||
|
||||
// Process imported scene
|
||||
context = New<OpenFbxImporterData>(path, options, scene);
|
||||
auto& globalSettings = context->GlobalSettings;
|
||||
ProcessNodes(*context, scene->getRoot(), -1);
|
||||
|
||||
// Apply model scene global scale factor
|
||||
context->Nodes[0].LocalTransform = Transform(Vector3::Zero, Quaternion::Identity, globalSettings.UnitScaleFactor) * context->Nodes[0].LocalTransform;
|
||||
|
||||
// Log scene info
|
||||
LOG(Info, "Loaded FBX model, Frame Rate: {0}, Unit Scale Factor: {1}", context->FrameRate, globalSettings.UnitScaleFactor);
|
||||
LOG(Info, "Up: {1}{0}", globalSettings.UpAxis == ofbx::UpVector_AxisX ? TEXT("X") : globalSettings.UpAxis == ofbx::UpVector_AxisY ? TEXT("Y") : TEXT("Z"), globalSettings.UpAxisSign == 1 ? TEXT("+") : TEXT("-"));
|
||||
LOG(Info, "Front: {1}{0}", globalSettings.FrontAxis == ofbx::FrontVector_ParityEven ? TEXT("ParityEven") : TEXT("ParityOdd"), globalSettings.FrontAxisSign == 1 ? TEXT("+") : TEXT("-"));
|
||||
LOG(Info, "{0} Handed{1}", globalSettings.CoordAxis == ofbx::CoordSystem_RightHanded ? TEXT("Right") : TEXT("Left"), globalSettings.CoordAxisSign == 1 ? TEXT("") : TEXT(" (negative)"));
|
||||
LOG(Info, "Imported scene: Up={0}, Front={1}, Right={2}", context->Up, context->Front, context->Right);
|
||||
|
||||
// Extract embedded textures
|
||||
if (data.Types & ImportDataTypes::Textures)
|
||||
{
|
||||
String outputPath;
|
||||
for (int i = 0, c = scene->getEmbeddedDataCount(); i < c; i++)
|
||||
{
|
||||
String pathStr(path);
|
||||
outputPath = String(StringUtils::GetDirectoryName(pathStr)) / TEXT("textures");
|
||||
FileSystem::CreateDirectory(outputPath);
|
||||
const ofbx::DataView aEmbedded = scene->getEmbeddedData(i);
|
||||
ofbx::DataView aFilename = scene->getEmbeddedFilename(i);
|
||||
char filenameData[256];
|
||||
aFilename.toString(filenameData);
|
||||
if (outputPath.IsEmpty())
|
||||
{
|
||||
String pathStr(path);
|
||||
outputPath = String(StringUtils::GetDirectoryName(pathStr)) / TEXT("textures");
|
||||
FileSystem::CreateDirectory(outputPath);
|
||||
}
|
||||
const String filenameStr(filenameData);
|
||||
String embeddedPath = outputPath / StringUtils::GetFileName(filenameStr);
|
||||
if (FileSystem::FileExists(embeddedPath))
|
||||
continue;
|
||||
LOG(Info, "Extracing embedded resource to {0}", embeddedPath);
|
||||
if (File::WriteAllBytes(embeddedPath, aEmbedded.begin + 4, (int32)(aEmbedded.end - aEmbedded.begin - 4)))
|
||||
{
|
||||
LOG(Error, "Failed to write data to file");
|
||||
}
|
||||
}
|
||||
const String filenameStr(filenameData);
|
||||
String embeddedPath = outputPath / StringUtils::GetFileName(filenameStr);
|
||||
if (FileSystem::FileExists(embeddedPath))
|
||||
continue;
|
||||
LOG(Info, "Extracing embedded resource to {0}", embeddedPath);
|
||||
if (File::WriteAllBytes(embeddedPath, aEmbedded.begin + 4, (int32)(aEmbedded.end - aEmbedded.begin - 4)))
|
||||
}
|
||||
|
||||
// Transform nodes to match the engine coordinates system - DirectX (UpVector = +Y, FrontVector = +Z, CoordSystem = -X (LeftHanded))
|
||||
if (context->Up == Vector3(1, 0, 0) && context->Front == Vector3(0, 0, 1) && context->Right == Vector3(0, 1, 0))
|
||||
{
|
||||
context->RootConvertRotation = Quaternion::Euler(0, 180, 0);
|
||||
}
|
||||
else if (context->Up == Vector3(0, 1, 0) && context->Front == Vector3(-1, 0, 0) && context->Right == Vector3(0, 0, 1))
|
||||
{
|
||||
context->RootConvertRotation = Quaternion::Euler(90, -90, 0);
|
||||
}
|
||||
/*Vector3 engineUp(0, 1, 0);
|
||||
Vector3 engineFront(0, 0, 1);
|
||||
Vector3 engineRight(-1, 0, 0);*/
|
||||
/*Vector3 engineUp(1, 0, 0);
|
||||
Vector3 engineFront(0, 0, 1);
|
||||
Vector3 engineRight(0, 1, 0);
|
||||
if (context->Up != engineUp || context->Front != engineFront || context->Right != engineRight)
|
||||
{
|
||||
LOG(Info, "Converting imported scene nodes to match engine coordinates system");
|
||||
context->RootConvertRotation = Quaternion::GetRotationFromTo(context->Up, engineUp, engineUp);
|
||||
//context->RootConvertRotation *= Quaternion::GetRotationFromTo(rotation * context->Right, engineRight, engineRight);
|
||||
//context->RootConvertRotation *= Quaternion::GetRotationFromTo(rotation * context->Front, engineFront, engineFront);
|
||||
}*/
|
||||
/*Vector3 hackUp = FbxVectorFromAxisAndSign(globalSettings.UpAxis, globalSettings.UpAxisSign);
|
||||
if (hackUp == Vector3::UnitX)
|
||||
context->RootConvertRotation = Quaternion::Euler(-90, 0, 0);
|
||||
else if (hackUp == Vector3::UnitZ)
|
||||
context->RootConvertRotation = Quaternion::Euler(90, 0, 0);*/
|
||||
if (!context->RootConvertRotation.IsIdentity())
|
||||
{
|
||||
for (int32 i = 0; i < context->Nodes.Count(); i++)
|
||||
{
|
||||
LOG(Error, "Failed to write data to file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transform nodes to match the engine coordinates system - DirectX (UpVector = +Y, FrontVector = +Z, CoordSystem = -X (LeftHanded))
|
||||
if (importerData.Up == Vector3(1, 0, 0) && importerData.Front == Vector3(0, 0, 1) && importerData.Right == Vector3(0, 1, 0))
|
||||
{
|
||||
importerData.RootConvertRotation = Quaternion::Euler(0, 180, 0);
|
||||
}
|
||||
else if (importerData.Up == Vector3(0, 1, 0) && importerData.Front == Vector3(-1, 0, 0) && importerData.Right == Vector3(0, 0, 1))
|
||||
{
|
||||
importerData.RootConvertRotation = Quaternion::Euler(90, -90, 0);
|
||||
}
|
||||
/*Vector3 engineUp(0, 1, 0);
|
||||
Vector3 engineFront(0, 0, 1);
|
||||
Vector3 engineRight(-1, 0, 0);*/
|
||||
/*Vector3 engineUp(1, 0, 0);
|
||||
Vector3 engineFront(0, 0, 1);
|
||||
Vector3 engineRight(0, 1, 0);
|
||||
if (importerData.Up != engineUp || importerData.Front != engineFront || importerData.Right != engineRight)
|
||||
{
|
||||
LOG(Info, "Converting imported scene nodes to match engine coordinates system");
|
||||
importerData.RootConvertRotation = Quaternion::GetRotationFromTo(importerData.Up, engineUp, engineUp);
|
||||
//importerData.RootConvertRotation *= Quaternion::GetRotationFromTo(rotation * importerData.Right, engineRight, engineRight);
|
||||
//importerData.RootConvertRotation *= Quaternion::GetRotationFromTo(rotation * importerData.Front, engineFront, engineFront);
|
||||
}*/
|
||||
/*Vector3 hackUp = FbxVectorFromAxisAndSign(globalSettings.UpAxis, globalSettings.UpAxisSign);
|
||||
if (hackUp == Vector3::UnitX)
|
||||
importerData.RootConvertRotation = Quaternion::Euler(-90, 0, 0);
|
||||
else if (hackUp == Vector3::UnitZ)
|
||||
importerData.RootConvertRotation = Quaternion::Euler(90, 0, 0);*/
|
||||
if (!importerData.RootConvertRotation.IsIdentity())
|
||||
{
|
||||
for (int32 i = 0; i < importerData.Nodes.Count(); i++)
|
||||
{
|
||||
if (importerData.Nodes[i].ParentIndex == -1)
|
||||
{
|
||||
importerData.Nodes[i].LocalTransform.Orientation = importerData.RootConvertRotation * importerData.Nodes[i].LocalTransform.Orientation;
|
||||
break;
|
||||
if (context->Nodes[i].ParentIndex == -1)
|
||||
{
|
||||
context->Nodes[i].LocalTransform.Orientation = context->RootConvertRotation * context->Nodes[i].LocalTransform.Orientation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DeleteMe<OpenFbxImporterData> contextCleanup(options.SplitContext ? nullptr : context);
|
||||
|
||||
// Build final skeleton bones hierarchy before importing meshes
|
||||
if (data.Types & ImportDataTypes::Skeleton)
|
||||
{
|
||||
if (ImportBones(importerData, errorMsg))
|
||||
if (ImportBones(*context, errorMsg))
|
||||
{
|
||||
LOG(Warning, "Failed to import skeleton bones.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Sorting::QuickSort(importerData.Bones.Get(), importerData.Bones.Count());
|
||||
Sorting::QuickSort(context->Bones.Get(), context->Bones.Count());
|
||||
}
|
||||
|
||||
// Import geometry (meshes and materials)
|
||||
if (data.Types & ImportDataTypes::Geometry)
|
||||
if (data.Types & ImportDataTypes::Geometry && context->Scene->getMeshCount() > 0)
|
||||
{
|
||||
if (ImportMeshes(importerData, errorMsg))
|
||||
const int meshCount = context->Scene->getMeshCount();
|
||||
if (options.SplitObjects && options.ObjectIndex == -1)
|
||||
{
|
||||
LOG(Warning, "Failed to import meshes.");
|
||||
return true;
|
||||
// Import the first object within this call
|
||||
options.SplitObjects = false;
|
||||
options.ObjectIndex = 0;
|
||||
|
||||
if (meshCount > 1 && options.OnSplitImport.IsBinded())
|
||||
{
|
||||
// Split all animations into separate assets
|
||||
LOG(Info, "Splitting imported {0} meshes", meshCount);
|
||||
for (int32 i = 1; i < meshCount; i++)
|
||||
{
|
||||
auto splitOptions = options;
|
||||
splitOptions.ObjectIndex = i;
|
||||
splitOptions.SplitContext = context;
|
||||
const auto aMesh = context->Scene->getMesh(i);
|
||||
const String objectName(aMesh->name);
|
||||
options.OnSplitImport(splitOptions, objectName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.ObjectIndex != -1)
|
||||
{
|
||||
// Import the selected mesh
|
||||
const auto meshIndex = Math::Clamp<int32>(options.ObjectIndex, 0, meshCount - 1);
|
||||
if (ImportMesh(meshIndex, data, *context, errorMsg))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Import all meshes
|
||||
for (int32 meshIndex = 0; meshIndex < meshCount; meshIndex++)
|
||||
{
|
||||
if (ImportMesh(meshIndex, data, *context, errorMsg))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import skeleton
|
||||
if (data.Types & ImportDataTypes::Skeleton)
|
||||
{
|
||||
data.Skeleton.Nodes.Resize(importerData.Nodes.Count(), false);
|
||||
for (int32 i = 0; i < importerData.Nodes.Count(); i++)
|
||||
data.Skeleton.Nodes.Resize(context->Nodes.Count(), false);
|
||||
for (int32 i = 0; i < context->Nodes.Count(); i++)
|
||||
{
|
||||
auto& node = data.Skeleton.Nodes[i];
|
||||
auto& aNode = importerData.Nodes[i];
|
||||
auto& aNode = context->Nodes[i];
|
||||
|
||||
node.Name = aNode.Name;
|
||||
node.ParentIndex = aNode.ParentIndex;
|
||||
node.LocalTransform = aNode.LocalTransform;
|
||||
}
|
||||
|
||||
data.Skeleton.Bones.Resize(importerData.Bones.Count(), false);
|
||||
for (int32 i = 0; i < importerData.Bones.Count(); i++)
|
||||
data.Skeleton.Bones.Resize(context->Bones.Count(), false);
|
||||
for (int32 i = 0; i < context->Bones.Count(); i++)
|
||||
{
|
||||
auto& bone = data.Skeleton.Bones[i];
|
||||
auto& aBone = importerData.Bones[i];
|
||||
auto& aBone = context->Bones[i];
|
||||
const auto boneNodeIndex = aBone.NodeIndex;
|
||||
|
||||
// Find the parent bone
|
||||
int32 parentBoneIndex = -1;
|
||||
for (int32 j = importerData.Nodes[boneNodeIndex].ParentIndex; j != -1; j = importerData.Nodes[j].ParentIndex)
|
||||
for (int32 j = context->Nodes[boneNodeIndex].ParentIndex; j != -1; j = context->Nodes[j].ParentIndex)
|
||||
{
|
||||
parentBoneIndex = importerData.FindBone(j);
|
||||
parentBoneIndex = context->FindBone(j);
|
||||
if (parentBoneIndex != -1)
|
||||
break;
|
||||
}
|
||||
aBone.ParentBoneIndex = parentBoneIndex;
|
||||
|
||||
const auto parentBoneNodeIndex = aBone.ParentBoneIndex == -1 ? -1 : importerData.Bones[aBone.ParentBoneIndex].NodeIndex;
|
||||
const auto parentBoneNodeIndex = aBone.ParentBoneIndex == -1 ? -1 : context->Bones[aBone.ParentBoneIndex].NodeIndex;
|
||||
|
||||
bone.ParentIndex = aBone.ParentBoneIndex;
|
||||
bone.NodeIndex = aBone.NodeIndex;
|
||||
bone.LocalTransform = CombineTransformsFromNodeIndices(importerData.Nodes, parentBoneNodeIndex, boneNodeIndex);
|
||||
bone.LocalTransform = CombineTransformsFromNodeIndices(context->Nodes, parentBoneNodeIndex, boneNodeIndex);
|
||||
bone.OffsetMatrix = aBone.OffsetMatrix;
|
||||
}
|
||||
}
|
||||
@@ -1209,57 +1271,54 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, con
|
||||
// Import animations
|
||||
if (data.Types & ImportDataTypes::Animations)
|
||||
{
|
||||
const int animCount = scene->getAnimationStackCount();
|
||||
if (options.AnimationIndex != -1)
|
||||
const int animCount = context->Scene->getAnimationStackCount();
|
||||
if (options.SplitObjects && options.ObjectIndex == -1)
|
||||
{
|
||||
// Import the first object within this call
|
||||
options.SplitObjects = false;
|
||||
options.ObjectIndex = 0;
|
||||
|
||||
if (animCount > 1 && options.OnSplitImport.IsBinded())
|
||||
{
|
||||
// Split all animations into separate assets
|
||||
LOG(Info, "Splitting imported {0} animations", animCount);
|
||||
for (int32 i = 1; i < animCount; i++)
|
||||
{
|
||||
auto splitOptions = options;
|
||||
splitOptions.ObjectIndex = i;
|
||||
splitOptions.SplitContext = context;
|
||||
const ofbx::AnimationStack* stack = context->Scene->getAnimationStack(i);
|
||||
const ofbx::AnimationLayer* layer = stack->getLayer(0);
|
||||
const String objectName(layer->name);
|
||||
options.OnSplitImport(splitOptions, objectName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.ObjectIndex != -1)
|
||||
{
|
||||
// Import selected animation
|
||||
const auto animIndex = Math::Clamp<int32>(options.AnimationIndex, 0, animCount - 1);
|
||||
ImportAnimation(animIndex, scene, data, importerData);
|
||||
const auto animIndex = Math::Clamp<int32>(options.ObjectIndex, 0, animCount - 1);
|
||||
ImportAnimation(animIndex, data, *context);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Import first valid animation
|
||||
for (int32 animIndex = 0; animIndex < animCount; animIndex++)
|
||||
{
|
||||
if (!ImportAnimation(animIndex, scene, data, importerData))
|
||||
if (!ImportAnimation(animIndex, data, *context))
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (data.Animation.Channels.IsEmpty())
|
||||
{
|
||||
LOG(Warning, "Loaded scene has no animations");
|
||||
}
|
||||
|
||||
if (importerData.ConvertRH)
|
||||
{
|
||||
for (int32 i = 0; i < data.Animation.Channels.Count(); i++)
|
||||
{
|
||||
auto& anim = data.Animation.Channels[i];
|
||||
auto& posKeys = anim.Position.GetKeyframes();
|
||||
auto& rotKeys = anim.Rotation.GetKeyframes();
|
||||
|
||||
for (int32 k = 0; k < posKeys.Count(); k++)
|
||||
{
|
||||
posKeys[k].Value.Z *= -1.0f;
|
||||
}
|
||||
|
||||
for (int32 k = 0; k < rotKeys.Count(); k++)
|
||||
{
|
||||
rotKeys[k].Value.X *= -1.0f;
|
||||
rotKeys[k].Value.Y *= -1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import nodes
|
||||
if (data.Types & ImportDataTypes::Nodes)
|
||||
{
|
||||
data.Nodes.Resize(importerData.Nodes.Count());
|
||||
for (int32 i = 0; i < importerData.Nodes.Count(); i++)
|
||||
data.Nodes.Resize(context->Nodes.Count());
|
||||
for (int32 i = 0; i < context->Nodes.Count(); i++)
|
||||
{
|
||||
auto& node = data.Nodes[i];
|
||||
auto& aNode = importerData.Nodes[i];
|
||||
auto& aNode = context->Nodes[i];
|
||||
|
||||
node.Name = aNode.Name;
|
||||
node.ParentIndex = aNode.ParentIndex;
|
||||
|
||||
@@ -54,7 +54,6 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
|
||||
SERIALIZE(OptimizeKeyframes);
|
||||
SERIALIZE(EnableRootMotion);
|
||||
SERIALIZE(RootNodeName);
|
||||
SERIALIZE(AnimationIndex);
|
||||
SERIALIZE(GenerateLODs);
|
||||
SERIALIZE(BaseLOD);
|
||||
SERIALIZE(LODCount);
|
||||
@@ -62,6 +61,8 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
|
||||
SERIALIZE(ImportMaterials);
|
||||
SERIALIZE(ImportTextures);
|
||||
SERIALIZE(RestoreMaterialsOnReimport);
|
||||
SERIALIZE(SplitObjects);
|
||||
SERIALIZE(ObjectIndex);
|
||||
}
|
||||
|
||||
void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
@@ -90,7 +91,6 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
|
||||
DESERIALIZE(OptimizeKeyframes);
|
||||
DESERIALIZE(EnableRootMotion);
|
||||
DESERIALIZE(RootNodeName);
|
||||
DESERIALIZE(AnimationIndex);
|
||||
DESERIALIZE(GenerateLODs);
|
||||
DESERIALIZE(BaseLOD);
|
||||
DESERIALIZE(LODCount);
|
||||
@@ -98,6 +98,14 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
|
||||
DESERIALIZE(ImportMaterials);
|
||||
DESERIALIZE(ImportTextures);
|
||||
DESERIALIZE(RestoreMaterialsOnReimport);
|
||||
DESERIALIZE(SplitObjects);
|
||||
DESERIALIZE(ObjectIndex);
|
||||
|
||||
// [Deprecated on 23.11.2021, expires on 21.11.2023]
|
||||
int32 AnimationIndex = -1;
|
||||
DESERIALIZE(AnimationIndex);
|
||||
if (AnimationIndex != -1)
|
||||
ObjectIndex = AnimationIndex;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -26,7 +26,7 @@ void RemoveNamespace(String& name)
|
||||
name = name.Substring(namespaceStart + 1);
|
||||
}
|
||||
|
||||
bool ModelTool::ImportData(const String& path, ImportedModelData& data, Options options, String& errorMsg)
|
||||
bool ModelTool::ImportData(const String& path, ImportedModelData& data, Options& options, String& errorMsg)
|
||||
{
|
||||
// Validate options
|
||||
options.Scale = Math::Clamp(options.Scale, 0.0001f, 100000.0f);
|
||||
@@ -313,7 +313,7 @@ void MeshOptDeallocate(void* ptr)
|
||||
Allocator::Free(ptr);
|
||||
}
|
||||
|
||||
bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options options, String& errorMsg, const String& autoImportOutput)
|
||||
bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& options, String& errorMsg, const String& autoImportOutput)
|
||||
{
|
||||
LOG(Info, "Importing model from \'{0}\'", path);
|
||||
const auto startTime = DateTime::NowUTC();
|
||||
|
||||
@@ -193,7 +193,6 @@ public:
|
||||
bool OptimizeKeyframes = true;
|
||||
bool EnableRootMotion = false;
|
||||
String RootNodeName;
|
||||
int32 AnimationIndex = -1;
|
||||
|
||||
// Level Of Detail
|
||||
bool GenerateLODs = false;
|
||||
@@ -206,6 +205,14 @@ public:
|
||||
bool ImportTextures = true;
|
||||
bool RestoreMaterialsOnReimport = true;
|
||||
|
||||
// Splitting
|
||||
bool SplitObjects = false;
|
||||
int32 ObjectIndex = -1;
|
||||
|
||||
// Runtime data for objects splitting during import (used internally)
|
||||
void* SplitContext = nullptr;
|
||||
Function<bool(Options& splitOptions, const String& objectName)> OnSplitImport;
|
||||
|
||||
public:
|
||||
|
||||
// [ISerializable]
|
||||
@@ -223,7 +230,7 @@ public:
|
||||
/// <param name="options">The import options.</param>
|
||||
/// <param name="errorMsg">The error message container.</param>
|
||||
/// <returns>True if fails, otherwise false.</returns>
|
||||
static bool ImportData(const String& path, ImportedModelData& data, Options options, String& errorMsg);
|
||||
static bool ImportData(const String& path, ImportedModelData& data, Options& options, String& errorMsg);
|
||||
|
||||
/// <summary>
|
||||
/// Imports the model.
|
||||
@@ -234,7 +241,7 @@ public:
|
||||
/// <param name="errorMsg">The error message container.</param>
|
||||
/// <param name="autoImportOutput">The output folder for the additional imported data - optional. Used to auto-import textures and material assets.</param>
|
||||
/// <returns>True if fails, otherwise false.</returns>
|
||||
static bool ImportModel(const String& path, ModelData& meshData, Options options, String& errorMsg, const String& autoImportOutput = String::Empty);
|
||||
static bool ImportModel(const String& path, ModelData& meshData, Options& options, String& errorMsg, const String& autoImportOutput = String::Empty);
|
||||
|
||||
public:
|
||||
|
||||
@@ -267,13 +274,13 @@ public:
|
||||
private:
|
||||
|
||||
#if USE_ASSIMP
|
||||
static bool ImportDataAssimp(const char* path, ImportedModelData& data, const Options& options, String& errorMsg);
|
||||
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, const Options& options, String& errorMsg);
|
||||
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, const Options& options, String& errorMsg);
|
||||
static bool ImportDataOpenFBX(const char* path, ImportedModelData& data, Options& options, String& errorMsg);
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user