/* * 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. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. // Description : draw, create decals on the world #include "StdAfx.h" #include #include "DecalManager.h" #include "3dEngine.h" #include #include "ObjMan.h" #include "MatMan.h" #include #include "RenderMeshMerger.h" #include "RenderMeshUtils.h" #include "VisAreas.h" #include "DecalRenderNode.h" #include "Environment/OceanEnvironmentBus.h" #ifndef RENDER_MESH_TEST_DISTANCE #define RENDER_MESH_TEST_DISTANCE (0.2f) #endif static const int MAX_ASSEMBLE_SIZE = 5; CDecalManager::CDecalManager() { m_nCurDecal = 0; memset(m_arrbActiveDecals, 0, sizeof(m_arrbActiveDecals)); } CDecalManager::~CDecalManager() { CDecal::ResetStaticData(); } bool CDecalManager::AdjustDecalPosition(CryEngineDecalInfo& DecalInfo, bool bMakeFatTest) { Matrix34A objMat, objMatInv; Matrix33 objRot, objRotInv; CStatObj* pEntObject = (CStatObj*)DecalInfo.ownerInfo.GetOwner(objMat); if (!pEntObject || !pEntObject->GetRenderMesh() || !pEntObject->GetRenderTrisCount()) { return false; } objRot = Matrix33(objMat); objRot.NoScale(); // No scale. objRotInv = objRot; objRotInv.Invert(); float fWorldScale = objMat.GetColumn(0).GetLength(); // GetScale float fWorldScaleInv = 1.0f / fWorldScale; // transform decal into object space objMatInv = objMat; objMatInv.Invert(); // put into normal object space hit direction of projection Vec3 vOS_HitDir = objRotInv.TransformVector(DecalInfo.vHitDirection).GetNormalized(); // put into position object space hit position Vec3 vOS_HitPos = objMatInv.TransformPoint(DecalInfo.vPos); vOS_HitPos -= vOS_HitDir * RENDER_MESH_TEST_DISTANCE * fWorldScaleInv; _smart_ptr pMat = DecalInfo.ownerInfo.pRenderNode ? DecalInfo.ownerInfo.pRenderNode->GetMaterial() : NULL; Vec3 vOS_OutPos(0, 0, 0), vOS_OutNormal(0, 0, 0), vTmp; IRenderMesh* pRM = pEntObject->GetRenderMesh(); AABB aabbRNode; pRM->GetBBox(aabbRNode.min, aabbRNode.max); Vec3 vOut(0, 0, 0); if (!Intersect::Ray_AABB(Ray(vOS_HitPos, vOS_HitDir), aabbRNode, vOut)) { return false; } if (!pRM || !pRM->GetVerticesCount()) { return false; } if (RayRenderMeshIntersection(pRM, vOS_HitPos, vOS_HitDir, vOS_OutPos, vOS_OutNormal, false, 0, pMat)) { // now check that none of decal sides run across edges Vec3 srcp = vOS_OutPos + 0.01f * fWorldScaleInv * vOS_OutNormal; /// Rise hit point a little bit above hit plane. Vec3 vDecalNormal = vOS_OutNormal; float fMaxHitDistance = 0.02f * fWorldScaleInv; // get decal directions Vec3 vRi(0, 0, 0), vUp(0, 0, 0); if (fabs(vOS_OutNormal.Dot(Vec3(0, 0, 1))) > 0.999f) { // horiz surface vRi = Vec3(0, 1, 0); vUp = Vec3(1, 0, 0); } else { vRi = vOS_OutNormal.Cross(Vec3(0, 0, 1)); vRi.Normalize(); vUp = vOS_OutNormal.Cross(vRi); vUp.Normalize(); } vRi *= DecalInfo.fSize * 0.65f; vUp *= DecalInfo.fSize * 0.65f; if (!bMakeFatTest || ( RayRenderMeshIntersection(pRM, srcp + vUp, -vDecalNormal, vTmp, vTmp, true, fMaxHitDistance, pMat) && RayRenderMeshIntersection(pRM, srcp - vUp, -vDecalNormal, vTmp, vTmp, true, fMaxHitDistance, pMat) && RayRenderMeshIntersection(pRM, srcp + vRi, -vDecalNormal, vTmp, vTmp, true, fMaxHitDistance, pMat) && RayRenderMeshIntersection(pRM, srcp - vRi, -vDecalNormal, vTmp, vTmp, true, fMaxHitDistance, pMat))) { DecalInfo.vPos = objMat.TransformPoint(vOS_OutPos + vOS_OutNormal * 0.001f * fWorldScaleInv); DecalInfo.vNormal = objRot.TransformVector(vOS_OutNormal); return true; } } return false; } struct HitPosInfo { HitPosInfo() { memset(this, 0, sizeof(HitPosInfo)); } Vec3 vPos, vNormal; float fDistance; }; int __cdecl CDecalManager__CmpHitPos(const void* v1, const void* v2) { HitPosInfo* p1 = (HitPosInfo*)v1; HitPosInfo* p2 = (HitPosInfo*)v2; if (p1->fDistance > p2->fDistance) { return 1; } else if (p1->fDistance < p2->fDistance) { return -1; } return 0; } bool CDecalManager::RayRenderMeshIntersection(IRenderMesh* pRenderMesh, const Vec3& vInPos, const Vec3& vInDir, Vec3& vOutPos, Vec3& vOutNormal, bool bFastTest, float fMaxHitDistance, _smart_ptr pMat) { SRayHitInfo hitInfo; hitInfo.bUseCache = GetCVars()->e_DecalsHitCache != 0; hitInfo.bInFirstHit = bFastTest; hitInfo.inRay.origin = vInPos; hitInfo.inRay.direction = vInDir.GetNormalized(); hitInfo.inReferencePoint = vInPos; hitInfo.fMaxHitDistance = fMaxHitDistance; bool bRes = CRenderMeshUtils::RayIntersection(pRenderMesh, hitInfo, pMat); vOutPos = hitInfo.vHitPos; vOutNormal = hitInfo.vHitNormal; return bRes; } bool CDecalManager::SpawnHierarchical(const CryEngineDecalInfo& rootDecalInfo, CDecal* pCallerManagedDecal) { // decal on terrain or simple decal on always static object if (!rootDecalInfo.ownerInfo.pRenderNode) { return Spawn(rootDecalInfo, pCallerManagedDecal); } bool bSuccess = false; AABB decalBoxWS; float fSize = rootDecalInfo.fSize; decalBoxWS.max = rootDecalInfo.vPos + Vec3(fSize, fSize, fSize); decalBoxWS.min = rootDecalInfo.vPos - Vec3(fSize, fSize, fSize); for (int nEntitySlotId = 0; nEntitySlotId < 16; nEntitySlotId++) { CStatObj* _pStatObj = NULL; Matrix34A entSlotMatrix; entSlotMatrix.SetIdentity(); if (_pStatObj = (CStatObj*)rootDecalInfo.ownerInfo.pRenderNode->GetEntityStatObj(nEntitySlotId, ~0, &entSlotMatrix, true)) { if (_pStatObj->m_nFlags & STATIC_OBJECT_COMPOUND) { if (int nSubCount = _pStatObj->GetSubObjectCount()) { // spawn decals on stat obj sub objects CryEngineDecalInfo decalInfo = rootDecalInfo; decalInfo.ownerInfo.nRenderNodeSlotId = nEntitySlotId; if (rootDecalInfo.ownerInfo.nRenderNodeSlotSubObjectId >= 0) { decalInfo.ownerInfo.nRenderNodeSlotSubObjectId = rootDecalInfo.ownerInfo.nRenderNodeSlotSubObjectId; bSuccess |= Spawn(decalInfo, pCallerManagedDecal); } else { for (int nSubId = 0; nSubId < nSubCount; nSubId++) { IStatObj::SSubObject& subObj = _pStatObj->SubObject(nSubId); if (subObj.pStatObj && !subObj.bHidden && subObj.nType == STATIC_SUB_OBJECT_MESH) { Matrix34 subObjMatrix = entSlotMatrix * subObj.tm; AABB subObjAABB = AABB::CreateTransformedAABB(subObjMatrix, subObj.pStatObj->GetAABB()); if (Overlap::AABB_AABB(subObjAABB, decalBoxWS)) { decalInfo.ownerInfo.nRenderNodeSlotSubObjectId = nSubId; bSuccess |= Spawn(decalInfo, pCallerManagedDecal); } } } } } } else { AABB subObjAABB = AABB::CreateTransformedAABB(entSlotMatrix, _pStatObj->GetAABB()); if (Overlap::AABB_AABB(subObjAABB, decalBoxWS)) { CryEngineDecalInfo decalInfo = rootDecalInfo; decalInfo.ownerInfo.nRenderNodeSlotId = nEntitySlotId; decalInfo.ownerInfo.nRenderNodeSlotSubObjectId = -1; // no childs bSuccess |= Spawn(decalInfo, pCallerManagedDecal); } } } else if (ICharacterInstance* pChar = rootDecalInfo.ownerInfo.pRenderNode->GetEntityCharacter(nEntitySlotId, &entSlotMatrix)) { // spawn decals on CGA components ISkeletonPose* pSkeletonPose = pChar->GetISkeletonPose(); uint32 numJoints = pChar->GetIDefaultSkeleton().GetJointCount(); CryEngineDecalInfo decalInfo = rootDecalInfo; decalInfo.ownerInfo.nRenderNodeSlotId = nEntitySlotId; if (rootDecalInfo.ownerInfo.nRenderNodeSlotSubObjectId >= 0) { decalInfo.ownerInfo.nRenderNodeSlotSubObjectId = rootDecalInfo.ownerInfo.nRenderNodeSlotSubObjectId; bSuccess |= Spawn(decalInfo, pCallerManagedDecal); } else { // spawn decal on every sub-object intersecting decal bbox for (uint32 nJointId = 0; nJointId < numJoints; nJointId++) { IStatObj* pStatObj = pSkeletonPose->GetStatObjOnJoint(nJointId); if (pStatObj && !(pStatObj->GetFlags() & STATIC_OBJECT_HIDDEN) && pStatObj->GetRenderMesh()) { assert(!pStatObj->GetSubObjectCount()); Matrix34 tm34 = entSlotMatrix * Matrix34(pSkeletonPose->GetAbsJointByID(nJointId)); AABB objBoxWS = AABB::CreateTransformedAABB(tm34, pStatObj->GetAABB()); // DrawBBox(objBoxWS); // DrawBBox(decalBoxWS); if (Overlap::AABB_AABB(objBoxWS, decalBoxWS)) { decalInfo.ownerInfo.nRenderNodeSlotSubObjectId = nJointId; bSuccess |= Spawn(decalInfo, pCallerManagedDecal); } } } } } } return bSuccess; } bool CDecalManager::Spawn(CryEngineDecalInfo DecalInfo, CDecal* pCallerManagedDecal) { FUNCTION_PROFILER_3DENGINE; Vec3 vCamPos = GetSystem()->GetViewCamera().GetPosition(); // do not spawn if too far float fZoom = GetObjManager() ? Get3DEngine()->GetZoomFactor() : 1.f; float fDecalDistance = DecalInfo.vPos.GetDistance(vCamPos); if (!pCallerManagedDecal && (fDecalDistance > Get3DEngine()->GetMaxViewDistance() || fDecalDistance * fZoom > DecalInfo.fSize * ENTITY_DECAL_DIST_FACTOR * 3.f)) { return false; } int overlapCount(0); int targetSize(0); int overlapIds[MAX_ASSEMBLE_SIZE]; // do not spawn new decals if they could overlap the existing and similar ones if (!pCallerManagedDecal && !GetCVars()->e_DecalsOverlapping && DecalInfo.fSize && !DecalInfo.bSkipOverlappingTest) { for (int i = 0; i < DECAL_COUNT; i++) { if (m_arrbActiveDecals[i]) { // skip overlapping check if decals are very different in size if ((m_arrDecals[i].m_iAssembleSize > 0) == DecalInfo.bAssemble) { float fSizeRatio = m_arrDecals[i].m_fWSSize / DecalInfo.fSize; if (((m_arrDecals[i].m_iAssembleSize > 0) || (fSizeRatio > 0.5f && fSizeRatio < 2.f)) && m_arrDecals[i].m_nGroupId != DecalInfo.nGroupId) { float fDist = m_arrDecals[i].m_vWSPos.GetSquaredDistance(DecalInfo.vPos); if (fDist < sqr(m_arrDecals[i].m_fWSSize * 0.5f + DecalInfo.fSize * 0.5f) && (DecalInfo.vNormal.Dot(m_arrDecals[i].m_vFront) > 0.f)) { if (DecalInfo.bAssemble && m_arrDecals[i].m_iAssembleSize < MAX_ASSEMBLE_SIZE) { if (overlapCount < MAX_ASSEMBLE_SIZE) { overlapIds[overlapCount] = i; overlapCount++; } else { m_arrbActiveDecals[i] = false; } } else { return true; } } } } } } } float fAssembleSizeModifier(1.0f); if (DecalInfo.bAssemble) { Vec3 avgPos(0.0f, 0.0f, 0.0f); int validAssembles(0); for (int i = 0; i < overlapCount; i++) { int id = overlapIds[i]; float fDist = m_arrDecals[id].m_vWSPos.GetSquaredDistance(DecalInfo.vPos); float minDist = sqr(m_arrDecals[id].m_fWSSize * 0.4f); if (fDist > minDist) { avgPos += m_arrDecals[id].m_vWSPos; targetSize += m_arrDecals[id].m_iAssembleSize; validAssembles++; } } if (overlapCount && !validAssembles) { return true; } for (int i = 0; i < overlapCount; i++) { int id = overlapIds[i]; m_arrbActiveDecals[id] = false; } ++validAssembles; ++targetSize; avgPos += DecalInfo.vPos; if (targetSize > 1) { avgPos /= float(validAssembles); DecalInfo.vPos = avgPos; targetSize = min(targetSize, MAX_ASSEMBLE_SIZE); const float sizetable[MAX_ASSEMBLE_SIZE] = {1.0f, 1.5f, 2.3f, 3.5f, 3.5f}; const char sValue[2] = { aznumeric_caster('0' + targetSize), 0 }; cry_strcat(DecalInfo.szMaterialName, sValue); fAssembleSizeModifier = sizetable[targetSize - 1]; } } if (GetCVars()->e_Decals > 1) { DrawSphere(DecalInfo.vPos, DecalInfo.fSize); } // update lifetime for near decals under control by the decal manager if (!pCallerManagedDecal) { if (DecalInfo.fSize > 1 && GetCVars()->e_DecalsNeighborMaxLifeTime) { // force near decals to fade faster float fCurrTime = GetTimer()->GetCurrTime(); for (int i = 0; i < DECAL_COUNT; i++) { if (m_arrbActiveDecals[i] && m_arrDecals[i].m_nGroupId != DecalInfo.nGroupId) { if (m_arrDecals[i].m_vWSPos.GetSquaredDistance(DecalInfo.vPos) < sqr(m_arrDecals[i].m_fWSSize / 1.5f + DecalInfo.fSize / 2.0f)) { if ((m_arrDecals[i]).m_fLifeBeginTime < fCurrTime - 0.1f) { if (m_arrDecals[i].m_fLifeTime > GetCVars()->e_DecalsNeighborMaxLifeTime) { if (m_arrDecals[i].m_fLifeTime < 10000) // decals spawn by cut scenes need to stay { m_arrDecals[i].m_fLifeTime = GetCVars()->e_DecalsNeighborMaxLifeTime; } } } } } } } // loop position in array m_nCurDecal = (m_nCurDecal + 1) & (DECAL_COUNT - 1); //if(m_nCurDecal>=DECAL_COUNT) // m_nCurDecal=0; } // create reference to decal which is to be filled CDecal& newDecal(pCallerManagedDecal ? *pCallerManagedDecal : m_arrDecals[ m_nCurDecal ]); newDecal.m_bDeferred = DecalInfo.bDeferred; newDecal.m_iAssembleSize = targetSize; // free old pRM newDecal.FreeRenderData(); newDecal.m_nGroupId = DecalInfo.nGroupId; // get material if specified newDecal.m_pMaterial = 0; if (DecalInfo.szMaterialName[0] != '\0') { newDecal.m_pMaterial = GetMatMan()->LoadMaterial(DecalInfo.szMaterialName, false, true); if (!newDecal.m_pMaterial) { newDecal.m_pMaterial = GetMatMan()->LoadMaterial("EngineAssets/Materials/Decals/Default", true, true); newDecal.m_pMaterial->AddRef(); Warning("CDecalManager::Spawn: Specified decal material \"%s\" not found!\n", DecalInfo.szMaterialName); } } newDecal.m_sortPrio = DecalInfo.sortPrio; // set up user defined decal basis if provided bool useDefinedUpRight(false); Vec3 userDefinedUp; Vec3 userDefinedRight; if (DecalInfo.pExplicitRightUpFront) { userDefinedRight = DecalInfo.pExplicitRightUpFront->GetColumn(0); userDefinedUp = DecalInfo.pExplicitRightUpFront->GetColumn(1); DecalInfo.vNormal = DecalInfo.pExplicitRightUpFront->GetColumn(2); useDefinedUpRight = true; } // just in case DecalInfo.vNormal.NormalizeSafe(); // remember object we need to follow newDecal.m_ownerInfo.nRenderNodeSlotId = DecalInfo.ownerInfo.nRenderNodeSlotId; newDecal.m_ownerInfo.nRenderNodeSlotSubObjectId = DecalInfo.ownerInfo.nRenderNodeSlotSubObjectId; newDecal.m_vWSPos = DecalInfo.vPos; newDecal.m_fWSSize = DecalInfo.fSize * fAssembleSizeModifier; // If owner entity and object is specified - make decal use entity geometry float _fObjScale = 1.f; Matrix34A _objMat; Matrix33 worldRot; IStatObj* pStatObj = DecalInfo.ownerInfo.GetOwner(_objMat); if (pStatObj) { worldRot = Matrix33(_objMat); _objMat.Invert(); } float fWrapMinSize = GetFloatCVar(e_DecalsDefferedDynamicMinSize); bool bSpawnOnVegetationWithBending = false; if (DecalInfo.ownerInfo.pRenderNode && DecalInfo.ownerInfo.pRenderNode->GetRenderNodeType() == eERType_Vegetation && DecalInfo.fSize > fWrapMinSize && !DecalInfo.bDeferred) { // CVegetation * pVeg = (CVegetation*)DecalInfo.ownerInfo.pRenderNode; // StatInstGroup & rGroup = GetObjManager()->m_lstStaticTypes[pVeg->m_nObjectTypeID]; // if(rGroup.fBending>0) bSpawnOnVegetationWithBending = true; // Check owner material ID (need for decals vertex modifications) SRayHitInfo hitInfo; memset(&hitInfo, 0, sizeof(hitInfo)); Vec3 vHitPos = _objMat.TransformPoint(DecalInfo.vPos); // put hit normal into the object space Vec3 vRayDir = DecalInfo.vHitDirection.GetNormalized() * worldRot; hitInfo.inReferencePoint = vHitPos; hitInfo.inRay.origin = vHitPos - vRayDir * 4.0f; hitInfo.inRay.direction = vRayDir; hitInfo.bInFirstHit = false; hitInfo.bUseCache = true; if (pStatObj->RayIntersection(hitInfo, 0)) { newDecal.m_ownerInfo.nMatID = hitInfo.nHitMatID; } } if (DecalInfo.ownerInfo.pRenderNode && DecalInfo.ownerInfo.nRenderNodeSlotId >= 0 && (DecalInfo.fSize > fWrapMinSize || pCallerManagedDecal || bSpawnOnVegetationWithBending) && !DecalInfo.bDeferred) { newDecal.m_eDecalType = eDecalType_OS_OwnersVerticesUsed; IRenderMesh* pSourceRenderMesh = NULL; if (pStatObj) { pSourceRenderMesh = pStatObj->GetRenderMesh(); } if (!pSourceRenderMesh) { return false; } // transform decal into object space Matrix33 objRotInv = Matrix33(_objMat); objRotInv.NoScale(); if (useDefinedUpRight) { userDefinedRight = objRotInv.TransformVector(userDefinedRight).GetNormalized(); userDefinedUp = objRotInv.TransformVector(userDefinedUp).GetNormalized(); assert(fabsf(DecalInfo.vNormal.Dot(-DecalInfo.vHitDirection.GetNormalized()) - 1.0f) < 1e-4f); } // make decals smaller but longer if hit direction is near perpendicular to surface normal float fSizeModificator = 0.25f + 0.75f * fabs(DecalInfo.vHitDirection.GetNormalized().Dot(DecalInfo.vNormal)); // put into normal object space hit direction of projection DecalInfo.vNormal = -objRotInv.TransformVector((DecalInfo.vHitDirection - DecalInfo.vNormal * 0.25f).GetNormalized()); if (!DecalInfo.vNormal.IsZero()) { DecalInfo.vNormal.Normalize(); } // put into position object space hit position DecalInfo.vPos = _objMat.TransformPoint(DecalInfo.vPos); // find object scale float fObjScale = 1.f; Vec3 vTest(0, 0, 1.f); vTest = _objMat.TransformVector(vTest); fObjScale = 1.f / vTest.len(); if (fObjScale < 0.01f) { return false; } // transform size into object space DecalInfo.fSize /= fObjScale; DecalInfo.fSize *= (DecalInfo.bAssemble ? fAssembleSizeModifier : fSizeModificator); if (DecalInfo.bForceEdge) { SRayHitInfo hitInfo; hitInfo.bUseCache = GetCVars()->e_DecalsHitCache != 0; hitInfo.bInFirstHit = false; hitInfo.inRay.origin = DecalInfo.vPos + DecalInfo.vNormal; hitInfo.inRay.direction = -DecalInfo.vNormal; hitInfo.inReferencePoint = DecalInfo.vPos + DecalInfo.vNormal; hitInfo.inRetTriangle = true; CRenderMeshUtils::RayIntersection(pSourceRenderMesh, hitInfo, pStatObj ? pStatObj->GetMaterial() : NULL); MoveToEdge(pSourceRenderMesh, DecalInfo.fSize, hitInfo.vHitPos, hitInfo.vHitNormal, hitInfo.vTri0, hitInfo.vTri1, hitInfo.vTri2); DecalInfo.vPos = hitInfo.vHitPos; DecalInfo.vNormal = hitInfo.vHitNormal; } // make decal geometry if (!newDecal.m_pMaterial) { // I'm not sure what consequences will be if m_pMaterial is null, so we warn about it just in case. Feel free to remove this if you hit it and all is well. Warning("CDecalManager::Spawn: Decal material is null while creating BigDecalRenderMesh"); } newDecal.m_pRenderMesh = MakeBigDecalRenderMesh(pSourceRenderMesh, DecalInfo.vPos, DecalInfo.fSize, DecalInfo.vNormal, newDecal.m_pMaterial, pStatObj ? pStatObj->GetMaterial() : NULL); if (!newDecal.m_pRenderMesh) { return false; // no geometry found } } else if (!DecalInfo.ownerInfo.pRenderNode && DecalInfo.ownerInfo.pDecalReceivers && (DecalInfo.fSize > fWrapMinSize || pCallerManagedDecal) && !DecalInfo.bDeferred) { newDecal.m_eDecalType = eDecalType_WS_Merged; assert(!newDecal.m_pRenderMesh); // put into normal hit direction of projection DecalInfo.vNormal = -DecalInfo.vHitDirection; if (!DecalInfo.vNormal.IsZero()) { DecalInfo.vNormal.Normalize(); } Vec3 vSize(DecalInfo.fSize * 1.333f, DecalInfo.fSize * 1.333f, DecalInfo.fSize * 1.333f); AABB decalAABB(DecalInfo.vPos - vSize, DecalInfo.vPos + vSize); // build list of affected brushes PodArray lstRMI; for (int nObj = 0; nObj < DecalInfo.ownerInfo.pDecalReceivers->Count(); nObj++) { IRenderNode* pDecalOwner = DecalInfo.ownerInfo.pDecalReceivers->Get(nObj)->pNode; Matrix34A objMat; if (IStatObj* pEntObject = pDecalOwner->GetEntityStatObj(DecalInfo.ownerInfo.nRenderNodeSlotId, 0, &objMat)) { SRenderMeshInfoInput rmi; rmi.pMesh = pEntObject->GetRenderMesh(); rmi.pMat = pEntObject->GetMaterial(); rmi.mat = objMat; if (rmi.pMesh) { AABB transAABB = AABB::CreateTransformedAABB(rmi.mat, pEntObject->GetAABB()); if (Overlap::AABB_AABB(decalAABB, transAABB)) { lstRMI.Add(rmi); } } else if (int nSubObjCount = pEntObject->GetSubObjectCount()) { // multi sub objects for (int nSubObj = 0; nSubObj < nSubObjCount; nSubObj++) { IStatObj::SSubObject* pSubObj = pEntObject->GetSubObject(nSubObj); if (pSubObj->pStatObj) { rmi.pMesh = pSubObj->pStatObj->GetRenderMesh(); rmi.pMat = pSubObj->pStatObj->GetMaterial(); rmi.mat = objMat * pSubObj->tm; if (rmi.pMesh) { AABB transAABB = AABB::CreateTransformedAABB(rmi.mat, pSubObj->pStatObj->GetAABB()); if (Overlap::AABB_AABB(decalAABB, transAABB)) { lstRMI.Add(rmi); } } } } } } } if (!lstRMI.Count()) { return false; } SDecalClipInfo DecalClipInfo; DecalClipInfo.vPos = DecalInfo.vPos; DecalClipInfo.fRadius = DecalInfo.fSize; DecalClipInfo.vProjDir = DecalInfo.vNormal; PodArray outRenderMeshes; CRenderMeshMerger Merger; SMergeInfo info; info.sMeshName = "MergedDecal"; info.sMeshType = "MergedDecal"; info.pDecalClipInfo = &DecalClipInfo; info.vResultOffset = DecalInfo.vPos; newDecal.m_pRenderMesh = Merger.MergeRenderMeshes(lstRMI.GetElements(), lstRMI.Count(), outRenderMeshes, info); if (!newDecal.m_pRenderMesh) { return false; // no geometry found } assert(newDecal.m_pRenderMesh->GetChunks().size() == 1); } else if (DecalInfo.ownerInfo.pRenderNode && (DecalInfo.ownerInfo.pRenderNode->GetRenderNodeType() == eERType_RenderComponent || DecalInfo.ownerInfo.pRenderNode->GetRenderNodeType() == eERType_StaticMeshRenderComponent || DecalInfo.ownerInfo.pRenderNode->GetRenderNodeType() == eERType_DynamicMeshRenderComponent || DecalInfo.ownerInfo.pRenderNode->GetRenderNodeType() == eERType_SkinnedMeshRenderComponent) && DecalInfo.ownerInfo.nRenderNodeSlotId >= 0) { newDecal.m_eDecalType = eDecalType_OS_SimpleQuad; Matrix34A objMat; // transform decal from world space into entity space IStatObj* pEntObject = DecalInfo.ownerInfo.GetOwner(objMat); if (!pEntObject) { return false; } assert(pEntObject); objMat.Invert(); if (useDefinedUpRight) { userDefinedRight = objMat.TransformVector(userDefinedRight).GetNormalized(); userDefinedUp = objMat.TransformVector(userDefinedUp).GetNormalized(); assert(fabsf(DecalInfo.vNormal.Dot(-DecalInfo.vHitDirection.GetNormalized()) - 1.0f) < 1e-4f); } DecalInfo.vNormal = objMat.TransformVector(DecalInfo.vNormal).GetNormalized(); DecalInfo.vPos = objMat.TransformPoint(DecalInfo.vPos); // find object scale if (DecalInfo.ownerInfo.pRenderNode->GetRenderNodeType() == eERType_Vegetation) { _fObjScale = ((CVegetation*)(DecalInfo.ownerInfo.pRenderNode))->GetScale(); } else { Vec3 vTest(0, 0, 1.f); vTest = objMat.TransformVector(vTest); _fObjScale = 1.f / vTest.len(); } DecalInfo.fSize /= _fObjScale; } else { bool isHole = true; auto enumerationCallback = [&](AzFramework::Terrain::TerrainDataRequests* terrain) -> bool { isHole = false; if (!DecalInfo.preventDecalOnGround && DecalInfo.fSize > (fWrapMinSize * 2.f) && !DecalInfo.ownerInfo.pRenderNode && (DecalInfo.vPos.z - terrain->GetHeightFromFloats(DecalInfo.vPos.x, DecalInfo.vPos.y)) < DecalInfo.fSize && !DecalInfo.bDeferred) { newDecal.m_eDecalType = eDecalType_WS_OnTheGround; const AZ::Vector2 terrainGridResolution = terrain->GetTerrainGridResolution(); const float unitSizeX = terrainGridResolution.GetX(); const float unitSizeY = terrainGridResolution.GetY(); const float x1 = (DecalInfo.vPos.x - DecalInfo.fSize) / unitSizeX * unitSizeX - unitSizeX; const float x2 = (DecalInfo.vPos.x + DecalInfo.fSize) / unitSizeX * unitSizeX + unitSizeX; const float y1 = (DecalInfo.vPos.y - DecalInfo.fSize) / unitSizeY * unitSizeY - unitSizeY; const float y2 = (DecalInfo.vPos.y + DecalInfo.fSize) / unitSizeY * unitSizeY + unitSizeY; for (float x = x1; x <= x2; x += unitSizeX) { for (float y = y1; y <= y2; y += unitSizeY) { if (terrain->GetIsHoleFromFloats(x, y)) { isHole = true; return false; } } } } // Only one handler should exist. return false; }; AzFramework::Terrain::TerrainDataRequestBus::EnumerateHandlers(enumerationCallback); if (isHole) { return false; } else { newDecal.m_eDecalType = eDecalType_WS_SimpleQuad; } DecalInfo.ownerInfo.pRenderNode = NULL; } // spawn if (!useDefinedUpRight) { if (DecalInfo.vNormal.Dot(Vec3(0, 0, 1)) > 0.999f) { // floor newDecal.m_vRight = Vec3(0, 1, 0); newDecal.m_vUp = Vec3(-1, 0, 0); } else if (DecalInfo.vNormal.Dot(Vec3(0, 0, -1)) > 0.999f) { // ceil newDecal.m_vRight = Vec3(1, 0, 0); newDecal.m_vUp = Vec3(0, -1, 0); } else if (!DecalInfo.vNormal.IsZero()) { newDecal.m_vRight = DecalInfo.vNormal.Cross(Vec3(0, 0, 1)); newDecal.m_vRight.Normalize(); newDecal.m_vUp = DecalInfo.vNormal.Cross(newDecal.m_vRight); newDecal.m_vUp.Normalize(); } // rotate vectors if (!DecalInfo.vNormal.IsZero()) { AngleAxis rotation(DecalInfo.fAngle, DecalInfo.vNormal); newDecal.m_vRight = rotation * newDecal.m_vRight; newDecal.m_vUp = rotation * newDecal.m_vUp; } } else { newDecal.m_vRight = userDefinedRight; newDecal.m_vUp = userDefinedUp; } newDecal.m_vFront = DecalInfo.vNormal; newDecal.m_vPos = DecalInfo.vPos; newDecal.m_vPos += DecalInfo.vNormal * 0.001f / _fObjScale; newDecal.m_fSize = DecalInfo.fSize; newDecal.m_fLifeTime = DecalInfo.fLifeTime * GetCVars()->e_DecalsLifeTimeScale; assert(!DecalInfo.pIStatObj); // not used -> not supported newDecal.m_ownerInfo.pRenderNode = DecalInfo.ownerInfo.pRenderNode; if (DecalInfo.ownerInfo.pRenderNode) { DecalInfo.ownerInfo.pRenderNode->m_nInternalFlags |= IRenderNode::DECAL_OWNER; } newDecal.m_fGrowTime = DecalInfo.fGrowTime; newDecal.m_fGrowTimeAlpha = DecalInfo.fGrowTimeAlpha; newDecal.m_fLifeBeginTime = GetTimer()->GetCurrTime(); if (DecalInfo.pIStatObj && !pCallerManagedDecal) { //assert(!"Geometry decals neede to be re-debugged"); DecalInfo.pIStatObj->AddRef(); } if (!pCallerManagedDecal) { m_arrbActiveDecals[ m_nCurDecal ] = true; ++m_nCurDecal; } #ifdef _DEBUG if (newDecal.m_ownerInfo.pRenderNode) { cry_strcpy(newDecal.m_decalOwnerEntityClassName, newDecal.m_ownerInfo.pRenderNode->GetEntityClassName()); cry_strcpy(newDecal.m_decalOwnerName, newDecal.m_ownerInfo.pRenderNode->GetName()); newDecal.m_decalOwnerType = newDecal.m_ownerInfo.pRenderNode->GetRenderNodeType(); } else { newDecal.m_decalOwnerEntityClassName[0] = '\0'; newDecal.m_decalOwnerName[0] = '\0'; newDecal.m_decalOwnerType = eERType_NotRenderNode; } #endif return true; } void CDecalManager::Update(const float fFrameTime) { CryPrefetch(&m_arrbActiveDecals[0]); CryPrefetch(&m_arrbActiveDecals[128]); CryPrefetch(&m_arrbActiveDecals[256]); CryPrefetch(&m_arrbActiveDecals[384]); for (int i = 0; i < DECAL_COUNT; i++) { if (m_arrbActiveDecals[i]) { IRenderNode* pRenderNode = m_arrDecals[i].m_ownerInfo.pRenderNode; if (m_arrDecals[i].Update(m_arrbActiveDecals[i], fFrameTime)) { if (pRenderNode && m_arrTempUpdatedOwners.Find(pRenderNode) < 0) { m_arrTempUpdatedOwners.Add(pRenderNode); } } } } for (int i = 0; i < m_arrTempUpdatedOwners.Count(); i++) { m_arrTempUpdatedOwners[i]->m_nInternalFlags &= ~IRenderNode::UPDATE_DECALS; } m_arrTempUpdatedOwners.Clear(); } void CDecalManager::Render(const SRenderingPassInfo& passInfo) { FUNCTION_PROFILER_3DENGINE; if (!passInfo.RenderDecals() || !GetObjManager()) { return; } float fCurrTime = GetTimer()->GetCurrTime(); float fZoom = passInfo.GetZoomFactor(); static int nLastUpdateStreamingPrioriryRoundId = 0; bool bPrecacheMaterial = nLastUpdateStreamingPrioriryRoundId != GetObjManager()->GetUpdateStreamingPrioriryRoundId(); nLastUpdateStreamingPrioriryRoundId = GetObjManager()->GetUpdateStreamingPrioriryRoundId(); static int nLastUpdateStreamingPrioriryRoundIdFast = 0; bool bPrecacheMaterialFast = nLastUpdateStreamingPrioriryRoundIdFast != GetObjManager()->GetUpdateStreamingPrioriryRoundIdFast(); nLastUpdateStreamingPrioriryRoundIdFast = GetObjManager()->GetUpdateStreamingPrioriryRoundIdFast(); const CCamera& rCamera = passInfo.GetCamera(); // draw for (int i = 0; i < DECAL_COUNT; i++) { if (m_arrbActiveDecals[i]) { CDecal* pDecal = &m_arrDecals[i]; pDecal->m_vWSPos = pDecal->GetWorldPosition(); float fDist = rCamera.GetPosition().GetDistance(pDecal->m_vWSPos) * fZoom; float fMaxViewDist = pDecal->m_fWSSize * ENTITY_DECAL_DIST_FACTOR * 3.0f; if (fDist < fMaxViewDist) { if (rCamera.IsSphereVisible_F(Sphere(pDecal->m_vWSPos, pDecal->m_fWSSize))) { bool bAfterWater = GetObjManager()->IsAfterWater(pDecal->m_vWSPos, passInfo); if (pDecal->m_pMaterial) { if (passInfo.IsGeneralPass()) { if (bPrecacheMaterialFast && (fDist < GetFloatCVar(e_StreamPredictionMinFarZoneDistance))) { if (CMatInfo* pMatInfo = (CMatInfo*)pDecal->m_pMaterial.get()) { pMatInfo->PrecacheMaterial(fDist, NULL, true); } } if (bPrecacheMaterial) { if (CMatInfo* pMatInfo = (CMatInfo*)pDecal->m_pMaterial.get()) { pMatInfo->PrecacheMaterial(fDist, NULL, false); } } } // TODO: take entity orientation into account Vec3 vSize(pDecal->m_fWSSize, pDecal->m_fWSSize, pDecal->m_fWSSize); AABB aabb(pDecal->m_vWSPos - vSize, pDecal->m_vWSPos + vSize); float fDistFading = SATURATE((1.f - fDist / fMaxViewDist) * DIST_FADING_FACTOR); SRendItemSorter rendItemSorter = SRendItemSorter::CreateRendItemSorter(passInfo); pDecal->Render(fCurrTime, bAfterWater, fDistFading, fDist, passInfo, rendItemSorter); if (GetCVars()->e_Decals > 1) { Vec3 vCenter = pDecal->m_vWSPos; AABB aabbCenter(vCenter - vSize * 0.05f, vCenter + vSize * 0.05f); DrawBBox(aabb); DrawBBox(aabbCenter, Col_Yellow); Vec3 vNormal(Vec3(pDecal->m_vUp).Cross(-pDecal->m_vRight).GetNormalized()); Matrix34A objMat; IStatObj* pEntObject = pDecal->m_ownerInfo.GetOwner(objMat); if (pEntObject) { vNormal = objMat.TransformVector(vNormal).GetNormalized(); } DrawLine(vCenter, vCenter + vNormal * pDecal->m_fWSSize); if (pDecal->m_pRenderMesh) { pDecal->m_pRenderMesh->GetBBox(aabb.min, aabb.max); DrawBBox(aabb, Col_Red); } } } } } } } } ////////////////////////////////////////////////////////////////////////// void CDecalManager::OnEntityDeleted(IRenderNode* pRenderNode) { FUNCTION_PROFILER_3DENGINE; // remove decals of this entity for (int i = 0; i < DECAL_COUNT; i++) { if (m_arrbActiveDecals[i]) { if (m_arrDecals[i].m_ownerInfo.pRenderNode == pRenderNode) { if (GetCVars()->e_Decals == 2) { CDecal& decal = m_arrDecals[i]; Vec3 vPos = decal.GetWorldPosition(); const char* szOwnerName = "none"; #ifdef _DEBUG szOwnerName = decal.m_decalOwnerName; #endif PrintMessage("Debug: C3DEngine::OnDecalDeleted: Pos=(%.1f,%.1f,%.1f) Size=%.2f DecalMaterial=%s OwnerName=%s", vPos.x, vPos.y, vPos.z, decal.m_fSize, decal.m_pMaterial ? decal.m_pMaterial->GetName() : "none", szOwnerName); } m_arrbActiveDecals[i] = false; m_arrDecals[i].FreeRenderData(); } } } // update decal render nodes PodArray lstObjects; Get3DEngine()->GetObjectsByTypeGlobal(lstObjects, eERType_Decal, NULL); if (Get3DEngine()->GetVisAreaManager()) { Get3DEngine()->GetVisAreaManager()->GetObjectsByType(lstObjects, eERType_Decal, NULL); } for (int i = 0; i < lstObjects.Count(); i++) { ((CDecalRenderNode*)lstObjects[i])->RequestUpdate(); } } ////////////////////////////////////////////////////////////////////////// void CDecalManager::OnRenderMeshDeleted(IRenderMesh* pRenderMesh) { // remove decals of this entity for (int i = 0; i < DECAL_COUNT; i++) { if (m_arrbActiveDecals[i]) { if ( (m_arrDecals[i].m_ownerInfo.pRenderNode && ( m_arrDecals[i].m_ownerInfo.pRenderNode->GetRenderMesh(0) == pRenderMesh || m_arrDecals[i].m_ownerInfo.pRenderNode->GetRenderMesh(1) == pRenderMesh || m_arrDecals[i].m_ownerInfo.pRenderNode->GetRenderMesh(2) == pRenderMesh)) || (m_arrDecals[i].m_pRenderMesh && m_arrDecals[i].m_pRenderMesh->GetVertexContainer() == pRenderMesh) ) { m_arrbActiveDecals[i] = false; m_arrDecals[i].FreeRenderData(); // PrintMessage("CDecalManager::OnRenderMeshDeleted succseed"); } } } } void CDecalManager::MoveToEdge(IRenderMesh* pRM, const float fRadius, Vec3& vOutPos, Vec3& vOutNormal, const Vec3& vTri0, const Vec3& vTri1, const Vec3& vTri2) { FUNCTION_PROFILER_3DENGINE; AABB boxRM; pRM->GetBBox(boxRM.min, boxRM.max); Sphere sp(vOutPos, fRadius); if (!Overlap::Sphere_AABB(sp, boxRM)) { return; } // get position offset and stride int nPosStride = 0; byte* pPos = pRM->GetPosPtr(nPosStride, FSL_READ); vtx_idx* pInds = pRM->GetIndexPtr(FSL_READ); if (!pPos || !pInds) { return; } int nInds = pRM->GetIndicesCount(); // if(nInds>6000) // return; // skip insane objects assert(nInds % 3 == 0); if (!vOutNormal.IsZero()) { vOutNormal.Normalize(); } else { return; } float bestDot = 2.0f; Vec3 bestNormal(ZERO); Vec3 bestPoint(ZERO); // render tris TRenderChunkArray& Chunks = pRM->GetChunks(); for (int nChunkId = 0; nChunkId < Chunks.size(); nChunkId++) { CRenderChunk* pChunk = &Chunks[nChunkId]; if (pChunk->m_nMatFlags & MTL_FLAG_NODRAW || !pChunk->pRE) { continue; } int nLastIndexId = pChunk->nFirstIndexId + pChunk->nNumIndices; for (int i = pChunk->nFirstIndexId; i < nLastIndexId; i += 3) { assert(pInds[i + 0] < pChunk->nFirstVertId + pChunk->nNumVerts); assert(pInds[i + 1] < pChunk->nFirstVertId + pChunk->nNumVerts); assert(pInds[i + 2] < pChunk->nFirstVertId + pChunk->nNumVerts); assert(pInds[i + 0] >= pChunk->nFirstVertId); assert(pInds[i + 1] >= pChunk->nFirstVertId); assert(pInds[i + 2] >= pChunk->nFirstVertId); // get tri vertices Vec3 v0 = (*(Vec3*)&pPos[nPosStride * pInds[i + 0]]); Vec3 v1 = (*(Vec3*)&pPos[nPosStride * pInds[i + 1]]); Vec3 v2 = (*(Vec3*)&pPos[nPosStride * pInds[i + 2]]); bool first = false; bool second = false; bool third = false; if (v0 == vTri0 || v0 == vTri1 || v0 == vTri2) { first = true; } else if (v1 == vTri0 || v1 == vTri1 || v1 == vTri2) { second = true; } else if (v2 == vTri0 || v2 == vTri1 || v2 == vTri2) { third = true; } if (first || second || third) { // get triangle normal Vec3 vNormal = (v1 - v0).Cross(v2 - v0).GetNormalized(); float testDot = vNormal.Dot(vOutNormal); if (testDot < bestDot) { bestDot = testDot; bestNormal = vNormal; if (first) { bestPoint = v0; } else if (second) { bestPoint = v1; } else if (third) { bestPoint = v2; } } } } } if (bestDot < 1.0f) { vOutNormal = (bestNormal + vOutNormal).GetNormalized(); vOutPos.x = bestPoint.x; vOutPos.y = bestPoint.y; } } void CDecalManager::FillBigDecalIndices(IRenderMesh* pRM, Vec3 vPos, float fRadius, Vec3 vProjDirIn, PodArray* plstIndices, _smart_ptr pMat, AABB& meshBBox, float& texelAreaDensity) { FUNCTION_PROFILER_3DENGINE; AABB boxRM; pRM->GetBBox(boxRM.min, boxRM.max); Sphere sp(vPos, fRadius); if (!Overlap::Sphere_AABB(sp, boxRM)) { return; } IRenderMesh::ThreadAccessLock lockrm(pRM); HWVSphere hwSphere(sp); // get position offset and stride int nInds = pRM->GetIndicesCount(); if (nInds > GetCVars()->e_DecalsMaxTrisInObject * 3) { return; // skip insane objects } CDecalRenderNode::m_nFillBigDecalIndicesCounter++; int nPosStride = 0; byte* pPos = pRM->GetPosPtr(nPosStride, FSL_READ); if (!pPos) { return; } vtx_idx* pInds = pRM->GetIndexPtr(FSL_READ); if (!pInds) { return; } assert(nInds % 3 == 0); plstIndices->Clear(); bool bPointProj(vProjDirIn.IsZeroFast()); if (!bPointProj) { vProjDirIn.Normalize(); } if (!pMat) { return; } plstIndices->PreAllocate(16); hwvec3 vProjDir = HWVLoadVecUnaligned(&vProjDirIn); int usedTrianglesTotal = 0; TRenderChunkArray& Chunks = pRM->GetChunks(); { hwvec3 meshBBoxMin = HWVLoadVecUnaligned(&meshBBox.min); hwvec3 meshBBoxMax = HWVLoadVecUnaligned(&meshBBox.max); SIMDFConstant(fEpsilon, 0.001f); const int kNumChunks = Chunks.size(); if (bPointProj) { hwvec3 hwvPos = HWVLoadVecUnaligned(&vPos); for (int nChunkId = 0; nChunkId < kNumChunks; nChunkId++) { CRenderChunk* pChunk = &Chunks[nChunkId]; PrefetchLine(&Chunks[nChunkId + 1], 0); PrefetchLine(&pInds[pChunk->nFirstIndexId], 0); if (pChunk->m_nMatFlags & MTL_FLAG_NODRAW || !pChunk->pRE) { continue; } const SShaderItem& shaderItem = pMat->GetShaderItem(pChunk->m_nMatID); if (!shaderItem.m_pShader || !shaderItem.m_pShaderResources) { continue; } if (shaderItem.m_pShader->GetFlags() & (EF_NODRAW | EF_DECAL)) { continue; } PrefetchLine(plstIndices->GetElements(), 0); int usedTriangles = 0; int nLastIndexId = pChunk->nFirstIndexId + pChunk->nNumIndices; int i = pChunk->nFirstIndexId; int iPosIndex0 = nPosStride * pInds[i + 0]; int iPosIndex1 = nPosStride * pInds[i + 1]; int iPosIndex2 = nPosStride * pInds[i + 2]; for (; i < nLastIndexId; i += 3) { assert(pInds[i + 0] < pChunk->nFirstVertId + pChunk->nNumVerts); assert(pInds[i + 1] < pChunk->nFirstVertId + pChunk->nNumVerts); assert(pInds[i + 2] < pChunk->nFirstVertId + pChunk->nNumVerts); assert(pInds[i + 0] >= pChunk->nFirstVertId); assert(pInds[i + 1] >= pChunk->nFirstVertId); assert(pInds[i + 2] >= pChunk->nFirstVertId); PrefetchLine(&pInds[i], 128); int iNextPosIndex0 = 0; int iNextPosIndex1 = 0; int iNextPosIndex2 = 0; if (i + 5 < nLastIndexId) { iNextPosIndex0 = nPosStride * pInds[i + 3]; iNextPosIndex1 = nPosStride * pInds[i + 4]; iNextPosIndex2 = nPosStride * pInds[i + 5]; PrefetchLine(&pPos[iNextPosIndex0], 0); PrefetchLine(&pPos[iNextPosIndex1], 0); PrefetchLine(&pPos[iNextPosIndex2], 0); } // get tri vertices const hwvec3 v0 = HWVLoadVecUnaligned(reinterpret_cast(&pPos[iPosIndex0])); const hwvec3 v1 = HWVLoadVecUnaligned(reinterpret_cast(&pPos[iPosIndex1])); const hwvec3 v2 = HWVLoadVecUnaligned(reinterpret_cast(&pPos[iPosIndex2])); // test the face hwvec3 v0v1Diff = HWVSub(v0, v1); hwvec3 v2v1Diff = HWVSub(v2, v1); hwvec3 vPosv0Diff = HWVSub(hwvPos, v0); hwvec3 vCrossResult = HWVCross(v0v1Diff, v2v1Diff); simdf fDot = HWV3Dot(vPosv0Diff, vCrossResult); if (SIMDFGreaterThan(fDot, fEpsilon)) { if (Overlap::HWVSphere_TriangleFromPoints(hwSphere, v0, v1, v2)) { plstIndices->AddList(&pInds[i], 3); hwvec3 triBBoxMax1 = HWVMax(v1, v0); hwvec3 triBBoxMax2 = HWVMax(meshBBoxMax, v2); hwvec3 triBBoxMin1 = HWVMin(v1, v0); hwvec3 triBBoxMin2 = HWVMin(meshBBoxMin, v2); meshBBoxMax = HWVMax(triBBoxMax1, triBBoxMax2); meshBBoxMin = HWVMin(triBBoxMin1, triBBoxMin2); usedTriangles++; } } iPosIndex0 = iNextPosIndex0; iPosIndex1 = iNextPosIndex1; iPosIndex2 = iNextPosIndex2; } if (pChunk->m_texelAreaDensity > 0.0f && pChunk->m_texelAreaDensity != (float)UINT_MAX) { texelAreaDensity += usedTriangles * pChunk->m_texelAreaDensity; usedTrianglesTotal += usedTriangles; } } } else { for (int nChunkId = 0; nChunkId < kNumChunks; nChunkId++) { CRenderChunk* pChunk = &Chunks[nChunkId]; if (nChunkId + 1 < kNumChunks) { PrefetchLine(&Chunks[nChunkId + 1], 0); } PrefetchLine(&pInds[pChunk->nFirstIndexId], 0); if (pChunk->m_nMatFlags & MTL_FLAG_NODRAW || !pChunk->pRE) { continue; } const SShaderItem& shaderItem = pMat->GetShaderItem(pChunk->m_nMatID); if (!shaderItem.m_pShader || !shaderItem.m_pShaderResources) { continue; } if (shaderItem.m_pShader->GetFlags() & (EF_NODRAW | EF_DECAL)) { continue; } PrefetchLine(plstIndices->GetElements(), 0); int usedTriangles = 0; const int nLastIndexId = pChunk->nFirstIndexId + pChunk->nNumIndices; const int nLastValidIndexId = nLastIndexId - 1; int i = pChunk->nFirstIndexId; int iNextPosIndex0 = 0; int iNextPosIndex1 = 0; int iNextPosIndex2 = 0; if (i + 5 < nLastIndexId) { iNextPosIndex0 = nPosStride * pInds[i + 3]; iNextPosIndex1 = nPosStride * pInds[i + 4]; iNextPosIndex2 = nPosStride * pInds[i + 5]; PrefetchLine(&pPos[iNextPosIndex0], 0); PrefetchLine(&pPos[iNextPosIndex1], 0); PrefetchLine(&pPos[iNextPosIndex2], 0); } hwvec3 v0Next = HWVLoadVecUnaligned(reinterpret_cast(&pPos[nPosStride * pInds[i + 0]])); hwvec3 v1Next = HWVLoadVecUnaligned(reinterpret_cast(&pPos[nPosStride * pInds[i + 1]])); hwvec3 v2Next = HWVLoadVecUnaligned(reinterpret_cast(&pPos[nPosStride * pInds[i + 2]])); const int nLastIndexToUse = nLastIndexId - 3; for (; i < nLastIndexToUse; i += 3) { assert(pInds[i + 0] < pChunk->nFirstVertId + pChunk->nNumVerts); assert(pInds[i + 1] < pChunk->nFirstVertId + pChunk->nNumVerts); assert(pInds[i + 2] < pChunk->nFirstVertId + pChunk->nNumVerts); assert(pInds[i + 0] >= pChunk->nFirstVertId); assert(pInds[i + 1] >= pChunk->nFirstVertId); assert(pInds[i + 2] >= pChunk->nFirstVertId); const int iLookaheadIdx = min_branchless(i + 8, nLastValidIndexId); const int iPrefetchIndex2 = nPosStride * pInds[iLookaheadIdx]; // get tri vertices const hwvec3 v0 = v0Next; const hwvec3 v1 = v1Next; const hwvec3 v2 = v2Next; //Need to prefetch further ahead byte* pPrefetch = &pPos[iPrefetchIndex2]; PrefetchLine(pPrefetch, 0); v0Next = HWVLoadVecUnaligned(reinterpret_cast(&pPos[iNextPosIndex0])); // get triangle normal hwvec3 v1v0Diff = HWVSub(v1, v0); hwvec3 v2v0Diff = HWVSub(v2, v0); v1Next = HWVLoadVecUnaligned(reinterpret_cast(&pPos[iNextPosIndex1])); hwvec3 vNormal = HWVCross(v1v0Diff, v2v0Diff); simdf fDot = HWV3Dot(vNormal, vProjDir); v2Next = HWVLoadVecUnaligned(reinterpret_cast(&pPos[iNextPosIndex2])); // test the face if (SIMDFGreaterThan(fDot, fEpsilon)) { if (Overlap::HWVSphere_TriangleFromPoints(hwSphere, v0, v1, v2)) { plstIndices->AddList(&pInds[i], 3); hwvec3 triBBoxMax1 = HWVMax(v1, v0); hwvec3 triBBoxMax2 = HWVMax(meshBBoxMax, v2); hwvec3 triBBoxMin1 = HWVMin(v1, v0); hwvec3 triBBoxMin2 = HWVMin(meshBBoxMin, v2); meshBBoxMax = HWVMax(triBBoxMax1, triBBoxMax2); meshBBoxMin = HWVMin(triBBoxMin1, triBBoxMin2); usedTriangles++; } } iNextPosIndex0 = nPosStride * pInds[iLookaheadIdx - 2]; iNextPosIndex1 = nPosStride * pInds[iLookaheadIdx - 1]; iNextPosIndex2 = iPrefetchIndex2; } const hwvec3 v0 = v0Next; const hwvec3 v1 = v1Next; const hwvec3 v2 = v2Next; // get triangle normal hwvec3 v1v0Diff = HWVSub(v1, v0); hwvec3 v2v0Diff = HWVSub(v2, v0); hwvec3 vNormal = HWVCross(v1v0Diff, v2v0Diff); simdf fDot = HWV3Dot(vNormal, vProjDir); // test the face if (SIMDFGreaterThan(fDot, fEpsilon)) { if (Overlap::HWVSphere_TriangleFromPoints(hwSphere, v0, v1, v2)) { plstIndices->AddList(&pInds[i], 3); hwvec3 triBBoxMax1 = HWVMax(v1, v0); hwvec3 triBBoxMax2 = HWVMax(meshBBoxMax, v2); hwvec3 triBBoxMin1 = HWVMin(v1, v0); hwvec3 triBBoxMin2 = HWVMin(meshBBoxMin, v2); meshBBoxMax = HWVMax(triBBoxMax1, triBBoxMax2); meshBBoxMin = HWVMin(triBBoxMin1, triBBoxMin2); // triBBoxMax = HWVMax(triBBoxMax, v2); // triBBoxMin = HWVMin(triBBoxMin, v2); // // meshBBoxMax = HWVMax(triBBoxMax, meshBBoxMax); // meshBBoxMin = HWVMin(triBBoxMin, meshBBoxMin); usedTriangles++; } } if (pChunk->m_texelAreaDensity > 0.0f && pChunk->m_texelAreaDensity != (float)UINT_MAX) { texelAreaDensity += usedTriangles * pChunk->m_texelAreaDensity; usedTrianglesTotal += usedTriangles; } } } HWVSaveVecUnaligned(&meshBBox.max, meshBBoxMax); HWVSaveVecUnaligned(&meshBBox.min, meshBBoxMin); } if (usedTrianglesTotal != 0) { texelAreaDensity = texelAreaDensity / usedTrianglesTotal; } } _smart_ptr CDecalManager::MakeBigDecalRenderMesh(IRenderMesh* pSourceRenderMesh, Vec3 vPos, float fRadius, Vec3 vProjDir, _smart_ptr pDecalMat, _smart_ptr pSrcMat) { if (!pSourceRenderMesh || pSourceRenderMesh->GetVerticesCount() == 0) { return 0; } // make indices of this decal PodArray lstIndices; AABB meshBBox(vPos, vPos); float texelAreaDensity = 0.0f; if (pSourceRenderMesh && pSourceRenderMesh->GetVerticesCount()) { FillBigDecalIndices(pSourceRenderMesh, vPos, fRadius, vProjDir, &lstIndices, pSrcMat, meshBBox, texelAreaDensity); } if (!lstIndices.Count()) { return 0; } // make fake vert buffer with one vertex // todo: remove this PodArray EmptyVertBuffer; EmptyVertBuffer.Add(SVF_P3S_C4B_T2S()); _smart_ptr pRenderMesh = 0; pRenderMesh = GetRenderer()->CreateRenderMeshInitialized(EmptyVertBuffer.GetElements(), EmptyVertBuffer.Count(), eVF_P3S_C4B_T2S, lstIndices.GetElements(), lstIndices.Count(), prtTriangleList, "BigDecalOnStatObj", "BigDecal", eRMT_Static, 1, 0, 0, 0, false, false, 0); pRenderMesh->SetVertexContainer(pSourceRenderMesh); pRenderMesh->SetChunk(pDecalMat, 0, pSourceRenderMesh->GetVerticesCount(), 0, lstIndices.Count(), texelAreaDensity, eVF_P3S_C4B_T2S); pRenderMesh->SetBBox(meshBBox.min, meshBBox.max); return pRenderMesh; } void CDecalManager::GetMemoryUsage(ICrySizer* pSizer) const { pSizer->Add (*this); } void CDecalManager::DeleteDecalsInRange(AABB* pAreaBox, IRenderNode* pEntity) { if (GetCVars()->e_Decals > 1 && pAreaBox) { DrawBBox(*pAreaBox); } for (int i = 0; i < DECAL_COUNT; i++) { if (!m_arrbActiveDecals[i]) { continue; } if (pEntity && (pEntity != m_arrDecals[i].m_ownerInfo.pRenderNode)) { continue; } if (pAreaBox) { Vec3 vPos = m_arrDecals[i].GetWorldPosition(); Vec3 vSize(m_arrDecals[i].m_fWSSize, m_arrDecals[i].m_fWSSize, m_arrDecals[i].m_fWSSize); AABB decalBox(vPos - vSize, vPos + vSize); if (!Overlap::AABB_AABB(*pAreaBox, decalBox)) { continue; } } if (m_arrDecals[i].m_eDecalType != eDecalType_WS_OnTheGround) { m_arrbActiveDecals[i] = false; } m_arrDecals[i].FreeRenderData(); if (GetCVars()->e_Decals == 2) { CDecal& decal = m_arrDecals[i]; PrintMessage("Debug: CDecalManager::DeleteDecalsInRange: Pos=(%.1f,%.1f,%.1f) Size=%.2f DecalMaterial=%s", decal.m_vPos.x, decal.m_vPos.y, decal.m_vPos.z, decal.m_fSize, decal.m_pMaterial ? decal.m_pMaterial->GetName() : "none"); } } } void CDecalManager::Serialize(TSerialize ser) { ser.BeginGroup("StaticDecals"); if (ser.IsReading()) { Reset(); } uint32 dwDecalCount = 0; for (uint32 dwI = 0; dwI < DECAL_COUNT; dwI++) { if (m_arrbActiveDecals[dwI]) { dwDecalCount++; } } ser.Value("DecalCount", dwDecalCount); if (ser.IsWriting()) { for (uint32 _dwI = 0; _dwI < DECAL_COUNT; _dwI++) { if (m_arrbActiveDecals[_dwI]) { CDecal& ref = m_arrDecals[_dwI]; ser.BeginGroup("Decal"); ser.Value("Pos", ref.m_vPos); ser.Value("Right", ref.m_vRight); ser.Value("Up", ref.m_vUp); ser.Value("Front", ref.m_vFront); ser.Value("Size", ref.m_fSize); ser.Value("WSPos", ref.m_vWSPos); ser.Value("WSSize", ref.m_fWSSize); ser.Value("fLifeTime", ref.m_fLifeTime); // serialize material, handle legacy decals with textureID converted to material created at runtime string matName(""); if (ref.m_pMaterial && ref.m_pMaterial->GetName()) { matName = ref.m_pMaterial->GetName(); } ser.Value("MatName", matName); // TODO: IStatObj * m_pStatObj; // only if real 3d object is used as decal () // TODO: IRenderNode * m_pDecalOwner; // transformation parent object (0 means parent is the world) ser.Value("nRenderNodeSlotId", ref.m_ownerInfo.nRenderNodeSlotId); ser.Value("nRenderNodeSlotSubObjectId", ref.m_ownerInfo.nRenderNodeSlotSubObjectId); int nDecalType = (int)ref.m_eDecalType; ser.Value("eDecalType", nDecalType); ser.Value("fGrowTime", ref.m_fGrowTime); ser.Value("fGrowTimeAlpha", ref.m_fGrowTimeAlpha); ser.Value("fLifeBeginTime", ref.m_fLifeBeginTime); bool bBigDecalUsed = ref.IsBigDecalUsed(); ser.Value("bBigDecal", bBigDecalUsed); // m_arrBigDecalRMs[] will be created on the fly so no need load/save it if (bBigDecalUsed) { for (uint32 dwI = 0; dwI < sizeof(ref.m_arrBigDecalRMCustomData) / sizeof(ref.m_arrBigDecalRMCustomData[0]); ++dwI) { char szName[16]; sprintf_s(szName, "BDCD%d", dwI); ser.Value(szName, ref.m_arrBigDecalRMCustomData[dwI]); } } ser.EndGroup(); } } } else if (ser.IsReading()) { m_nCurDecal = 0; for (uint32 _dwI = 0; _dwI < dwDecalCount; _dwI++) { if (m_nCurDecal < DECAL_COUNT) { CDecal& ref = m_arrDecals[m_nCurDecal]; ref.FreeRenderData(); ser.BeginGroup("Decal"); ser.Value("Pos", ref.m_vPos); ser.Value("Right", ref.m_vRight); ser.Value("Up", ref.m_vUp); ser.Value("Front", ref.m_vFront); ser.Value("Size", ref.m_fSize); ser.Value("WSPos", ref.m_vWSPos); ser.Value("WSSize", ref.m_fWSSize); ser.Value("fLifeTime", ref.m_fLifeTime); // serialize material, handle legacy decals with textureID converted to material created at runtime string matName(""); ser.Value("MatName", matName); bool isTempMat(false); ser.Value("IsTempMat", isTempMat); ref.m_pMaterial = 0; if (!matName.empty()) { ref.m_pMaterial = GetMatMan()->LoadMaterial(matName.c_str(), false, true); if (!ref.m_pMaterial) { Warning("Decal material \"%s\" not found!\n", matName.c_str()); } } // TODO: IStatObj * m_pStatObj; // only if real 3d object is used as decal () // TODO: IRenderNode * m_pDecalOwner; // transformation parent object (0 means parent is the world) ser.Value("nRenderNodeSlotId", ref.m_ownerInfo.nRenderNodeSlotId); ser.Value("nRenderNodeSlotSubObjectId", ref.m_ownerInfo.nRenderNodeSlotSubObjectId); int nDecalType = eDecalType_Undefined; ser.Value("eDecalType", nDecalType); ref.m_eDecalType = (EDecal_Type)nDecalType; ser.Value("fGrowTime", ref.m_fGrowTime); ser.Value("fGrowTimeAlpha", ref.m_fGrowTimeAlpha); ser.Value("fLifeBeginTime", ref.m_fLifeBeginTime); // no need to to store m_arrBigDecalRMs[] as it becomes recreated bool bBigDecalsAreaUsed = false; ser.Value("bBigDecals", bBigDecalsAreaUsed); if (bBigDecalsAreaUsed) { for (uint32 dwI = 0; dwI < sizeof(ref.m_arrBigDecalRMCustomData) / sizeof(ref.m_arrBigDecalRMCustomData[0]); ++dwI) { char szName[16]; sprintf_s(szName, "BDCD%d", dwI); ser.Value(szName, ref.m_arrBigDecalRMCustomData[dwI]); } } // m_arrBigDecalRMs[] will be created on the fly so no need load/save it m_arrbActiveDecals[m_nCurDecal] = bool(nDecalType != eDecalType_Undefined); ++m_nCurDecal; ser.EndGroup(); } } } ser.EndGroup(); } _smart_ptr CDecalManager::GetMaterialForDecalTexture(const char* pTextureName) { if (!pTextureName || *pTextureName == 0) { return 0; } IMaterialManager* pMatMan = GetMatMan(); _smart_ptr pMat = pMatMan->FindMaterial(pTextureName); if (pMat) { return pMat; } _smart_ptr pMatSrc = pMatMan->LoadMaterial("EngineAssets/Materials/Decals/Default", false, true); if (pMatSrc) { _smart_ptr pMatDst = pMatMan->CreateMaterial(pTextureName, pMatSrc->GetFlags() | MTL_FLAG_NON_REMOVABLE); if (pMatDst) { SShaderItem& si = pMatSrc->GetShaderItem(); SInputShaderResources isr = si.m_pShaderResources; // This will create texture data insertion to the table for the diffuse slot isr.m_TexturesResourcesMap[EFTT_DIFFUSE].m_Name = pTextureName; SShaderItem siDst = GetRenderer()->EF_LoadShaderItem(si.m_pShader->GetName(), true, 0, &isr, si.m_pShader->GetGenerationMask()); pMatDst->AssignShaderItem(siDst); return pMatDst; } } return 0; } IStatObj* SDecalOwnerInfo::GetOwner(Matrix34A& objMat) { if (!pRenderNode) { return NULL; } IStatObj* pStatObj = NULL; if (pStatObj = pRenderNode->GetEntityStatObj(nRenderNodeSlotId, nRenderNodeSlotSubObjectId, &objMat, true)) { if (nRenderNodeSlotSubObjectId >= 0 && nRenderNodeSlotSubObjectId < pStatObj->GetSubObjectCount()) { IStatObj::SSubObject* pSubObj = pStatObj->GetSubObject(nRenderNodeSlotSubObjectId); pStatObj = pSubObj->pStatObj; objMat = objMat * pSubObj->tm; } } else if (ICharacterInstance* pChar = pRenderNode->GetEntityCharacter(nRenderNodeSlotId, &objMat)) { if (nRenderNodeSlotSubObjectId >= 0) { pStatObj = pChar->GetISkeletonPose()->GetStatObjOnJoint(nRenderNodeSlotSubObjectId); const QuatT q = pChar->GetISkeletonPose()->GetAbsJointByID(nRenderNodeSlotSubObjectId); objMat = objMat * Matrix34(q); } } if (pStatObj && (pStatObj->GetFlags() & STATIC_OBJECT_HIDDEN)) { return NULL; } if (pStatObj) { if (int nMinLod = ((CStatObj*)pStatObj)->GetMinUsableLod()) { if (IStatObj* pLodObj = pStatObj->GetLodObject(nMinLod)) { pStatObj = pLodObj; } } } return pStatObj; }