diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Assets/Icons/Resources.qrc b/Gems/AtomLyIntegration/EMotionFXAtom/Assets/Icons/Resources.qrc
index 28ae322d5b..7924ef1c4e 100644
--- a/Gems/AtomLyIntegration/EMotionFXAtom/Assets/Icons/Resources.qrc
+++ b/Gems/AtomLyIntegration/EMotionFXAtom/Assets/Icons/Resources.qrc
@@ -1,5 +1,6 @@
Camera_category.svg
+ Visualization.svg
diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Assets/Icons/Visualization.svg b/Gems/AtomLyIntegration/EMotionFXAtom/Assets/Icons/Visualization.svg
new file mode 100644
index 0000000000..3d1b40d1b6
--- /dev/null
+++ b/Gems/AtomLyIntegration/EMotionFXAtom/Assets/Icons/Visualization.svg
@@ -0,0 +1,9 @@
+
+
diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.cpp
new file mode 100644
index 0000000000..eaaf04fcf2
--- /dev/null
+++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.cpp
@@ -0,0 +1,415 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace AZ::Render
+{
+ AtomActorDebugDraw::AtomActorDebugDraw(AZ::EntityId entityId)
+ {
+ m_auxGeomFeatureProcessor = RPI::Scene::GetFeatureProcessorForEntity(entityId);
+ }
+
+ void AtomActorDebugDraw::DebugDraw(const EMotionFX::ActorRenderFlagBitset& renderFlags, EMotionFX::ActorInstance* instance)
+ {
+ if (!m_auxGeomFeatureProcessor || !instance)
+ {
+ return;
+ }
+
+ RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue();
+ if (!auxGeom)
+ {
+ return;
+ }
+
+ // Render aabb
+ if (renderFlags[EMotionFX::ActorRenderFlag::RENDER_AABB])
+ {
+ RenderAABB(instance);
+ }
+
+ // Render skeleton
+ if (renderFlags[EMotionFX::ActorRenderFlag::RENDER_LINESKELETON])
+ {
+ RenderSkeleton(instance);
+ }
+
+ // Render internal EMFX debug lines.
+ if (renderFlags[EMotionFX::ActorRenderFlag::RENDER_EMFX_DEBUG])
+ {
+ RenderEMFXDebugDraw(instance);
+ }
+
+ // Render vertex normal, face normal, tagent and wireframe.
+ const bool renderVertexNormals = renderFlags[EMotionFX::ActorRenderFlag::RENDER_VERTEXNORMALS];
+ const bool renderFaceNormals = renderFlags[EMotionFX::ActorRenderFlag::RENDER_FACENORMALS];
+ const bool renderTangents = renderFlags[EMotionFX::ActorRenderFlag::RENDER_TANGENTS];
+ const bool renderWireframe = renderFlags[EMotionFX::ActorRenderFlag::RENDER_WIREFRAME];
+
+ if (renderVertexNormals || renderFaceNormals || renderTangents || renderWireframe)
+ {
+ // Iterate through all enabled nodes
+ const EMotionFX::Pose* pose = instance->GetTransformData()->GetCurrentPose();
+ const size_t geomLODLevel = instance->GetLODLevel();
+ const size_t numEnabled = instance->GetNumEnabledNodes();
+ for (size_t i = 0; i < numEnabled; ++i)
+ {
+ EMotionFX::Node* node = instance->GetActor()->GetSkeleton()->GetNode(instance->GetEnabledNode(i));
+ EMotionFX::Mesh* mesh = instance->GetActor()->GetMesh(geomLODLevel, node->GetNodeIndex());
+ const AZ::Transform globalTM = pose->GetWorldSpaceTransform(node->GetNodeIndex()).ToAZTransform();
+
+ m_currentMesh = nullptr;
+
+ if (!mesh)
+ {
+ continue;
+ }
+
+ RenderNormals(mesh, globalTM, renderVertexNormals, renderFaceNormals);
+ if (renderTangents)
+ {
+ RenderTangents(mesh, globalTM);
+ }
+ }
+ }
+ }
+
+ void AtomActorDebugDraw::PrepareForMesh(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM)
+ {
+ // Check if we have already prepared for the given mesh
+ if (m_currentMesh == mesh)
+ {
+ return;
+ }
+
+ // Set our new current mesh
+ m_currentMesh = mesh;
+
+ // Get the number of vertices and the data
+ const uint32 numVertices = m_currentMesh->GetNumVertices();
+ AZ::Vector3* positions = (AZ::Vector3*)m_currentMesh->FindVertexData(EMotionFX::Mesh::ATTRIB_POSITIONS);
+
+ // Check if the vertices fits in our buffer
+ if (m_worldSpacePositions.size() < numVertices)
+ {
+ m_worldSpacePositions.resize(numVertices);
+ }
+
+ // Pre-calculate the world space positions
+ for (uint32 i = 0; i < numVertices; ++i)
+ {
+ m_worldSpacePositions[i] = worldTM.TransformPoint(positions[i]);
+ }
+ }
+
+ void AtomActorDebugDraw::RenderAABB(EMotionFX::ActorInstance* instance)
+ {
+ RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue();
+ const AZ::Aabb& aabb = instance->GetAabb();
+ auxGeom->DrawAabb(aabb, AZ::Color(0.0f, 1.0f, 1.0f, 1.0f), RPI::AuxGeomDraw::DrawStyle::Line);
+ }
+
+ void AtomActorDebugDraw::RenderSkeleton(EMotionFX::ActorInstance* instance)
+ {
+ RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue();
+
+ const EMotionFX::TransformData* transformData = instance->GetTransformData();
+ const EMotionFX::Skeleton* skeleton = instance->GetActor()->GetSkeleton();
+ const EMotionFX::Pose* pose = transformData->GetCurrentPose();
+
+ const size_t lodLevel = instance->GetLODLevel();
+ const size_t numJoints = skeleton->GetNumNodes();
+
+ m_auxVertices.clear();
+ m_auxVertices.reserve(numJoints * 2);
+
+ for (size_t jointIndex = 0; jointIndex < numJoints; ++jointIndex)
+ {
+ const EMotionFX::Node* joint = skeleton->GetNode(jointIndex);
+ if (!joint->GetSkeletalLODStatus(lodLevel))
+ {
+ continue;
+ }
+
+ const size_t parentIndex = joint->GetParentIndex();
+ if (parentIndex == InvalidIndex)
+ {
+ continue;
+ }
+
+ const AZ::Vector3 parentPos = pose->GetWorldSpaceTransform(parentIndex).m_position;
+ m_auxVertices.emplace_back(parentPos);
+
+ const AZ::Vector3 bonePos = pose->GetWorldSpaceTransform(jointIndex).m_position;
+ m_auxVertices.emplace_back(bonePos);
+ }
+
+ const AZ::Color skeletonColor(0.604f, 0.804f, 0.196f, 1.0f);
+ RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs;
+ lineArgs.m_verts = m_auxVertices.data();
+ lineArgs.m_vertCount = static_cast(m_auxVertices.size());
+ lineArgs.m_colors = &skeletonColor;
+ lineArgs.m_colorCount = 1;
+ lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off;
+ auxGeom->DrawLines(lineArgs);
+ }
+
+ void AtomActorDebugDraw::RenderEMFXDebugDraw(EMotionFX::ActorInstance* instance)
+ {
+ RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue();
+
+ EMotionFX::DebugDraw& debugDraw = EMotionFX::GetDebugDraw();
+ debugDraw.Lock();
+ EMotionFX::DebugDraw::ActorInstanceData* actorInstanceData = debugDraw.GetActorInstanceData(instance);
+ actorInstanceData->Lock();
+ const AZStd::vector& lines = actorInstanceData->GetLines();
+ if (lines.empty())
+ {
+ actorInstanceData->Unlock();
+ debugDraw.Unlock();
+ return;
+ }
+
+ m_auxVertices.clear();
+ m_auxVertices.reserve(lines.size() * 2);
+ m_auxColors.clear();
+ m_auxColors.reserve(m_auxVertices.size());
+
+ for (const EMotionFX::DebugDraw::Line& line : actorInstanceData->GetLines())
+ {
+ m_auxVertices.emplace_back(line.m_start);
+ m_auxColors.emplace_back(line.m_startColor);
+ m_auxVertices.emplace_back(line.m_end);
+ m_auxColors.emplace_back(line.m_endColor);
+ }
+
+ AZ_Assert(m_auxVertices.size() == m_auxColors.size(), "Number of vertices and number of colors need to match.");
+ actorInstanceData->Unlock();
+ debugDraw.Unlock();
+
+ RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs;
+ lineArgs.m_verts = m_auxVertices.data();
+ lineArgs.m_vertCount = static_cast(m_auxVertices.size());
+ lineArgs.m_colors = m_auxColors.data();
+ lineArgs.m_colorCount = static_cast(m_auxColors.size());
+ lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off;
+ auxGeom->DrawLines(lineArgs);
+ }
+
+ void AtomActorDebugDraw::RenderNormals(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM, bool vertexNormals, bool faceNormals)
+ {
+ if (!mesh)
+ {
+ return;
+ }
+
+ if (!vertexNormals && !faceNormals)
+ {
+ return;
+ }
+
+ RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue();
+ if (!auxGeom)
+ {
+ return;
+ }
+
+ // TODO: Move line color to a render setting.
+ const float faceNormalsScale = 0.01f;
+ const AZ::Color colorFaceNormals = AZ::Colors::Lime;
+ const float vertexNormalsScale = 0.01f;
+ const AZ::Color colorVertexNormals = AZ::Colors::Orange;
+
+ PrepareForMesh(mesh, worldTM);
+
+ AZ::Vector3* normals = (AZ::Vector3*)mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_NORMALS);
+
+ // Render face normals
+ if (faceNormals)
+ {
+ const size_t numSubMeshes = mesh->GetNumSubMeshes();
+ for (uint32 subMeshIndex = 0; subMeshIndex < numSubMeshes; ++subMeshIndex)
+ {
+ EMotionFX::SubMesh* subMesh = mesh->GetSubMesh(subMeshIndex);
+ const uint32 numTriangles = subMesh->GetNumPolygons();
+ const uint32 startVertex = subMesh->GetStartVertex();
+ const uint32* indices = subMesh->GetIndices();
+
+ m_auxVertices.clear();
+ m_auxVertices.reserve(numTriangles * 2);
+ m_auxColors.clear();
+ m_auxColors.reserve(m_auxVertices.size());
+
+ for (uint32 triangleIndex = 0; triangleIndex < numTriangles; ++triangleIndex)
+ {
+ const uint32 triangleStartIndex = triangleIndex * 3;
+ const uint32 indexA = indices[triangleStartIndex + 0] + startVertex;
+ const uint32 indexB = indices[triangleStartIndex + 1] + startVertex;
+ const uint32 indexC = indices[triangleStartIndex + 2] + startVertex;
+
+ const AZ::Vector3& posA = m_worldSpacePositions[indexA];
+ const AZ::Vector3& posB = m_worldSpacePositions[indexB];
+ const AZ::Vector3& posC = m_worldSpacePositions[indexC];
+
+ const AZ::Vector3 normalDir = (posB - posA).Cross(posC - posA).GetNormalized();
+
+ // Calculate the center pos
+ const AZ::Vector3 normalPos = (posA + posB + posC) * (1.0f / 3.0f);
+
+ m_auxVertices.emplace_back(normalPos);
+ m_auxColors.emplace_back(colorFaceNormals);
+ m_auxVertices.emplace_back(normalPos + (normalDir * faceNormalsScale));
+ m_auxColors.emplace_back(colorFaceNormals);
+ }
+ }
+
+ RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs;
+ lineArgs.m_verts = m_auxVertices.data();
+ lineArgs.m_vertCount = static_cast(m_auxVertices.size());
+ lineArgs.m_colors = m_auxColors.data();
+ lineArgs.m_colorCount = static_cast(m_auxColors.size());
+ lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off;
+ auxGeom->DrawLines(lineArgs);
+ }
+
+ // render vertex normals
+ if (vertexNormals)
+ {
+ const size_t numSubMeshes = mesh->GetNumSubMeshes();
+ for (uint32 subMeshIndex = 0; subMeshIndex < numSubMeshes; ++subMeshIndex)
+ {
+ EMotionFX::SubMesh* subMesh = mesh->GetSubMesh(subMeshIndex);
+ const uint32 numVertices = subMesh->GetNumVertices();
+ const uint32 startVertex = subMesh->GetStartVertex();
+
+ m_auxVertices.clear();
+ m_auxVertices.reserve(numVertices * 2);
+ m_auxColors.clear();
+ m_auxColors.reserve(m_auxVertices.size());
+
+ for (uint32 j = 0; j < numVertices; ++j)
+ {
+ const uint32 vertexIndex = j + startVertex;
+ const AZ::Vector3& position = m_worldSpacePositions[vertexIndex];
+ const AZ::Vector3 normal = worldTM.TransformVector(normals[vertexIndex]).GetNormalizedSafe() * vertexNormalsScale;
+
+ m_auxVertices.emplace_back(position);
+ m_auxColors.emplace_back(colorFaceNormals);
+ m_auxVertices.emplace_back(position + normal);
+ m_auxColors.emplace_back(colorFaceNormals);
+ }
+ }
+
+ RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs;
+ lineArgs.m_verts = m_auxVertices.data();
+ lineArgs.m_vertCount = static_cast(m_auxVertices.size());
+ lineArgs.m_colors = m_auxColors.data();
+ lineArgs.m_colorCount = static_cast(m_auxColors.size());
+ lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off;
+ auxGeom->DrawLines(lineArgs);
+ }
+ }
+
+ void AtomActorDebugDraw::RenderTangents(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM)
+ {
+ if (!mesh)
+ {
+ return;
+ }
+
+ RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue();
+ if (!auxGeom)
+ {
+ return;
+ }
+
+ // TODO: Move line color to a render setting.
+ const AZ::Color colorTangents = AZ::Colors::Red;
+ const AZ::Color mirroredBitangentColor = AZ::Colors::Yellow;
+ const AZ::Color colorBitangents = AZ::Colors::White;
+ const float scale = 0.01f;
+
+ // Get the tangents and check if this mesh actually has tangents
+ AZ::Vector4* tangents = static_cast(mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_TANGENTS));
+ if (!tangents)
+ {
+ return;
+ }
+
+ AZ::Vector3* bitangents = static_cast(mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_BITANGENTS));
+
+ PrepareForMesh(mesh, worldTM);
+
+ AZ::Vector3* normals = (AZ::Vector3*)mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_NORMALS);
+ const uint32 numVertices = mesh->GetNumVertices();
+
+ m_auxVertices.clear();
+ m_auxVertices.reserve(numVertices * 2);
+ m_auxColors.clear();
+ m_auxColors.reserve(m_auxVertices.size());
+
+ // Render the tangents and bitangents
+ AZ::Vector3 orgTangent, tangent, bitangent;
+ for (uint32 i = 0; i < numVertices; ++i)
+ {
+ orgTangent.Set(tangents[i].GetX(), tangents[i].GetY(), tangents[i].GetZ());
+ tangent = (worldTM.TransformVector(orgTangent)).GetNormalized();
+
+ if (bitangents)
+ {
+ bitangent = bitangents[i];
+ }
+ else
+ {
+ bitangent = tangents[i].GetW() * normals[i].Cross(orgTangent);
+ }
+ bitangent = (worldTM.TransformVector(bitangent)).GetNormalizedSafe();
+
+ m_auxVertices.emplace_back(m_worldSpacePositions[i]);
+ m_auxColors.emplace_back(colorTangents);
+ m_auxVertices.emplace_back(m_worldSpacePositions[i] + (tangent * scale));
+ m_auxColors.emplace_back(colorTangents);
+
+ if (tangents[i].GetW() < 0.0f)
+ {
+ m_auxVertices.emplace_back(m_worldSpacePositions[i]);
+ m_auxColors.emplace_back(mirroredBitangentColor);
+ m_auxVertices.emplace_back(m_worldSpacePositions[i] + (bitangent * scale));
+ m_auxColors.emplace_back(mirroredBitangentColor);
+ }
+ else
+ {
+ m_auxVertices.emplace_back(m_worldSpacePositions[i]);
+ m_auxColors.emplace_back(colorBitangents);
+ m_auxVertices.emplace_back(m_worldSpacePositions[i] + (bitangent * scale));
+ m_auxColors.emplace_back(colorBitangents);
+ }
+ }
+
+ RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs;
+ lineArgs.m_verts = m_auxVertices.data();
+ lineArgs.m_vertCount = static_cast(m_auxVertices.size());
+ lineArgs.m_colors = m_auxColors.data();
+ lineArgs.m_colorCount = static_cast(m_auxColors.size());
+ lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off;
+ auxGeom->DrawLines(lineArgs);
+ }
+} // namespace AZ::Render
diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.h
new file mode 100644
index 0000000000..9f8b137f13
--- /dev/null
+++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+namespace EMotionFX
+{
+ class Mesh;
+ class ActorInstance;
+}
+
+namespace AZ::RPI
+{
+ class AuxGeomDraw;
+ class AuxGeomFeatureProcessorInterface;
+}
+
+namespace AZ::Render
+{
+ // Ultility class for atom debug render on actor
+ class AtomActorDebugDraw
+ {
+ public:
+ AtomActorDebugDraw(AZ::EntityId entityId);
+
+ void DebugDraw(const EMotionFX::ActorRenderFlagBitset& renderFlags, EMotionFX::ActorInstance* instance);
+
+ private:
+
+ void PrepareForMesh(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM);
+ void RenderAABB(EMotionFX::ActorInstance* instance);
+ void RenderSkeleton(EMotionFX::ActorInstance* instance);
+ void RenderEMFXDebugDraw(EMotionFX::ActorInstance* instance);
+ void RenderNormals(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM, bool vertexNormals, bool faceNormals);
+ void RenderTangents(EMotionFX::Mesh* mesh, const AZ::Transform& worldTM);
+
+ EMotionFX::Mesh* m_currentMesh = nullptr; /**< A pointer to the mesh whose world space positions are in the pre-calculated positions buffer.
+ NULL in case we haven't pre-calculated any positions yet. */
+ AZStd::vector m_worldSpacePositions; /**< The buffer used to store world space positions for rendering normals
+ tangents and the wireframe. */
+
+ RPI::AuxGeomFeatureProcessorInterface* m_auxGeomFeatureProcessor = nullptr;
+ AZStd::vector m_auxVertices;
+ AZStd::vector m_auxColors;
+ };
+}
diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp
index a3161002a0..4d1b42a0eb 100644
--- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp
+++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp
@@ -8,6 +8,7 @@
#include
#include
+#include
#include
#include
@@ -59,7 +60,7 @@ namespace AZ
AzFramework::BoundsRequestBus::Handler::BusConnect(m_entityId);
}
- m_auxGeomFeatureProcessor = RPI::Scene::GetFeatureProcessorForEntity(m_entityId);
+ m_atomActorDebugDraw = AZStd::make_unique(entityId);
}
AtomActorInstance::~AtomActorInstance()
@@ -78,6 +79,11 @@ namespace AZ
UpdateBounds();
}
+ void AtomActorInstance::DebugDraw(const EMotionFX::ActorRenderFlagBitset& renderFlags)
+ {
+ m_atomActorDebugDraw->DebugDraw(renderFlags, m_actorInstance);
+ }
+
void AtomActorInstance::UpdateBounds()
{
// Update RenderActorInstance world bounding box
@@ -99,116 +105,6 @@ namespace AZ
AZ::Interface::Get()->RefreshEntityLocalBoundsUnion(m_entityId);
}
- void AtomActorInstance::DebugDraw(const DebugOptions& debugOptions)
- {
- if (m_auxGeomFeatureProcessor)
- {
- if (RPI::AuxGeomDrawPtr auxGeom = m_auxGeomFeatureProcessor->GetDrawQueue())
- {
- if (debugOptions.m_drawAABB)
- {
- const AZ::Aabb& aabb = m_actorInstance->GetAabb();
- auxGeom->DrawAabb(aabb, AZ::Color(0.0f, 1.0f, 1.0f, 1.0f), RPI::AuxGeomDraw::DrawStyle::Line);
- }
-
- if (debugOptions.m_drawSkeleton)
- {
- RenderSkeleton(auxGeom.get());
- }
-
- if (debugOptions.m_emfxDebugDraw)
- {
- RenderEMFXDebugDraw(auxGeom.get());
- }
- }
- }
- }
-
- void AtomActorInstance::RenderSkeleton(RPI::AuxGeomDraw* auxGeom)
- {
- AZ_Assert(m_actorInstance, "Valid actor instance required.");
- const EMotionFX::TransformData* transformData = m_actorInstance->GetTransformData();
- const EMotionFX::Skeleton* skeleton = m_actorInstance->GetActor()->GetSkeleton();
- const EMotionFX::Pose* pose = transformData->GetCurrentPose();
-
- const size_t lodLevel = m_actorInstance->GetLODLevel();
- const size_t numJoints = skeleton->GetNumNodes();
-
- m_auxVertices.clear();
- m_auxVertices.reserve(numJoints * 2);
-
- for (size_t jointIndex = 0; jointIndex < numJoints; ++jointIndex)
- {
- const EMotionFX::Node* joint = skeleton->GetNode(jointIndex);
- if (!joint->GetSkeletalLODStatus(lodLevel))
- {
- continue;
- }
-
- const size_t parentIndex = joint->GetParentIndex();
- if (parentIndex == InvalidIndex)
- {
- continue;
- }
-
- const AZ::Vector3 parentPos = pose->GetWorldSpaceTransform(parentIndex).m_position;
- m_auxVertices.emplace_back(parentPos);
-
- const AZ::Vector3 bonePos = pose->GetWorldSpaceTransform(jointIndex).m_position;
- m_auxVertices.emplace_back(bonePos);
- }
-
- const AZ::Color skeletonColor(0.604f, 0.804f, 0.196f, 1.0f);
- RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs;
- lineArgs.m_verts = m_auxVertices.data();
- lineArgs.m_vertCount = static_cast(m_auxVertices.size());
- lineArgs.m_colors = &skeletonColor;
- lineArgs.m_colorCount = 1;
- lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off;
- auxGeom->DrawLines(lineArgs);
- }
-
- void AtomActorInstance::RenderEMFXDebugDraw(RPI::AuxGeomDraw* auxGeom)
- {
- EMotionFX::DebugDraw& debugDraw = EMotionFX::GetDebugDraw();
- debugDraw.Lock();
- EMotionFX::DebugDraw::ActorInstanceData* actorInstanceData = debugDraw.GetActorInstanceData(m_actorInstance);
- actorInstanceData->Lock();
- const AZStd::vector& lines = actorInstanceData->GetLines();
- if (lines.empty())
- {
- actorInstanceData->Unlock();
- debugDraw.Unlock();
- return;
- }
-
- m_auxVertices.clear();
- m_auxVertices.reserve(lines.size() * 2);
- m_auxColors.clear();
- m_auxColors.reserve(m_auxVertices.size());
-
- for (const EMotionFX::DebugDraw::Line& line : actorInstanceData->GetLines())
- {
- m_auxVertices.emplace_back(line.m_start);
- m_auxColors.emplace_back(line.m_startColor);
- m_auxVertices.emplace_back(line.m_end);
- m_auxColors.emplace_back(line.m_endColor);
- }
-
- AZ_Assert(m_auxVertices.size() == m_auxColors.size(),
- "Number of vertices and number of colors need to match.");
- actorInstanceData->Unlock();
- debugDraw.Unlock();
-
- RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments lineArgs;
- lineArgs.m_verts = m_auxVertices.data();
- lineArgs.m_vertCount = static_cast(m_auxVertices.size());
- lineArgs.m_colors = m_auxColors.data();
- lineArgs.m_colorCount = static_cast(m_auxColors.size());
- lineArgs.m_depthTest = RPI::AuxGeomDraw::DepthTest::Off;
- auxGeom->DrawLines(lineArgs);
- }
-
AZ::Aabb AtomActorInstance::GetWorldBounds()
{
return m_worldAABB;
diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h
index 5ddab8bc61..7f646466a5 100644
--- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h
+++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h
@@ -53,6 +53,7 @@ namespace AZ
class SkinnedMeshInputBuffers;
class MeshFeatureProcessorInterface;
class AtomActor;
+ class AtomActorDebugDraw;
//! Render node for managing and rendering actor instances. Each Actor Component
//! creates an ActorRenderNode. The render node is responsible for drawing meshes and
@@ -85,8 +86,8 @@ namespace AZ
// RenderActorInstance overrides ...
void OnTick(float timeDelta) override;
+ void DebugDraw(const EMotionFX::ActorRenderFlagBitset& renderFlags);
void UpdateBounds() override;
- void DebugDraw(const DebugOptions& debugOptions) override;
void SetMaterials(const EMotionFX::Integration::ActorAsset::MaterialList& materialPerLOD) override { AZ_UNUSED(materialPerLOD); };
void SetSkinningMethod(EMotionFX::Integration::SkinningMethod emfxSkinningMethod) override;
SkinningMethod GetAtomSkinningMethod() const;
@@ -184,12 +185,8 @@ namespace AZ
void InitWrinkleMasks();
void UpdateWrinkleMasks();
- // Helper and debug geometry rendering
- void RenderSkeleton(RPI::AuxGeomDraw* auxGeom);
- void RenderEMFXDebugDraw(RPI::AuxGeomDraw* auxGeom);
- RPI::AuxGeomFeatureProcessorInterface* m_auxGeomFeatureProcessor = nullptr;
- AZStd::vector m_auxVertices;
- AZStd::vector m_auxColors;
+ // Debug geometry rendering
+ AZStd::unique_ptr m_atomActorDebugDraw;
AZStd::intrusive_ptr m_skinnedMeshInputBuffers = nullptr;
AZStd::intrusive_ptr m_skinnedMeshInstance;
diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp
index faa033956a..f6186be235 100644
--- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp
+++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp
@@ -206,6 +206,20 @@ namespace EMStudio
return result;
}
+ void AnimViewportRenderer::UpdateActorRenderFlag(EMotionFX::ActorRenderFlagBitset renderFlags)
+ {
+ for (AZ::Entity* entity : m_actorEntities)
+ {
+ EMotionFX::Integration::ActorComponent* actorComponent = entity->FindComponent();
+ if (!actorComponent)
+ {
+ AZ_Assert(false, "Found entity without actor component in the actor entity list.");
+ continue;
+ }
+ actorComponent->SetRenderFlag(renderFlags);
+ }
+ }
+
void AnimViewportRenderer::ResetEnvironment()
{
// Reset environment
diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.h
index 1de4cbdb1c..a4f67ddfd1 100644
--- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.h
+++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.h
@@ -52,6 +52,8 @@ namespace EMStudio
//! Return the center position of the existing objects.
AZ::Vector3 GetCharacterCenter() const;
+ void UpdateActorRenderFlag(EMotionFX::ActorRenderFlagBitset renderFlags);
+
private:
// This function resets the light, camera and other environment settings.
@@ -79,10 +81,6 @@ namespace EMStudio
AZ::Entity* m_postProcessEntity = nullptr;
AZ::Entity* m_iblEntity = nullptr;
- AZ::Entity* m_cameraEntity = nullptr;
- AZ::Component* m_cameraComponent = nullptr;
- AZ::Entity* m_modelEntity = nullptr;
- AZ::Data::AssetId m_modelAssetId;
AZ::Entity* m_gridEntity = nullptr;
AZStd::vector m_actorEntities;
diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRequestBus.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRequestBus.h
index 03784c2158..da4a054c53 100644
--- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRequestBus.h
+++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRequestBus.h
@@ -8,7 +8,7 @@
#pragma once
#include
-
+#include
namespace EMStudio
{
@@ -35,6 +35,9 @@ namespace EMStudio
//! Set the camera view mode.
virtual void SetCameraViewMode(CameraViewMode mode) = 0;
+
+ //! Toggle render option flag
+ virtual void ToggleRenderFlag(EMotionFX::ActorRenderFlag flag) = 0;
};
using AnimViewportRequestBus = AZ::EBus;
diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.cpp
index a47a773e10..50cd088f5d 100644
--- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.cpp
+++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.cpp
@@ -13,7 +13,6 @@
#include
#include
-
namespace EMStudio
{
AnimViewportToolBar::AnimViewportToolBar(QWidget* parent)
@@ -21,40 +20,107 @@ namespace EMStudio
{
AzQtComponents::ToolBar::addMainToolBarStyle(this);
+ // Add the render view options button
+ QToolButton* renderOptionsButton = new QToolButton(this);
+ {
+ QMenu* contextMenu = new QMenu(renderOptionsButton);
+
+ renderOptionsButton->setText("Render Options");
+ renderOptionsButton->setMenu(contextMenu);
+ renderOptionsButton->setPopupMode(QToolButton::InstantPopup);
+ renderOptionsButton->setVisible(true);
+ renderOptionsButton->setIcon(QIcon(":/EMotionFXAtom/Visualization.svg"));
+ addWidget(renderOptionsButton);
+
+ CreateViewOptionEntry(contextMenu, "Solid", EMotionFX::ActorRenderFlag::RENDER_SOLID);
+ CreateViewOptionEntry(contextMenu, "Wireframe", EMotionFX::ActorRenderFlag::RENDER_WIREFRAME);
+ CreateViewOptionEntry(contextMenu, "Lighting", EMotionFX::ActorRenderFlag::RENDER_LIGHTING);
+ CreateViewOptionEntry(contextMenu, "Backface Culling", EMotionFX::ActorRenderFlag::RENDER_BACKFACECULLING);
+ contextMenu->addSeparator();
+ CreateViewOptionEntry(contextMenu, "Vertex Normals", EMotionFX::ActorRenderFlag::RENDER_VERTEXNORMALS);
+ CreateViewOptionEntry(contextMenu, "Face Normals", EMotionFX::ActorRenderFlag::RENDER_FACENORMALS);
+ CreateViewOptionEntry(contextMenu, "Tangents", EMotionFX::ActorRenderFlag::RENDER_TANGENTS);
+ CreateViewOptionEntry(contextMenu, "Actor Bounding Boxes", EMotionFX::ActorRenderFlag::RENDER_AABB);
+ contextMenu->addSeparator();
+ CreateViewOptionEntry(contextMenu, "Line Skeleton", EMotionFX::ActorRenderFlag::RENDER_LINESKELETON);
+ CreateViewOptionEntry(contextMenu, "Solid Skeleton", EMotionFX::ActorRenderFlag::RENDER_SKELETON);
+ CreateViewOptionEntry(contextMenu, "Joint Names", EMotionFX::ActorRenderFlag::RENDER_NODENAMES);
+ CreateViewOptionEntry(contextMenu, "Joint Orientations", EMotionFX::ActorRenderFlag::RENDER_NODEORIENTATION);
+ CreateViewOptionEntry(contextMenu, "Actor Bind Pose", EMotionFX::ActorRenderFlag::RENDER_ACTORBINDPOSE);
+ contextMenu->addSeparator();
+ }
+
// Add the camera button
QToolButton* cameraButton = new QToolButton(this);
- QMenu* cameraMenu = new QMenu(cameraButton);
+ {
+ QMenu* cameraMenu = new QMenu(cameraButton);
- // Add the camera option
- const AZStd::vector> cameraOptionNames = {
- { CameraViewMode::FRONT, "Front" }, { CameraViewMode::BACK, "Back" }, { CameraViewMode::TOP, "Top" },
- { CameraViewMode::BOTTOM, "Bottom" }, { CameraViewMode::LEFT, "Left" }, { CameraViewMode::RIGHT, "Right" },
- };
+ // Add the camera option
+ const AZStd::vector> cameraOptionNames = {
+ { CameraViewMode::FRONT, "Front" }, { CameraViewMode::BACK, "Back" }, { CameraViewMode::TOP, "Top" },
+ { CameraViewMode::BOTTOM, "Bottom" }, { CameraViewMode::LEFT, "Left" }, { CameraViewMode::RIGHT, "Right" },
+ };
- for (const auto& pair : cameraOptionNames)
- {
- CameraViewMode mode = pair.first;
+ for (const auto& pair : cameraOptionNames)
+ {
+ CameraViewMode mode = pair.first;
+ cameraMenu->addAction(
+ pair.second.c_str(),
+ [mode]()
+ {
+ // Send the reset camera event.
+ AnimViewportRequestBus::Broadcast(&AnimViewportRequestBus::Events::SetCameraViewMode, mode);
+ });
+ }
+
+ cameraMenu->addSeparator();
cameraMenu->addAction(
- pair.second.c_str(),
- [mode]()
+ "Reset Camera",
+ []()
{
// Send the reset camera event.
- AnimViewportRequestBus::Broadcast(&AnimViewportRequestBus::Events::SetCameraViewMode, mode);
+ AnimViewportRequestBus::Broadcast(&AnimViewportRequestBus::Events::ResetCamera);
});
+ cameraButton->setMenu(cameraMenu);
+ cameraButton->setText("Camera Option");
+ cameraButton->setPopupMode(QToolButton::InstantPopup);
+ cameraButton->setVisible(true);
+ cameraButton->setIcon(QIcon(":/EMotionFXAtom/Camera_category.svg"));
+ addWidget(cameraButton);
}
+ }
- cameraMenu->addSeparator();
- cameraMenu->addAction("Reset Camera",
- []()
+ void AnimViewportToolBar::CreateViewOptionEntry(
+ QMenu* menu, const char* menuEntryName, uint32_t actionIndex, bool visible, char* iconFileName)
+ {
+ QAction* action = menu->addAction(
+ menuEntryName,
+ [actionIndex]()
{
// Send the reset camera event.
- AnimViewportRequestBus::Broadcast(&AnimViewportRequestBus::Events::ResetCamera);
+ AnimViewportRequestBus::Broadcast(
+ &AnimViewportRequestBus::Events::ToggleRenderFlag, (EMotionFX::ActorRenderFlag)actionIndex);
});
- cameraButton->setMenu(cameraMenu);
- cameraButton->setText("Camera Option");
- cameraButton->setPopupMode(QToolButton::InstantPopup);
- cameraButton->setVisible(true);
- cameraButton->setIcon(QIcon(":/EMotionFXAtom/Camera_category.svg"));
- addWidget(cameraButton);
+ action->setCheckable(true);
+ action->setVisible(visible);
+
+ if (iconFileName)
+ {
+ action->setIcon(QIcon(iconFileName));
+ }
+
+ m_actions[actionIndex] = action;
+ }
+
+ void AnimViewportToolBar::SetRenderFlags(EMotionFX::ActorRenderFlagBitset renderFlags)
+ {
+ for (size_t i = 0; i < renderFlags.size(); ++i)
+ {
+ QAction* action = m_actions[i];
+ if (action)
+ {
+ action->setChecked(renderFlags[i]);
+ }
+ }
}
} // namespace EMStudio
diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.h
index 23ef5fdcd8..57633e5284 100644
--- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.h
+++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportToolBar.h
@@ -11,8 +11,11 @@
#if !defined(Q_MOC_RUN)
#include
#include
+#include
#endif
+#include
+
namespace EMStudio
{
class AnimViewportToolBar : public QToolBar
@@ -20,5 +23,13 @@ namespace EMStudio
public:
AnimViewportToolBar(QWidget* parent = nullptr);
~AnimViewportToolBar() = default;
+
+ void SetRenderFlags(EMotionFX::ActorRenderFlagBitset renderFlags);
+
+ private:
+ void CreateViewOptionEntry(
+ QMenu* menu, const char* menuEntryName, uint32_t actionIndex, bool visible = true, char* iconFileName = nullptr);
+
+ QAction* m_actions[EMotionFX::ActorRenderFlag::NUM_RENDERFLAGS] = { nullptr };
};
}
diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.cpp
index 2e05864adc..7af5c1607a 100644
--- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.cpp
+++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.cpp
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#include
#include
@@ -32,6 +33,7 @@ namespace EMStudio
m_renderer = AZStd::make_unique(GetViewportContext());
+ LoadRenderFlags();
SetupCameras();
SetupCameraController();
Reinit();
@@ -41,6 +43,7 @@ namespace EMStudio
AnimViewportWidget::~AnimViewportWidget()
{
+ SaveRenderFlags();
AnimViewportRequestBus::Handler::BusDisconnect();
}
@@ -50,7 +53,14 @@ namespace EMStudio
{
ResetCamera();
}
+
m_renderer->Reinit();
+ m_renderer->UpdateActorRenderFlag(m_renderFlags);
+ }
+
+ EMotionFX::ActorRenderFlagBitset AnimViewportWidget::GetRenderFlags() const
+ {
+ return m_renderFlags;
}
void AnimViewportWidget::SetupCameras()
@@ -123,7 +133,7 @@ namespace EMStudio
SetCameraViewMode(CameraViewMode::DEFAULT);
}
- void AnimViewportWidget::SetCameraViewMode([[maybe_unused]]CameraViewMode mode)
+ void AnimViewportWidget::SetCameraViewMode(CameraViewMode mode)
{
// Set the camera view mode.
const AZ::Vector3 targetPosition = m_renderer->GetCharacterCenter();
@@ -155,4 +165,38 @@ namespace EMStudio
}
GetViewportContext()->SetCameraTransform(AZ::Transform::CreateLookAt(cameraPosition, targetPosition));
}
+
+ void AnimViewportWidget::ToggleRenderFlag(EMotionFX::ActorRenderFlag flag)
+ {
+ m_renderFlags[flag] = !m_renderFlags[flag];
+ m_renderer->UpdateActorRenderFlag(m_renderFlags);
+ }
+
+ void AnimViewportWidget::LoadRenderFlags()
+ {
+ AZStd::string renderFlagsFilename(EMStudioManager::GetInstance()->GetAppDataFolder());
+ renderFlagsFilename += "AnimViewportRenderFlags.cfg";
+ QSettings settings(renderFlagsFilename.c_str(), QSettings::IniFormat, this);
+
+ for (uint32 i = 0; i < EMotionFX::ActorRenderFlag::NUM_RENDERFLAGS; ++i)
+ {
+ QString name = QString(i);
+ const bool isEnabled = settings.value(name).toBool();
+ m_renderFlags[i] = isEnabled;
+ }
+ m_renderer->UpdateActorRenderFlag(m_renderFlags);
+ }
+
+ void AnimViewportWidget::SaveRenderFlags()
+ {
+ AZStd::string renderFlagsFilename(EMStudioManager::GetInstance()->GetAppDataFolder());
+ renderFlagsFilename += "AnimViewportRenderFlags.cfg";
+ QSettings settings(renderFlagsFilename.c_str(), QSettings::IniFormat, this);
+
+ for (uint32 i = 0; i < EMotionFX::ActorRenderFlag::NUM_RENDERFLAGS; ++i)
+ {
+ QString name = QString(i);
+ settings.setValue(name, (bool)m_renderFlags[i]);
+ }
+ }
} // namespace EMStudio
diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.h
index 6d708b0996..8aa316a8ba 100644
--- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.h
+++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.h
@@ -7,9 +7,11 @@
*/
#pragma once
+#include
#include
#include
#include
+#include
namespace EMStudio
{
@@ -25,14 +27,19 @@ namespace EMStudio
AnimViewportRenderer* GetAnimViewportRenderer() { return m_renderer.get(); }
void Reinit(bool resetCamera = true);
+ EMotionFX::ActorRenderFlagBitset GetRenderFlags() const;
private:
void SetupCameras();
void SetupCameraController();
+ void LoadRenderFlags();
+ void SaveRenderFlags();
+
// AnimViewportRequestBus::Handler overrides
void ResetCamera();
void SetCameraViewMode(CameraViewMode mode);
+ void ToggleRenderFlag(EMotionFX::ActorRenderFlag flag);
static constexpr float CameraDistance = 2.0f;
@@ -40,5 +47,6 @@ namespace EMStudio
AZStd::shared_ptr m_rotateCamera;
AZStd::shared_ptr m_translateCamera;
AZStd::shared_ptr m_orbitDollyScrollCamera;
+ EMotionFX::ActorRenderFlagBitset m_renderFlags;
};
}
diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp
index bc74592485..ce2e76a6c7 100644
--- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp
+++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp
@@ -90,12 +90,14 @@ namespace EMStudio
verticalLayout->setSpacing(1);
verticalLayout->setMargin(0);
+ // Add the viewport widget
+ m_animViewportWidget = new AnimViewportWidget(m_innerWidget);
+
// Add the tool bar
AnimViewportToolBar* toolBar = new AnimViewportToolBar(m_innerWidget);
- verticalLayout->addWidget(toolBar);
+ toolBar->SetRenderFlags(m_animViewportWidget->GetRenderFlags());
- // Add the viewport widget
- m_animViewportWidget = new AnimViewportWidget(m_innerWidget);
+ verticalLayout->addWidget(toolBar);
verticalLayout->addWidget(m_animViewportWidget);
// Register command callbacks.
diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/emotionfx_atom_files.cmake b/Gems/AtomLyIntegration/EMotionFXAtom/Code/emotionfx_atom_files.cmake
index 4ada953caf..413984b96d 100644
--- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/emotionfx_atom_files.cmake
+++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/emotionfx_atom_files.cmake
@@ -17,4 +17,6 @@ set(FILES
Source/AtomActor.cpp
Source/AtomActorInstance.h
Source/AtomActorInstance.cpp
+ Source/AtomActorDebugDraw.h
+ Source/AtomActorDebugDraw.cpp
)
diff --git a/Gems/EMotionFX/Code/Include/Integration/ActorComponentBus.h b/Gems/EMotionFX/Code/Include/Integration/ActorComponentBus.h
index 4161b3c77d..1fd3e55f12 100644
--- a/Gems/EMotionFX/Code/Include/Integration/ActorComponentBus.h
+++ b/Gems/EMotionFX/Code/Include/Integration/ActorComponentBus.h
@@ -85,9 +85,6 @@ namespace EMotionFX
/// Detach from parent entity, if attached.
virtual void DetachFromEntity() {}
- /// Enables debug-drawing of the actor's root.
- virtual void DebugDrawRoot(bool /*enable*/) {}
-
/// Enables rendering of the actor.
virtual bool GetRenderCharacter() const = 0;
virtual void SetRenderCharacter(bool enable) = 0;
diff --git a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp
index 206cae3f59..8520896dd2 100644
--- a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp
+++ b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp
@@ -209,7 +209,6 @@ namespace EMotionFX
->Event("GetJointTransform", &ActorComponentRequestBus::Events::GetJointTransform)
->Event("AttachToEntity", &ActorComponentRequestBus::Events::AttachToEntity)
->Event("DetachFromEntity", &ActorComponentRequestBus::Events::DetachFromEntity)
- ->Event("DebugDrawRoot", &ActorComponentRequestBus::Events::DebugDrawRoot)
->Event("GetRenderCharacter", &ActorComponentRequestBus::Events::GetRenderCharacter)
->Event("SetRenderCharacter", &ActorComponentRequestBus::Events::SetRenderCharacter)
->Event("GetRenderActorVisible", &ActorComponentRequestBus::Events::GetRenderActorVisible)
@@ -238,8 +237,7 @@ namespace EMotionFX
//////////////////////////////////////////////////////////////////////////
ActorComponent::ActorComponent(const Configuration* configuration)
- : m_debugDrawRoot(false)
- , m_sceneFinishSimHandler([this]([[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
+ : m_sceneFinishSimHandler([this]([[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
float fixedDeltatime)
{
if (m_actorInstance)
@@ -252,6 +250,8 @@ namespace EMotionFX
{
m_configuration = *configuration;
}
+
+ m_debugRenderFlags[RENDER_SOLID] = true;
}
//////////////////////////////////////////////////////////////////////////
@@ -341,12 +341,6 @@ namespace EMotionFX
}
}
- //////////////////////////////////////////////////////////////////////////
- void ActorComponent::DebugDrawRoot(bool enable)
- {
- m_debugDrawRoot = enable;
- }
-
//////////////////////////////////////////////////////////////////////////
bool ActorComponent::GetRenderCharacter() const
{
@@ -400,6 +394,11 @@ namespace EMotionFX
return m_sceneFinishSimHandler.IsConnected();
}
+ void ActorComponent::SetRenderFlag(ActorRenderFlagBitset renderFlags)
+ {
+ m_debugRenderFlags = renderFlags;
+ }
+
void ActorComponent::CheckActorCreation()
{
// Create actor instance.
@@ -573,13 +572,13 @@ namespace EMotionFX
m_actorInstance->SetIsVisible(isInCameraFrustum && m_configuration.m_renderCharacter);
}
- RenderActorInstance::DebugOptions debugOptions;
- debugOptions.m_drawAABB = m_configuration.m_renderBounds;
- debugOptions.m_drawSkeleton = m_configuration.m_renderSkeleton;
- debugOptions.m_drawRootTransform = m_debugDrawRoot;
- debugOptions.m_rootWorldTransform = GetEntity()->GetTransform()->GetWorldTM();
- debugOptions.m_emfxDebugDraw = true;
- m_renderActorInstance->DebugDraw(debugOptions);
+ m_renderActorInstance->SetIsVisible(m_debugRenderFlags[RENDER_SOLID]);
+
+ // The configuration stores some debug option. When that is enabled, we override it on top of the render flags.
+ m_debugRenderFlags[RENDER_AABB] = m_debugRenderFlags[RENDER_AABB] || m_configuration.m_renderBounds;
+ m_debugRenderFlags[RENDER_SKELETON] = m_debugRenderFlags[RENDER_SKELETON] || m_configuration.m_renderSkeleton;
+ m_debugRenderFlags[RENDER_EMFX_DEBUG] = true;
+ m_renderActorInstance->DebugDraw(m_debugRenderFlags);
}
}
diff --git a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h
index 15d9736f34..9eecea2f54 100644
--- a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h
+++ b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h
@@ -116,7 +116,6 @@ namespace EMotionFX
ActorInstance* GetActorInstance() override { return m_actorInstance.get(); }
void AttachToEntity(AZ::EntityId targetEntityId, AttachmentType attachmentType) override;
void DetachFromEntity() override;
- void DebugDrawRoot(bool enable) override;
bool GetRenderCharacter() const override;
void SetRenderCharacter(bool enable) override;
bool GetRenderActorVisible() const override;
@@ -181,6 +180,8 @@ namespace EMotionFX
bool IsPhysicsSceneSimulationFinishEventConnected() const;
AZ::Data::Asset GetActorAsset() const { return m_configuration.m_actorAsset; }
+ void SetRenderFlag(ActorRenderFlagBitset renderFlags);
+
private:
// AZ::TransformNotificationBus::MultiHandler
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
@@ -201,7 +202,7 @@ namespace EMotionFX
AZStd::vector m_attachments;
AZStd::unique_ptr m_renderActorInstance;
- bool m_debugDrawRoot; ///< Enables drawing of actor root and facing.
+ ActorRenderFlagBitset m_debugRenderFlags; ///< Actor debug render flag
AzPhysics::SceneEvents::OnSceneSimulationFinishHandler m_sceneFinishSimHandler;
};
diff --git a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp
index 5da9716d15..d0274b3c68 100644
--- a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp
+++ b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp
@@ -180,6 +180,7 @@ namespace EMotionFX
, m_lodLevel(0)
, m_actorAsset(AZ::Data::AssetLoadBehavior::NoLoad)
{
+ m_debugRenderFlags[RENDER_SOLID] = true;
}
//////////////////////////////////////////////////////////////////////////
@@ -604,11 +605,10 @@ namespace EMotionFX
m_renderActorInstance->OnTick(deltaTime);
m_renderActorInstance->UpdateBounds();
- RenderActorInstance::DebugOptions debugOptions;
- debugOptions.m_drawAABB = m_renderBounds;
- debugOptions.m_drawSkeleton = m_renderSkeleton;
- debugOptions.m_emfxDebugDraw = true;
- m_renderActorInstance->DebugDraw(debugOptions);
+ m_debugRenderFlags[RENDER_AABB] = m_renderBounds;
+ m_debugRenderFlags[RENDER_SKELETON] = m_renderSkeleton;
+ m_debugRenderFlags[RENDER_EMFX_DEBUG] = true;
+ m_renderActorInstance->DebugDraw(m_debugRenderFlags);
}
}
@@ -951,5 +951,10 @@ namespace EMotionFX
LmbrCentral::AttachmentComponentRequestBus::Event(attachment, &LmbrCentral::AttachmentComponentRequestBus::Events::Reattach, true);
}
}
+
+ void EditorActorComponent::SetRenderFlag(ActorRenderFlagBitset renderFlags)
+ {
+ m_debugRenderFlags = renderFlags;
+ }
} //namespace Integration
} // namespace EMotionFX
diff --git a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h
index 1d682b47d4..f4c663a92f 100644
--- a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h
+++ b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h
@@ -104,6 +104,8 @@ namespace EMotionFX
ActorComponent::GetRequiredServices(required);
}
+ void SetRenderFlag(ActorRenderFlagBitset renderFlags);
+
static void Reflect(AZ::ReflectContext* context);
private:
@@ -162,6 +164,7 @@ namespace EMotionFX
size_t m_lodLevel;
ActorComponent::BoundingBoxConfiguration m_bboxConfig;
bool m_forceUpdateJointsOOV = false;
+ ActorRenderFlagBitset m_debugRenderFlags; ///< Actor debug render flag
// \todo attachmentTarget node nr
// Note: LOD work in progress. For now we use one material instead of a list of material, because we don't have the support for LOD with multiple scene files.
diff --git a/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderActorInstance.h b/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderActorInstance.h
index 47da0d4cca..8afb2a2f9a 100644
--- a/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderActorInstance.h
+++ b/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderActorInstance.h
@@ -16,6 +16,7 @@
#include
#include
+#include
namespace EMotionFX
{
@@ -33,16 +34,7 @@ namespace EMotionFX
virtual ~RenderActorInstance() = default;
virtual void OnTick(float timeDelta) = 0;
-
- struct DebugOptions
- {
- bool m_drawAABB = false;
- bool m_drawSkeleton = false;
- bool m_drawRootTransform = false;
- AZ::Transform m_rootWorldTransform = AZ::Transform::CreateIdentity();
- bool m_emfxDebugDraw = false;
- };
- virtual void DebugDraw(const DebugOptions& debugOptions) = 0;
+ virtual void DebugDraw(const EMotionFX::ActorRenderFlagBitset& renderFlags) = 0;
SkinningMethod GetSkinningMethod() const;
virtual void SetSkinningMethod(SkinningMethod skinningMethod);
diff --git a/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderFlag.h b/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderFlag.h
new file mode 100644
index 0000000000..e053eae6d5
--- /dev/null
+++ b/Gems/EMotionFX/Code/Source/Integration/Rendering/RenderFlag.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+#pragma once
+
+#include
+
+namespace EMotionFX
+{
+ enum ActorRenderFlag
+ {
+ RENDER_SOLID = 0,
+ RENDER_WIREFRAME = 1,
+ RENDER_LIGHTING = 2,
+ RENDER_SHADOWS = 3,
+ RENDER_FACENORMALS = 4,
+ RENDER_VERTEXNORMALS = 5,
+ RENDER_TANGENTS = 6,
+ RENDER_AABB = 7,
+ RENDER_SKELETON = 8,
+ RENDER_LINESKELETON = 9,
+ RENDER_NODEORIENTATION = 10,
+ RENDER_NODENAMES = 11,
+ RENDER_GRID = 12,
+ RENDER_BACKFACECULLING = 13,
+ RENDER_ACTORBINDPOSE = 14,
+ RENDER_RAGDOLL_COLLIDERS = 15,
+ RENDER_RAGDOLL_JOINTLIMITS = 16,
+ RENDER_HITDETECTION_COLLIDERS = 17,
+ RENDER_USE_GRADIENTBACKGROUND = 18,
+ RENDER_MOTIONEXTRACTION = 19,
+ RENDER_CLOTH_COLLIDERS = 20,
+ RENDER_SIMULATEDOBJECT_COLLIDERS = 21,
+ RENDER_SIMULATEJOINTS = 22,
+ RENDER_EMFX_DEBUG = 23,
+ NUM_RENDERFLAGS = 24
+ };
+
+ using ActorRenderFlagBitset = AZStd::bitset;
+}
diff --git a/Gems/EMotionFX/Code/Tests/RenderBackendManagerTests.cpp b/Gems/EMotionFX/Code/Tests/RenderBackendManagerTests.cpp
index 89d6fab93b..be990f444c 100644
--- a/Gems/EMotionFX/Code/Tests/RenderBackendManagerTests.cpp
+++ b/Gems/EMotionFX/Code/Tests/RenderBackendManagerTests.cpp
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -64,7 +65,7 @@ namespace EMotionFX
}
MOCK_METHOD1(OnTick, void(float));
- MOCK_METHOD1(DebugDraw, void(const DebugOptions&));
+ MOCK_METHOD1(DebugDraw, void(const EMotionFX::ActorRenderFlagBitset&));
MOCK_CONST_METHOD0(IsVisible, bool());
MOCK_METHOD1(SetIsVisible, void(bool));
MOCK_METHOD1(SetMaterials, void(const ActorAsset::MaterialList&));