/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #ifdef _DEBUG # pragma push_macro("AZ_NUMERICCAST_ENABLED") # undef AZ_NUMERICCAST_ENABLED # define AZ_NUMERICCAST_ENABLED 1 #endif // #ifdef _DEBUG #include <EMotionFX_precompiled.h> #include <AzCore/Casting/numeric_cast.h> #include <AzCore/Component/EntityId.h> #include <AzCore/Component/TickBus.h> #include <AzCore/Component/TransformBus.h> #include <AzCore/Debug/Profiler.h> #include <AzCore/Jobs/LegacyJobExecutor.h> #include <AzCore/Math/Transform.h> #include <AzCore/base.h> #include <LmbrCentral/Rendering/MaterialAsset.h> #include <LmbrCentral/Rendering/MeshComponentBus.h> #include <Integration/Rendering/Cry/CryRenderActor.h> #include <Integration/Rendering/Cry/CryRenderActorInstance.h> #include <Integration/Rendering/Cry/CryRenderBackendCommon.h> #include <Integration/System/SystemCommon.h> #include <Integration/System/SystemComponent.h> #include <I3DEngine.h> #include <IRenderAuxGeom.h> #include <IRenderMesh.h> #include <MathConversion.h> #include <QTangent.h> #if defined(EMOTIONFXANIMATION_EDITOR) # include <Material/Material.h> #endif namespace EMotionFX { namespace Integration { AZ_CLASS_ALLOCATOR_IMPL(CryRenderActorInstance, EMotionFXAllocator, 0) AZ_CLASS_ALLOCATOR_IMPL(CryRenderActorInstance::MaterialOwner, EMotionFXAllocator, 0) CryRenderActorInstance::CryRenderActorInstance(AZ::EntityId entityId, const EMotionFXPtr<EMotionFX::ActorInstance>& actorInstance, const AZ::Data::Asset<ActorAsset>& asset, const AZ::Transform& worldTransform) : IRenderNode() , RenderActorInstance(asset, actorInstance.get(), entityId) , m_renderTransform(AZTransformToLYTransform(worldTransform)) , m_worldBoundingBox(AABB::RESET) , m_isRegisteredWithRenderer(false) , m_renderNodeLifetime(new int) , m_materialReadyEventSent(false) { LmbrCentral::RenderNodeRequestBus::Handler::BusConnect(entityId); m_materialOwner.reset(aznew MaterialOwner(this, entityId)); memset(m_arrSkinningRendererData, 0, sizeof(m_arrSkinningRendererData)); QueueBuildRenderMesh(); if (m_entityId.IsValid()) { AZ::TransformNotificationBus::Handler::BusConnect(m_entityId); LmbrCentral::RenderBoundsRequestBus::Handler::BusConnect(entityId); LmbrCentral::SkeletalHierarchyRequestBus::Handler::BusConnect(m_entityId); LmbrCentral::MeshComponentRequestBus::Handler::BusConnect(entityId); m_modificationHelper.Connect(m_entityId); AZ::Transform entityTransform = AZ::Transform::CreateIdentity(); EBUS_EVENT_ID_RESULT(entityTransform, m_entityId, AZ::TransformBus, GetWorldTM); UpdateWorldTransform(entityTransform); } } CryRenderActorInstance::~CryRenderActorInstance() { LmbrCentral::RenderNodeRequestBus::Handler::BusDisconnect(); if (gEnv) { int nFrameID = gEnv->pRenderer->EF_GetSkinningPoolID(); int nList = nFrameID % 3; if (m_arrSkinningRendererData[nList].nFrameID == nFrameID && m_arrSkinningRendererData[nList].pSkinningData) { AZ::LegacyJobExecutor* pAsyncDataJobExecutor = m_arrSkinningRendererData[nList].pSkinningData->pAsyncDataJobExecutor; if (pAsyncDataJobExecutor) { pAsyncDataJobExecutor->WaitForCompletion(); } } } DeregisterWithRenderer(); if (m_entityId.IsValid()) { m_modificationHelper.Disconnect(); LmbrCentral::MeshComponentRequestBus::Handler::BusDisconnect(); LmbrCentral::RenderBoundsRequestBus::Handler::BusDisconnect(m_entityId); LmbrCentral::SkeletalHierarchyRequestBus::Handler::BusDisconnect(m_entityId); AZ::TransformNotificationBus::Handler::BusDisconnect(m_entityId); } } void CryRenderActorInstance::SetMaterials(const ActorAsset::MaterialList& materialPerLOD) { if (gEnv && gEnv->p3DEngine) { // Initialize materials from input paths. // Once materials are converted to real AZ assets, this conversion can be completely removed. m_materialPerLOD.clear(); m_materialPerLOD.reserve(materialPerLOD.size()); for (auto& materialReference : materialPerLOD) { const AZStd::string& path = materialReference.GetAssetPath(); // Create render material. If it fails or isn't specified, use the material from base LOD. _smart_ptr<IMaterial> material = path.empty() ? nullptr : gEnv->p3DEngine->GetMaterialManager()->LoadMaterial(path.c_str()); if (!material && m_materialPerLOD.size() > 0) { material = m_materialPerLOD.front(); } m_materialPerLOD.emplace_back(material); } } } void CryRenderActorInstance::SetIsVisible(bool isVisible) { RenderActorInstance::SetIsVisible(isVisible); // Set the cry render node visibility accordingly via the MeshComponentRequestBus. SetVisibility(m_isVisible); } void CryRenderActorInstance::UpdateWorldBoundingBox() { const MCore::AABB& emfxAabb = m_actorInstance->GetAABB(); m_worldBoundingBox = AABB(AZVec3ToLYVec3(emfxAabb.GetMin()), AZVec3ToLYVec3(emfxAabb.GetMax())); if (m_isRegisteredWithRenderer) { gEnv->p3DEngine->RegisterEntity(this); } } void CryRenderActorInstance::RegisterWithRenderer() { if (!m_isRegisteredWithRenderer && gEnv && gEnv->p3DEngine) { SetRndFlags(ERF_CASTSHADOWMAPS | ERF_HAS_CASTSHADOWMAPS | ERF_COMPONENT_ENTITY, true); UpdateWorldBoundingBox(); gEnv->p3DEngine->RegisterEntity(this); m_isRegisteredWithRenderer = true; } } void CryRenderActorInstance::DeregisterWithRenderer() { if (m_isRegisteredWithRenderer && gEnv && gEnv->p3DEngine) { gEnv->p3DEngine->FreeRenderNodeState(this); m_isRegisteredWithRenderer = false; } } void CryRenderActorInstance::UpdateWorldTransform(const AZ::Transform& entityTransform) { m_renderTransform = AZTransformToLYTransform(entityTransform); UpdateWorldBoundingBox(); } void CryRenderActorInstance::OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) { AZ_UNUSED(local); UpdateWorldTransform(world); } AZ::u32 CryRenderActorInstance::GetJointCount() { return m_actorInstance->GetActor()->GetSkeleton()->GetNumNodes(); } const char* CryRenderActorInstance::GetJointNameByIndex(AZ::u32 jointIndex) { EMotionFX::Skeleton* skeleton = m_actorInstance->GetActor()->GetSkeleton(); const AZ::u32 numNodes = skeleton->GetNumNodes(); if (jointIndex < numNodes) { return skeleton->GetNode(jointIndex)->GetName(); } return nullptr; } AZ::s32 CryRenderActorInstance::GetJointIndexByName(const char* jointName) { if (jointName) { EMotionFX::Skeleton* skeleton = m_actorInstance->GetActor()->GetSkeleton(); const AZ::u32 numNodes = skeleton->GetNumNodes(); for (AZ::u32 nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { if (0 == azstricmp(jointName, skeleton->GetNode(nodeIndex)->GetName())) { return nodeIndex; } } } return -1; } AZ::Transform CryRenderActorInstance::GetJointTransformCharacterRelative(AZ::u32 jointIndex) { const EMotionFX::TransformData* transforms = m_actorInstance->GetTransformData(); if (transforms && jointIndex < transforms->GetNumTransforms()) { return MCore::EmfxTransformToAzTransform(transforms->GetCurrentPose()->GetModelSpaceTransform(jointIndex)); } return AZ::Transform::CreateIdentity(); } Matrix34 AZTransformToCryMatrix(const AZ::Transform& transform) { AZ::Vector3 col0; AZ::Vector3 col1; AZ::Vector3 col2; AZ::Vector3 col3; transform.GetColumns(&col0, &col1, &col2, &col3); return Matrix34(col0.GetX(), col1.GetX(), col2.GetX(), col3.GetX(), col0.GetY(), col1.GetY(), col2.GetY(), col3.GetY(), col0.GetZ(), col1.GetZ(), col2.GetZ(), col3.GetZ()); } DualQuat AZMatrixToCryDualQuat(const AZ::Transform& transform) { return DualQuat(AZTransformToCryMatrix(transform)); } void CryRenderActorInstance::Render(const struct SRendParams& inRenderParams, const struct SRenderingPassInfo& passInfo) { if (!SystemComponent::emfx_actorRenderEnabled) { return; } ActorAsset* data = m_actorAsset.Get(); if (!data) { // Asset is not loaded. AZ_WarningOnce("ActorRenderNode", false, "Actor asset is not loaded. Rendering aborted."); return; } CryRenderActor* renderActor = GetRenderActor(); if (!renderActor) { return; } if (!m_renderTransform.IsValid()) { AZ_Warning("ActorRenderNode", false, "Render node has no valid transform."); return; } if (renderActor->GetNumLODs() == 0 || m_renderMeshesPerLOD.empty()) { return; // not ready for rendering } AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::Animation, "CryRenderActorInstance::Render"); AZ::u32 useLodIndex = m_actorInstance->GetLODLevel(); SRendParams rParams(inRenderParams); rParams.fAlpha = 1.f; IMaterial* previousMaterial = rParams.pMaterial; const int previousObjectFlags = rParams.dwFObjFlags; rParams.dwFObjFlags |= FOB_DYNAMIC_OBJECT; rParams.pMatrix = &m_renderTransform; rParams.lodValue = useLodIndex; CRenderObject* pObj = gEnv->pRenderer->EF_GetObject_Temp(passInfo.ThreadID()); pObj->m_fSort = rParams.fCustomSortOffset; pObj->m_fAlpha = rParams.fAlpha; pObj->m_fDistance = rParams.fDistance; pObj->m_II.m_AmbColor = rParams.AmbientColor; SRenderObjData* pD = gEnv->pRenderer->EF_GetObjData(pObj, true, passInfo.ThreadID()); if (rParams.pShaderParams && rParams.pShaderParams->size() > 0) { pD->SetShaderParams(rParams.pShaderParams); } pD->m_uniqueObjectId = reinterpret_cast<uintptr_t>(this); rParams.pMatrix = &m_renderTransform; pObj->m_II.m_Matrix = *rParams.pMatrix; pObj->m_nClipVolumeStencilRef = rParams.nClipVolumeStencilRef; pObj->m_nTextureID = rParams.nTextureID; pObj->m_ObjFlags |= rParams.dwFObjFlags; rParams.dwFObjFlags &= ~FOB_NEAREST; pObj->m_nMaterialLayers = rParams.nMaterialLayersBlend; pD->m_nHUDSilhouetteParams = rParams.nHUDSilhouettesParams; pD->m_nCustomData = rParams.nCustomData; pD->m_nCustomFlags = rParams.nCustomFlags; pObj->m_DissolveRef = rParams.nDissolveRef; pObj->m_nSort = fastround_positive(rParams.fDistance * 2.0f); if (SSkinningData* skinningData = GetSkinningData()) { pD->m_pSkinningData = skinningData; pObj->m_ObjFlags |= FOB_SKINNED; pObj->m_ObjFlags |= FOB_DYNAMIC_OBJECT; pObj->m_ObjFlags |= FOB_MOTION_BLUR; // Shader code is associating this with skin offset - this parameter is currently not used by our skeleton pD->m_fTempVars[0] = pD->m_fTempVars[1] = pD->m_fTempVars[2] = 0; } MeshLOD* meshLOD = renderActor->GetMeshLOD(useLodIndex); if (meshLOD) { if (meshLOD->m_hasDynamicMeshes) { m_actorInstance->UpdateMorphMeshDeformers(0.0f); } IMaterial* pMaterial = rParams.pMaterial; // Grab material for this LOD. if (!pMaterial && !m_materialPerLOD.empty()) { const AZ::u32 materialIndex = AZ::GetClamp<AZ::u32>(useLodIndex, 0, m_materialPerLOD.size() - 1); pMaterial = m_materialPerLOD[materialIndex]; } // Otherwise, fall back to default material. if (!pMaterial) { pMaterial = gEnv->p3DEngine->GetMaterialManager()->GetDefaultMaterial(); } // Send render meshes for editing by other components if required. if (!m_modificationHelper.GetMeshModified()) { for (const LmbrCentral::MeshModificationRequestHelper::MeshLODPrimIndex& meshIndices : m_modificationHelper.MeshesToEdit()) { if (meshIndices.lodIndex >= m_renderMeshesPerLOD.size() || meshIndices.primitiveIndex >= m_renderMeshesPerLOD[meshIndices.lodIndex].size()) { AZ_Warning("ActorRenderNode", false, "Mesh indices out of range"); continue; } IRenderMesh* renderMesh = m_renderMeshesPerLOD[meshIndices.lodIndex][meshIndices.primitiveIndex]; LmbrCentral::MeshModificationNotificationBus::Event( m_entityId, &LmbrCentral::MeshModificationNotificationBus::Events::ModifyMesh, meshIndices.lodIndex, meshIndices.primitiveIndex, renderMesh); } m_modificationHelper.SetMeshModified(true); } const bool morphsUpdated = MorphTargetWeightsWereUpdated(useLodIndex); const size_t numPrimitives = meshLOD->m_primitives.size(); for (size_t prim = 0; prim < numPrimitives; ++prim) { const Primitive& primitive = meshLOD->m_primitives[prim]; if (primitive.m_isDynamic && morphsUpdated) { UpdateDynamicSkin(useLodIndex, prim); } if (useLodIndex < m_renderMeshesPerLOD.size() && prim < m_renderMeshesPerLOD[useLodIndex].size()) { IRenderMesh* renderMesh = m_renderMeshesPerLOD[useLodIndex][prim]; if (renderMesh) { renderMesh->Render(rParams, pObj, pMaterial, passInfo); } } } } // Restore previous state. rParams.pMaterial = previousMaterial; rParams.dwFObjFlags = previousObjectFlags; } SSkinningData* CryRenderActorInstance::GetSkinningData() { AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::Animation, "CryRenderActorInstance::GetSkinningData"); // Get data to fill. const int nFrameID = gEnv->pRenderer->EF_GetSkinningPoolID(); const int nList = nFrameID % 3; const int nPrevList = (nFrameID - 1) % 3; // Before allocating new skinning date, check if we already have for this frame. if (m_arrSkinningRendererData[nList].nFrameID == nFrameID && m_arrSkinningRendererData[nList].pSkinningData) { return m_arrSkinningRendererData[nList].pSkinningData; } const EMotionFX::TransformData* transforms = m_actorInstance->GetTransformData(); const AZ::Transform* skinningMatrices = transforms->GetSkinningMatrices(); const AZ::u32 transformCount = transforms->GetNumTransforms(); SSkinningData* renderSkinningData = gEnv->pRenderer->EF_CreateSkinningData(transformCount, false, m_skinningMethod == SkinningMethod::Linear); if (m_skinningMethod == SkinningMethod::Linear) { Matrix34* renderTransforms = renderSkinningData->pBoneMatrices; for (AZ::u32 transformIndex = 0; transformIndex < transformCount; ++transformIndex) { renderTransforms[transformIndex] = AZTransformToCryMatrix(skinningMatrices[transformIndex]); } } else if (m_skinningMethod == SkinningMethod::DualQuat) { DualQuat* renderTransforms = renderSkinningData->pBoneQuatsS; for (AZ::u32 transformIndex = 0; transformIndex < transformCount; ++transformIndex) { renderTransforms[transformIndex] = AZMatrixToCryDualQuat(skinningMatrices[transformIndex]); } } // Set data for motion blur. if (m_arrSkinningRendererData[nPrevList].nFrameID == (nFrameID - 1) && m_arrSkinningRendererData[nPrevList].pSkinningData) { renderSkinningData->nHWSkinningFlags |= eHWS_MotionBlured; renderSkinningData->pPreviousSkinningRenderData = m_arrSkinningRendererData[nPrevList].pSkinningData; AZ::LegacyJobExecutor* pAsyncDataJobExecutor = renderSkinningData->pPreviousSkinningRenderData->pAsyncDataJobExecutor; if (pAsyncDataJobExecutor) { pAsyncDataJobExecutor->WaitForCompletion(); } } else { // If we don't have motion blur data, use the some as for the current frame. renderSkinningData->pPreviousSkinningRenderData = renderSkinningData; } m_arrSkinningRendererData[nList].nFrameID = nFrameID; m_arrSkinningRendererData[nList].pSkinningData = renderSkinningData; return renderSkinningData; } bool CryRenderActorInstance::GetLodDistances(const SFrameLodInfo& frameLodInfo, float* distances) const { for (int lodIndex = 0; lodIndex < SMeshLodInfo::s_nMaxLodCount; ++lodIndex) { distances[lodIndex] = FLT_MAX; } return true; } EERType CryRenderActorInstance::GetRenderNodeType() { return eERType_RenderComponent; } const char* CryRenderActorInstance::GetName() const { return "ActorRenderNode"; } const char* CryRenderActorInstance::GetEntityClassName() const { return "ActorRenderNode"; } Vec3 CryRenderActorInstance::GetPos(bool bWorldOnly /* = true */) const { return m_renderTransform.GetTranslation(); } const AABB CryRenderActorInstance::GetBBox() const { return m_worldBoundingBox; } void CryRenderActorInstance::GetLocalBounds(AABB& bbox) { const MCore::AABB& emfxAabb = m_actorInstance->GetStaticBasedAABB(); bbox = AABB(AZVec3ToLYVec3(emfxAabb.GetMin()), AZVec3ToLYVec3(emfxAabb.GetMax())); } void CryRenderActorInstance::SetBBox(const AABB& WSBBox) { m_worldBoundingBox = WSBBox; } void CryRenderActorInstance::OffsetPosition(const Vec3& delta) { // Recalculate local transform AZ::Transform localTransform = AZ::Transform::CreateIdentity(); EBUS_EVENT_ID_RESULT(localTransform, m_entityId, AZ::TransformBus, GetLocalTM); localTransform.SetTranslation(localTransform.GetTranslation() + LYVec3ToAZVec3(delta)); EBUS_EVENT_ID(m_entityId, AZ::TransformBus, SetLocalTM, localTransform); } void CryRenderActorInstance::SetMaterial(_smart_ptr<IMaterial> pMat) { AZ_Assert(m_materialPerLOD.size() < 2, "Attempting to override actor's multiple LOD materials with a single material"); m_materialPerLOD.clear(); m_materialPerLOD.emplace_back(pMat); } _smart_ptr<IMaterial> CryRenderActorInstance::GetMaterial(Vec3* pHitPos /* = nullptr */) { AZ_UNUSED(pHitPos); if (!m_materialPerLOD.empty()) { return m_materialPerLOD.front(); } return nullptr; } _smart_ptr<IMaterial> CryRenderActorInstance::GetMaterialOverride() { return nullptr; } IStatObj* CryRenderActorInstance::GetEntityStatObj(unsigned int /*nPartId*/, unsigned int /*nSubPartId*/, Matrix34A* /*pMatrix*/, bool /*bReturnOnlyVisible*/) { return nullptr; } _smart_ptr<IMaterial> CryRenderActorInstance::GetEntitySlotMaterial(unsigned int /*nPartId*/, bool /*bReturnOnlyVisible*/, bool* /*pbDrawNear */) { return GetMaterial(nullptr); } ICharacterInstance* CryRenderActorInstance::GetEntityCharacter(unsigned int /*nSlot*/, Matrix34A* /*pMatrix*/, bool /*bReturnOnlyVisible*/) { return nullptr; } float CryRenderActorInstance::GetMaxViewDist() { return (100.f * IRenderNode::GetViewDistanceMultiplier()); // \todo } void CryRenderActorInstance::GetMemoryUsage(class ICrySizer* /*pSizer*/) const { } bool CryRenderActorInstance::MorphTargetWeightsWereUpdated(uint32 lodLevel) { bool differentMorpthTargets = false; MorphSetupInstance* morphSetupInstance = m_actorInstance->GetMorphSetupInstance(); if (morphSetupInstance) { // if there is no morph setup, we have nothing to do MorphSetup* morphSetup = m_actorAsset.Get()->GetActor()->GetMorphSetup(lodLevel); if (morphSetup) { const uint32 numTargets = morphSetup->GetNumMorphTargets(); if (numTargets != m_lastMorphTargetWeights.size()) { differentMorpthTargets = true; m_lastMorphTargetWeights.resize(numTargets); } for (uint32 i = 0; i < numTargets; ++i) { // get the morph target MorphTarget* morphTarget = morphSetup->GetMorphTarget(i); MorphSetupInstance::MorphTarget* morphTargetInstance = morphSetupInstance->FindMorphTargetByID(morphTarget->GetID()); if (morphTargetInstance) { const float currentWeight = morphTargetInstance->GetWeight(); if (!AZ::IsClose(currentWeight, m_lastMorphTargetWeights[i], MCore::Math::epsilon)) { m_lastMorphTargetWeights[i] = currentWeight; differentMorpthTargets = true; } } } } else if (!m_lastMorphTargetWeights.empty()) { differentMorpthTargets = true; m_lastMorphTargetWeights.clear(); } } else if (!m_lastMorphTargetWeights.empty()) { differentMorpthTargets = true; m_lastMorphTargetWeights.clear(); } return differentMorpthTargets; } void CryRenderActorInstance::UpdateDynamicSkin(size_t lodIndex, size_t primitiveIndex) { ActorAsset* actorAsset = m_actorAsset.Get(); if (!actorAsset) { // Asset is not loaded. AZ_WarningOnce("ActorRenderNode", false, "Actor asset is not loaded. Rendering aborted."); return; } CryRenderActor* renderActor = GetRenderActor(); if (!renderActor) { return; } MeshLOD* meshLOD = renderActor->GetMeshLOD(lodIndex); if (!meshLOD) { return; } const Primitive& primitive = meshLOD->m_primitives[primitiveIndex]; _smart_ptr<IRenderMesh>& renderMesh = m_renderMeshesPerLOD[lodIndex][primitiveIndex]; IRenderMesh::ThreadAccessLock lockRenderMesh(renderMesh); strided_pointer<Vec3> destVertices; strided_pointer<Vec3> destNormals; strided_pointer<SPipQTangents> destTangents; destVertices.data = reinterpret_cast<Vec3*>(renderMesh->GetPosPtr(destVertices.iStride, FSL_SYSTEM_UPDATE)); destNormals.data = reinterpret_cast<Vec3*>(renderMesh->GetNormPtr(destNormals.iStride, FSL_SYSTEM_UPDATE)); AZ_Assert(destVertices, "Unexpected null pointer for vertices"); AZ_Assert(destNormals, "Unexpected null pointer for normals"); ActorAsset* assetData = m_actorAsset.Get(); AZ_Assert(assetData, "Invalid asset data"); const EMotionFX::SubMesh* subMesh = primitive.m_subMesh; const EMotionFX::Mesh* mesh = subMesh->GetParentMesh(); const AZ::Vector3* sourcePositions = static_cast<AZ::Vector3*>(mesh->FindVertexData(EMotionFX::Mesh::ATTRIB_POSITIONS)); const AZ::Vector3* sourceNormals = static_cast<AZ::Vector3*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_NORMALS)); // TODO: this shouldn't use the original data, this is a bug, but left here on purpose to hide an issue. const AZ::Vector3* sourceBitangents = static_cast<AZ::Vector3*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_BITANGENTS)); // Due to time constraints we will fix this later. const AZ::Vector4* sourceTangents = static_cast<AZ::Vector4*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_TANGENTS)); if (!destTangents) { destTangents.data = reinterpret_cast<SPipQTangents*>(renderMesh->GetQTangentPtr(destTangents.iStride, FSL_SYSTEM_UPDATE)); } AZ_Assert(static_cast<bool>(destTangents), "Expected a destination tangent buffer"); const AZ::u32 startVertex = subMesh->GetStartVertex(); const size_t numSubMeshVertices = subMesh->GetNumVertices(); for (size_t i = 0; i < numSubMeshVertices; ++i) { const AZ::u32 vertexIndex = startVertex + i; const AZ::Vector3& sourcePosition = sourcePositions[vertexIndex]; destVertices[i] = Vec3(sourcePosition.GetX(), sourcePosition.GetY(), sourcePosition.GetZ()); const AZ::Vector3& sourceNormal = sourceNormals[vertexIndex]; destNormals[i] = Vec3(sourceNormal.GetX(), sourceNormal.GetY(), sourceNormal.GetZ()); if (sourceTangents) { // We only need to update the tangents if they are in the mesh, otherwise they will be 0 or not // be present at the destination const AZ::Vector4& sourceTangent = sourceTangents[vertexIndex]; const AZ::Vector3 sourceNormalV3(sourceNormals[vertexIndex]); AZ::Vector3 bitangent; if (sourceBitangents) { bitangent = sourceBitangents[vertexIndex]; } else { bitangent = sourceNormalV3.Cross(sourceTangent.GetAsVector3()) * sourceTangent.GetW(); } const SMeshTangents meshTangent( Vec3(sourceTangent.GetX(), sourceTangent.GetY(), sourceTangent.GetZ()), Vec3(bitangent.GetX(), bitangent.GetY(), bitangent.GetZ()), Vec3(sourceNormalV3.GetX(), sourceNormalV3.GetY(), sourceNormalV3.GetZ())); const Quat q = MeshTangentFrameToQTangent(meshTangent); destTangents[i] = SPipQTangents( Vec4sf( PackingSNorm::tPackF2B(q.v.x), PackingSNorm::tPackF2B(q.v.y), PackingSNorm::tPackF2B(q.v.z), PackingSNorm::tPackF2B(q.w))); } // if (sourceTangents) } // for all vertices renderMesh->UnlockStream(VSF_GENERAL); if (destTangents) { renderMesh->UnlockStream(VSF_QTANGENTS); } } void CryRenderActorInstance::QueueBuildRenderMesh() { AZStd::shared_ptr<int>& renderNodeLifetime = m_renderNodeLifetime; const AZStd::function<void()> finalizeOnMainThread = [this, renderNodeLifetime]() { // RenderMesh creation must be performed on the main thread, // as required by the renderer. if (renderNodeLifetime.use_count() != 1) { BuildRenderMeshPerLOD(); } }; AZ::SystemTickBus::QueueFunction(finalizeOnMainThread); } void CryRenderActorInstance::BuildRenderMeshPerLOD() { m_renderMeshesPerLOD.clear(); // Release smart pointers CryRenderActor* renderActor = GetRenderActor(); if (!renderActor) { return; } // Populate m_renderMeshesPerLOD. If the mesh doesn't require to be unique, we reuse the render mesh from the actor. If the // mesh requires to be unique, we create a copy of the actor's render mesh since this actor instance will be modifying it. const size_t lodCount = renderActor->GetNumLODs(); m_renderMeshesPerLOD.resize(lodCount); for (size_t i = 0; i < lodCount; ++i) { MeshLOD* meshLOD = renderActor->GetMeshLOD(i); AZ_Assert(meshLOD, "Render Actor's Meshes for LOD %d are not loaded.", i); const size_t numPrims = meshLOD->m_primitives.size(); m_renderMeshesPerLOD[i].resize(numPrims); for (size_t primIndex = 0; primIndex < numPrims; ++primIndex) { Primitive& primitive = meshLOD->m_primitives[primIndex]; _smart_ptr<IRenderMesh> renderMesh; if (primitive.m_useUniqueMesh) { // Create a copy since each actor instance can be deforming differently and we need to send different meshes to render renderMesh = gEnv->pRenderer->CreateRenderMesh("EMotion FX Actor", primitive.m_renderMesh->GetSourceName(), nullptr, eRMT_Dynamic); const AZ::u32 renderMeshFlags = FSM_ENABLE_NORMALSTREAM | FSM_VERTEX_VELOCITY; renderMesh->SetMesh(*primitive.m_mesh, 0, renderMeshFlags, false); } else { // Reuse the same render mesh renderMesh = primitive.m_renderMesh; } m_renderMeshesPerLOD[i][primIndex] = renderMesh; } } } void CryRenderActorInstance::OnTick(float timeDelta) { UpdateBounds(); if (!m_materialReadyEventSent && m_materialOwner->IsMaterialOwnerReady()) { LmbrCentral::MaterialOwnerNotificationBus::Event(GetEntityId(), &LmbrCentral::MaterialOwnerNotifications::OnMaterialOwnerReady); m_materialReadyEventSent = true; } } void CryRenderActorInstance::UpdateBounds() { UpdateWorldBoundingBox(); // Update RenderActorInstance world bounding box #if defined(EMOTIONFXANIMATION_EDITOR) const AABB renderNodeWorldBox = GetBBox(); m_worldAABB = AZ::Aabb::CreateFromMinMax(AZ::Vector3(renderNodeWorldBox.min.x, renderNodeWorldBox.min.y, renderNodeWorldBox.min.z), AZ::Vector3(renderNodeWorldBox.max.x, renderNodeWorldBox.max.y, renderNodeWorldBox.max.z)); #else // The bounding box is moving with the actor instance. It is static in the way that it does not change shape. // The entity and actor transforms are kept in sync already. m_worldAABB = AZ::Aabb::CreateFromMinMax(m_actorInstance->GetAABB().GetMin(), m_actorInstance->GetAABB().GetMax()); #endif // Update RenderActorInstance local bounding box #if defined(EMOTIONFXANIMATION_EDITOR) AABB renderNodeLocalBox; IRenderNode::GetLocalBounds(renderNodeLocalBox); m_localAABB = AZ::Aabb::CreateFromMinMax(AZ::Vector3(renderNodeLocalBox.min.x, renderNodeLocalBox.min.y, renderNodeLocalBox.min.z), AZ::Vector3(renderNodeLocalBox.max.x, renderNodeLocalBox.max.y, renderNodeLocalBox.max.z)); #else m_localAABB = AZ::Aabb::CreateFromMinMax(m_actorInstance->GetStaticBasedAABB().GetMin(), m_actorInstance->GetStaticBasedAABB().GetMax()); #endif } bool CryRenderActorInstance::IsInCameraFrustum() const { if (!gEnv || !gEnv->pSystem) { return false; } const CCamera& camera = gEnv->pSystem->GetViewCamera(); return camera.IsAABBVisible_F(m_worldBoundingBox); } void CryRenderActorInstance::DebugDraw(const DebugOptions& debugOptions) { if (!gEnv || !gEnv->pRenderer) { return; } if (debugOptions.m_drawSkeleton) { DrawSkeleton(); } if (debugOptions.m_drawAABB) { DrawAABB(); } if (debugOptions.m_drawRootTransform) { DrawRootTransform(debugOptions.m_rootWorldTransform); } if (debugOptions.m_emfxDebugDraw) { EmfxDebugDraw(); } } void CryRenderActorInstance::DrawAABB() { gEnv->pRenderer->GetIRenderAuxGeom()->DrawAABB(GetBBox(), false, Col_Cyan, eBBD_Faceted); } void CryRenderActorInstance::DrawSkeleton() { if (!m_actorInstance) { return; } const EMotionFX::TransformData* transformData = m_actorInstance->GetTransformData(); const EMotionFX::Skeleton* skeleton = m_actorInstance->GetActor()->GetSkeleton(); const EMotionFX::Pose* pose = transformData->GetCurrentPose(); const AZ::u32 transformCount = transformData->GetNumTransforms(); const AZ::u32 lodLevel = m_actorInstance->GetLODLevel(); for (AZ::u32 index = 0; index < skeleton->GetNumNodes(); ++index) { const EMotionFX::Node* node = skeleton->GetNode(index); const AZ::u32 parentIndex = node->GetParentIndex(); if (parentIndex == MCORE_INVALIDINDEX32) { continue; } if (!node->GetSkeletalLODStatus(lodLevel)) { continue; } const AZ::Vector3 bonePos = pose->GetWorldSpaceTransform(index).mPosition; const AZ::Vector3 parentPos = pose->GetWorldSpaceTransform(parentIndex).mPosition; gEnv->pRenderer->GetIRenderAuxGeom()->DrawBone(AZVec3ToLYVec3(parentPos), AZVec3ToLYVec3(bonePos), Col_YellowGreen); } } void CryRenderActorInstance::DrawRootTransform(const AZ::Transform& worldTransform) { gEnv->pRenderer->GetIRenderAuxGeom()->DrawCone(AZVec3ToLYVec3(worldTransform.GetTranslation() + AZ::Vector3(0.0f, 0.0f, 0.1f)), AZVec3ToLYVec3(worldTransform.GetColumn(1)), 0.05f, 0.5f, Col_Green); } void CryRenderActorInstance::EmfxDebugDraw() { IRenderAuxGeom* geomRenderer = gEnv->pRenderer->GetIRenderAuxGeom(); EMotionFX::DebugDraw& debugDraw = EMotionFX::GetDebugDraw(); debugDraw.Lock(); DebugDraw::ActorInstanceData* actorInstanceData = debugDraw.GetActorInstanceData(m_actorInstance); actorInstanceData->Lock(); for (const DebugDraw::Line& line : actorInstanceData->GetLines()) { const ColorF startColor(line.m_startColor.GetR(), line.m_startColor.GetG(), line.m_startColor.GetB(), line.m_startColor.GetA()); const ColorF endColor(line.m_endColor.GetR(), line.m_endColor.GetG(), line.m_endColor.GetB(), line.m_endColor.GetA()); geomRenderer->DrawLine(Vec3(line.m_start), startColor, Vec3(line.m_end), endColor, 1.0f); } actorInstanceData->Unlock(); debugDraw.Unlock(); } ////////////////////////////////////////////////////////////////////////// // RenderNodeRequestBus::Handler IRenderNode* CryRenderActorInstance::GetRenderNode() { return this; } const float CryRenderActorInstance::s_renderNodeRequestBusOrder = 100.f; float CryRenderActorInstance::GetRenderNodeRequestBusOrder() const { return s_renderNodeRequestBusOrder; } // RenderNodeRequestBus::Handler ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// // MeshComponentRequestBus::Handler bool CryRenderActorInstance::GetVisibility() { return !IsHidden(); } void CryRenderActorInstance::SetVisibility(bool isVisible) { Hide(!isVisible); } // MeshComponentRequestBus::Handler ////////////////////////////////////////////////////////////////////////// void CryRenderActorInstance::SetMeshAsset(const AZ::Data::AssetId& id) { AZ::Data::Asset<ActorAsset> asset = AZ::Data::AssetManager::Instance().GetAsset<ActorAsset>(id, false); if (asset) { m_actorAsset = asset; QueueBuildRenderMesh(); } } ////////////////////////////////////////////////////////////////////////// // MaterialOwnerRequestBus::Handler CryRenderActorInstance::MaterialOwner::MaterialOwner(CryRenderActorInstance* renderActorInstance, AZ::EntityId entityId) : m_renderActorInstance(renderActorInstance) { const bool registerBus = true; Activate(renderActorInstance, entityId, registerBus); } CryRenderActorInstance::MaterialOwner::~MaterialOwner() { Deactivate(); } ////////////////////////////////////////////////////////////////////////// #if defined(EMOTIONFXANIMATION_EDITOR) void CryRenderActorInstance::MaterialOwner::SetMaterial(_smart_ptr<IMaterial> material) { // Set m_materialPerActor and m_materialPerLOD, which contains the material asset references if (material) { if (material->IsSubMaterial()) { // Attempt to apply the parent material if material is a sub-material CMaterial* editorMaterial = static_cast<CMaterial*>(material->GetUserData()); if (editorMaterial && editorMaterial->GetParent() && editorMaterial->GetParent()->GetMatInfo()) { material = editorMaterial->GetParent()->GetMatInfo(); AZ_Warning("EMotionFX", false, "Cannot apply a sub-material directly to an actor. Applying the parent material group '%s' instead.", material->GetName()); } else { AZ_Error("EMotionFX", false, "Cannot apply sub-material '%s' directly to an actor. Try applying the parent material group instead.", material->GetName()); return; } } // Apply the material to the actor m_renderActorInstance->m_onMaterialChangedCallback(material->GetName()); } else { // If material is nullptr, re-set m_materialPerLOD to the default for this actor m_renderActorInstance->m_onMaterialChangedCallback(""); } } #else void CryRenderActorInstance::MaterialOwner::SetMaterial(_smart_ptr<IMaterial> material) { if (material && material->IsSubMaterial()) { AZ_Error("MaterialOwnerRequestBus", false, "Material Owner cannot be given a Sub-Material."); } else { m_renderActorInstance->SetMaterial(material); } } #endif _smart_ptr<IMaterial> CryRenderActorInstance::MaterialOwner::GetMaterial() { _smart_ptr<IMaterial> material = m_renderActorInstance->GetMaterial(); if (!m_renderActorInstance->IsReady()) { if (material) { AZ_Warning("MaterialOwnerRequestBus", false, "A Material was found, but Material Owner is not ready. May have unexpected results. (Try using MaterialOwnerNotificationBus.OnMaterialOwnerReady or MaterialOwnerRequestBus.IsMaterialOwnerReady)"); } else { AZ_Error("MaterialOwnerRequestBus", false, "Material Owner is not ready and no Material was found. Assets probably have not finished loading yet. (Try using MaterialOwnerNotificationBus.OnMaterialOwnerReady or MaterialOwnerRequestBus.IsMaterialOwnerReady)"); } } return material; } // MaterialOwnerRequestBus::Handler ////////////////////////////////////////////////////////////////////////// CryRenderActor* CryRenderActorInstance::GetRenderActor() const { ActorAsset* actorAsset = m_actorAsset.Get(); if (!actorAsset) { AZ_Assert(false, "Actor asset is not loaded."); return nullptr; } CryRenderActor* renderActor = azdynamic_cast<CryRenderActor*>(actorAsset->GetRenderActor()); if (!renderActor) { AZ_Assert(false, "Expecting a Cry render backend actor."); return nullptr; } return renderActor; } } //namespace Integration } // namespace EMotionFX #ifdef _DEBUG # pragma pop_macro("AZ_NUMERICCAST_ENABLED") #endif // #ifdef _DEBUG