#include "boo/System.hpp" #include "GameGlobalObjects.hpp" #include "CBooRenderer.hpp" #include "CTexture.hpp" #include "CModel.hpp" #include "Particle/CParticleGen.hpp" #include "Particle/CGenDescription.hpp" #include "Particle/CDecal.hpp" #include "Particle/CElementGen.hpp" #include "CMetroidModelInstance.hpp" #include "Collision/CAreaOctTree.hpp" #define FOGVOL_RAMP_RES 256 #define SPHERE_RAMP_RES 32 namespace urde { static rstl::reserved_vector sDataHolder; static rstl::reserved_vector, 50> sBucketsHolder; static rstl::reserved_vector sPlaneObjectDataHolder; static rstl::reserved_vector sPlaneObjectBucketHolder; rstl::reserved_vector Buckets::sBucketIndex; rstl::reserved_vector* Buckets::sData = nullptr; rstl::reserved_vector, 50>* Buckets::sBuckets = nullptr; rstl::reserved_vector* Buckets::sPlaneObjectData = nullptr; rstl::reserved_vector* Buckets::sPlaneObjectBucket = nullptr; const float Buckets::skWorstMinMaxDistance[2] = {99999.f, -99999.f}; float Buckets::sMinMaxDistance[2]; void Buckets::Clear() { sData->clear(); sBucketIndex.clear(); sPlaneObjectData->clear(); sPlaneObjectBucket->clear(); for (rstl::reserved_vector& bucket : *sBuckets) bucket.clear(); sMinMaxDistance[0] = skWorstMinMaxDistance[0]; sMinMaxDistance[1] = skWorstMinMaxDistance[1]; } void Buckets::Sort() { float delta = std::max(1.f, sMinMaxDistance[1] - sMinMaxDistance[0]); sPlaneObjectBucket->resize(8); std::sort(sPlaneObjectBucket->begin(), sPlaneObjectBucket->end(), [](u16 a, u16 b) -> bool { return (*sPlaneObjectData)[a].GetDistance() >= (*sPlaneObjectData)[b].GetDistance(); }); u32 precision = 50 / (8 + 1); float pitch = 1.f / (delta / float(precision - 2)); int accum = 0; for (u16 idx : *sPlaneObjectBucket) { ++accum; CDrawablePlaneObject& planeObj = (*sPlaneObjectData)[idx]; planeObj.x24_targetBucket = precision * accum; } for (CDrawable& drawable : *sData) { int slot; if (sPlaneObjectBucket->empty()) { slot = zeus::clamp(1, int((drawable.GetDistance() - sMinMaxDistance[0]) * pitch), 49); } else { /* TODO: Planar sort distribution */ } if (slot == -1) slot = 49; (*sBuckets)[slot].push_back(&drawable); } int bucketIdx = sBuckets->size(); for (auto it = sBuckets->rbegin() ; it != sBuckets->rend() ; ++it) { --bucketIdx; rstl::reserved_vector& bucket = *it; if (bucket.size()) { sBucketIndex.push_back(bucketIdx); std::sort(bucket.begin(), bucket.end(), [](CDrawable* a, CDrawable* b) -> bool { return a->GetDistance() >= b->GetDistance(); }); } } for (auto it = sPlaneObjectBucket->rbegin() ; it != sPlaneObjectBucket->rend() ; ++it) { CDrawablePlaneObject& planeObj = (*sPlaneObjectData)[*it]; rstl::reserved_vector& bucket = (*sBuckets)[planeObj.x24_targetBucket]; bucket.push_back(&planeObj); } } void Buckets::InsertPlaneObject(float dist, float something, const zeus::CAABox& aabb, bool b1, const zeus::CPlane& plane, bool b2, EDrawableType dtype, const void* data) { sPlaneObjectData->push_back(CDrawablePlaneObject(dtype, dist, something, aabb, b1, plane, b2, data)); } void Buckets::Insert(const zeus::CVector3f& pos, const zeus::CAABox& aabb, EDrawableType dtype, const void* data, const zeus::CPlane& plane, u16 extraSort) { float dist = plane.pointToPlaneDist(pos); sData->push_back(CDrawable(dtype, extraSort, dist, aabb, data)); if (sMinMaxDistance[0] > dist) sMinMaxDistance[0] = dist; if (sMinMaxDistance[1] < dist) sMinMaxDistance[1] = dist; } void Buckets::Shutdown() { sData = nullptr; sBuckets = nullptr; sPlaneObjectData = nullptr; sPlaneObjectBucket = nullptr; } void Buckets::Init() { sData = &sDataHolder; sBuckets = &sBucketsHolder; sBuckets->resize(50); sPlaneObjectData = &sPlaneObjectDataHolder; sPlaneObjectBucket = &sPlaneObjectBucketHolder; sMinMaxDistance[0] = skWorstMinMaxDistance[0]; sMinMaxDistance[1] = skWorstMinMaxDistance[1]; } CBooRenderer::CAreaListItem::CAreaListItem (const std::vector* geom, const CAreaRenderOctTree* octTree, std::vector>&& textures, std::vector&& models, int areaIdx) : x0_geometry(geom), x4_octTree(octTree), x8_textures(std::move(textures)), x10_models(std::move(models)), x18_areaIdx(areaIdx) {} CBooRenderer::CAreaListItem::~CAreaListItem() {} void CBooRenderer::ActivateLightsForModel(CAreaListItem* item, CBooModel& model) { std::vector thisLights; thisLights.reserve(4); if (x300_dynamicLights.size()) { /* void* unk3 = nullptr; int x14 = 0; if (item && item->x18_areaIdx != -1) { unk3 = item->x28_unk3; x14 = item->x4_octTree->x14_; } void* unk3Cur = unk3; */ float lightRads[] = {-1.f, -1.f, -1.f, -1.f}; for (int i=0 ; i<4 ; ++i) { for (CLight& light : x300_dynamicLights) { /* if (unk3) { } */ if (light.x40_loadedIdx == i) continue; float rad = light.GetRadius(); if (model.x20_aabb.intersects(zeus::CSphere{light.x0_pos, rad})) { if (rad > lightRads[i]) { lightRads[i] = rad; light.x40_loadedIdx = i; thisLights.push_back(light); break; } } } } } //model.ActivateLights(thisLights); } void CBooRenderer::RenderBucketItems(CAreaListItem* item) { CModelFlags flags; flags.m_extendedShaderIdx = 1; for (u16 idx : Buckets::sBucketIndex) { rstl::reserved_vector& bucket = (*Buckets::sBuckets)[idx]; for (CDrawable* drawable : bucket) { switch (drawable->GetType()) { case EDrawableType::Particle: { static_cast((void*)drawable->GetData())->Render(); break; } case EDrawableType::WorldSurface: { CBooSurface* surf = static_cast((void*)drawable->GetData()); CBooModel* model = surf->m_parent; if (model) { ActivateLightsForModel(item, *model); model->DrawSurface(*surf, flags); } break; } default: { if (xa8_drawableCallback) { xa8_drawableCallback(drawable->GetData(), xac_callbackContext, int(drawable->GetType()) - 2); } break; } } } } } void CBooRenderer::HandleUnsortedModel(CAreaListItem* item, CBooModel& model) { ActivateLightsForModel(item, model); CBooSurface* surf = model.x38_firstUnsortedSurface; CModelFlags flags; flags.m_extendedShaderIdx = 1; while (surf) { model.DrawSurface(*surf, flags); surf = surf->m_next; } } void CBooRenderer::GenerateFogVolumeRampTex(boo::IGraphicsDataFactory::Context& ctx) { u8 data[FOGVOL_RAMP_RES][FOGVOL_RAMP_RES] = {}; for (int y=0 ; yGetPaletteTexture(); } CBooRenderer::CBooRenderer(IObjectStore& store, IFactory& resFac) : x8_factory(resFac), xc_store(store), x2a8_thermalRand(20) { g_Renderer = this; xee_24_ = true; m_gfxToken = CGraphics::CommitResources([&](boo::IGraphicsDataFactory::Context& ctx) -> bool { GenerateFogVolumeRampTex(ctx); GenerateSphereRampTex(ctx); return true; }); LoadThermoPalette(); m_thermHotFilter.emplace(); Buckets::Init(); } void CBooRenderer::AddWorldSurfaces(CBooModel& model) { CBooSurface* surf = model.x3c_firstSortedSurface; while (surf) { const CBooModel::MaterialSet::Material& mat = model.GetMaterialByIndex(surf->m_data.matIdx); zeus::CAABox aabb = surf->GetBounds(); zeus::CVector3f pt = aabb.closestPointAlongVector(xb0_viewPlane.vec); Buckets::Insert(pt, aabb, EDrawableType::WorldSurface, surf, xb0_viewPlane, mat.heclIr.m_blendDst != boo::BlendFactor::Zero); surf = surf->m_next; } } std::list::iterator CBooRenderer::FindStaticGeometry(const std::vector* geometry) { return std::find_if(x1c_areaListItems.begin(), x1c_areaListItems.end(), [&](CAreaListItem& item) -> bool {return item.x0_geometry == geometry;}); } void CBooRenderer::AddStaticGeometry(const std::vector* geometry, const CAreaRenderOctTree* octTree, int areaIdx) { auto search = FindStaticGeometry(geometry); if (search == x1c_areaListItems.end()) { std::vector> textures; std::vector models; if (geometry->size()) { (*geometry)[0].m_instance->MakeTexturesFromMats(textures, xc_store); models.reserve(geometry->size()); for (const CMetroidModelInstance& inst : *geometry) models.push_back(inst.m_instance); } x1c_areaListItems.emplace_back(geometry, octTree, std::move(textures), std::move(models), areaIdx); } } void CBooRenderer::EnablePVS(const CPVSVisSet* set, u32 modelCount) { xc8_pvs.emplace(*set); xe0_pvsModelCount = modelCount; } void CBooRenderer::DisablePVS() { xc8_pvs = std::experimental::nullopt; } void CBooRenderer::RemoveStaticGeometry(const std::vector* geometry) { auto search = FindStaticGeometry(geometry); if (search != x1c_areaListItems.end()) x1c_areaListItems.erase(search); } void CBooRenderer::DrawUnsortedGeometry(int areaIdx, int mask, int targetMask) { //SetupRendererStates(true); CAreaListItem* lastOctreeItem = nullptr; for (CAreaListItem& item : x1c_areaListItems) { if (areaIdx != -1 && item.x18_areaIdx != areaIdx) continue; if (item.x4_octTree) lastOctreeItem = &item; CPVSVisSet* pvs = nullptr; if (xc8_pvs) pvs = &*xc8_pvs; if (xe0_pvsModelCount != item.x10_models.size()) pvs = nullptr; u32 idx = 0; for (auto it = item.x10_models.begin() ; it != item.x10_models.end() ; ++it, ++idx) { CBooModel* model = *it; if (pvs) { bool vis = pvs->GetVisible(idx) != EPVSVisSetState::EndOfTree; switch (xc4_pvsMode) { case EPVSMode::PVS: { if (!vis) { model->x40_25_modelVisible = false; continue; } break; } case EPVSMode::PVSAndMask: { if (!vis && (model->x41_mask & mask) != targetMask) { model->x40_25_modelVisible = false; continue; } } default: break; } } if ((model->x41_mask & mask) != targetMask) { model->x40_25_modelVisible = false; continue; } if (!x44_frustumPlanes.aabbFrustumTest(model->x20_aabb)) { model->x40_25_modelVisible = false; continue; } if (x318_25_drawWireframe) { model->x40_25_modelVisible = false; //HandleUnsortedModelWireframe(); continue; } model->x40_25_modelVisible = true; HandleUnsortedModel(lastOctreeItem, *model); } } //SetupCGraphicsStates(); } void CBooRenderer::DrawSortedGeometry(int areaIdx, int mask, int targetMask) { //SetupRendererStates(true); CAreaListItem* lastOctreeItem = nullptr; for (CAreaListItem& item : x1c_areaListItems) { if (areaIdx != -1 && item.x18_areaIdx != areaIdx) continue; if (item.x4_octTree) lastOctreeItem = &item; for (auto it = item.x10_models.begin() ; it != item.x10_models.end() ; ++it) { CBooModel* model = *it; if (model->x40_25_modelVisible) AddWorldSurfaces(*model); } } Buckets::Sort(); RenderBucketItems(lastOctreeItem); //SetupCGraphicsStates(); //DrawRenderBucketsDebug(); Buckets::Clear(); } void CBooRenderer::DrawStaticGeometry(int modelCount, int mask, int targetMask) { DrawUnsortedGeometry(modelCount, mask, targetMask); DrawSortedGeometry(modelCount, mask, targetMask); } void CBooRenderer::PostRenderFogs() { } void CBooRenderer::AddParticleGen(const CParticleGen& gen) { auto bounds = gen.GetBounds(); if (bounds) { zeus::CVector3f pt = bounds.value().closestPointAlongVector(xb0_viewPlane.vec); Buckets::Insert(pt, bounds.value(), EDrawableType::Particle, &gen, xb0_viewPlane, 0); } } void CBooRenderer::AddPlaneObject(const void*, const zeus::CAABox&, const zeus::CPlane&, int) { } void CBooRenderer::AddDrawable(const void* obj, const zeus::CVector3f& pos, const zeus::CAABox& aabb, int mode, EDrawableSorting sorting) { if (sorting == EDrawableSorting::UnsortedCallback) xa8_drawableCallback(obj, xac_callbackContext, mode); else Buckets::Insert(pos, aabb, EDrawableType(mode + 2), obj, xb0_viewPlane, 0); } void CBooRenderer::SetDrawableCallback(TDrawableCallback cb, const void* ctx) { xa8_drawableCallback = cb; xac_callbackContext = ctx; } void CBooRenderer::SetWorldViewpoint(const zeus::CTransform& xf) { CGraphics::SetViewPointMatrix(xf); xb0_viewPlane.vec = xf.basis[1]; xb0_viewPlane.d = xf.basis[1].dot(xf.origin); } void CBooRenderer::SetPerspective(float fovy, float width, float height, float znear, float zfar) { CGraphics::SetPerspective(fovy, width / height, znear, zfar); } void CBooRenderer::SetPerspective(float fovy, float aspect, float znear, float zfar) { CGraphics::SetPerspective(fovy, aspect, znear, zfar); } zeus::CRectangle CBooRenderer::SetViewportOrtho(bool centered, float znear, float zfar) { float left = centered ? g_Viewport.x10_halfWidth : 0; float bottom = centered ? g_Viewport.x14_halfHeight : 0; float top = centered ? g_Viewport.x14_halfHeight : g_Viewport.xc_height; float right = centered ? g_Viewport.x10_halfWidth : g_Viewport.x8_width; CGraphics::SetOrtho(left, right, top, bottom, znear, zfar); CGraphics::SetViewPointMatrix(zeus::CTransform::Identity()); CGraphics::SetModelMatrix(zeus::CTransform::Identity()); return zeus::CRectangle(left, bottom, right, top); } void CBooRenderer::SetClippingPlanes(const zeus::CFrustum& frustum) { x44_frustumPlanes = frustum; } void CBooRenderer::SetViewport(int l, int b, int w, int h) { CGraphics::SetViewport(l, b, w, h); CGraphics::SetScissor(l, b, w, h); } void CBooRenderer::SetDebugOption(EDebugOption, int) { } void CBooRenderer::BeginScene() { CGraphics::SetViewport(0, 0, g_Viewport.x8_width, g_Viewport.xc_height); CGraphics::SetPerspective(75.f, CGraphics::g_ProjAspect, 1.f, 4096.f); CGraphics::SetModelMatrix(zeus::CTransform::Identity()); CGraphics::BeginScene(); } void CBooRenderer::EndScene() { CGraphics::EndScene(); } void CBooRenderer::SetAmbientColor(const zeus::CColor& color) { CGraphics::SetAmbientColor(color); } void CBooRenderer::DrawString(const char*, int, int) { } u32 CBooRenderer::GetFPS() { return 0; } void CBooRenderer::DrawSpaceWarp(const zeus::CVector3f& pt, float strength) { m_spaceWarpFilter.setStrength(strength); m_spaceWarpFilter.draw(pt); } void CBooRenderer::DrawThermalModel(const CModel& model, const zeus::CColor& mulCol, const zeus::CColor& addCol) { CModelFlags flags; flags.m_extendedShaderIdx = 2; flags.color = mulCol; flags.addColor = addCol; model.Draw(flags); } void CBooRenderer::DrawXRayOutline(const zeus::CAABox& aabb) { CModelFlags flags; flags.m_extendedShaderIdx = 3; for (CAreaListItem& item : x1c_areaListItems) { if (item.x4_octTree) { std::vector bitmap; item.x4_octTree->FindOverlappingModels(bitmap, aabb); for (u32 c=0 ; cx14_bitmapWordCount ; ++c) { for (u32 b=0 ; b<32 ; ++b) { if (bitmap[c] & (1 << b)) { CBooModel* model = item.x10_models[c * 32 + b]; model->UpdateUniformData(flags, nullptr, nullptr); const CBooSurface* surf = model->x38_firstUnsortedSurface; while (surf) { if (surf->GetBounds().intersects(aabb)) model->DrawSurface(*surf, flags); surf = surf->m_next; } } } } } } } void CBooRenderer::SetWireframeFlags(int) { } void CBooRenderer::SetWorldFog(ERglFogMode mode, float startz, float endz, const zeus::CColor& color) { if (x318_28_disableFog) mode = ERglFogMode::None; CGraphics::SetFog(mode, startz, endz, color); } void CBooRenderer::RenderFogVolume(const zeus::CColor&, const zeus::CAABox&, const TLockedToken*, const CSkinnedModel*) { } void CBooRenderer::SetThermal(bool thermal, float level, const zeus::CColor& color) { x318_29_thermalVisor = thermal; x2f0_thermalVisorLevel = level; x2f4_thermColor = color; CDecal::SetMoveRedToAlphaBuffer(false); CElementGen::SetMoveRedToAlphaBuffer(false); } void CBooRenderer::SetThermalColdScale(float scale) { x2f8_thermColdScale = zeus::clamp(0.f, scale, 1.f); } void CBooRenderer::DoThermalBlendCold() { zeus::CColor a = zeus::CColor::lerp(x2f4_thermColor, zeus::CColor::skWhite, x2f8_thermColdScale); m_thermColdFilter.setColorA(a); float bFac = 0.f; float bAlpha = 1.f; if (x2f8_thermColdScale < 0.5f) { bAlpha = x2f8_thermColdScale * 2.f; bFac = (1.f - bAlpha) / 8.f; } zeus::CColor b{bFac, bFac, bFac, bAlpha}; m_thermColdFilter.setColorB(b); zeus::CColor c = zeus::CColor::lerp(zeus::CColor::skBlack, zeus::CColor::skWhite, zeus::clamp(0.f, (x2f8_thermColdScale - 0.25f) * 4.f / 3.f, 1.f)); m_thermColdFilter.setColorC(c); m_thermColdFilter.setScale(x2f8_thermColdScale); m_thermColdFilter.setShift(x2a8_thermalRand.Next() % 32); m_thermColdFilter.draw(); CElementGen::SetMoveRedToAlphaBuffer(true); CDecal::SetMoveRedToAlphaBuffer(true); } void CBooRenderer::DoThermalBlendHot() { m_thermHotFilter->draw(); } u32 CBooRenderer::GetStaticWorldDataSize() { return 0; } void CBooRenderer::PrepareDynamicLights(const std::vector& lights) { x300_dynamicLights = lights; for (CAreaListItem& area : x1c_areaListItems) { if (const CAreaRenderOctTree* arot = area.x4_octTree) { area.x1c_lightOctreeWords.clear(); area.x1c_lightOctreeWords.resize(arot->x14_bitmapWordCount * lights.size()); u32* wordPtr = area.x1c_lightOctreeWords.data(); for (const CLight& light : lights) { float radius = light.GetRadius(); zeus::CVector3f vMin = light.GetPosition() - radius; zeus::CVector3f vMax = light.GetPosition() + radius; zeus::CAABox aabb(vMin, vMax); arot->FindOverlappingModels(wordPtr, aabb); wordPtr += arot->x14_bitmapWordCount; } } } } void CBooRenderer::SetWorldLightFadeLevel(float level) { x2fc_tevReg1Color = zeus::CColor(level, level, level, 1.f); } }