mirror of
				https://github.com/AxioDL/metaforce.git
				synced 2025-10-27 15:30:23 +00:00 
			
		
		
		
	Prevents some implementations (libstdc++ and libc++) from completely zeroing out the underlying buffer and instead sets a single byte flag. Eliminates unnecessary memsets.
		
			
				
	
	
		
			685 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			685 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "Runtime/World/CFishCloud.hpp"
 | |
| 
 | |
| #include "Runtime/CSimplePool.hpp"
 | |
| #include "Runtime/CStateManager.hpp"
 | |
| #include "Runtime/GameGlobalObjects.hpp"
 | |
| #include "Runtime/Graphics/CBooRenderer.hpp"
 | |
| #include "Runtime/Graphics/CSkinnedModel.hpp"
 | |
| #include "Runtime/Graphics/CVertexMorphEffect.hpp"
 | |
| #include "Runtime/Weapon/CWeapon.hpp"
 | |
| #include "Runtime/World/CActorParameters.hpp"
 | |
| #include "Runtime/World/CPlayer.hpp"
 | |
| #include "Runtime/World/CWorld.hpp"
 | |
| 
 | |
| #include "TCastTo.hpp" // Generated file, do not modify include path
 | |
| 
 | |
| namespace urde {
 | |
| 
 | |
| CFishCloud::CFishCloud(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info,
 | |
|                        const zeus::CVector3f& scale, const zeus::CTransform& xf, CModelData&& mData,
 | |
|                        const CAnimRes& aRes, u32 numBoids, float speed, float separationRadius, float cohesionMagnitude,
 | |
|                        float alignmentWeight, float separationMagnitude, float weaponRepelMagnitude,
 | |
|                        float playerRepelMagnitude, float containmentMagnitude, float scatterVel, float maxScatterAngle,
 | |
|                        float weaponRepelDampingSpeed, float playerRepelDampingSpeed, float containmentRadius,
 | |
|                        u32 updateShift, const zeus::CColor& color, bool killable, float weaponKillRadius,
 | |
|                        CAssetId part1, u32 partCount1, CAssetId part2, u32 partCount2, CAssetId part3, u32 partCount3,
 | |
|                        CAssetId part4, u32 partCount4, u32 deathSfx, bool repelFromThreats, bool hotInThermal)
 | |
| : CActor(uid, active, name, info, xf, std::move(mData), {EMaterialTypes::NoStepLogic},
 | |
|          CActorParameters::None().HotInThermal(hotInThermal), kInvalidUniqueId)
 | |
| , x11c_updateMask(u32((1 << updateShift) - 1))
 | |
| , x120_scale(scale)
 | |
| , x130_speed(speed)
 | |
| , x134_numBoids(numBoids)
 | |
| , x138_separationRadius(separationRadius)
 | |
| , x13c_cohesionMagnitude(cohesionMagnitude)
 | |
| , x140_alignmentWeight(alignmentWeight)
 | |
| , x144_separationMagnitude(separationMagnitude)
 | |
| , x148_weaponRepelMagnitude(weaponRepelMagnitude)
 | |
| , x14c_playerRepelMagnitude(playerRepelMagnitude)
 | |
| , x150_scatterVel(scatterVel)
 | |
| , x154_maxScatterAngle(maxScatterAngle)
 | |
| , x158_containmentMagnitude(containmentMagnitude)
 | |
| , x15c_playerRepelDampingSpeed(playerRepelDampingSpeed)
 | |
| , x160_weaponRepelDampingSpeed(weaponRepelDampingSpeed)
 | |
| , x164_playerRepelDamping(playerRepelDampingSpeed)
 | |
| , x168_weaponRepelDamping(weaponRepelDampingSpeed)
 | |
| , x16c_color(color)
 | |
| , x170_weaponKillRadius(weaponKillRadius)
 | |
| , x174_containmentRadius(containmentRadius)
 | |
| , x234_deathSfx(deathSfx != 0xffffffff ? CSfxManager::TranslateSFXID(u16(deathSfx & 0xffff)) : u16(0xffff)) {
 | |
|   x250_28_killable = killable;
 | |
|   x250_29_repelFromThreats = repelFromThreats;
 | |
|   x108_modifierSources.reserve(10);
 | |
|   x250_25_worldSpace = true; // The result of a close_enough paradox (weird inlined test?)
 | |
|   if (aRes.GetId().IsValid()) {
 | |
|     x1b0_models.emplace_back(std::make_unique<CModelData>(aRes));
 | |
|     x1b0_models.emplace_back(std::make_unique<CModelData>(aRes));
 | |
|     x1b0_models.emplace_back(std::make_unique<CModelData>(aRes));
 | |
|     x1b0_models.emplace_back(std::make_unique<CModelData>(aRes));
 | |
|     x250_27_validModel = true;
 | |
|   }
 | |
|   if (part1.IsValid())
 | |
|     x1c4_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part1}));
 | |
|   if (part2.IsValid())
 | |
|     x1c4_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part2}));
 | |
|   if (part3.IsValid())
 | |
|     x1c4_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part3}));
 | |
|   if (part4.IsValid())
 | |
|     x1c4_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part4}));
 | |
|   for (const auto& p : x1c4_particleDescs) {
 | |
|     x1f8_particleGens.emplace_back(std::make_unique<CElementGen>(p));
 | |
|     x1f8_particleGens.back()->SetParticleEmission(false);
 | |
|   }
 | |
|   x21c_deathParticleCounts.push_back(partCount1);
 | |
|   x21c_deathParticleCounts.push_back(partCount2);
 | |
|   x21c_deathParticleCounts.push_back(partCount3);
 | |
|   x21c_deathParticleCounts.push_back(partCount4);
 | |
|   zeus::CAABox aabb = GetBoundingBox();
 | |
|   x238_partitionPitch = (aabb.max - aabb.min) / 7.f;
 | |
|   x244_ooPartitionPitch = 1.f / x238_partitionPitch;
 | |
| }
 | |
| 
 | |
| void CFishCloud::Accept(IVisitor& visitor) { visitor.Visit(this); }
 | |
| 
 | |
| void CFishCloud::UpdateParticles(float dt) {
 | |
|   for (auto& p : x1f8_particleGens)
 | |
|     p->Update(dt);
 | |
| }
 | |
| 
 | |
| void CFishCloud::UpdatePartitionList() {
 | |
|   xf8_boidPartitionLists.clear();
 | |
|   xf8_boidPartitionLists.resize(xf8_boidPartitionLists.capacity());
 | |
|   auto aabb = GetBoundingBox();
 | |
|   for (auto& b : xe8_boids) {
 | |
|     zeus::CVector3f idxs = (b.x0_pos - aabb.min) * x244_ooPartitionPitch;
 | |
|     int idx = int(idxs.x()) + int(idxs.y()) * 7 + int(idxs.z()) * 49;
 | |
|     if (idx >= 0 && idx < 343) {
 | |
|       b.x1c_next = xf8_boidPartitionLists[idx];
 | |
|       xf8_boidPartitionLists[idx] = &b;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool CFishCloud::PointInBox(const zeus::CAABox& aabb, const zeus::CVector3f& point) const {
 | |
|   if (!x250_25_worldSpace)
 | |
|     return aabb.pointInside(point);
 | |
|   return GetUntransformedBoundingBox().pointInside(GetTransform().transposeRotate(point - GetTranslation()));
 | |
| }
 | |
| 
 | |
| zeus::CPlane CFishCloud::FindClosestPlane(const zeus::CAABox& aabb, const zeus::CVector3f& point) const {
 | |
|   if (!x250_25_worldSpace) {
 | |
|     float minDist = FLT_MAX;
 | |
|     zeus::CAABox::EBoxFaceId minFace = zeus::CAABox::EBoxFaceId::YMin;
 | |
|     for (int i = 0; i < 6; ++i) {
 | |
|       auto tri = aabb.getTri(zeus::CAABox::EBoxFaceId(i), 0);
 | |
|       float dist = zeus::CPlane(tri.x10_verts[0], tri.x10_verts[2], tri.x10_verts[1]).pointToPlaneDist(point);
 | |
|       if (dist >= 0.f && dist < minDist) {
 | |
|         minDist = dist;
 | |
|         minFace = zeus::CAABox::EBoxFaceId(i);
 | |
|       }
 | |
|     }
 | |
|     auto tri = aabb.getTri(minFace, 0);
 | |
|     return zeus::CPlane(tri.x10_verts[0], tri.x10_verts[2], tri.x10_verts[1]);
 | |
|   } else {
 | |
|     auto unPoint = GetTransform().transposeRotate(point - GetTranslation());
 | |
|     auto unAabb = GetUntransformedBoundingBox();
 | |
|     float minDist = FLT_MAX;
 | |
|     zeus::CAABox::EBoxFaceId minFace = zeus::CAABox::EBoxFaceId::YMin;
 | |
|     for (int i = 0; i < 6; ++i) {
 | |
|       auto tri = unAabb.getTri(zeus::CAABox::EBoxFaceId(i), 0);
 | |
|       float dist = zeus::CPlane(tri.x10_verts[0], tri.x10_verts[2], tri.x10_verts[1]).pointToPlaneDist(unPoint);
 | |
|       if (dist >= 0.f && dist < minDist) {
 | |
|         minDist = dist;
 | |
|         minFace = zeus::CAABox::EBoxFaceId(i);
 | |
|       }
 | |
|     }
 | |
|     auto tri = unAabb.getTri(minFace, 0);
 | |
|     return zeus::CPlane(GetTransform() * tri.x10_verts[0],
 | |
|                         GetTransform() * tri.x10_verts[2],
 | |
|                         GetTransform() * tri.x10_verts[1]);
 | |
|   }
 | |
| }
 | |
| 
 | |
| CFishCloud::CBoid* CFishCloud::GetListAt(const zeus::CVector3f& pos) {
 | |
|   zeus::CAABox aabb = GetBoundingBox();
 | |
|   zeus::CVector3f ints = (pos - aabb.min) * x244_ooPartitionPitch;
 | |
|   int idx = int(ints.x()) + int(ints.y()) * 7 + int(ints.z()) * 49;
 | |
|   if (idx < 0 || idx >= 343)
 | |
|     return nullptr;
 | |
|   return xf8_boidPartitionLists[idx];
 | |
| }
 | |
| 
 | |
| void CFishCloud::BuildBoidNearList(const zeus::CVector3f& pos, float radius,
 | |
|                                    rstl::reserved_vector<CBoid*, 25>& nearList) {
 | |
|   float radiusSq = radius * radius;
 | |
|   CBoid* b = GetListAt(pos);
 | |
|   while (b && nearList.size() < 25) {
 | |
|     if (b->x20_active) {
 | |
|       float distSq = (b->GetTranslation() - pos).magSquared();
 | |
|       if (distSq != 0.f && distSq < radiusSq)
 | |
|         nearList.push_back(b);
 | |
|     }
 | |
|     b = b->x1c_next;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CFishCloud::BuildBoidNearPartitionList(const zeus::CVector3f& pos, float radius,
 | |
|                                             rstl::reserved_vector<CBoid*, 25>& nearList) {
 | |
|   float radiusSq = radius * radius;
 | |
|   zeus::CAABox aabb = GetBoundingBox();
 | |
|   float x = std::max(radius * x244_ooPartitionPitch.x(), float(x238_partitionPitch.x()));
 | |
|   float y = std::max(radius * x244_ooPartitionPitch.y(), float(x238_partitionPitch.y()));
 | |
|   float z = std::max(radius * x244_ooPartitionPitch.z(), float(x238_partitionPitch.z()));
 | |
|   float nx = 0.01f - x;
 | |
|   float ny = 0.01f - y;
 | |
|   float nz = 0.01f - z;
 | |
|   for (float lnx = nx; lnx < x; lnx += x238_partitionPitch.x()) {
 | |
|     float cx = lnx + pos.x();
 | |
|     if (cx < aabb.min.x())
 | |
|       continue;
 | |
|     if (cx >= aabb.max.x())
 | |
|       break;
 | |
|     for (float lny = ny; lny < y; lny += x238_partitionPitch.y()) {
 | |
|       float cy = lny + pos.y();
 | |
|       if (cy < aabb.min.y())
 | |
|         continue;
 | |
|       if (cy >= aabb.max.y())
 | |
|         break;
 | |
|       for (float lnz = nz; lnz < z; lnz += x238_partitionPitch.z()) {
 | |
|         float cz = lnz + pos.z();
 | |
|         if (cz < aabb.min.z())
 | |
|           continue;
 | |
|         if (cz >= aabb.max.z())
 | |
|           break;
 | |
|         zeus::CVector3f ints = (zeus::CVector3f(cx, cy, cz) - aabb.min) * x244_ooPartitionPitch;
 | |
|         int idx = int(ints.x()) + int(ints.y()) * 7 + int(ints.z()) * 49;
 | |
|         if (idx < 0)
 | |
|           continue;
 | |
|         if (idx < 343) {
 | |
|           CBoid* boid = xf8_boidPartitionLists[idx];
 | |
|           while (boid) {
 | |
|             if (boid->x20_active) {
 | |
|               float distSq = (boid->x0_pos - pos).magSquared();
 | |
|               if (distSq != 0.f && distSq < radiusSq) {
 | |
|                 nearList.push_back(boid);
 | |
|                 if (nearList.size() == 25)
 | |
|                   return;
 | |
|               }
 | |
|             }
 | |
|             boid = boid->x1c_next;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CFishCloud::PlaceBoid(CStateManager& mgr, CBoid& boid, const zeus::CAABox& aabb) const {
 | |
|   auto plane = FindClosestPlane(aabb, boid.x0_pos);
 | |
|   boid.x0_pos -= plane.pointToPlaneDist(boid.x0_pos) * plane.normal() + 0.0001f * plane.normal();
 | |
|   boid.xc_vel.y() = mgr.GetActiveRandom()->Float() - 0.5f;
 | |
|   boid.xc_vel.x() = mgr.GetActiveRandom()->Float() - 0.5f;
 | |
|   boid.xc_vel.z() = 0.f;
 | |
|   if (!x250_25_worldSpace) {
 | |
|     if (!aabb.pointInside(boid.x0_pos)) {
 | |
|       boid.x0_pos.z() = mgr.GetActiveRandom()->Float() * (aabb.max.z() - aabb.min.z()) + aabb.min.z();
 | |
|       boid.x0_pos.y() = mgr.GetActiveRandom()->Float() * (aabb.max.y() - aabb.min.y()) + aabb.min.y();
 | |
|       boid.x0_pos.x() = mgr.GetActiveRandom()->Float() * (aabb.max.x() - aabb.min.x()) + aabb.min.x();
 | |
|     }
 | |
|   } else {
 | |
|     if (!PointInBox(aabb, boid.x0_pos)) {
 | |
|       auto unAabb = GetUntransformedBoundingBox();
 | |
|       boid.x0_pos.z() = mgr.GetActiveRandom()->Float() * (unAabb.max.z() - unAabb.min.z()) + unAabb.min.z();
 | |
|       boid.x0_pos.y() = mgr.GetActiveRandom()->Float() * (unAabb.max.y() - unAabb.min.y()) + unAabb.min.y();
 | |
|       boid.x0_pos.x() = mgr.GetActiveRandom()->Float() * (unAabb.max.x() - unAabb.min.x()) + unAabb.min.x();
 | |
|       boid.x0_pos = GetTransform() * boid.x0_pos;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CFishCloud::ApplySeparation(CBoid& boid, const rstl::reserved_vector<CBoid*, 25>& nearList) const {
 | |
|   if (nearList.empty())
 | |
|     return;
 | |
|   float minDist = FLT_MAX;
 | |
|   zeus::CVector3f pos;
 | |
|   for (CBoid* b : nearList) {
 | |
|     float dist = (boid.GetTranslation() - b->GetTranslation()).magSquared();
 | |
|     if (dist < minDist) {
 | |
|       minDist = dist;
 | |
|       pos = b->GetTranslation();
 | |
|     }
 | |
|   }
 | |
|   ApplySeparation(boid, pos, x138_separationRadius, x144_separationMagnitude);
 | |
| }
 | |
| 
 | |
| void CFishCloud::ApplySeparation(CBoid& boid, const zeus::CVector3f& separateFrom,
 | |
|                                  float separationRadius, float separationMagnitude) const {
 | |
|   zeus::CVector3f delta = boid.GetTranslation() - separateFrom;
 | |
|   if (delta.canBeNormalized()) {
 | |
|     float deltaDistSq = delta.magSquared();
 | |
|     float capDeltaDistSq = separationRadius * separationRadius;
 | |
|     if (deltaDistSq < capDeltaDistSq)
 | |
|       boid.xc_vel += (1.f - deltaDistSq / capDeltaDistSq) * delta.normalized() * separationMagnitude;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CFishCloud::ApplyCohesion(CBoid& boid, const rstl::reserved_vector<CBoid*, 25>& nearList) const {
 | |
|   if (nearList.empty())
 | |
|     return;
 | |
|   zeus::CVector3f avg;
 | |
|   for (CBoid* b : nearList)
 | |
|     avg += b->GetTranslation();
 | |
|   avg = avg / float(nearList.size());
 | |
|   ApplyCohesion(boid, avg, x138_separationRadius, x13c_cohesionMagnitude);
 | |
| }
 | |
| 
 | |
| void CFishCloud::ApplyCohesion(CBoid& boid, const zeus::CVector3f& cohesionFrom,
 | |
|                                float cohesionRadius, float cohesionMagnitude) const {
 | |
|   zeus::CVector3f delta = cohesionFrom - boid.GetTranslation();
 | |
|   if (delta.canBeNormalized()) {
 | |
|     float distSq = delta.magSquared();
 | |
|     float capDistSq = cohesionRadius * cohesionRadius;
 | |
|     boid.xc_vel += ((distSq > capDistSq) ? 1.f : distSq / capDistSq) * delta.normalized() * cohesionMagnitude;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CFishCloud::ApplyAlignment(CBoid& boid, const rstl::reserved_vector<CBoid*, 25>& nearList) const {
 | |
|   if (nearList.empty())
 | |
|     return;
 | |
|   zeus::CVector3f avg;
 | |
|   for (CBoid* b : nearList)
 | |
|     avg += b->xc_vel;
 | |
|   avg = avg / float(nearList.size());
 | |
|   boid.xc_vel += zeus::CVector3f::getAngleDiff(boid.xc_vel, avg) / M_PIF *
 | |
|     (avg * x140_alignmentWeight);
 | |
| }
 | |
| 
 | |
| void CFishCloud::ApplyAttraction(CBoid& boid, const zeus::CVector3f& attractTo,
 | |
|                                  float attractionRadius, float attractionMagnitude) const {
 | |
|   zeus::CVector3f delta = attractTo - boid.GetTranslation();
 | |
|   if (delta.canBeNormalized()) {
 | |
|     float distSq = delta.magSquared();
 | |
|     float capDistSq = attractionRadius * attractionRadius;
 | |
|     boid.xc_vel += ((distSq > capDistSq) ? 0.f : (1.f - distSq / capDistSq)) * delta.normalized() * attractionMagnitude;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CFishCloud::ApplyRepulsion(CBoid& boid, const zeus::CVector3f& attractTo,
 | |
|                                 float repulsionRadius, float repulsionMagnitude) const {
 | |
|   ApplySeparation(boid, attractTo, repulsionRadius, repulsionMagnitude);
 | |
| }
 | |
| 
 | |
| void CFishCloud::ApplySwirl(CBoid& boid, const zeus::CVector3f& swirlPoint, bool clockwise,
 | |
|                             float magnitude, float radius) const {
 | |
|   zeus::CVector3f delta = boid.x0_pos - swirlPoint;
 | |
|   float deltaMag = delta.magnitude();
 | |
|   zeus::CVector3f alignVec;
 | |
|   if (clockwise)
 | |
|     alignVec = delta.normalized().cross(zeus::skUp);
 | |
|   else
 | |
|     alignVec = zeus::skUp.cross(delta / deltaMag);
 | |
|   float weight = deltaMag > radius ? 0.f : 1.f - deltaMag / radius;
 | |
|   boid.xc_vel += zeus::CVector3f::getAngleDiff(boid.xc_vel, alignVec) / M_PIF *
 | |
|     weight * (magnitude * alignVec);
 | |
| }
 | |
| 
 | |
| void CFishCloud::ApplyContainment(CBoid& boid, const zeus::CAABox& aabb) const {
 | |
|   if (boid.xc_vel.canBeNormalized()) {
 | |
|     if (!PointInBox(aabb, boid.xc_vel.normalized() * x130_speed * x174_containmentRadius + boid.x0_pos)) {
 | |
|       ApplyAttraction(boid, aabb.center(), 100000.f, x158_containmentMagnitude);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CFishCloud::ScatterBoid(CStateManager& mgr, CBoid& b) const {
 | |
|   float angle = (mgr.GetActiveRandom()->Float() - 0.5f) * M_PIF * x154_maxScatterAngle;
 | |
|   float cosAngle = std::cos(angle);
 | |
|   float sinAngle = std::sin(angle);
 | |
|   b.xc_vel.x() += x150_scatterVel * (b.xc_vel.y() * sinAngle + b.xc_vel.x() * cosAngle);
 | |
|   b.xc_vel.y() += x150_scatterVel * (b.xc_vel.y() * cosAngle + b.xc_vel.x() * sinAngle);
 | |
| }
 | |
| 
 | |
| void CFishCloud::Think(float dt, CStateManager& mgr) {
 | |
|   if (!GetActive())
 | |
|     return;
 | |
|   const CGameArea* area = mgr.GetWorld()->GetAreaAlways(x4_areaId);
 | |
|   auto occState = area->IsPostConstructed() ? area->GetOcclusionState() : CGameArea::EOcclusionState::Occluded;
 | |
|   if (occState == CGameArea::EOcclusionState::Visible) {
 | |
|     x168_weaponRepelDamping = std::max(0.f, x168_weaponRepelDamping - x160_weaponRepelDampingSpeed * dt * 0.1f);
 | |
|     if (x250_26_enableWeaponRepelDamping)
 | |
|       x168_weaponRepelDamping = std::min(x160_weaponRepelDampingSpeed * dt + x168_weaponRepelDamping,
 | |
|                                          x148_weaponRepelMagnitude);
 | |
|     x164_playerRepelDamping = std::max(0.f, x164_playerRepelDamping - x15c_playerRepelDampingSpeed * dt * 0.1f);
 | |
|     if (x250_30_enablePlayerRepelDamping)
 | |
|       x164_playerRepelDamping = std::min(x15c_playerRepelDampingSpeed * dt + x164_playerRepelDamping,
 | |
|                                          x14c_playerRepelMagnitude);
 | |
|     x250_26_enableWeaponRepelDamping = false;
 | |
|     x250_30_enablePlayerRepelDamping = false;
 | |
|     ++x118_thinkCounter;
 | |
|     UpdateParticles(dt);
 | |
|     UpdatePartitionList();
 | |
|     zeus::CAABox aabb = GetBoundingBox();
 | |
|     int idx = 0;
 | |
|     for (auto& b : xe8_boids) {
 | |
|       if (b.x20_active && (idx & x11c_updateMask) == (x118_thinkCounter & x11c_updateMask)) {
 | |
|         rstl::reserved_vector<CBoid*, 25> nearList;
 | |
|         if (x250_31_updateWithoutPartitions)
 | |
|           BuildBoidNearList(b.x0_pos, x138_separationRadius, nearList);
 | |
|         else
 | |
|           BuildBoidNearPartitionList(b.x0_pos, x138_separationRadius, nearList);
 | |
|         for (int i = 0; i < 5; ++i) {
 | |
|           switch (i) {
 | |
|           case 1:
 | |
|             ApplySeparation(b, nearList);
 | |
|             break;
 | |
|           case 2:
 | |
|             if (!x250_24_randomMovement || mgr.GetActiveRandom()->Float() > x12c_randomMovementTimer)
 | |
|               ApplyCohesion(b, nearList);
 | |
|             break;
 | |
|           case 3:
 | |
|             if (!x250_24_randomMovement || mgr.GetActiveRandom()->Float() > x12c_randomMovementTimer)
 | |
|               ApplyAlignment(b, nearList);
 | |
|             break;
 | |
|           case 4:
 | |
|             ScatterBoid(mgr, b);
 | |
|             break;
 | |
|           default:
 | |
|             break;
 | |
|           }
 | |
|           if (b.xc_vel.magSquared() > 3.2f)
 | |
|             break;
 | |
|         }
 | |
|         if (!x250_24_randomMovement && b.xc_vel.magSquared() < 3.2f) {
 | |
|           for (auto& m : x108_modifierSources) {
 | |
|             if (TCastToPtr<CActor> act = mgr.ObjectById(m.x0_source)) {
 | |
|               if (m.xd_isSwirl) {
 | |
|                 ApplySwirl(b, act->GetTranslation(), m.xc_isRepulsor, m.x8_priority, m.x4_radius);
 | |
|               } else if (m.xc_isRepulsor) {
 | |
|                 ApplyRepulsion(b, act->GetTranslation(), m.x4_radius, m.x8_priority);
 | |
|               } else {
 | |
|                 ApplyAttraction(b, act->GetTranslation(), m.x4_radius, m.x8_priority);
 | |
|               }
 | |
|             } else {
 | |
|               if (m.xc_isRepulsor)
 | |
|                 RemoveRepulsor(m.x0_source);
 | |
|               else
 | |
|                 RemoveAttractor(m.x0_source);
 | |
|               break;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       ++idx;
 | |
|     }
 | |
|     for (auto& b : xe8_boids) {
 | |
|       if (b.x20_active) {
 | |
|         ApplyContainment(b, aabb);
 | |
|         float velMag = b.xc_vel.magnitude();
 | |
|         if (!zeus::close_enough(velMag, 0.f))
 | |
|           b.xc_vel = b.xc_vel / velMag;
 | |
|         b.xc_vel.z() *= 0.99f;
 | |
|       }
 | |
|     }
 | |
|     if (x12c_randomMovementTimer > 0.f) {
 | |
|       x12c_randomMovementTimer -= dt;
 | |
|     } else {
 | |
|       x12c_randomMovementTimer = 0.f;
 | |
|       x250_24_randomMovement = false;
 | |
|     }
 | |
|     for (auto& b : xe8_boids) {
 | |
|       if (b.x20_active) {
 | |
|         b.x0_pos += b.xc_vel * dt * x130_speed;
 | |
|         if (!PointInBox(aabb, b.x0_pos))
 | |
|           PlaceBoid(mgr, b, aabb);
 | |
|       }
 | |
|     }
 | |
|     if (x250_27_validModel) {
 | |
|       for (auto& m : x1b0_models) {
 | |
|         m->GetAnimationData()->SetPlaybackRate(1.f);
 | |
|         m->AdvanceAnimation(dt, mgr, x4_areaId, true);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CFishCloud::CreatePartitionList() {
 | |
|   xf8_boidPartitionLists.reserve(343);
 | |
| }
 | |
| 
 | |
| void CFishCloud::AllocateSkinnedModels(CStateManager& mgr, CModelData::EWhichModel which) {
 | |
|   int idx = 0;
 | |
|   for (auto& m : x1b0_models) {
 | |
|     m->EnableLooping(true);
 | |
|     m->AdvanceAnimation(
 | |
|       m->GetAnimationData()->GetAnimTimeRemaining("Whole Body"sv) * 0.25f * idx, mgr, x4_areaId, true);
 | |
|     ++idx;
 | |
|   }
 | |
|   x230_whichModel = which;
 | |
| }
 | |
| 
 | |
| void CFishCloud::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {
 | |
|   CActor::AcceptScriptMsg(msg, sender, mgr);
 | |
|   switch (msg) {
 | |
|   case EScriptObjectMessage::Registered: {
 | |
|     xe8_boids.reserve(x134_numBoids);
 | |
|     zeus::CAABox aabb = GetUntransformedBoundingBox();
 | |
|     zeus::CVector3f extent = aabb.max - aabb.min;
 | |
|     zeus::CVector3f randPoint;
 | |
|     for (int i = 0; i < x134_numBoids; ++i) {
 | |
|       randPoint.z() = mgr.GetActiveRandom()->Float() * extent.z() + aabb.min.z();
 | |
|       randPoint.y() = mgr.GetActiveRandom()->Float() * extent.y() + aabb.min.y();
 | |
|       randPoint.x() = mgr.GetActiveRandom()->Float() * extent.x() + aabb.min.x();
 | |
|       zeus::CVector3f vel;
 | |
|       vel.y() = mgr.GetActiveRandom()->Float() - 0.5f;
 | |
|       vel.x() = mgr.GetActiveRandom()->Float() - 0.5f;
 | |
|       xe8_boids.emplace_back(x34_transform * randPoint, vel,
 | |
|                              0.2f * std::pow(mgr.GetActiveRandom()->Float(), 7.f) + 0.9f);
 | |
|     }
 | |
|     CreatePartitionList();
 | |
|     if (x250_27_validModel)
 | |
|       AllocateSkinnedModels(mgr, CModelData::EWhichModel::Normal);
 | |
|     break;
 | |
|   }
 | |
|   default:
 | |
|     break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CFishCloud::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {
 | |
|   CActor::PreRender(mgr, frustum);
 | |
|   if (x250_27_validModel) {
 | |
|     for (auto& m : x1b0_models) {
 | |
|       m->GetAnimationData()->PreRender();
 | |
|     }
 | |
|   }
 | |
|   xe4_30_outOfFrustum = false;
 | |
| }
 | |
| 
 | |
| void CFishCloud::AddParticlesToRenderer() const {
 | |
|   for (const auto& p : x1f8_particleGens)
 | |
|     g_Renderer->AddParticleGen(*p);
 | |
| }
 | |
| 
 | |
| void CFishCloud::RenderBoid(int idx, const CBoid& boid, u32& drawMask,
 | |
|                             bool thermalHot, const CModelFlags& flags) const {
 | |
|   u32 modelIndex = idx & 0x3;
 | |
|   CModelData& mData = *x1b0_models[modelIndex];
 | |
|   CSkinnedModel& model = mData.PickAnimatedModel(CModelData::EWhichModel::Normal);
 | |
|   if (!model.GetModelInst()->TryLockTextures())
 | |
|     return;
 | |
|   u32 thisDrawMask = 1u << modelIndex;
 | |
|   if (drawMask & thisDrawMask) {
 | |
|     drawMask &= ~thisDrawMask;
 | |
|     mData.GetAnimationData()->BuildPose();
 | |
|   }
 | |
|   model.GetModelInst()->SetAmbientColor(zeus::skWhite);
 | |
|   CGraphics::SetModelMatrix(zeus::lookAt(boid.x0_pos, boid.x0_pos + boid.xc_vel));
 | |
|   if (thermalHot) {
 | |
|     CModelFlags thermFlags(0, 0, 3, zeus::skWhite);
 | |
|     mData.RenderThermal(zeus::skWhite, zeus::CColor(0.f, 0.25f), thermFlags);
 | |
|   } else {
 | |
|     mData.GetAnimationData()->Render(model, flags, std::nullopt, nullptr);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CFishCloud::Render(const CStateManager& mgr) const {
 | |
|   if (!GetActive())
 | |
|     return;
 | |
|   SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(fmt("CFishCloud::Render {} {} {}"),
 | |
|                                           x8_uid, xc_editorId, x10_name).c_str(), zeus::skOrange);
 | |
|   bool thermalHot = mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot;
 | |
|   CModelFlags flags(0, 0, 3, zeus::skWhite);
 | |
|   if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay)
 | |
|     flags = CModelFlags(5, 0, 3, x16c_color);
 | |
|   else
 | |
|     flags = CModelFlags(1, 0, 3, x16c_color);
 | |
|   AddParticlesToRenderer();
 | |
|   if (x250_27_validModel) {
 | |
|     // Ambient white
 | |
|     int idx = 0;
 | |
|     u32 drawMask = 0xffffffff;
 | |
|     for (const auto& b : xe8_boids) {
 | |
|       if (b.x20_active)
 | |
|         RenderBoid(idx, b, drawMask, thermalHot, flags);
 | |
|       ++idx;
 | |
|     }
 | |
|   } else {
 | |
|     CGraphics::SetModelMatrix(zeus::CTransform());
 | |
|     for (const auto& b : xe8_boids) {
 | |
|       if (b.x20_active) {
 | |
|         x64_modelData->SetScale(zeus::CVector3f(b.x18_scale));
 | |
|         x64_modelData->Render(mgr, zeus::lookAt(b.x0_pos, b.x0_pos + b.xc_vel), nullptr, flags);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CFishCloud::CalculateRenderBounds() {
 | |
|   x9c_renderBounds = GetBoundingBox();
 | |
| }
 | |
| 
 | |
| std::optional<zeus::CAABox> CFishCloud::GetTouchBounds() const {
 | |
|   return {GetBoundingBox()};
 | |
| }
 | |
| 
 | |
| void CFishCloud::CreateBoidDeathParticle(CBoid& b) const {
 | |
|   auto it = x21c_deathParticleCounts.begin();
 | |
|   for (auto& p : x1f8_particleGens) {
 | |
|     p->SetParticleEmission(true);
 | |
|     p->SetTranslation(b.x0_pos);
 | |
|     p->ForceParticleCreation(*it);
 | |
|     p->SetParticleEmission(false);
 | |
|     ++it;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CFishCloud::KillBoid(CBoid& b) const {
 | |
|   b.x20_active = false;
 | |
|   CreateBoidDeathParticle(b);
 | |
|   CAudioSys::C3DEmitterParmData parmData = {};
 | |
|   parmData.x0_pos = b.x0_pos;
 | |
|   parmData.x18_maxDist = 250.f;
 | |
|   parmData.x1c_distComp = 0.1f;
 | |
|   parmData.x20_flags = 0x1;
 | |
|   parmData.x24_sfxId = x234_deathSfx;
 | |
|   parmData.x26_maxVol = 1.f;
 | |
|   parmData.x27_minVol = 0.157f;
 | |
|   parmData.x29_prio = 0x7f;
 | |
|   CSfxManager::AddEmitter(parmData, true, 0x7f, false, x4_areaId);
 | |
| }
 | |
| 
 | |
| void CFishCloud::Touch(CActor& other, CStateManager& mgr) {
 | |
|   CActor::Touch(other, mgr);
 | |
|   if (TCastToPtr<CWeapon> weap = other) {
 | |
|     if (!x250_26_enableWeaponRepelDamping && x250_29_repelFromThreats) {
 | |
|       int idx = 0;
 | |
|       for (auto& b : xe8_boids) {
 | |
|         if ((idx & 0x3) == (x118_thinkCounter & 0x3))
 | |
|           ApplyRepulsion(b, weap->GetTranslation(), 8.f, x148_weaponRepelMagnitude - x168_weaponRepelDamping);
 | |
|         ++idx;
 | |
|       }
 | |
|     }
 | |
|     x250_26_enableWeaponRepelDamping = true;
 | |
|     if (x250_28_killable) {
 | |
|       if (auto tb = weap->GetTouchBounds()) {
 | |
|         for (auto& b : xe8_boids) {
 | |
|           if (b.x20_active &&
 | |
|               tb->intersects(zeus::CAABox(weap->GetTranslation() - x170_weaponKillRadius,
 | |
|                                           weap->GetTranslation() + x170_weaponKillRadius)))
 | |
|             KillBoid(b);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (x250_29_repelFromThreats) {
 | |
|     if (TCastToPtr<CPlayer> player = other) {
 | |
|       zeus::CVector3f playerPos = player->GetTranslation();
 | |
|       for (auto& b : xe8_boids) {
 | |
|         zeus::CVector3f adjPlayerPos = playerPos;
 | |
|         float zDelta = b.x0_pos.z() - adjPlayerPos.z();
 | |
|         if (zDelta > 0.f && zDelta < 2.3f)
 | |
|           adjPlayerPos.z() = float(b.x0_pos.z());
 | |
|         adjPlayerPos.x() += mgr.GetActiveRandom()->Float() * 0.2f - 0.1f;
 | |
|         adjPlayerPos.y() += mgr.GetActiveRandom()->Float() * 0.2f - 0.1f;
 | |
|         ApplyRepulsion(b, adjPlayerPos, 8.f, x14c_playerRepelMagnitude - x164_playerRepelDamping);
 | |
|       }
 | |
|     }
 | |
|     x250_30_enablePlayerRepelDamping = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| zeus::CAABox CFishCloud::GetUntransformedBoundingBox() const {
 | |
|   zeus::CVector3f extent = x120_scale * 0.75f;
 | |
|   return zeus::CAABox(-extent, extent);
 | |
| }
 | |
| 
 | |
| zeus::CAABox CFishCloud::GetBoundingBox() const {
 | |
|   return GetUntransformedBoundingBox().getTransformedAABox(x34_transform);
 | |
| }
 | |
| 
 | |
| void CFishCloud::RemoveRepulsor(TUniqueId sourceId) {
 | |
|   CModifierSource source(sourceId, true, false, 0.f, 0.f);
 | |
|   auto it = rstl::binary_find(x108_modifierSources.begin(), x108_modifierSources.end(), source);
 | |
|   if (it != x108_modifierSources.end())
 | |
|     x108_modifierSources.erase(it);
 | |
| }
 | |
| 
 | |
| void CFishCloud::RemoveAttractor(TUniqueId sourceId) {
 | |
|   CModifierSource source(sourceId, false, false, 0.f, 0.f);
 | |
|   auto it = rstl::binary_find(x108_modifierSources.begin(), x108_modifierSources.end(), source);
 | |
|   if (it != x108_modifierSources.end())
 | |
|     x108_modifierSources.erase(it);
 | |
| }
 | |
| 
 | |
| bool CFishCloud::AddRepulsor(TUniqueId sourceId, bool swirl, float radius, float priority) {
 | |
|   CModifierSource source(sourceId, true, swirl, radius, priority);
 | |
|   auto it = rstl::binary_find(x108_modifierSources.begin(), x108_modifierSources.end(), source);
 | |
|   if (it != x108_modifierSources.end()) {
 | |
|     it->x4_radius = radius;
 | |
|     it->x8_priority = priority;
 | |
|     return true;
 | |
|   } else if (x108_modifierSources.size() < x108_modifierSources.capacity()) {
 | |
|     x108_modifierSources.insert(std::lower_bound(
 | |
|       x108_modifierSources.begin(), x108_modifierSources.end(), source), source);
 | |
|     return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool CFishCloud::AddAttractor(TUniqueId sourceId, bool swirl, float radius, float priority) {
 | |
|   CModifierSource source(sourceId, false, swirl, radius, priority);
 | |
|   auto it = rstl::binary_find(x108_modifierSources.begin(), x108_modifierSources.end(), source);
 | |
|   if (it != x108_modifierSources.end()) {
 | |
|     it->x4_radius = radius;
 | |
|     it->x8_priority = priority;
 | |
|     return true;
 | |
|   } else if (x108_modifierSources.size() < x108_modifierSources.capacity()) {
 | |
|     x108_modifierSources.insert(std::lower_bound(
 | |
|       x108_modifierSources.begin(), x108_modifierSources.end(), source), source);
 | |
|     return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| } // namespace urde
 |