2
0
mirror of https://github.com/AxioDL/metaforce.git synced 2025-12-10 09:47:43 +00:00

Implement CFishCloud

This commit is contained in:
Jack Andersen
2019-04-02 18:32:31 -10:00
parent 8e2db0795b
commit b72cc490e8
25 changed files with 943 additions and 159 deletions

View File

@@ -1,26 +1,678 @@
#include "CFishCloud.hpp"
#include "CActorParameters.hpp"
#include "TCastTo.hpp"
#include "GameGlobalObjects.hpp"
#include "CSimplePool.hpp"
#include "Weapon/CWeapon.hpp"
#include "CStateManager.hpp"
#include "World/CWorld.hpp"
#include "World/CPlayer.hpp"
#include "Graphics/CVertexMorphEffect.hpp"
#include "Graphics/CSkinnedModel.hpp"
#include "Graphics/CBooRenderer.hpp"
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 w1, float f1, float f2, float f3, float f4, float f5, float f6,
float f7, float f8, float f9, float f10, float f11, float f12, float f13, u32 w2,
const zeus::CColor& color, bool b1, float f14, CAssetId part1, u32 w3, CAssetId part2, u32 w4,
CAssetId part3, u32 w5, CAssetId part4, u32 w6, u32 w7, bool b2, bool b3)
: CActor(uid, active, name, info, xf, std::move(mData), {EMaterialTypes::NoStepLogic}, CActorParameters::None(),
kInvalidUniqueId) {}
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(new CModelData(aRes));
x1b0_models.emplace_back(new CModelData(aRes));
x1b0_models.emplace_back(new CModelData(aRes));
x1b0_models.emplace_back(new 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(new 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::RemoveRepulsor(TUniqueId) {}
void CFishCloud::UpdateParticles(float dt) {
for (auto& p : x1f8_particleGens)
p->Update(dt);
}
void CFishCloud::RemoveAttractor(TUniqueId) {}
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;
}
}
}
void CFishCloud::AddRepulsor(TUniqueId, float, float) {}
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()));
}
void CFishCloud::AddAttractor(TUniqueId, float, float) {}
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_[0], tri.x10_[2], tri.x10_[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_[0], tri.x10_[2], tri.x10_[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_[0], tri.x10_[2], tri.x10_[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_[0], GetTransform() * tri.x10_[2], GetTransform() * tri.x10_[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) {
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->AnimationData()->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->AnimationData()->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->AnimationData()->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.AnimationData()->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.AnimationData()->Render(model, flags, {}, nullptr);
}
}
void CFishCloud::Render(const CStateManager& mgr) const {
if (!GetActive())
return;
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();
}
rstl::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