From 77d02ea657e4c381640f26931cdd3b83d38ac8fd Mon Sep 17 00:00:00 2001 From: Roman <69218254+amzn-rhhong@users.noreply.github.com> Date: Tue, 26 Oct 2021 01:59:02 -0700 Subject: [PATCH] Add some debug rendering options. (#4950) * [WIP] Adding rendering options Signed-off-by: rhhong * code review feedback. Also add the renderflag as qsettings. Signed-off-by: rhhong * fix broken test Signed-off-by: rhhong * fix linux build Signed-off-by: rhhong --- .../EMotionFXAtom/Assets/Icons/Resources.qrc | 1 + .../Assets/Icons/Visualization.svg | 9 + .../Code/Source/AtomActorDebugDraw.cpp | 415 ++++++++++++++++++ .../Code/Source/AtomActorDebugDraw.h | 56 +++ .../Code/Source/AtomActorInstance.cpp | 118 +---- .../Code/Source/AtomActorInstance.h | 11 +- .../Tools/EMStudio/AnimViewportRenderer.cpp | 14 + .../Tools/EMStudio/AnimViewportRenderer.h | 6 +- .../Tools/EMStudio/AnimViewportRequestBus.h | 5 +- .../Tools/EMStudio/AnimViewportToolBar.cpp | 112 ++++- .../Code/Tools/EMStudio/AnimViewportToolBar.h | 11 + .../Tools/EMStudio/AnimViewportWidget.cpp | 46 +- .../Code/Tools/EMStudio/AnimViewportWidget.h | 8 + .../Code/Tools/EMStudio/AtomRenderPlugin.cpp | 8 +- .../Code/emotionfx_atom_files.cmake | 2 + .../Include/Integration/ActorComponentBus.h | 3 - .../Integration/Components/ActorComponent.cpp | 31 +- .../Integration/Components/ActorComponent.h | 5 +- .../Components/EditorActorComponent.cpp | 15 +- .../Editor/Components/EditorActorComponent.h | 3 + .../Rendering/RenderActorInstance.h | 12 +- .../Source/Integration/Rendering/RenderFlag.h | 44 ++ .../Code/Tests/RenderBackendManagerTests.cpp | 3 +- 23 files changed, 751 insertions(+), 187 deletions(-) create mode 100644 Gems/AtomLyIntegration/EMotionFXAtom/Assets/Icons/Visualization.svg create mode 100644 Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.cpp create mode 100644 Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorDebugDraw.h create mode 100644 Gems/EMotionFX/Code/Source/Integration/Rendering/RenderFlag.h 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 @@ + + + + Icons / System / View + Created with Sketch. + + + + 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&));