#include "Runtime/World/CPathFindArea.hpp" #include "Runtime/CToken.hpp" #include "Runtime/IVParamObj.hpp" namespace metaforce { static logvisor::Module Log("CPathFindArea"); CPFAreaOctree::CPFAreaOctree(CMemoryInStream& in) { x0_isLeaf = in.readUint32Big(); x4_aabb.readBoundingBoxBig(in); x1c_center.readBig(in); for (auto& ptr : x28_children) ptr = reinterpret_cast(in.readUint32Big()); x48_regions.set_size(in.readUint32Big()); x48_regions.set_data(reinterpret_cast(in.readUint32Big())); } void CPFAreaOctree::Fixup(CPFArea& area) { x0_isLeaf = x0_isLeaf != 0 ? 1 : 0; if (x0_isLeaf) { if (x48_regions.empty()) return; x48_regions.set_data(&area.x160_octreeRegionLookup[reinterpret_cast(x48_regions.data())]); return; } for (auto & ptr : x28_children) { if ((reinterpret_cast(ptr) & 0x80000000) == 0) ptr = &area.x158_octree[reinterpret_cast(ptr)]; else ptr = nullptr; } } u32 CPFAreaOctree::GetChildIndex(const zeus::CVector3f& point) const { u32 idx = 0x0u; if (point.x() > x1c_center.x()) idx = 0x1u; if (point.y() > x1c_center.y()) idx |= 0x2u; if (point.z() > x1c_center.z()) idx |= 0x4u; return idx; } rstl::prereserved_vector* CPFAreaOctree::GetRegionList(const zeus::CVector3f& point) { if (x0_isLeaf) return &x48_regions; return x28_children[GetChildIndex(point)]->GetRegionList(point); } void CPFAreaOctree::GetRegionListList(rstl::reserved_vector*, 32>& listOut, const zeus::CVector3f& point, float padding) { if (listOut.size() >= listOut.capacity()) return; if (x0_isLeaf) { listOut.push_back(&x48_regions); return; } for (CPFAreaOctree* ch : x28_children) { if (ch->IsPointInsidePaddedAABox(point, padding)) ch->GetRegionListList(listOut, point, padding); } } bool CPFAreaOctree::IsPointInsidePaddedAABox(const zeus::CVector3f& point, float padding) const { return point.x() >= x4_aabb.min.x() - padding && point.x() <= x4_aabb.max.x() + padding && point.y() >= x4_aabb.min.y() - padding && point.y() <= x4_aabb.max.y() + padding && point.z() >= x4_aabb.min.z() - padding && point.z() <= x4_aabb.max.z() + padding; } CPFOpenList::CPFOpenList() { x40_region.SetData(&x90_regionData); Clear(); } void CPFOpenList::Clear() { x40_region.Data()->SetOpenMore(&x40_region); x40_region.Data()->SetOpenLess(&x40_region); x0_bitSet.Clear(); } void CPFOpenList::Push(CPFRegion* reg) { x0_bitSet.Add(reg->GetIndex()); CPFRegion* other = x40_region.Data()->GetOpenMore(); while (other != &x40_region && reg->Data()->GetCost() > other->Data()->GetCost()) other = other->Data()->GetOpenMore(); other->Data()->GetOpenLess()->Data()->SetOpenMore(reg); reg->Data()->SetOpenLess(other->Data()->GetOpenLess()); other->Data()->SetOpenLess(reg); reg->Data()->SetOpenMore(other); } CPFRegion* CPFOpenList::Pop() { CPFRegion* reg = x40_region.Data()->GetOpenMore(); if (reg != &x40_region) { x0_bitSet.Rmv(reg->GetIndex()); reg->Data()->GetOpenMore()->Data()->SetOpenLess(reg->Data()->GetOpenLess()); reg->Data()->GetOpenLess()->Data()->SetOpenMore(reg->Data()->GetOpenMore()); reg->Data()->SetOpenMore(nullptr); reg->Data()->SetOpenLess(nullptr); return reg; } return nullptr; } void CPFOpenList::Pop(CPFRegion* reg) { x0_bitSet.Rmv(reg->GetIndex()); reg->Data()->GetOpenMore()->Data()->SetOpenLess(reg->Data()->GetOpenLess()); reg->Data()->GetOpenLess()->Data()->SetOpenMore(reg->Data()->GetOpenMore()); reg->Data()->SetOpenMore(nullptr); reg->Data()->SetOpenLess(nullptr); } bool CPFOpenList::Test(const CPFRegion* reg) const { return x0_bitSet.Test(reg->GetIndex()); } CPFArea::CPFArea(std::unique_ptr&& buf, u32 len) { CMemoryInStream r(buf.get(), len); u32 version = r.readUint32Big(); if (version != 4) Log.report(logvisor::Fatal, FMT_STRING("Unexpected PATH version {}, should be 4"), version); u32 numNodes = r.readUint32Big(); x140_nodes.reserve(numNodes); for (u32 i = 0; i < numNodes; ++i) x140_nodes.emplace_back(r); u32 numLinks = r.readUint32Big(); x148_links.reserve(numLinks); for (u32 i = 0; i < numLinks; ++i) x148_links.emplace_back(r); u32 numRegions = r.readUint32Big(); x150_regions.reserve(numRegions); for (u32 i = 0; i < numRegions; ++i) x150_regions.emplace_back(r); x178_regionDatas.resize(numRegions); u32 maxRegionNodes = 0; for (CPFRegion& region : x150_regions) region.Fixup(*this, maxRegionNodes); maxRegionNodes = std::max(maxRegionNodes, 4u); x10_tmpPolyPoints.reserve(maxRegionNodes); u32 numBitfieldWords = (numRegions * (numRegions - 1) / 2 + 31) / 32; x168_connectionsGround.reserve(numBitfieldWords); for (u32 i = 0; i < numBitfieldWords; ++i) x168_connectionsGround.push_back(r.readUint32Big()); x170_connectionsFlyers.reserve(numBitfieldWords); for (u32 i = 0; i < numBitfieldWords; ++i) x170_connectionsFlyers.push_back(r.readUint32Big()); r.seek(((((numRegions * numRegions) + 31) / 32) - numBitfieldWords) * 2 * sizeof(u32)); u32 numRegionLookups = r.readUint32Big(); x160_octreeRegionLookup.reserve(numRegionLookups); for (u32 i = 0; i < numRegionLookups; ++i) x160_octreeRegionLookup.push_back(reinterpret_cast(r.readUint32Big())); for (CPFRegion*& rl : x160_octreeRegionLookup) rl = &x150_regions[reinterpret_cast(rl)]; u32 numOctreeNodes = r.readUint32Big(); x158_octree.reserve(numOctreeNodes); for (u32 i = 0; i < numOctreeNodes; ++i) x158_octree.emplace_back(r); for (CPFAreaOctree& node : x158_octree) node.Fixup(*this); } rstl::prereserved_vector* CPFArea::GetOctreeRegionList(const zeus::CVector3f& point) { if (x30_hasCachedRegionList && zeus::close_enough(point, x24_cachedRegionListPoint)) return x20_cachedRegionList; return x158_octree.back().GetRegionList(point); } u32 CPFArea::FindRegions(rstl::reserved_vector& regions, const zeus::CVector3f& point, u32 flags, u32 indexMask) { bool isFlyer = (flags & 0x2u) != 0; bool isSwimmer = (flags & 0x4u) != 0; for (CPFRegion* region : *GetOctreeRegionList(point)) { if (region->GetFlags() & 0xffu & flags && (region->GetFlags() >> 16u) & 0xffu & indexMask && region->IsPointInside(point) && (isFlyer || isSwimmer || region->PointHeight(point) < 3.f)) { regions.push_back(region); if (regions.size() == regions.capacity()) break; } } return u32(regions.size()); } CPFRegion* CPFArea::FindClosestRegion(const zeus::CVector3f& point, u32 flags, u32 indexMask, float padding) { rstl::reserved_vector*, 32> regionListList; x158_octree.back().GetRegionListList(regionListList, point, padding); bool isFlyer = (flags & 0x2u) != 0; CPFRegion* ret = nullptr; for (rstl::prereserved_vector* list : regionListList) { for (CPFRegion* region : *list) { if (region->Data()->GetCookie() != x34_regionFindCookie) { if (region->GetFlags() & 0xffu & flags && (region->GetFlags() >> 16u) & 0xffu & indexMask && region->IsPointInsidePaddedAABox(point, padding) && (isFlyer || region->PointHeight(point) < 3.f)) { if (region->FindBestPoint(x10_tmpPolyPoints, point, flags, padding * padding)) { ret = region; padding = region->Data()->GetBestPointDistanceSquared() == 0.0 ? 0.f : std::sqrt(region->Data()->GetBestPointDistanceSquared()); x4_closestPoint = region->Data()->GetBestPoint(); } } region->Data()->SetCookie(x34_regionFindCookie); } } } ++x34_regionFindCookie; return ret; } zeus::CVector3f CPFArea::FindClosestReachablePoint(rstl::reserved_vector& regs, const zeus::CVector3f& point, u32 flags, u32 indexMask) { zeus::CVector3f ret; float closestDistSq = FLT_MAX; for (CPFRegion& reg : x150_regions) { if (reg.GetFlags() & 0xffu & flags && (reg.GetFlags() >> 16u) & 0xffu & indexMask) { for (CPFRegion* oreg : regs) { if (PathExists(oreg, ®, flags)) { float distSq = (reg.GetCentroid() - point).magSquared(); if (distSq < closestDistSq) { closestDistSq = distSq; ret = reg.GetCentroid(); break; } } } } } return ret; } bool CPFArea::PathExists(const CPFRegion* r1, const CPFRegion* r2, u32 flags) const { if (r1 == r2 || (flags & 0x4u) != 0) return true; u32 i1 = r1->GetIndex(); u32 i2 = r2->GetIndex(); if (i1 > i2) std::swap(i1, i2); u32 remRegions = u32(x150_regions.size()) - i1; u32 remConnections = remRegions * (remRegions - 1) / 2; u32 totalConnections = u32(x150_regions.size()) * (u32(x150_regions.size()) - 1) / 2; u32 bit = totalConnections - remConnections + i2 - (i1 + 1); auto d = std::div(bit, 32); if ((flags & 0x2u) != 0) return ((x170_connectionsFlyers[d.quot] >> u32(d.rem)) & 0x1u) != 0; else return ((x168_connectionsGround[d.quot] >> u32(d.rem)) & 0x1u) != 0; } CFactoryFnReturn FPathFindAreaFactory(const metaforce::SObjectTag& tag, std::unique_ptr&& in, u32 len, const metaforce::CVParamTransfer& vparms, CObjectReference*) { return TToken::GetIObjObjectFor(std::make_unique(std::move(in), len)); } } // namespace metaforce