Merge remote-tracking branch 'origin/master' into 1.7

# Conflicts:
#	Source/Engine/Level/Actors/AnimatedModel.cpp
This commit is contained in:
Wojtek Figat
2023-10-05 10:44:03 +02:00
83 changed files with 1809 additions and 519 deletions

View File

@@ -8,6 +8,7 @@
#include "Engine/Core/Math/Matrix.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/File.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
// Import Assimp library
@@ -516,8 +517,47 @@ bool ImportTexture(ImportedModelData& result, AssimpImporterData& data, aiString
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(result, data, aFilename, textureIndex, type);
if (aMaterial->GetTexture(aTextureType, 0, &aFilename, nullptr, nullptr, nullptr, nullptr) == AI_SUCCESS)
{
// Check for embedded textures
String filename = String(aFilename.C_Str()).TrimTrailing();
if (filename.StartsWith(TEXT(AI_EMBEDDED_TEXNAME_PREFIX)))
{
const aiTexture* aTex = data.Scene->GetEmbeddedTexture(aFilename.C_Str());
const StringView texIndexName(filename.Get() + (ARRAY_COUNT(AI_EMBEDDED_TEXNAME_PREFIX) - 1));
uint32 texIndex;
if (!aTex && !StringUtils::Parse(texIndexName.Get(), texIndexName.Length(), &texIndex) && texIndex >= 0 && texIndex < data.Scene->mNumTextures)
aTex = data.Scene->mTextures[texIndex];
if (aTex && aTex->mHeight == 0 && aTex->mWidth > 0)
{
// Export embedded texture to temporary file
filename = String::Format(TEXT("{0}_tex_{1}.{2}"), StringUtils::GetFileNameWithoutExtension(data.Path), texIndexName, String(aTex->achFormatHint));
File::WriteAllBytes(String(StringUtils::GetDirectoryName(data.Path)) / filename, (const byte*)aTex->pcData, (int32)aTex->mWidth);
}
}
// Find texture file path
String path;
if (ModelTool::FindTexture(data.Path, filename, path))
return true;
// Check if already used
textureIndex = 0;
while (textureIndex < result.Textures.Count())
{
if (result.Textures[textureIndex].FilePath == path)
return true;
textureIndex++;
}
// Import texture
auto& texture = result.Textures.AddOne();
texture.FilePath = path;
texture.Type = type;
texture.AssetID = Guid::Empty;
return true;
}
return false;
}
bool ImportMaterials(ImportedModelData& result, AssimpImporterData& data, String& errorMsg)
@@ -706,8 +746,15 @@ bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, Opti
return true;
}
// Create root node
AssimpNode& rootNode = context->Nodes.AddOne();
rootNode.ParentIndex = -1;
rootNode.LodIndex = 0;
rootNode.Name = TEXT("Root");
rootNode.LocalTransform = Transform::Identity;
// Process imported scene nodes
ProcessNodes(*context, context->Scene->mRootNode, -1);
ProcessNodes(*context, context->Scene->mRootNode, 0);
}
DeleteMe<AssimpImporterData> contextCleanup(options.SplitContext ? nullptr : context);
@@ -823,7 +870,13 @@ bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, Opti
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;
data.Animation.FramesPerSecond = animations->mTicksPerSecond;
if (data.Animation.FramesPerSecond <= 0)
{
data.Animation.FramesPerSecond = context->Options.DefaultFrameRate;
if (data.Animation.FramesPerSecond <= 0)
data.Animation.FramesPerSecond = 30.0f;
}
for (unsigned i = 0; i < animations->mNumChannels; i++)
{

View File

@@ -12,6 +12,8 @@
#include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Platform/File.h"
#define OPEN_FBX_CONVERT_SPACE 1
// Import OpenFBX library
// Source: https://github.com/nem0/OpenFBX
#include <ThirdParty/OpenFBX/ofbx.h>
@@ -85,12 +87,16 @@ struct OpenFbxImporterData
const ModelTool::Options& Options;
ofbx::GlobalSettings GlobalSettings;
#if OPEN_FBX_CONVERT_SPACE
Quaternion RootConvertRotation = Quaternion::Identity;
Float3 Up;
Float3 Front;
Float3 Right;
bool ConvertRH;
#else
static constexpr bool ConvertRH = false;
#endif
float FrameRate;
Quaternion RootConvertRotation = Quaternion::Identity;
Array<FbxNode> Nodes;
Array<FbxBone> Bones;
@@ -103,7 +109,9 @@ struct OpenFbxImporterData
, Path(path)
, Options(options)
, GlobalSettings(*scene->getGlobalSettings())
#if OPEN_FBX_CONVERT_SPACE
, ConvertRH(GlobalSettings.CoordAxis == ofbx::CoordSystem_RightHanded)
#endif
, Nodes(static_cast<int32>(scene->getMeshCount() * 4.0f))
{
float frameRate = scene->getSceneFrameRate();
@@ -114,6 +122,7 @@ struct OpenFbxImporterData
frameRate = 30.0f;
}
FrameRate = frameRate;
#if OPEN_FBX_CONVERT_SPACE
const float coordAxisSign = GlobalSettings.CoordAxis == ofbx::CoordSystem_LeftHanded ? -1.0f : +1.0f;
switch (GlobalSettings.UpAxis)
{
@@ -170,6 +179,7 @@ struct OpenFbxImporterData
break;
default: ;
}
#endif
}
bool ImportMaterialTexture(ImportedModelData& result, const ofbx::Material* mat, ofbx::Texture::TextureType textureType, int32& textureIndex, TextureEntry::TypeHint type) const
@@ -366,13 +376,12 @@ void ProcessNodes(OpenFbxImporterData& data, const ofbx::Object* aNode, int32 pa
{
node.LodIndex = data.Nodes[parentIndex].LodIndex;
if (node.LodIndex == 0)
{
node.LodIndex = ModelTool::DetectLodIndex(node.Name);
}
ASSERT(Math::IsInRange(node.LodIndex, 0, MODEL_MAX_LODS - 1));
}
auto transform = ToMatrix(aNode->evalLocal(aNode->getLocalTranslation(), aNode->getLocalRotation()));
#if OPEN_FBX_CONVERT_SPACE
if (data.ConvertRH)
{
// Mirror all base vectors at the local Z axis
@@ -388,6 +397,7 @@ void ProcessNodes(OpenFbxImporterData& data, const ofbx::Object* aNode, int32 pa
transform.M33 = -transform.M33;
transform.M43 = -transform.M43;
}
#endif
transform.Decompose(node.LocalTransform);
data.Nodes.Add(node);
@@ -416,8 +426,9 @@ Matrix GetOffsetMatrix(OpenFbxImporterData& data, const ofbx::Mesh* mesh, const
}
}
}
//return Matrix::Identity;
return ToMatrix(node->getGlobalTransform());
#elif 1
#else
Matrix t = Matrix::Identity;
const int32 boneIdx = data.FindBone(node);
int32 idx = data.Bones[boneIdx].NodeIndex;
@@ -427,17 +438,6 @@ Matrix GetOffsetMatrix(OpenFbxImporterData& data, const ofbx::Mesh* mesh, const
idx = data.Nodes[idx].ParentIndex;
} while (idx != -1);
return t;
#else
auto* skin = mesh->getGeometry()->getSkin();
for (int i = 0, c = skin->getClusterCount(); i < c; i++)
{
const ofbx::Cluster* cluster = skin->getCluster(i);
if (cluster->getLink() == node)
{
return ToMatrix(cluster->getTransformLinkMatrix());
}
}
return Matrix::Identity;
#endif
}
@@ -455,17 +455,14 @@ bool ImportBones(OpenFbxImporterData& data, String& errorMsg)
const auto aMesh = data.Scene->getMesh(i);
const auto aGeometry = aMesh->getGeometry();
const ofbx::Skin* skin = aGeometry->getSkin();
if (skin == nullptr || IsMeshInvalid(aMesh))
continue;
for (int clusterIndex = 0, c = skin->getClusterCount(); clusterIndex < c; clusterIndex++)
for (int clusterIndex = 0, clusterCount = skin->getClusterCount(); clusterIndex < clusterCount; clusterIndex++)
{
const ofbx::Cluster* cluster = skin->getCluster(clusterIndex);
if (cluster->getIndicesCount() == 0)
continue;
const auto link = cluster->getLink();
ASSERT(link != nullptr);
@@ -487,7 +484,7 @@ bool ImportBones(OpenFbxImporterData& data, String& errorMsg)
// Add bone
boneIndex = data.Bones.Count();
data.Bones.EnsureCapacity(Math::Max(128, boneIndex + 16));
data.Bones.EnsureCapacity(256);
data.Bones.Resize(boneIndex + 1);
auto& bone = data.Bones[boneIndex];
@@ -495,7 +492,7 @@ bool ImportBones(OpenFbxImporterData& data, String& errorMsg)
bone.NodeIndex = nodeIndex;
bone.ParentBoneIndex = -1;
bone.FbxObj = link;
bone.OffsetMatrix = GetOffsetMatrix(data, aMesh, link);
bone.OffsetMatrix = GetOffsetMatrix(data, aMesh, link) * Matrix::Scaling(data.GlobalSettings.UnitScaleFactor);
bone.OffsetMatrix.Invert();
// Mirror offset matrices (RH to LH)
@@ -509,6 +506,15 @@ bool ImportBones(OpenFbxImporterData& data, String& errorMsg)
m.M32 = -m.M32;
m.M34 = -m.M34;
}
// Convert bone matrix if scene uses root transform
if (!data.RootConvertRotation.IsIdentity())
{
Matrix m;
Matrix::RotationQuaternion(data.RootConvertRotation, m);
m.Invert();
bone.OffsetMatrix = m * bone.OffsetMatrix;
}
}
}
}
@@ -547,9 +553,7 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb
// Vertex positions
mesh.Positions.Resize(vertexCount, false);
for (int i = 0; i < vertexCount; i++)
{
mesh.Positions.Get()[i] = ToFloat3(vertices[i + firstVertexOffset]);
}
// Indices (dummy index buffer)
if (vertexCount % 3 != 0)
@@ -559,24 +563,18 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb
}
mesh.Indices.Resize(vertexCount, false);
for (int i = 0; i < vertexCount; i++)
{
mesh.Indices.Get()[i] = i;
}
// Texture coordinates
if (uvs)
{
mesh.UVs.Resize(vertexCount, false);
for (int i = 0; i < vertexCount; i++)
{
mesh.UVs.Get()[i] = ToFloat2(uvs[i + firstVertexOffset]);
}
if (data.ConvertRH)
{
for (int32 v = 0; v < vertexCount; v++)
{
mesh.UVs[v].Y = 1.0f - mesh.UVs[v].Y;
}
}
}
@@ -593,16 +591,12 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb
{
mesh.Normals.Resize(vertexCount, false);
for (int i = 0; i < vertexCount; i++)
{
mesh.Normals.Get()[i] = ToFloat3(normals[i + firstVertexOffset]);
}
if (data.ConvertRH)
{
// Mirror normals along the Z axis
for (int32 i = 0; i < vertexCount; i++)
{
mesh.Normals.Get()[i].Z *= -1.0f;
}
}
}
@@ -615,16 +609,12 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb
{
mesh.Tangents.Resize(vertexCount, false);
for (int i = 0; i < vertexCount; i++)
{
mesh.Tangents.Get()[i] = ToFloat3(tangents[i + firstVertexOffset]);
}
if (data.ConvertRH)
{
// Mirror tangents along the Z axis
for (int32 i = 0; i < vertexCount; i++)
{
mesh.Tangents.Get()[i].Z *= -1.0f;
}
}
}
@@ -670,15 +660,11 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb
{
mesh.LightmapUVs.Resize(vertexCount, false);
for (int i = 0; i < vertexCount; i++)
{
mesh.LightmapUVs.Get()[i] = ToFloat2(lightmapUVs[i + firstVertexOffset]);
}
if (data.ConvertRH)
{
for (int32 v = 0; v < vertexCount; v++)
{
mesh.LightmapUVs[v].Y = 1.0f - mesh.LightmapUVs[v].Y;
}
}
}
else
@@ -692,9 +678,7 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb
{
mesh.Colors.Resize(vertexCount, false);
for (int i = 0; i < vertexCount; i++)
{
mesh.Colors.Get()[i] = ToColor(colors[i + firstVertexOffset]);
}
}
// Blend Indices and Blend Weights
@@ -705,13 +689,11 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb
mesh.BlendIndices.SetAll(Int4::Zero);
mesh.BlendWeights.SetAll(Float4::Zero);
for (int clusterIndex = 0, c = skin->getClusterCount(); clusterIndex < c; clusterIndex++)
for (int clusterIndex = 0, clusterCount = skin->getClusterCount(); clusterIndex < clusterCount; clusterIndex++)
{
const ofbx::Cluster* cluster = skin->getCluster(clusterIndex);
if (cluster->getIndicesCount() == 0)
continue;
const auto link = cluster->getLink();
ASSERT(link != nullptr);
@@ -815,15 +797,11 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb
{
// Mirror positions along the Z axis
for (int32 i = 0; i < vertexCount; i++)
{
mesh.Positions[i].Z *= -1.0f;
}
for (auto& blendShapeData : mesh.BlendShapes)
{
for (auto& v : blendShapeData.Vertices)
{
v.PositionDelta.Z *= -1.0f;
}
}
}
@@ -834,9 +812,7 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb
{
// Invert the order
for (int32 i = 0; i < mesh.Indices.Count(); i += 3)
{
Swap(mesh.Indices[i], mesh.Indices[i + 2]);
}
}
if ((data.Options.CalculateTangents || !tangents) && mesh.UVs.HasItems())
@@ -888,9 +864,7 @@ bool ImportMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofbx
{
node.LodIndex = data.Nodes[0].LodIndex;
if (node.LodIndex == 0)
{
node.LodIndex = ModelTool::DetectLodIndex(node.Name);
}
ASSERT(Math::IsInRange(node.LodIndex, 0, MODEL_MAX_LODS - 1));
}
node.LocalTransform = Transform::Identity;
@@ -999,7 +973,6 @@ void ImportCurve(const ofbx::AnimationCurveNode* curveNode, LinearCurve<T>& curv
{
if (curveNode == nullptr)
return;
const auto keyframes = curve.Resize(info.FramesCount);
const auto bone = curveNode->getBone();
Frame localFrame;
@@ -1143,6 +1116,14 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, Opt
}
fileData.Resize(0);
// Tweak scene if exported by Blender
auto& globalInfo = *scene->getGlobalInfo();
if (StringAnsiView(globalInfo.AppName).StartsWith(StringAnsiView("Blender"), StringSearchCase::IgnoreCase))
{
auto ptr = const_cast<ofbx::GlobalSettings*>(scene->getGlobalSettings());
ptr->UpAxis = (ofbx::UpVector)((int32)ptr->UpAxis + 1);
}
// Process imported scene
context = New<OpenFbxImporterData>(path, options, scene);
auto& globalSettings = context->GlobalSettings;
@@ -1153,10 +1134,13 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, Opt
// Log scene info
LOG(Info, "Loaded FBX model, Frame Rate: {0}, Unit Scale Factor: {1}", context->FrameRate, globalSettings.UnitScaleFactor);
LOG(Info, "{0}, {1}, {2}", String(globalInfo.AppName), String(globalInfo.AppVersion), String(globalInfo.AppVendor));
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)"));
#if OPEN_FBX_CONVERT_SPACE
LOG(Info, "Imported scene: Up={0}, Front={1}, Right={2}", context->Up, context->Front, context->Right);
#endif
// Extract embedded textures
if (EnumHasAnyFlags(data.Types, ImportDataTypes::Textures))
@@ -1186,6 +1170,7 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, Opt
}
}
#if OPEN_FBX_CONVERT_SPACE
// Transform nodes to match the engine coordinates system - DirectX (UpVector = +Y, FrontVector = +Z, CoordSystem = -X (LeftHanded))
if (context->Up == Float3(1, 0, 0) && context->Front == Float3(0, 0, 1) && context->Right == Float3(0, 1, 0))
{
@@ -1215,15 +1200,16 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ImportedModelData& data, Opt
context->RootConvertRotation = Quaternion::Euler(90, 0, 0);*/
if (!context->RootConvertRotation.IsIdentity())
{
for (int32 i = 0; i < context->Nodes.Count(); i++)
for (auto& node : context->Nodes)
{
if (context->Nodes[i].ParentIndex == -1)
if (node.ParentIndex == -1)
{
context->Nodes[i].LocalTransform.Orientation = context->RootConvertRotation * context->Nodes[i].LocalTransform.Orientation;
node.LocalTransform.Orientation = context->RootConvertRotation * node.LocalTransform.Orientation;
break;
}
}
}
#endif
}
DeleteMe<OpenFbxImporterData> contextCleanup(options.SplitContext ? nullptr : context);

View File

@@ -31,6 +31,7 @@
#include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/ContentImporters/CreateMaterial.h"
#include "Engine/ContentImporters/CreateMaterialInstance.h"
#include "Engine/ContentImporters/CreateCollisionData.h"
#include "Engine/Serialization/Serialization.h"
#include "Editor/Utilities/EditorUtilities.h"
@@ -387,6 +388,8 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
SERIALIZE(SloppyOptimization);
SERIALIZE(LODTargetError);
SERIALIZE(ImportMaterials);
SERIALIZE(ImportMaterialsAsInstances);
SERIALIZE(InstanceToImportAs);
SERIALIZE(ImportTextures);
SERIALIZE(RestoreMaterialsOnReimport);
SERIALIZE(GenerateSDF);
@@ -430,6 +433,8 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
DESERIALIZE(SloppyOptimization);
DESERIALIZE(LODTargetError);
DESERIALIZE(ImportMaterials);
DESERIALIZE(ImportMaterialsAsInstances);
DESERIALIZE(InstanceToImportAs);
DESERIALIZE(ImportTextures);
DESERIALIZE(RestoreMaterialsOnReimport);
DESERIALIZE(GenerateSDF);
@@ -503,11 +508,11 @@ bool ModelTool::ImportData(const String& path, ImportedModelData& data, Options&
if (ImportDataAssimp(importPath.Get(), data, options, errorMsg))
return true;
#elif USE_AUTODESK_FBX_SDK
if (ImportDataAutodeskFbxSdk(importPath.Get(), data, options, errorMsg))
return true;
if (ImportDataAutodeskFbxSdk(importPath.Get(), data, options, errorMsg))
return true;
#elif USE_OPEN_FBX
if (ImportDataOpenFBX(importPath.Get(), data, options, errorMsg))
return true;
if (ImportDataOpenFBX(importPath.Get(), data, options, errorMsg))
return true;
#else
LOG(Error, "Compiled without model importing backend.");
return true;
@@ -620,62 +625,62 @@ bool ModelTool::ImportData(const String& path, ImportedModelData& data, Options&
bool SortDepths(const Pair<int32, int32>& a, const Pair<int32, int32>& b)
{
return a.First < b.First;
return a.First < b.First;
}
void CreateLinearListFromTree(Array<SkeletonNode>& nodes, Array<int32>& mapping)
{
// Customized breadth first tree algorithm (each node has no direct reference to the children so we build the cache for the nodes depth level)
const int32 count = nodes.Count();
Array<Pair<int32, int32>> depths(count); // Pair.First = Depth, Pair.Second = Node Index
depths.SetSize(count);
depths.Set(-1);
for (int32 i = 0; i < count; i++)
{
// Skip evaluated nodes
if (depths[i].First != -1)
continue;
const int32 count = nodes.Count();
Array<Pair<int32, int32>> depths(count); // Pair.First = Depth, Pair.Second = Node Index
depths.Resize(count);
depths.SetAll(-1);
for (int32 i = 0; i < count; i++)
{
// Skip evaluated nodes
if (depths[i].First != -1)
continue;
// Find the first node with calculated depth and get the distance to it
int32 end = i;
int32 lastDepth;
int32 relativeDepth = 0;
do
{
lastDepth = depths[end].First;
end = nodes[end].ParentIndex;
relativeDepth++;
} while (end != -1 && lastDepth == -1);
// Find the first node with calculated depth and get the distance to it
int32 end = i;
int32 lastDepth;
int32 relativeDepth = 0;
do
{
lastDepth = depths[end].First;
end = nodes[end].ParentIndex;
relativeDepth++;
} while (end != -1 && lastDepth == -1);
// Set the depth (second item is the node index)
depths[i] = MakePair(lastDepth + relativeDepth, i);
}
for (int32 i = 0; i < count; i++)
{
// Strange divide by 2 but works
depths[i].First = depths[i].First >> 1;
}
// Set the depth (second item is the node index)
depths[i] = ToPair(lastDepth + relativeDepth, i);
}
for (int32 i = 0; i < count; i++)
{
// Strange divide by 2 but works
depths[i].First = depths[i].First >> 1;
}
// Order nodes by depth O(n*log(n))
depths.Sort(SortDepths);
// Order nodes by depth O(n*log(n))
depths.Sort(SortDepths);
// Extract nodes mapping O(n^2)
mapping.EnsureCapacity(count, false);
mapping.SetSize(count);
for (int32 i = 0; i < count; i++)
{
int32 newIndex = -1;
for (int32 j = 0; j < count; j++)
{
if (depths[j].Second == i)
{
newIndex = j;
break;
}
}
ASSERT(newIndex != -1);
mapping[i] = newIndex;
}
// Extract nodes mapping O(n^2)
mapping.EnsureCapacity(count, false);
mapping.Resize(count);
for (int32 i = 0; i < count; i++)
{
int32 newIndex = -1;
for (int32 j = 0; j < count; j++)
{
if (depths[j].Second == i)
{
newIndex = j;
break;
}
}
ASSERT(newIndex != -1);
mapping[i] = newIndex;
}
}
#endif
@@ -841,19 +846,35 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
const auto mesh = data.LODs[0].Meshes[i];
if (mesh->BlendIndices.IsEmpty() || mesh->BlendWeights.IsEmpty())
{
LOG(Warning, "Imported mesh \'{0}\' has missing skinning data. It may result in invalid rendering.", mesh->Name);
auto indices = Int4::Zero;
auto weights = Float4::UnitX;
// Check if use a single bone for skinning
auto nodeIndex = data.Skeleton.FindNode(mesh->Name);
auto boneIndex = data.Skeleton.FindBone(nodeIndex);
if (boneIndex != -1)
if (boneIndex == -1 && nodeIndex != -1 && data.Skeleton.Bones.Count() < MAX_BONES_PER_MODEL)
{
LOG(Warning, "Using auto-detected bone {0} (index {1})", data.Skeleton.Nodes[nodeIndex].Name, boneIndex);
// Add missing bone to be used by skinned model from animated nodes pose
boneIndex = data.Skeleton.Bones.Count();
auto& bone = data.Skeleton.Bones.AddOne();
bone.ParentIndex = -1;
bone.NodeIndex = nodeIndex;
bone.LocalTransform = CombineTransformsFromNodeIndices(data.Nodes, -1, nodeIndex);
CalculateBoneOffsetMatrix(data.Skeleton.Nodes, bone.OffsetMatrix, bone.NodeIndex);
LOG(Warning, "Using auto-created bone {0} (index {1}) for mesh \'{2}\'", data.Skeleton.Nodes[nodeIndex].Name, boneIndex, mesh->Name);
indices.X = boneIndex;
}
else if (boneIndex != -1)
{
// Fallback to already added bone
LOG(Warning, "Using auto-detected bone {0} (index {1}) for mesh \'{2}\'", data.Skeleton.Nodes[nodeIndex].Name, boneIndex, mesh->Name);
indices.X = boneIndex;
}
else
{
// No bone
LOG(Warning, "Imported mesh \'{0}\' has missing skinning data. It may result in invalid rendering.", mesh->Name);
}
mesh->BlendIndices.Resize(mesh->Positions.Count());
mesh->BlendWeights.Resize(mesh->Positions.Count());
@@ -978,23 +999,6 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
importedFileNames.Add(filename);
#if COMPILE_WITH_ASSETS_IMPORTER
auto assetPath = autoImportOutput / filename + ASSET_FILES_EXTENSION_WITH_DOT;
CreateMaterial::Options materialOptions;
materialOptions.Diffuse.Color = material.Diffuse.Color;
if (material.Diffuse.TextureIndex != -1)
materialOptions.Diffuse.Texture = data.Textures[material.Diffuse.TextureIndex].AssetID;
materialOptions.Diffuse.HasAlphaMask = material.Diffuse.HasAlphaMask;
materialOptions.Emissive.Color = material.Emissive.Color;
if (material.Emissive.TextureIndex != -1)
materialOptions.Emissive.Texture = data.Textures[material.Emissive.TextureIndex].AssetID;
materialOptions.Opacity.Value = material.Opacity.Value;
if (material.Opacity.TextureIndex != -1)
materialOptions.Opacity.Texture = data.Textures[material.Opacity.TextureIndex].AssetID;
if (material.Normals.TextureIndex != -1)
materialOptions.Normals.Texture = data.Textures[material.Normals.TextureIndex].AssetID;
if (material.TwoSided || material.Diffuse.HasAlphaMask)
materialOptions.Info.CullMode = CullMode::TwoSided;
if (!Math::IsOne(material.Opacity.Value) || material.Opacity.TextureIndex != -1)
materialOptions.Info.BlendMode = MaterialBlendMode::Transparent;
// When splitting imported meshes allow only the first mesh to import assets (mesh[0] is imported after all following ones so import assets during mesh[1])
if (!options.SplitObjects && options.ObjectIndex != 1 && options.ObjectIndex != -1)
@@ -1006,7 +1010,42 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
continue;
}
AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialTag, assetPath, material.AssetID, &materialOptions);
if (options.ImportMaterialsAsInstances)
{
// Create material instance
AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialInstanceTag, assetPath, material.AssetID);
if (MaterialInstance* materialInstance = Content::Load<MaterialInstance>(assetPath))
{
materialInstance->SetBaseMaterial(options.InstanceToImportAs);
materialInstance->Save();
}
else
{
LOG(Error, "Failed to load material instance after creation. ({0})", assetPath);
}
}
else
{
// Create material
CreateMaterial::Options materialOptions;
materialOptions.Diffuse.Color = material.Diffuse.Color;
if (material.Diffuse.TextureIndex != -1)
materialOptions.Diffuse.Texture = data.Textures[material.Diffuse.TextureIndex].AssetID;
materialOptions.Diffuse.HasAlphaMask = material.Diffuse.HasAlphaMask;
materialOptions.Emissive.Color = material.Emissive.Color;
if (material.Emissive.TextureIndex != -1)
materialOptions.Emissive.Texture = data.Textures[material.Emissive.TextureIndex].AssetID;
materialOptions.Opacity.Value = material.Opacity.Value;
if (material.Opacity.TextureIndex != -1)
materialOptions.Opacity.Texture = data.Textures[material.Opacity.TextureIndex].AssetID;
if (material.Normals.TextureIndex != -1)
materialOptions.Normals.Texture = data.Textures[material.Normals.TextureIndex].AssetID;
if (material.TwoSided || material.Diffuse.HasAlphaMask)
materialOptions.Info.CullMode = CullMode::TwoSided;
if (!Math::IsOne(material.Opacity.Value) || material.Opacity.TextureIndex != -1)
materialOptions.Info.BlendMode = MaterialBlendMode::Transparent;
AssetsImportingManager::Create(AssetsImportingManager::CreateMaterialTag, assetPath, material.AssetID, &materialOptions);
}
#endif
}
@@ -1091,7 +1130,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
#if COMPILE_WITH_PHYSICS_COOKING
// Create collision
CollisionCooking::Argument arg;
arg.Type = CollisionDataType::TriangleMesh;
arg.Type = options.CollisionType;
arg.OverrideModelData = &collisionModel;
auto assetPath = autoImportOutput / StringUtils::GetFileNameWithoutExtension(path) + TEXT("Collision") ASSET_FILES_EXTENSION_WITH_DOT;
if (CreateCollisionData::CookMeshCollision(assetPath, arg))
@@ -1320,14 +1359,17 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
}
}
// Apply import transform on root bones
for (int32 i = 0; i < data.Skeleton.Bones.Count(); i++)
// Apply import transform on bones
Matrix importMatrixInv;
importTransform.GetWorld(importMatrixInv);
importMatrixInv.Invert();
for (SkeletonBone& bone : data.Skeleton.Bones)
{
auto& bone = data.Skeleton.Bones.Get()[i];
if (bone.ParentIndex == -1)
{
bone.LocalTransform = importTransform.LocalToWorld(bone.LocalTransform);
}
bone.OffsetMatrix = importMatrixInv * bone.OffsetMatrix;
}
}
@@ -1363,41 +1405,32 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
// use SkeletonMapping<SkeletonBone> to map bones?
// Calculate offset matrix (inverse bind pose transform) for every bone manually
/*for (SkeletonBone& bone : data.Skeleton.Bones)
{
for (int32 i = 0; i < data.Skeleton.Bones.Count(); i++)
{
Matrix t = Matrix::Identity;
int32 idx = data.Skeleton.Bones[i].NodeIndex;
do
{
t *= data.Skeleton.Nodes[idx].LocalTransform.GetWorld();
idx = data.Skeleton.Nodes[idx].ParentIndex;
} while (idx != -1);
t.Invert();
data.Skeleton.Bones[i].OffsetMatrix = t;
}
}
CalculateBoneOffsetMatrix(data.Skeleton.Nodes, bone.OffsetMatrix, bone.NodeIndex);
}*/
#if USE_SKELETON_NODES_SORTING
// Sort skeleton nodes and bones hierarchy (parents first)
// Then it can be used with a simple linear loop update
{
const int32 nodesCount = data.Skeleton.Nodes.Count();
const int32 bonesCount = data.Skeleton.Bones.Count();
Array<int32> mapping;
CreateLinearListFromTree(data.Skeleton.Nodes, mapping);
for (int32 i = 0; i < nodesCount; i++)
{
auto& node = data.Skeleton.Nodes[i];
node.ParentIndex = mapping[node.ParentIndex];
}
for (int32 i = 0; i < bonesCount; i++)
{
auto& bone = data.Skeleton.Bones[i];
bone.NodeIndex = mapping[bone.NodeIndex];
}
}
reorder_nodes_and_test_it_out!
// Then it can be used with a simple linear loop update
{
const int32 nodesCount = data.Skeleton.Nodes.Count();
const int32 bonesCount = data.Skeleton.Bones.Count();
Array<int32> mapping;
CreateLinearListFromTree(data.Skeleton.Nodes, mapping);
for (int32 i = 0; i < nodesCount; i++)
{
auto& node = data.Skeleton.Nodes[i];
node.ParentIndex = mapping[node.ParentIndex];
}
for (int32 i = 0; i < bonesCount; i++)
{
auto& bone = data.Skeleton.Bones[i];
bone.NodeIndex = mapping[bone.NodeIndex];
}
}
reorder_nodes_and_test_it_out
!
#endif
}
else if (options.Type == ModelType::Animation)
@@ -1474,7 +1507,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
// Group meshes that can be merged together
typedef Pair<int32, int32> MeshGroupKey;
const std::function<MeshGroupKey(MeshData* const&)> f = [](MeshData* const& x) -> MeshGroupKey
const Function<MeshGroupKey(MeshData* const&)> f = [](MeshData* const& x) -> MeshGroupKey
{
return MeshGroupKey(x->NodeIndex, x->MaterialSlotIndex);
};
@@ -1753,6 +1786,19 @@ bool ModelTool::FindTexture(const String& sourcePath, const String& file, String
return false;
}
void ModelTool::CalculateBoneOffsetMatrix(const Array<SkeletonNode>& nodes, Matrix& offsetMatrix, int32 nodeIndex)
{
offsetMatrix = Matrix::Identity;
int32 idx = nodeIndex;
do
{
const SkeletonNode& node = nodes[idx];
offsetMatrix *= node.LocalTransform.GetWorld();
idx = node.ParentIndex;
} while (idx != -1);
offsetMatrix.Invert();
}
#endif
#endif

View File

@@ -6,11 +6,13 @@
#include "Engine/Core/Config.h"
#include "Engine/Content/Assets/ModelBase.h"
#include "Engine/Physics/CollisionData.h"
#if USE_EDITOR
#include "Engine/Core/ISerializable.h"
#include "Engine/Graphics/Models/ModelData.h"
#include "Engine/Graphics/Models/SkeletonData.h"
#include "Engine/Animations/AnimationData.h"
#include "Engine/Content/Assets/MaterialBase.h"
class JsonWriter;
@@ -262,6 +264,9 @@ public:
// If specified, all meshes which name starts with this prefix will be imported as a separate collision data (excluded used for rendering).
API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
String CollisionMeshesPrefix = TEXT("");
// The type of collision that should be generated if has collision prefix specified.
API_FIELD(Attributes = "EditorOrder(105), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
CollisionDataType CollisionType = CollisionDataType::TriangleMesh;
public: // Transform
@@ -334,11 +339,17 @@ public:
// If checked, the importer will create materials for model meshes as specified in the file.
API_FIELD(Attributes="EditorOrder(400), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
bool ImportMaterials = true;
// If checked, the importer will create the model's materials as instances of a base material.
API_FIELD(Attributes = "EditorOrder(401), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterials))")
bool ImportMaterialsAsInstances = false;
// The material to import the model's materials as an instance of.
API_FIELD(Attributes = "EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances))")
AssetReference<MaterialBase> InstanceToImportAs;
// If checked, the importer will import texture files used by the model and any embedded texture resources.
API_FIELD(Attributes="EditorOrder(410), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
bool ImportTextures = true;
// If checked, the importer will try to restore the model material slots.
API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Materials\", \"Restore Materials On Reimport\"), VisibleIf(nameof(ShowGeometry))")
// If checked, the importer will try to keep the model's current material slots, instead of importing materials from the source file.
API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Materials\", \"Keep Material Slots on Reimport\"), VisibleIf(nameof(ShowGeometry))")
bool RestoreMaterialsOnReimport = true;
public: // SDF
@@ -419,6 +430,7 @@ public:
}
private:
static void CalculateBoneOffsetMatrix(const Array<SkeletonNode>& nodes, Matrix& offsetMatrix, int32 nodeIndex);
#if USE_ASSIMP
static bool ImportDataAssimp(const char* path, ImportedModelData& data, Options& options, String& errorMsg);
#endif