#include "CActorLights.hpp" #include "Graphics/CModel.hpp" #include "World/CGameArea.hpp" #include "CStateManager.hpp" #include "Collision/CGameCollision.hpp" #include "Camera/CFirstPersonCamera.hpp" #include "World/CGameLight.hpp" #include "TCastTo.hpp" #include "GameGlobalObjects.hpp" #include "World/CExplosion.hpp" #include "Graphics/CBooRenderer.hpp" namespace urde { s32 CActorLights::sFrameSchedulerCount = 0; CActorLights::CActorLights(u32 areaUpdateFramePeriod, const zeus::CVector3f& actorPosBias, int maxDynamicLights, int maxAreaLights, bool ambientChannelOverflow, bool layer2, bool disableWorldLights, float positionUpdateThreshold) : x2a8_areaUpdateFramePeriod(areaUpdateFramePeriod) , x2ac_actorPosBias(actorPosBias) , x2b8_maxAreaLights(maxAreaLights) , x2bc_maxDynamicLights(maxDynamicLights) , x2cc_actorPositionDeltaUpdateThreshold(positionUpdateThreshold * positionUpdateThreshold) { x298_24_dirty = true; x298_25_castShadows = true; x298_28_inArea = (!disableWorldLights && maxAreaLights > 0) ? true : false; x298_29_ambienceGenerated = ambientChannelOverflow; x298_30_layer2 = layer2; x298_31_disableWorldLights = disableWorldLights; x299_24_inBrightLight = true; sFrameSchedulerCount++; sFrameSchedulerCount &= 7; } void CActorLights::BuildConstantAmbientLighting() { x299_26_ambientOnly = true; x298_24_dirty = true; x29c_shadowLightArrIdx = -1; x2a0_shadowLightIdx = -1; } void CActorLights::BuildConstantAmbientLighting(const zeus::CColor& color) { x299_26_ambientOnly = false; x288_ambientColor = color; x294_aid = kInvalidAreaId; x298_24_dirty = true; x298_26_hasAreaLights = true; x29c_shadowLightArrIdx = -1; x2a0_shadowLightIdx = -1; } void CActorLights::BuildFakeLightList(const std::vector& lights, const zeus::CColor& color) { BuildConstantAmbientLighting(color); x0_areaLights.clear(); x144_dynamicLights = lights; } void CActorLights::BuildFaceLightList(const CStateManager& mgr, const CGameArea& area, const zeus::CAABox& aabb) { zeus::CTransform fpTransform = mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform(); x288_ambientColor = zeus::CColor::skBlack; x144_dynamicLights.clear(); zeus::CColor accumColor = zeus::CColor::skBlack; for (CEntity* light : mgr.GetLightObjectList()) { if (!light || !light->GetActive()) continue; CGameLight* castLight = static_cast(light); if (TCastToConstPtr explosion = mgr.GetObjectById(castLight->GetParentId())) { CLight originalLight = castLight->GetLight(); CLight explosionLight = originalLight; explosionLight.SetAttenuation( explosionLight.GetAttenuationConstant() * g_tweakGui->GetExplosionLightFalloffMultConstant(), explosionLight.GetAttenuationLinear() * g_tweakGui->GetExplosionLightFalloffMultLinear(), explosionLight.GetAttenuationQuadratic() * g_tweakGui->GetExplosionLightFalloffMultQuadratic()); zeus::CVector3f camToExplo = explosion->GetTranslation() - fpTransform.origin; if (fpTransform.transposeRotate(camToExplo).dot(zeus::CVector3f::skForward) >= 0.f) { camToExplo.y() = -camToExplo.y() + ITweakGui::FaceReflectionDistanceDebugValueToActualValue( g_tweakGui->GetFaceReflectionDistance()); camToExplo.z() = -camToExplo.z() + ITweakGui::FaceReflectionHeightDebugValueToActualValue(g_tweakGui->GetFaceReflectionHeight()); explosionLight.SetPosition(fpTransform * camToExplo); zeus::CSphere sphere(originalLight.GetPosition(), originalLight.GetRadius()); if (aabb.intersects(sphere)) { accumColor += explosionLight.GetNormalIndependentLightingAtPoint(fpTransform.origin); if (originalLight.GetIntensity() > FLT_EPSILON && originalLight.GetRadius() > FLT_EPSILON) x144_dynamicLights.push_back(explosionLight); } } } } float greyscale = accumColor.rgbDot(zeus::CColor(0.3f, 0.6f, 0.1f)); if (greyscale < 0.012f) x144_dynamicLights.clear(); if (greyscale > 0.03f) { float attMul = 1.f / (0.03f / greyscale); for (CLight& light : x144_dynamicLights) light.SetAttenuation(light.GetAttenuationConstant() * attMul, light.GetAttenuationLinear() * attMul, light.GetAttenuationQuadratic() * attMul); } } struct SLightValue { u32 x0_areaLightIdx; zeus::CColor x4_color; float x10_colorMag; float x14_accumulatedMag = 0.f; EPVSVisSetState x18_visiblity; }; void CActorLights::MergeOverflowLight(CLight& out, zeus::CColor& color, const CLight& in, float colorMag) { color += in.GetColor() * colorMag; out.SetAngleAttenuation(in.GetAngleAttenuationConstant() * colorMag + out.GetAngleAttenuationConstant(), in.GetAngleAttenuationLinear() * colorMag + out.GetAngleAttenuationLinear(), in.GetAngleAttenuationQuadratic() * colorMag + out.GetAngleAttenuationQuadratic()); out.SetAttenuation(in.GetAttenuationConstant() * colorMag + out.GetAttenuationConstant(), in.GetAttenuationLinear() * colorMag + out.GetAttenuationLinear(), in.GetAttenuationQuadratic() * colorMag + out.GetAttenuationQuadratic()); out.SetPosition(in.GetPosition() * colorMag + out.GetPosition()); out.SetDirection(in.GetDirection() * colorMag + out.GetDirection()); } void CActorLights::AddOverflowToLights(const CLight& light, const zeus::CColor& color, float mag) { if (mag < 0.001f || x2b8_maxAreaLights < 1) return; mag = 1.f / mag; zeus::CColor useColor = color * mag; useColor.a() = 1.f; x0_areaLights.push_back( CLight::BuildCustom(light.GetPosition() * mag, light.GetDirection() * mag, useColor, light.GetAttenuationConstant() * mag, light.GetAttenuationLinear() * mag, light.GetAttenuationQuadratic() * mag, light.GetAngleAttenuationConstant() * mag, light.GetAngleAttenuationLinear() * mag, light.GetAngleAttenuationQuadratic() * mag)); } void CActorLights::MoveAmbienceToLights(const zeus::CColor& color) { if (x298_29_ambienceGenerated) { x288_ambientColor += color * 0.333333f; x288_ambientColor.a() = 1.f; return; } zeus::CColor useColor = x0_areaLights[0].GetColor() + color; float maxComponent = std::max(useColor.r(), std::max(useColor.g(), useColor.b())); if (maxComponent > FLT_EPSILON) useColor *= (1.f / maxComponent); useColor.a() = 1.f; x0_areaLights[0].SetColor(useColor); } void CActorLights::MultiplyLightingLevels(float level) { x288_ambientColor *= level; for (CLight& light : x0_areaLights) { zeus::CColor color = light.GetColor(); color *= level; color.a() = 1.f; light.SetColor(color); } } void CActorLights::UpdateBrightLight() { if (x2dc_brightLightLag > 0 && x299_24_inBrightLight) --x2dc_brightLightLag; else if (x2dc_brightLightLag < 15 && !x299_24_inBrightLight) ++x2dc_brightLightLag; x299_25_useBrightLightLag = true; } bool CActorLights::BuildAreaLightList(const CStateManager& mgr, const CGameArea& area, const zeus::CAABox& aabb) { const std::vector& lightList = x298_30_layer2 ? area.GetPostConstructed()->x80_lightsB : area.GetPostConstructed()->x60_lightsA; const std::vector& gfxLightList = x298_30_layer2 ? area.GetPostConstructed()->x90_gfxLightsB : area.GetPostConstructed()->x70_gfxLightsA; float worldLightingLevel = area.GetPostConstructed()->x1128_worldLightingLevel; x298_26_hasAreaLights = lightList.size() != 0; if (!x298_26_hasAreaLights || !x298_28_inArea) { /* World lights disabled */ if (x298_31_disableWorldLights) x2d4_worldLightingLevel = worldLightingLevel; x29c_shadowLightArrIdx = -1; return true; } zeus::CVector3f vec; if (!x298_24_dirty && x294_aid == area.GetAreaId()) { /* Early return if not ready for update */ if (mgr.GetInputFrameIdx() - x2a4_lastUpdateFrame < x2a8_areaUpdateFramePeriod) return false; vec = aabb.center() + x2ac_actorPosBias; if (x2d4_worldLightingLevel == worldLightingLevel) if ((x2c0_lastActorPos - vec).magSquared() < x2cc_actorPositionDeltaUpdateThreshold) return false; x2c0_lastActorPos = vec; } else { if (x294_aid != area.GetAreaId()) x2d8_brightLightIdx = -1; x2a4_lastUpdateFrame = sFrameSchedulerCount + mgr.GetInputFrameIdx(); vec = aabb.center() + x2ac_actorPosBias; x2c0_lastActorPos = vec; } /* Reset lighting state */ x2d4_worldLightingLevel = worldLightingLevel; x298_24_dirty = false; x294_aid = area.GetAreaId(); x29c_shadowLightArrIdx = -1; x288_ambientColor = zeus::CColor::skClear; /* Find candidate lights via PVS */ bool use2ndLayer; if (x298_30_layer2) { if (const CPVSAreaSet* pvs = area.GetAreaVisSet()) use2ndLayer = pvs->Has2ndLayerLights(); else use2ndLayer = true; } else { use2ndLayer = false; } CPVSVisSet sets[3]; sets[0].Reset(EPVSVisSetState::OutOfBounds); sets[1].Reset(EPVSVisSetState::OutOfBounds); sets[2].Reset(EPVSVisSetState::OutOfBounds); if (const CPVSAreaSet* pvs = area.GetAreaVisSet()) { zeus::CVector3f localVec = area.GetInverseTransform() * vec; sets[0].SetTestPoint(pvs->GetVisOctree(), localVec); localVec = area.GetInverseTransform() * aabb.max; sets[1].SetTestPoint(pvs->GetVisOctree(), localVec); localVec = area.GetInverseTransform() * aabb.min; sets[2].SetTestPoint(pvs->GetVisOctree(), localVec); } std::vector valList; valList.reserve(lightList.size()); auto lightIt = lightList.begin(); int lightIdx = 0; for (const CLight& light : gfxLightList) { if (light.GetType() == ELightType::LocalAmbient) { /* Take ambient here */ x288_ambientColor = light.GetNormalIndependentLightingAtPoint(vec); } else { EPVSVisSetState visible = EPVSVisSetState::OutOfBounds; if (area.GetAreaVisSet()) { if (lightIt->DoesCastShadows()) { u32 pvsIdx; if (use2ndLayer) pvsIdx = area.Get2ndPVSLightFeature(lightIdx); else pvsIdx = area.Get1stPVSLightFeature(lightIdx); visible = sets[0].GetVisible(pvsIdx); if (visible != EPVSVisSetState::OutOfBounds) visible = std::max(visible, sets[1].GetVisible(pvsIdx)); if (visible != EPVSVisSetState::OutOfBounds) visible = std::max(visible, sets[2].GetVisible(pvsIdx)); } } if (visible != EPVSVisSetState::EndOfTree) { zeus::CSphere sphere(light.GetPosition(), light.GetRadius() * 2.f); if (aabb.intersects(sphere)) { /* Light passes as candidate */ valList.emplace_back(); SLightValue& value = valList.back(); value.x0_areaLightIdx = lightIdx; value.x4_color = light.GetNormalIndependentLightingAtPoint(vec); value.x4_color.a() = 0.f; value.x10_colorMag = value.x4_color.magnitude(); value.x18_visiblity = visible; } } } ++lightIt; ++lightIdx; } /* Sort lights most intense to least intense */ std::sort(valList.begin(), valList.end(), [](const SLightValue& a, const SLightValue& b) { return a.x10_colorMag > b.x10_colorMag; }); if (x298_27_findShadowLight) { /* Accumulate magnitudes up to most intense for shadow dynamic range check */ x288_ambientColor.a() = 0.f; float mag = x288_ambientColor.magnitude(); for (auto it = valList.rbegin(); it != valList.rend(); ++it) { mag += it->x10_colorMag; it->x14_accumulatedMag = mag; } } /* Ambient color for overflow area lights */ zeus::CColor overflowAmbColor = zeus::CColor::skClear; /* Averaged light for overflow area lights */ CLight overflowLight = CLight::BuildCustom(zeus::CVector3f::skZero, zeus::CVector3f::skZero, zeus::CColor::skBlack, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f); zeus::CColor overflowLightColor = zeus::CColor::skClear; float overflowMag = 0.f; /* Max significant lights */ int maxAreaLights = !x298_29_ambienceGenerated ? x2b8_maxAreaLights - 1 : x2b8_maxAreaLights; x0_areaLights.clear(); /* Filter for performing final light visibility test */ CMaterialFilter filter = CMaterialFilter::MakeIncludeExclude( CMaterialList(EMaterialTypes::Solid), CMaterialList(EMaterialTypes::Projectile, EMaterialTypes::ProjectilePassthrough, EMaterialTypes::SeeThrough)); u32 mostSigLightIdx = 0; /* Narrowphase test candidates starting with most intense */ for (int i = 0; i < valList.size(); ++i) { const SLightValue& value = valList[i]; const CLight& light = gfxLightList[value.x0_areaLightIdx]; if (x0_areaLights.size() < maxAreaLights) { /* Significant light */ bool actorToLightContact = true; bool castShadows = lightList[value.x0_areaLightIdx].DoesCastShadows() && x298_25_castShadows; bool outOfBounds = area.GetAreaVisSet() && value.x18_visiblity == EPVSVisSetState::OutOfBounds; if (castShadows) { /* Process shadow cast */ zeus::CVector3f delta = light.GetPosition() - vec; float deltaMag = delta.magnitude(); bool useShadow = false; if (x298_27_findShadowLight && x29c_shadowLightArrIdx == -1 && light.GetType() != ELightType::LocalAmbient && deltaMag > 2.f && !aabb.pointInside(light.GetPosition())) { /* Perform shadow dynamic range check */ if (!x0_areaLights.size() || (x0_areaLights.size() == 1 && value.x10_colorMag / valList[mostSigLightIdx].x10_colorMag > 0.5f)) { useShadow = value.x10_colorMag / value.x14_accumulatedMag > x2d0_shadowDynamicRangeThreshold / (1.f + x2d0_shadowDynamicRangeThreshold); } } if (useShadow) { /* Note shadow light */ x29c_shadowLightArrIdx = x0_areaLights.size(); x2a0_shadowLightIdx = value.x0_areaLightIdx; } else if (!outOfBounds) { /* Note brightest light contact */ delta = delta * 1.f / deltaMag; actorToLightContact = CGameCollision::RayStaticIntersectionArea(area, vec, delta, deltaMag, filter); if (i == 0) { x299_24_inBrightLight = actorToLightContact; if (x2d8_brightLightIdx != value.x0_areaLightIdx) { x2dc_brightLightLag = actorToLightContact ? 0 : 15; x2d8_brightLightIdx = value.x0_areaLightIdx; } x299_25_useBrightLightLag = false; actorToLightContact = true; } } } if (actorToLightContact) { /* Add to final list */ if (x0_areaLights.size() == 0) mostSigLightIdx = i; x0_areaLights.push_back(light); } } else { /* Overflow light */ if (!x298_29_ambienceGenerated && value.x10_colorMag > 0.001f) { /* Average parameters into final light */ MergeOverflowLight(overflowLight, overflowLightColor, light, value.x10_colorMag); overflowMag += value.x10_colorMag; } else { /* Average color into ambient channel */ overflowAmbColor += value.x4_color; } } } /* Finalize overflow lights */ if (!x298_29_ambienceGenerated) AddOverflowToLights(overflowLight, overflowLightColor, overflowMag); else MoveAmbienceToLights(overflowAmbColor); /* Clamp ambient color */ if (x288_ambientColor.r() > 1.f) x288_ambientColor.r() = 1.f; if (x288_ambientColor.g() > 1.f) x288_ambientColor.g() = 1.f; if (x288_ambientColor.b() > 1.f) x288_ambientColor.b() = 1.f; x288_ambientColor.a() = 1.f; /* Multiply down lighting with world fader level */ if (worldLightingLevel < 1.f) MultiplyLightingLevels(worldLightingLevel); return true; } void CActorLights::BuildDynamicLightList(const CStateManager& mgr, const zeus::CAABox& aabb) { UpdateBrightLight(); x299_26_ambientOnly = false; x144_dynamicLights.clear(); if (!x29a_findNearestDynamicLights) { for (const CLight& light : mgr.GetDynamicLightList()) { zeus::CSphere sphere(light.GetPosition(), light.GetRadius()); if (aabb.intersects(sphere)) x144_dynamicLights.push_back(light); if (x144_dynamicLights.size() >= x2bc_maxDynamicLights) break; } } else { const CLight* addedLights[8] = {}; for (int i = 0; i < x2bc_maxDynamicLights && i < 8; ++i) { float minRad = FLT_MAX; for (const CLight& light : mgr.GetDynamicLightList()) { zeus::CSphere sphere(light.GetPosition(), light.GetRadius()); float intRadius = aabb.intersectionRadius(sphere); if (intRadius >= 0.f && intRadius < minRad) { bool alreadyIn = false; for (int j = 0; j < i; ++j) { if (&light == addedLights[j]) { alreadyIn = true; break; } } if (alreadyIn) continue; addedLights[i] = &light; minRad = intRadius; } } if (addedLights[i]) x144_dynamicLights.push_back(*addedLights[i]); if (x144_dynamicLights.size() >= x2bc_maxDynamicLights) break; } } } std::vector CActorLights::BuildLightVector() const { std::vector lights; if (x0_areaLights.size()) { if (x2dc_brightLightLag && x299_25_useBrightLightLag) { CLight overrideLight = x0_areaLights[0]; overrideLight.SetColor(overrideLight.GetColor() * (1.f - x2dc_brightLightLag / 15.f)); lights.push_back(overrideLight); } else lights.push_back(x0_areaLights[0]); for (auto it = x0_areaLights.begin() + 1; it != x0_areaLights.end(); ++it) { lights.push_back(*it); } if (x29c_shadowLightArrIdx > 0) { /* Ensure shadow light comes first for shader extension */ std::swap(lights[0], lights[x29c_shadowLightArrIdx]); } } for (const CLight& light : x144_dynamicLights) lights.push_back(light); zeus::CColor ambColor = x288_ambientColor; ambColor.a() = 1.f; lights.push_back(CLight::BuildLocalAmbient(zeus::CVector3f::skZero, ambColor)); return lights; } void CActorLights::ActivateLights(CBooModel& model) const { std::vector lights; if (x298_28_inArea) { if (!x298_26_hasAreaLights || x299_26_ambientOnly) { // g_Renderer->SetAmbientColor(zeus::CColor::skWhite); lights.push_back(CLight::BuildLocalAmbient(zeus::CVector3f::skZero, zeus::CColor::skWhite)); model.ActivateLights(lights); return; } } lights = BuildLightVector(); model.ActivateLights(lights); if (x298_31_disableWorldLights) { zeus::CColor color(x2d4_worldLightingLevel); g_Renderer->SetGXRegister1Color(color); } } void CActorLights::DisableAreaLights() { x2b8_maxAreaLights = 0; x298_26_hasAreaLights = false; x298_28_inArea = false; } const CLight& CActorLights::GetLight(u32 idx) const { if (x298_28_inArea) { if (idx < x0_areaLights.size()) return x0_areaLights[idx]; return x144_dynamicLights[idx - x0_areaLights.size()]; } return x144_dynamicLights[idx]; } u32 CActorLights::GetActiveLightCount() const { if (x298_28_inArea) return x0_areaLights.size() + x144_dynamicLights.size(); return x144_dynamicLights.size(); } } // namespace urde