Skip to content

Commit

Permalink
Merge pull request #55 from RobotecAI/feature/intensity-actor-support
Browse files Browse the repository at this point in the history
Add actor intensity support
  • Loading branch information
alek-kam-robotec-ai authored Oct 10, 2024
2 parents b3bb1c5 + 98072f1 commit 75ab63a
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 128 deletions.
247 changes: 165 additions & 82 deletions Code/Source/Entity/ActorEntityManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <Entity/ActorEntityManager.h>

#include <AzCore/std/string/string.h>
#include <EMotionFX/Source/Actor.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <EMotionFX/Source/Mesh.h>
#include <EMotionFX/Source/Node.h>
Expand All @@ -30,7 +31,7 @@
namespace RGL
{
ActorEntityManager::ActorEntityManager(AZ::EntityId entityId)
: EntityManager(entityId)
: MaterialEntityManager(entityId)
{
AZ::EntityBus::Handler::BusConnect(m_entityId);
EMotionFX::Integration::ActorComponentNotificationBus::Handler::BusConnect(entityId);
Expand All @@ -54,124 +55,206 @@ namespace RGL
{
m_actorInstance = actorInstance;
EMotionFX::Actor* actor = actorInstance->GetActor();
[[maybe_unused]] const AZ::Entity* ActorEntity = actorInstance->GetEntity();
[[maybe_unused]] const AZ::Entity* actorEntity = actorInstance->GetEntity();

static constexpr size_t LodLevel = 0UL; // Highest LOD.
const size_t NodeCount = actor->GetNumNodes();
for (size_t jointIndex = 0LU; jointIndex < NodeCount; ++jointIndex)
static constexpr size_t LodLevel = 0U; // Highest LOD.
static constexpr size_t NodeIdx = 0U; // Default mesh node.
EMotionFX::Mesh* mesh = actor->GetMesh(LodLevel, NodeIdx);
if (!mesh)
{
EMotionFX::Mesh* mesh = actor->GetMesh(LodLevel, jointIndex);
if (!mesh)
{
continue;
}
AZ_Assert(false, "No mesh found at joint 0. Unable to process the actor instance.");
return;
}

Wrappers::RglMesh rglMesh = AZStd::move(EMotionFXMeshToRglMesh(*mesh));
if (rglMesh.IsValid())
{
m_emotionFxMeshes.emplace_back(mesh);
m_rglMeshes.emplace_back(AZStd::move(rglMesh));
}
else
{
AZ_Error(
__func__,
false,
"[Entity: %s:%s] Unable to create rgl mesh for actor instance.",
ActorEntity->GetName().c_str(),
ActorEntity->GetId().ToString().c_str());
}
if (ProcessEfxMesh(*mesh))
{
m_emotionFxMesh = mesh;
UpdateMaterialSlots(*actor);
m_isPoseUpdateNeeded = true;
}
}

m_entities.reserve(m_emotionFxMeshes.size());
for (const Wrappers::RglMesh& rglMesh : m_rglMeshes)
void ActorEntityManager::OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance)
{
AZ::Render::MaterialComponentNotificationBus::Handler::BusDisconnect();
ResetMaterialsMapping();
m_entities.clear();
m_rglSubMeshes.clear();
m_emotionFxMesh = nullptr;
}

void ActorEntityManager::OnEntityDeactivated(const AZ::EntityId& entityId)
{
AZ::Render::MaterialComponentNotificationBus::Handler::BusDisconnect();
EMotionFX::Integration::ActorComponentNotificationBus::Handler::BusDisconnect();
MaterialEntityManager::OnEntityDeactivated(entityId);
}

AZStd::vector<rgl_vec3i> ActorEntityManager::CollectIndexData(const EMotionFX::Mesh& mesh)
{
const size_t indexCount = mesh.GetNumIndices();
const size_t triangleCount = indexCount / 3LU;

uint32* indices = mesh.GetIndices();
AZStd::vector<rgl_vec3i> rglIndices;
rglIndices.reserve(triangleCount);

const size_t subMeshCount = mesh.GetNumSubMeshes();
for (size_t subMeshNr = 0; subMeshNr < subMeshCount; ++subMeshNr)
{
Wrappers::RglEntity entity(rglMesh);
if (entity.IsValid())
{
m_entities.emplace_back(AZStd::move(entity));
}
else
EMotionFX::SubMesh* subMesh = mesh.GetSubMesh(subMeshNr);
const size_t subMeshIndexCount = subMesh->GetNumIndices();
const size_t subMeshTriangleCount = subMeshIndexCount / 3LU;

const size_t indexBase = subMesh->GetStartIndex();
for (size_t triangle = 0LU; triangle < subMeshTriangleCount; ++triangle)
{
AZ_Error(
__func__,
false,
"[Entity: %s:%s] Unable to create rgl entity for actor instance.",
ActorEntity->GetName().c_str(),
ActorEntity->GetId().ToString().c_str());
const size_t triangleFirstIndex = triangle * 3LU + indexBase;

// Note: We cast the uint32 to int32_t but this conversion is unlikely to result in negative values.
rglIndices.push_back({
aznumeric_cast<int32_t>(indices[triangleFirstIndex]),
aznumeric_cast<int32_t>(indices[triangleFirstIndex + 1LU]),
aznumeric_cast<int32_t>(indices[triangleFirstIndex + 2LU]),
});
}
}

m_isPoseUpdateNeeded = true;
return rglIndices;
}

void ActorEntityManager::OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance)
AZStd::vector<rgl_vec3f> ActorEntityManager::GetMeshVertexPositions(const EMotionFX::Mesh& mesh)
{
m_entities.clear();
const size_t vertexCount = mesh.GetNumVertices();
const auto* vertices = static_cast<const AZ::Vector3*>(mesh.FindVertexData(EMotionFX::Mesh::ATTRIB_POSITIONS));
const auto vertexSpan = AZStd::span(vertices, vertexCount);

AZStd::vector<rgl_vec3f> rglVertices(vertexCount);
AZStd::transform(vertexSpan.begin(), vertexSpan.end(), rglVertices.begin(), Utils::RglVector3FromAzVec3f);
return rglVertices;
}

void ActorEntityManager::UpdateMeshVertices()
AZStd::optional<AZStd::vector<rgl_vec2f>> ActorEntityManager::CollectUvData(const EMotionFX::Mesh& mesh) const
{
m_actorInstance->UpdateMeshDeformers(0.0f);
const size_t vertexCount = mesh.GetNumVertices();
AZStd::vector<rgl_vec2f> rglUvs(vertexCount);

for (size_t i = 0; i < m_emotionFxMeshes.size(); ++i)
const auto* uvs = static_cast<const AZ::Vector2*>(mesh.FindVertexData(EMotionFX::Mesh::ATTRIB_UVCOORDS));
if (uvs == nullptr)
{
UpdateVertexPositions(*m_emotionFxMeshes[i]);
m_rglMeshes[i].UpdateVertices(m_positions.data(), m_positions.size());
AZ_Warning(
__func__, false, "Unable to collect UV data from an actor component of entity with ID: %s.", m_entityId.ToString().c_str());

return AZStd::nullopt;
}

auto uvSpan = AZStd::span(uvs, vertexCount);
AZStd::transform(uvSpan.begin(), uvSpan.end(), rglUvs.begin(), Utils::RglVec2fFromAzVector2);
return rglUvs;
}

void ActorEntityManager::UpdateVertexPositions(const EMotionFX::Mesh& mesh)
void ActorEntityManager::UpdateMaterialSlots(const EMotionFX::Actor& actor)
{
const size_t VertexCount = mesh.GetNumVertices();
auto* vertices = static_cast<const AZ::Vector3*>(mesh.FindVertexData(EMotionFX::Mesh::ATTRIB_POSITIONS));
const AZ::Data::Asset<AZ::RPI::ModelAsset>& modelAsset = actor.GetMeshAsset();
const auto lodAssets = modelAsset->GetLodAssets();
// Get Highest LOD
const auto modelLodAsset = lodAssets.begin()->Get();
const auto meshes = modelLodAsset->GetMeshes();

m_positions.clear();
m_positions.reserve(VertexCount);
for (size_t vertex = 0; vertex < VertexCount; ++vertex)
// Each mesh of the model is associated with one EFX sub mesh.
for (size_t subMeshIdx = 0; subMeshIdx < meshes.size(); ++subMeshIdx)
{
m_positions.push_back(Utils::RglVector3FromAzVec3f(vertices[vertex]));
const AZ::RPI::ModelMaterialSlot& slot = modelAsset->FindMaterialSlot(meshes[subMeshIdx].GetMaterialSlotId());
AssignMaterialSlotIdForMesh(slot.m_stableId, subMeshIdx);
}

// We can use material info only when the model is ready.
AZ::Render::MaterialComponentNotificationBus::Handler::BusConnect(m_entityId);
}

AZStd::vector<rgl_vec3i> ActorEntityManager::CollectIndexData(const EMotionFX::Mesh& mesh)
void ActorEntityManager::UpdateMeshVertices()
{
const size_t IndexCount = mesh.GetNumIndices();
const size_t TriangleCount = IndexCount / 3LU;
if (!m_emotionFxMesh)
{
return;
}

uint32* indices = mesh.GetIndices();
AZStd::vector<rgl_vec3i> rglIndices;
rglIndices.reserve(TriangleCount);
m_actorInstance->UpdateMeshDeformers(0.0f);

const size_t SubMeshCount = mesh.GetNumSubMeshes();
for (size_t subMeshNr = 0; subMeshNr < SubMeshCount; ++subMeshNr)
const auto vertexPositions = GetMeshVertexPositions(*m_emotionFxMesh);
const size_t subMeshCount = m_emotionFxMesh->GetNumSubMeshes();
for (size_t subMeshNr = 0; subMeshNr < subMeshCount; ++subMeshNr)
{
EMotionFX::SubMesh* subMesh = mesh.GetSubMesh(subMeshNr);
const size_t SubMeshIndexCount = subMesh->GetNumIndices();
const size_t SubMeshTriangleCount = SubMeshIndexCount / 3LU;
const EMotionFX::SubMesh* subMesh = m_emotionFxMesh->GetSubMesh(subMeshNr);
const size_t vertexBase = subMesh->GetStartVertex();
const size_t subMeshVertexCount = subMesh->GetNumVertices();

m_rglSubMeshes[subMeshNr].UpdateVertices(vertexPositions.data() + vertexBase, subMeshVertexCount);
}
}

bool ActorEntityManager::ProcessEfxMesh(const EMotionFX::Mesh& mesh)
{
const auto vertexPositions = GetMeshVertexPositions(mesh);

const size_t subMeshCount = mesh.GetNumSubMeshes();
const auto indices = CollectIndexData(mesh);
const auto uvData = CollectUvData(mesh);
for (size_t subMeshNr = 0; subMeshNr < subMeshCount; ++subMeshNr)
{
const EMotionFX::SubMesh* subMesh = mesh.GetSubMesh(subMeshNr);
const size_t vertexBase = subMesh->GetStartVertex();
const size_t vertexCount = subMesh->GetNumVertices();

const size_t indexBase = subMesh->GetStartIndex() / 3U; // RGL uses index triples.
const size_t indexCount = subMesh->GetNumIndices() / 3U;

auto rglMesh =
Wrappers::RglMesh(vertexPositions.data() + vertexBase, vertexCount, indices.data() + indexBase, indexCount);

const size_t IndexBase = subMesh->GetStartIndex();
const size_t VertexBase = subMesh->GetStartVertex();
for (size_t triangle = 0LU; triangle < SubMeshTriangleCount; ++triangle)
if (rglMesh.IsValid())
{
const size_t TriangleFirstIndex = triangle * 3LU + IndexBase;
if (uvData.has_value())
{
rglMesh.SetTextureCoordinates(uvData->data() + vertexBase, vertexCount);
}

// Note: We cast the uint32 to int32_t but this conversion is unlikely to result in negative values.
rglIndices.push_back({
aznumeric_cast<int32_t>(VertexBase + indices[TriangleFirstIndex]),
aznumeric_cast<int32_t>(VertexBase + indices[TriangleFirstIndex + 1LU]),
aznumeric_cast<int32_t>(VertexBase + indices[TriangleFirstIndex + 2LU]),
});
m_rglSubMeshes.push_back(AZStd::move(rglMesh));
}
else
{
m_rglSubMeshes.clear();
AZ_Error(
"RGL",
false,
"[Entity: %s] Rgl mesh creation failed for one of the sub meshes of an entity with an actor component. Some geometry "
"will not be present.",
m_entityId.ToString().c_str());
return false;
}
}

return rglIndices;
}
m_entities.reserve(m_rglSubMeshes.size());
for (size_t i = 0; i < m_rglSubMeshes.size(); ++i)
{
if (Wrappers::RglEntity subMeshEntity(m_rglSubMeshes[i]); subMeshEntity.IsValid())
{
m_entities.emplace_back(AZStd::move(subMeshEntity));
}
else
{
m_rglSubMeshes.clear();
m_entities.clear();
AZ_Error(
"RGL",
false,
"[Entity: %s] Rgl entity creation failed for one of the sub meshes of an entity with an actor component. Some geometry "
"will not be present.",
m_entityId.ToString().c_str());
return false;
}
}

Wrappers::RglMesh ActorEntityManager::EMotionFXMeshToRglMesh(const EMotionFX::Mesh& mesh)
{
UpdateVertexPositions(mesh);
const AZStd::vector<rgl_vec3i> RglIndices = CollectIndexData(mesh);
return { m_positions.data(), m_positions.size(), RglIndices.data(), RglIndices.size() };
return true;
}
} // namespace RGL
33 changes: 20 additions & 13 deletions Code/Source/Entity/ActorEntityManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,23 @@
*/
#pragma once

#include <Entity/EntityManager.h>
#include <Integration/ActorComponentBus.h>
#include <rgl/api/core.h>
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h>
#include <AzCore/std/containers/vector.h>
#include <Entity/MaterialEntityManager.h>
#include <Integration/ActorComponentBus.h>
#include <Wrappers/RglMesh.h>
#include <rgl/api/core.h>

namespace EMotionFX
{
class Mesh;
class ActorInstance;
}
} // namespace EMotionFX

namespace RGL
{
class ActorEntityManager
: public EntityManager
: public MaterialEntityManager
, public EMotionFX::Integration::ActorComponentNotificationBus::Handler
{
public:
Expand All @@ -47,17 +48,23 @@ namespace RGL
void OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance) override;
void OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance) override;

// AZ::EntityBus::Handler implementation overrides
void OnEntityDeactivated(const AZ::EntityId& entityId) override;

private:
static AZStd::vector<rgl_vec3i> CollectIndexData(const EMotionFX::Mesh& mesh);
static AZStd::vector<rgl_vec3f> GetMeshVertexPositions(const EMotionFX::Mesh& mesh);
AZStd::optional<AZStd::vector<rgl_vec2f>> CollectUvData(const EMotionFX::Mesh& mesh) const;

void UpdateMaterialSlots(const EMotionFX::Actor& actor);
void UpdateMeshVertices();
//! Loads mesh's vertex position data into the m_tempVertexPositions buffer.
bool ProcessEfxMesh(const EMotionFX::Mesh& mesh);

EMotionFX::ActorInstance* m_actorInstance = nullptr;
// We do not use the ModelLibrary since the actor mesh is
// skinned and the mesh sharing would not be useful.
AZStd::vector<EMotionFX::Mesh*> m_emotionFxMeshes;
AZStd::vector<Wrappers::RglMesh> m_rglMeshes;
AZStd::vector<rgl_vec3f> m_positions;

void UpdateMeshVertices();
void UpdateVertexPositions(const EMotionFX::Mesh& mesh);
AZStd::vector<rgl_vec3i> CollectIndexData(const EMotionFX::Mesh& mesh);
Wrappers::RglMesh EMotionFXMeshToRglMesh(const EMotionFX::Mesh& mesh);
EMotionFX::Mesh* m_emotionFxMesh;
AZStd::vector<Wrappers::RglMesh> m_rglSubMeshes;
};
} // namespace RGL
Loading

0 comments on commit 75ab63a

Please sign in to comment.