Merge branch 'FlaxEngine:master' into model-prefab-fix

This commit is contained in:
Menotdan
2024-02-11 02:38:24 -05:00
committed by GitHub
147 changed files with 3313 additions and 1499 deletions

View File

@@ -584,7 +584,6 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
{
// Procedural Texture Sample
textureBox->Cache = writeLocal(Value::InitForZero(ValueType::Float4), node);
createGradients(node);
auto proceduralSample = String::Format(TEXT(
" {{\n"
" float3 weights;\n"
@@ -613,19 +612,19 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
" uv1 = {0} + frac(sin(mul(float2x2(127.1, 311.7, 269.5, 183.3), vertex1)) * 43758.5453);\n"
" uv2 = {0} + frac(sin(mul(float2x2(127.1, 311.7, 269.5, 183.3), vertex2)) * 43758.5453);\n"
" uv3 = {0} + frac(sin(mul(float2x2(127.1, 311.7, 269.5, 183.3), vertex3)) * 43758.5453);\n"
" float4 tex1 = {1}.SampleGrad({4}, uv1, {2}, {3}, {6}) * weights.x;\n"
" float4 tex2 = {1}.SampleGrad({4}, uv2, {2}, {3}, {6}) * weights.y;\n"
" float4 tex3 = {1}.SampleGrad({4}, uv3, {2}, {3}, {6}) * weights.z;\n"
" {5} = tex1 + tex2 + tex3;\n"
" float2 fdx = ddx({0});\n"
" float2 fdy = ddy({0});\n"
" float4 tex1 = {1}.SampleGrad({2}, uv1, fdx, fdy, {4}) * weights.x;\n"
" float4 tex2 = {1}.SampleGrad({2}, uv2, fdx, fdy, {4}) * weights.y;\n"
" float4 tex3 = {1}.SampleGrad({2}, uv3, fdx, fdy, {4}) * weights.z;\n"
" {3} = tex1 + tex2 + tex3;\n"
" }}\n"
),
uvs.Value, // {0}
texture.Value, // {1}
_ddx.Value, // {2}
_ddy.Value, // {3}
samplerName, // {4}
textureBox->Cache.Value, // {5}
offset.Value // {6}
samplerName, // {2}
textureBox->Cache.Value, // {3}
offset.Value // {4}
);
_writer.Write(*proceduralSample);

View File

@@ -19,15 +19,15 @@
#include "Engine/Content/Content.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#if USE_EDITOR
#include "Engine/Core/Utilities.h"
#include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Core/Types/Pair.h"
#include "Engine/Core/Types/Variant.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Graphics/Models/SkeletonUpdater.h"
#include "Engine/Graphics/Models/SkeletonMapping.h"
#include "Engine/Core/Utilities.h"
#include "Engine/Core/Types/StringView.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/ContentImporters/CreateMaterial.h"
@@ -35,6 +35,7 @@
#include "Engine/ContentImporters/CreateCollisionData.h"
#include "Engine/Serialization/Serialization.h"
#include "Editor/Utilities/EditorUtilities.h"
#include "Engine/Animations/Graph/AnimGraph.h"
#include <ThirdParty/meshoptimizer/meshoptimizer.h>
#endif
@@ -361,7 +362,8 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
SERIALIZE(SkipEmptyCurves);
SERIALIZE(OptimizeKeyframes);
SERIALIZE(ImportScaleTracks);
SERIALIZE(EnableRootMotion);
SERIALIZE(RootMotion);
SERIALIZE(RootMotionFlags);
SERIALIZE(RootNodeName);
SERIALIZE(GenerateLODs);
SERIALIZE(BaseLOD);
@@ -374,6 +376,7 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
SERIALIZE(InstanceToImportAs);
SERIALIZE(ImportTextures);
SERIALIZE(RestoreMaterialsOnReimport);
SERIALIZE(SkipExistingMaterialsOnReimport);
SERIALIZE(GenerateSDF);
SERIALIZE(SDFResolution);
SERIALIZE(SplitObjects);
@@ -409,7 +412,8 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
DESERIALIZE(SkipEmptyCurves);
DESERIALIZE(OptimizeKeyframes);
DESERIALIZE(ImportScaleTracks);
DESERIALIZE(EnableRootMotion);
DESERIALIZE(RootMotion);
DESERIALIZE(RootMotionFlags);
DESERIALIZE(RootNodeName);
DESERIALIZE(GenerateLODs);
DESERIALIZE(BaseLOD);
@@ -422,6 +426,7 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
DESERIALIZE(InstanceToImportAs);
DESERIALIZE(ImportTextures);
DESERIALIZE(RestoreMaterialsOnReimport);
DESERIALIZE(SkipExistingMaterialsOnReimport);
DESERIALIZE(GenerateSDF);
DESERIALIZE(SDFResolution);
DESERIALIZE(SplitObjects);
@@ -433,6 +438,15 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
DESERIALIZE(AnimationIndex);
if (AnimationIndex != -1)
ObjectIndex = AnimationIndex;
// [Deprecated on 08.02.2024, expires on 08.02.2026]
bool EnableRootMotion = false;
DESERIALIZE(EnableRootMotion);
if (EnableRootMotion)
{
RootMotion = RootMotionMode::ExtractNode;
RootMotionFlags = AnimationRootMotionFlags::RootPositionXZ;
}
}
void RemoveNamespace(String& name)
@@ -807,6 +821,8 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
break;
case ModelType::Animation:
options.ImportTypes = ImportDataTypes::Animations;
if (options.RootMotion == RootMotionMode::ExtractCenterOfMass)
options.ImportTypes |= ImportDataTypes::Skeleton;
break;
case ModelType::Prefab:
options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes | ImportDataTypes::Animations;
@@ -1154,6 +1170,18 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
continue;
}
// Skip any materials that already exist from the model.
// This allows the use of "import as material instances" without material properties getting overridden on each import.
if (options.SkipExistingMaterialsOnReimport)
{
AssetInfo info;
if (Content::GetAssetInfo(assetPath, info))
{
material.AssetID = info.ID;
continue;
}
}
if (options.ImportMaterialsAsInstances)
{
// Create material instance
@@ -1396,6 +1424,129 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
}
}
// Process root motion setup
animation.RootMotionFlags = options.RootMotion != RootMotionMode::None ? options.RootMotionFlags : AnimationRootMotionFlags::None;
animation.RootNodeName = options.RootNodeName.TrimTrailing();
if (animation.RootMotionFlags != AnimationRootMotionFlags::None && animation.Channels.HasItems())
{
if (options.RootMotion == RootMotionMode::ExtractNode)
{
if (animation.RootNodeName.HasChars() && animation.GetChannel(animation.RootNodeName) == nullptr)
{
LOG(Warning, "Missing Root Motion node '{}'", animation.RootNodeName);
}
}
else if (options.RootMotion == RootMotionMode::ExtractCenterOfMass && data.Skeleton.Nodes.HasItems()) // TODO: finish implementing this
{
// Setup root node animation track
const auto& rootName = data.Skeleton.Nodes.First().Name;
auto rootChannelPtr = animation.GetChannel(rootName);
if (!rootChannelPtr)
{
animation.Channels.Insert(0, NodeAnimationData());
rootChannelPtr = &animation.Channels[0];
rootChannelPtr->NodeName = rootName;
}
animation.RootNodeName = rootName;
auto& rootChannel = *rootChannelPtr;
rootChannel.Position.Clear();
// Calculate skeleton center of mass position over the animation frames
const int32 frames = (int32)animation.Duration;
const int32 nodes = data.Skeleton.Nodes.Count();
Array<Float3> centerOfMass;
centerOfMass.Resize(frames);
for (int32 frame = 0; frame < frames; frame++)
{
auto& key = centerOfMass[frame];
// Evaluate skeleton pose at the animation frame
AnimGraphImpulse pose;
pose.Nodes.Resize(nodes);
for (int32 nodeIndex = 0; nodeIndex < nodes; nodeIndex++)
{
Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform;
auto& node = data.Skeleton.Nodes[nodeIndex];
if (auto* channel = animation.GetChannel(node.Name))
channel->Evaluate(frame, &srcNode, false);
pose.Nodes[nodeIndex] = srcNode;
}
// Calculate average location of the pose (center of mass)
key = Float3::Zero;
for (int32 nodeIndex = 0; nodeIndex < nodes; nodeIndex++)
key += pose.GetNodeModelTransformation(data.Skeleton, nodeIndex).Translation;
key /= nodes;
}
// Calculate skeleton center of mass movement over the animation frames
rootChannel.Position.Resize(frames);
const Float3 centerOfMassRefPose = centerOfMass[0];
for (int32 frame = 0; frame < frames; frame++)
{
auto& key = rootChannel.Position[frame];
key.Time = frame;
key.Value = centerOfMass[frame] - centerOfMassRefPose;
}
// Remove root motion from the children (eg. if Root moves, then Hips should skip that movement delta)
Float3 maxMotion = Float3::Zero;
for (int32 i = 0; i < animation.Channels.Count(); i++)
{
auto& anim = animation.Channels[i];
const int32 animNodeIndex = data.Skeleton.FindNode(anim.NodeName);
// Skip channels that have one of their parents already animated
{
int32 nodeIndex = animNodeIndex;
nodeIndex = data.Skeleton.Nodes[nodeIndex].ParentIndex;
while (nodeIndex > 0)
{
const String& nodeName = data.Skeleton.Nodes[nodeIndex].Name;
if (animation.GetChannel(nodeName) != nullptr)
break;
nodeIndex = data.Skeleton.Nodes[nodeIndex].ParentIndex;
}
if (nodeIndex > 0 || &anim == rootChannelPtr)
continue;
}
// Remove motion
auto& animPos = anim.Position.GetKeyframes();
for (int32 frame = 0; frame < animPos.Count(); frame++)
{
auto& key = animPos[frame];
// Evaluate root motion at the keyframe location
Float3 rootMotion;
rootChannel.Position.Evaluate(rootMotion, key.Time, false);
Float3::Max(maxMotion, rootMotion, maxMotion);
// Evaluate skeleton pose at the animation frame
AnimGraphImpulse pose;
pose.Nodes.Resize(nodes);
pose.Nodes[0] = data.Skeleton.Nodes[0].LocalTransform; // Use ref pose of root
for (int32 nodeIndex = 1; nodeIndex < nodes; nodeIndex++) // Skip new root
{
Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform;
auto& node = data.Skeleton.Nodes[nodeIndex];
if (auto* channel = animation.GetChannel(node.Name))
channel->Evaluate(frame, &srcNode, false);
pose.Nodes[nodeIndex] = srcNode;
}
// Convert root motion to the local space of this node so the node stays at the same location after adding new root channel
Transform parentNodeTransform = pose.GetNodeModelTransformation(data.Skeleton, data.Skeleton.Nodes[animNodeIndex].ParentIndex);
rootMotion = parentNodeTransform.WorldToLocal(rootMotion);
// Remove motion
key.Value -= rootMotion;
}
}
LOG(Info, "Calculated root motion: {}", maxMotion);
}
}
// Optimize the keyframes
if (options.OptimizeKeyframes)
{
@@ -1418,9 +1569,6 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
const int32 after = animation.GetKeyframesCount();
LOG(Info, "Optimized {0} animation keyframe(s). Before: {1}, after: {2}, Ratio: {3}%", before - after, before, after, Utilities::RoundTo2DecimalPlaces((float)after / before));
}
animation.EnableRootMotion = options.EnableRootMotion;
animation.RootNodeName = options.RootNodeName;
}
}

View File

@@ -129,6 +129,19 @@ public:
Custom = 1,
};
/// <summary>
/// Declares the imported animation Root Motion modes.
/// </summary>
API_ENUM(Attributes="HideInEditor") enum class RootMotionMode
{
// Root Motion feature is disabled.
None = 0,
// Motion is extracted from the root node (or node specified by name).
ExtractNode = 1,
// Motion is extracted from the center of mass movement (estimated based on the skeleton pose animation).
ExtractCenterOfMass = 2,
};
/// <summary>
/// Model import options.
/// </summary>
@@ -228,9 +241,12 @@ public:
bool ImportScaleTracks = false;
// Enables root motion extraction support from this animation.
API_FIELD(Attributes="EditorOrder(1060), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))")
bool EnableRootMotion = false;
RootMotionMode RootMotion = RootMotionMode::None;
// Adjusts root motion applying flags. Can customize how root node animation can affect target actor movement (eg. apply both position and rotation changes).
API_FIELD(Attributes="EditorOrder(1060), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowRootMotion))")
AnimationRootMotionFlags RootMotionFlags = AnimationRootMotionFlags::RootPositionXZ;
// The custom node name to be used as a root motion source. If not specified the actual root node will be used.
API_FIELD(Attributes="EditorOrder(1070), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))")
API_FIELD(Attributes="EditorOrder(1070), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowRootMotion))")
String RootNodeName = TEXT("");
public: // Level Of Detail
@@ -251,7 +267,7 @@ public:
API_FIELD(Attributes="EditorOrder(1140), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGeometry))")
bool SloppyOptimization = false;
// Only used if Sloppy is false. Target error is an approximate measure of the deviation from the original mesh using distance normalized to [0..1] range (e.g. 1e-2f means that simplifier will try to maintain the error to be below 1% of the mesh extents).
API_FIELD(Attributes="EditorOrder(1150), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(SloppyOptimization), true), Limit(0.01f, 1, 0.001f)")
API_FIELD(Attributes="EditorOrder(1150), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGeometry)), Limit(0.01f, 1, 0.001f)")
float LODTargetError = 0.05f;
public: // Materials
@@ -260,17 +276,20 @@ public:
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))")
API_FIELD(Attributes = "EditorOrder(401), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterials)), VisibleIf(nameof(ShowGeometry))")
bool ImportMaterialsAsInstances = false;
// The material used as the base material that will be instanced as the imported model's material.
API_FIELD(Attributes = "EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances))")
API_FIELD(Attributes = "EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances)), VisibleIf(nameof(ShowGeometry))")
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 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))")
// If checked, the importer will try to keep the model's current overridden material slots, instead of importing materials from the source file.
API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Materials\", \"Keep Overridden Materials\"), VisibleIf(nameof(ShowGeometry))")
bool RestoreMaterialsOnReimport = true;
// If checked, the importer will not reimport any material from this model which already exist in the sub-asset folder.
API_FIELD(Attributes = "EditorOrder(421), EditorDisplay(\"Materials\", \"Skip Existing Materials\"), VisibleIf(nameof(ShowGeometry))")
bool SkipExistingMaterialsOnReimport = true;
public: // SDF