From 93d9187f6dc0b4958057acccb869ee1e040d4b35 Mon Sep 17 00:00:00 2001 From: jeefo Date: Thu, 29 Jun 2023 20:17:46 +0300 Subject: [PATCH] fix: crash on predicted node index use after wiped refactor: reworked prediction to calc prediction for all bots at one job, instead of firing it for every bot --- inc/constant.h | 11 ---- inc/manager.h | 41 +++--------- inc/support.h | 3 - inc/vision.h | 65 +++++++++++++++++++ inc/yapb.h | 19 +----- src/botlib.cpp | 20 +----- src/combat.cpp | 6 +- src/manager.cpp | 67 ++++++++++++++----- src/support.cpp | 12 ---- src/vision.cpp | 139 ++++++++++++++++++++-------------------- vc/yapb.vcxproj | 1 + vc/yapb.vcxproj.filters | 3 + 12 files changed, 205 insertions(+), 182 deletions(-) create mode 100644 inc/vision.h diff --git a/inc/constant.h b/inc/constant.h index 874249b..17e1a45 100644 --- a/inc/constant.h +++ b/inc/constant.h @@ -404,17 +404,6 @@ CR_DECLARE_SCOPED_ENUM (GoalTactic, RescueHostage ) -// frustum sides -CR_DECLARE_SCOPED_ENUM (FrustumSide, - Top = 0, - Bottom, - Left, - Right, - Near, - Far, - Num -) - // some hard-coded desire defines used to override calculated ones namespace TaskPri { constexpr auto Normal { 35.0f }; diff --git a/inc/manager.h b/inc/manager.h index adc70fd..cc8d373 100644 --- a/inc/manager.h +++ b/inc/manager.h @@ -17,36 +17,6 @@ struct BotRequest { String name; }; -// initial frustum data -struct FrustumData : public Singleton { -private: - float Fov = 75.0f; - float AspectRatio = 1.33333f; - -public: - float MaxView = 4096.0f; - float MinView = 2.0f; - -public: - float farHeight; // height of the far frustum - float farWidth; // width of the far frustum - - float nearHeight; // height of the near frustum - float nearWidth; // width of the near frustum - -public: - FrustumData () { - nearHeight = 2.0f * cr::tanf (Fov * 0.0174532925f * 0.5f) * MinView; - nearWidth = nearHeight * AspectRatio; - - farHeight = 2.0f * cr::tanf (Fov * 0.0174532925f * 0.5f) * MaxView; - farWidth = farHeight * AspectRatio; - } -}; - -// declare global frustum data -CR_EXPOSE_GLOBAL_SINGLETON (FrustumData, frustum); - // manager class class BotManager final : public Singleton { public: @@ -68,6 +38,7 @@ private: float m_lastChatTime {}; // global chat time timestamp float m_timeBombPlanted {}; // time the bomb were planted float m_lastRadioTime[kGameTeamNum] {}; // global radio time + float m_predictUpdateTime {}; // time to update prediction entity int m_lastWinner {}; // the team who won previous round int m_lastDifficulty {}; // last bots difficulty @@ -89,7 +60,6 @@ private: SmallArray m_bots {}; // all available bots edict_t *m_killerEntity {}; // killer entity for bots - FrustumData m_frustumData {}; protected: BotCreateResult create (StringRef name, int difficulty, int personality, int team, int skin); @@ -152,6 +122,8 @@ public: void handleDeath (edict_t *killer, edict_t *victim); void setLastWinner (int winner); void checkBotModel (edict_t *ent, char *infobuffer); + void syncUpdateBotsPredict (); + void updateBotsPredict (); bool isTeamStacked (int team); bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned); @@ -318,12 +290,17 @@ public: public: template void enqueue (F &&fn) { - if (m_botWorker.threadCount () == 0) { + if (!available ()) { fn (); // no threads, no fun, just run task in current thread return; } m_botWorker.enqueue (cr::move (fn)); } + +public: + bool available () { + return m_botWorker.threadCount () > 0; + } }; // expose global diff --git a/inc/support.h b/inc/support.h index 4cf33a8..8c8cc89 100644 --- a/inc/support.h +++ b/inc/support.h @@ -99,9 +99,6 @@ public: // installs the sendto function interception void installSendTo (); - // check if object inside frustum plane - bool isObjectInsidePlane (FrustumPlane &plane, const Vector ¢er, float height, float radius); - // checks if same model omitting the models directory bool isModel (const edict_t *ent, StringRef model); diff --git a/inc/vision.h b/inc/vision.h new file mode 100644 index 0000000..d02a21e --- /dev/null +++ b/inc/vision.h @@ -0,0 +1,65 @@ +// +// YaPB, based on PODBot by Markus Klinge ("CountFloyd"). +// Copyright © YaPB Project Developers . +// +// SPDX-License-Identifier: MIT +// + +#pragma once + +// view frustum for bots +class Frustum : public Singleton { +public: + struct Plane { + Vector normal {}; + Vector point {}; + float result {}; + }; + + enum class PlaneSide : int { + Top = 0, + Bottom, + Left, + Right, + Near, + Far, + Num + }; + +public: + using Planes = Plane[static_cast (PlaneSide::Num)]; + +private: + static constexpr float kFov = 75.0f; + static constexpr float kAspectRatio = 16.0f / 9.0f; + static constexpr float kMaxViewDistance = 4096.0f; + static constexpr float kMinViewDistance = 2.0f; + +private: + float m_farHeight; // height of the far frustum + float m_farWidth; // width of the far frustum + float m_nearHeight; // height of the near frustum + float m_nearWidth; // width of the near frustum + +public: + explicit Frustum () { + m_nearHeight = 2.0f * cr::tanf (kFov * deg2rad (1.0f) * 0.5f) * kMinViewDistance; + m_nearWidth = m_nearHeight * kAspectRatio; + + m_farHeight = 2.0f * cr::tanf (kFov * deg2rad (1.0f) * 0.5f) * kMaxViewDistance; + m_farWidth = m_farHeight * kAspectRatio; + } + +public: + // updates bot view frustum + void calculate (Planes &planes, const Vector &viewAngle, const Vector &viewOffset); + + // check if object inside frustum plane + bool isObjectInsidePlane (const Plane &plane, const Vector ¢er, float height, float radius) const; + + // check if entity origin inside view plane + bool check (const Planes &planes, edict_t *ent) const; +}; + +// declare global frustum data +CR_EXPOSE_GLOBAL_SINGLETON (Frustum, frustum); diff --git a/inc/yapb.h b/inc/yapb.h index c97d8cd..f722aff 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -112,13 +112,6 @@ struct ClientNoise { float last; }; -// defines frustum data for bot -struct FrustumPlane { - Vector normal; - Vector point; - float result; -}; - // array of clients struct struct Client { edict_t *ent; // pointer to actual edict @@ -146,6 +139,7 @@ struct ChatCollection { // include bot graph stuff #include +#include // this structure links nodes returned from pathfinder class PathWalk final : public NonCopyable { @@ -224,8 +218,6 @@ public: private: mutable Mutex m_pathFindLock {}; - mutable Mutex m_predictLock {}; - mutable Mutex m_lookAnglesLock {}; private: uint32_t m_states {}; // sensing bitstates @@ -374,7 +366,7 @@ private: Path *m_path {}; // pointer to the current path node String m_chatBuffer {}; // space for strings (say text...) - FrustumPlane m_frustum[FrustumSide::Num] {}; + Frustum::Planes m_viewFrustum {}; private: int pickBestWeapon (Array &vec, int moneySave); @@ -444,7 +436,6 @@ private: bool isPenetrableObstacle2 (const Vector &dest); bool isPenetrableObstacle3 (const Vector &dest); bool isEnemyBehindShield (edict_t *enemy); - bool isEnemyInFrustum (edict_t *enemy); bool checkChatKeywords (String &reply); bool isReplyingToChat (); bool isReachableNode (int index); @@ -472,7 +463,6 @@ private: void checkBurstMode (float distance); void checkSilencer (); void updateAimDir (); - void syncUpdateLookAngles (); void updateLookAngles (); void updateBodyAngles (); void updateLookAnglesNewbie (const Vector &direction, float delta); @@ -487,7 +477,6 @@ private: void updatePracticeValue (int damage); void updatePracticeDamage (edict_t *attacker, int damage); void findShortestPath (int srcIndex, int destIndex); - void calculateFrustum (); void findPath (int srcIndex, int destIndex, FindPath pathType = FindPath::Fast); void syncFindPath (int srcIndex, int destIndex, FindPath pathType); void debugMsgInternal (const char *str); @@ -511,8 +500,6 @@ private: void selectSecondary (); void selectWeaponById (int id); void selectWeaponByIndex (int index); - void refreshEnemyPredict (); - void syncUpdatePredictedIndex (); void updatePredictedIndex (); void refreshModelName (char *infobuffer); @@ -523,6 +510,7 @@ private: void translateInput (); void moveToGoal (); void resetMovement (); + void refreshEnemyPredict (); void normal_ (); void spraypaint_ (); @@ -704,7 +692,6 @@ public: // need to wait until all threads will finish it's work before terminating bot object ~Bot () { MutexScopedLock lock1 (m_pathFindLock); - MutexScopedLock lock2 (m_lookAnglesLock); } public: diff --git a/src/botlib.cpp b/src/botlib.cpp index 39f7c76..ec4feb4 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -1623,17 +1623,12 @@ void Bot::overrideConditions () { } } -void Bot::syncUpdatePredictedIndex () { +void Bot::updatePredictedIndex () { auto wipePredict = [this] () { m_lastPredictIndex = kInvalidNodeIndex; m_lastPredictLength = kInfiniteDistanceLong; }; - if (!m_predictLock.tryLock ()) { - return; // allow only single instance of search per-bot - } - ScopedUnlock unlock (m_predictLock); - const auto lastEnemyOrigin = m_lastEnemyOrigin; const auto currentNodeIndex = m_currentNodeIndex; const auto &botOrigin = pev->origin; @@ -1671,15 +1666,6 @@ void Bot::syncUpdatePredictedIndex () { wipePredict (); } -void Bot::updatePredictedIndex () { - if (m_lastEnemyOrigin.empty ()) { - return; // do not run task if no last enemy - } - worker.enqueue ([this] () { - syncUpdatePredictedIndex (); - }); -} - void Bot::refreshEnemyPredict () { if (game.isNullEntity (m_enemy) && !game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty ()) { const auto distanceToLastEnemySq = m_lastEnemyOrigin.distanceSq (pev->origin); @@ -1693,10 +1679,6 @@ void Bot::refreshEnemyPredict () { m_aimFlags |= AimFlags::LastEnemy; } } - - if (m_aimFlags & AimFlags::PredictPath) { - updatePredictedIndex (); - } } void Bot::setConditions () { diff --git a/src/combat.cpp b/src/combat.cpp index ce24def..a1f9f3a 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -212,7 +212,7 @@ bool Bot::seesEnemy (edict_t *player) { return false; } - if (isEnemyInFrustum (player) && isInViewCone (player->v.origin) && checkBodyParts (player)) { + if (isInViewCone (player->v.origin) && frustum.check (m_viewFrustum, player) && checkBodyParts (player)) { m_seeEnemyTime = game.time (); m_lastEnemy = player; m_lastEnemyOrigin = m_enemyOrigin; @@ -277,7 +277,7 @@ bool Bot::lookupEnemies () { } // check the engine PVS - if (!isEnemyInFrustum (interesting) || !game.checkVisibility (interesting, set)) { + if (!frustum.check (m_viewFrustum, interesting) || !game.checkVisibility (interesting, set)) { continue; } @@ -303,7 +303,7 @@ bool Bot::lookupEnemies () { player = client.ent; // check the engine PVS - if (!isEnemyInFrustum (player) || !game.checkVisibility (player, set)) { + if (!frustum.check (m_viewFrustum, player) || !game.checkVisibility (player, set)) { continue; } diff --git a/src/manager.cpp b/src/manager.cpp index 3117f67..7013f68 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -75,8 +75,6 @@ BotManager::BotManager () { m_botsCanPause = false; m_roundOver = false; - m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter; - for (int i = 0; i < kGameTeamNum; ++i) { m_leaderChoosen[i] = false; m_economicsGood[i] = true; @@ -297,6 +295,9 @@ void BotManager::frame () { for (const auto &bot : m_bots) { bot->frame (); } + + // run prediction for bots + updateBotsPredict (); } void BotManager::addbot (StringRef name, int difficulty, int personality, int team, int skin, bool manual) { @@ -764,6 +765,30 @@ void BotManager::checkBotModel (edict_t *ent, char *infobuffer) { } } +void BotManager::syncUpdateBotsPredict () { + if (m_predictUpdateTime > game.time ()) { + return; + } + + // update predicted index for all the bots + for (const auto &bot : m_bots) { + if (!bot.get ()) { + continue; + } + if (bot->m_notKilled && (bot->m_aimFlags & AimFlags::PredictPath)) { + bot->updatePredictedIndex (); + } + } + m_predictUpdateTime = game.time () + 0.1f; +} + +void BotManager::updateBotsPredict () { + // push update predict task for all bots to queue + worker.enqueue ([this] { + syncUpdateBotsPredict (); + }); +} + void BotManager::setWeaponMode (int selection) { // this function sets bots weapon mode @@ -1304,7 +1329,6 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) { } } - void Bot::newRound () { // this function initializes a bot after creation & at the start of each round @@ -1552,6 +1576,12 @@ void Bot::resetPathSearchType () { if (cv_debug_goal.int_ () != kInvalidNodeIndex) { m_pathType = FindPath::Fast; } + + // no need to be safe on csdm + if (game.is (GameFlags::CSDM)) { + m_pathType = FindPath::Fast; + return; + } } void Bot::kill () { @@ -1969,6 +1999,7 @@ void BotManager::initRound () { m_timeBombPlanted = 0.0f; m_plantSearchUpdateTime = 0.0f; m_autoKillCheckTime = 0.0f; + m_predictUpdateTime = 0.0f; m_botsCanPause = false; resetFilters (); @@ -1993,33 +2024,37 @@ void BotManager::setBombPlanted (bool isPlanted) { } void BotThreadWorker::shutdown () { + if (!available ()) { + return; + } game.print ("Shutting down bot thread worker."); m_botWorker.shutdown (); } void BotThreadWorker::startup (int workers) { - const size_t count = m_botWorker.threadCount (); + String disableWorkerEnv = plat.env ("YAPB_SINGLE_THREADED"); + + // disable worker if requested via env variable or workers are disabled + if (workers == 0 || (!disableWorkerEnv.empty () && disableWorkerEnv == "1")) { + return; + } + const auto count = m_botWorker.threadCount (); if (count > 0) { logger.error ("Tried to start thread pool with existing %d threads in pool.", count); return; } + const auto maxThreads = plat.hardwareConcurrency (); + auto requestedThreads = workers; - int requestedThreadCount = workers; - const int hardwareConcurrency = plat.hardwareConcurrency (); - - if (requestedThreadCount == -1) { - requestedThreadCount = hardwareConcurrency / 4; - - if (requestedThreadCount == 0) { - requestedThreadCount = 1; - } + if (requestedThreads < 0 || requestedThreads >= maxThreads) { + requestedThreads = 1; } - requestedThreadCount = cr::clamp (requestedThreadCount, 1, hardwareConcurrency - 1); + requestedThreads = cr::clamp (requestedThreads, 1, maxThreads - 1); // notify user - game.print ("Starting up bot thread worker with %d threads.", requestedThreadCount); + game.print ("Starting up bot thread worker with %d threads.", requestedThreads); // start up the worker - m_botWorker.startup (static_cast (requestedThreadCount)); + m_botWorker.startup (static_cast (requestedThreads)); } diff --git a/src/support.cpp b/src/support.cpp index f74483b..63159f3 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -559,18 +559,6 @@ void BotSupport::installSendTo () { } } -bool BotSupport::isObjectInsidePlane (FrustumPlane &plane, const Vector ¢er, float height, float radius) { - auto isPointInsidePlane = [&] (const Vector &point) -> bool { - return plane.result + (plane.normal | point) >= 0.0f; - }; - - const Vector &test = plane.normal.get2d (); - const Vector &top = center + Vector (0.0f, 0.0f, height * 0.5f) + test * radius; - const Vector &bottom = center - Vector (0.0f, 0.0f, height * 0.5f) + test * radius; - - return isPointInsidePlane (top) || isPointInsidePlane (bottom); -} - bool BotSupport::isModel (const edict_t *ent, StringRef model) { return model.startsWith (ent->v.model.chars (9)); } diff --git a/src/vision.cpp b/src/vision.cpp index 58aecd7..5468d6b 100644 --- a/src/vision.cpp +++ b/src/vision.cpp @@ -138,13 +138,14 @@ void Bot::updateAimDir () { m_lookAtPredict = nullptr; }; - auto isPredictedIndexApplicable = [this] () -> bool { - int pathLength = m_lastPredictLength; - int predictNode = m_lastPredictIndex; + int pathLength = m_lastPredictLength; + int predictNode = m_lastPredictIndex; + auto isPredictedIndexApplicable = [&] () -> bool { if (predictNode != kInvalidNodeIndex) { if (!vistab.visible (m_currentNodeIndex, predictNode) || !vistab.visible (m_previousNodes[0], predictNode)) { predictNode = kInvalidNodeIndex; + pathLength = kInfiniteDistanceLong; } } return predictNode != kInvalidNodeIndex && pathLength < cv_max_nodes_for_predict.int_ (); @@ -152,7 +153,7 @@ void Bot::updateAimDir () { if (changePredictedEnemy) { if (isPredictedIndexApplicable ()) { - m_lookAtPredict = graph[m_lastPredictIndex].origin; + m_lookAtPredict = graph[predictNode].origin; m_timeNextTracking = game.time () + rg.get (0.5f, 1.0f); m_trackingEdict = m_lastEnemy; @@ -183,7 +184,7 @@ void Bot::updateAimDir () { m_lookAt = m_destOrigin; if (m_moveToGoal && m_seeEnemyTime + 4.0f < game.time () && !m_isStuck && m_moveSpeed > getShiftSpeed () && !(pev->button & IN_DUCK) && m_currentNodeIndex != kInvalidNodeIndex && !(m_pathFlags & (NodeFlag::Ladder | NodeFlag::Crouch)) && m_pathWalk.hasNext () && pev->origin.distanceSq (m_destOrigin) < cr::sqrf (512.0f)) { - auto nextPathIndex = m_pathWalk.next (); + const auto nextPathIndex = m_pathWalk.next (); if (vistab.visible (m_currentNodeIndex, nextPathIndex)) { m_lookAt = graph[nextPathIndex].origin + pev->view_ofs; @@ -276,55 +277,6 @@ void Bot::checkDarkness () { m_checkDarkTime = game.time () + rg.get (2.0f, 4.0f); } - -void Bot::calculateFrustum () { - // this function updates bot view frustum - - Vector forward, right, up; - pev->v_angle.angleVectors (&forward, &right, &up); - - static Vector fc, nc, fbl, fbr, ftl, ftr, nbl, nbr, ntl, ntr; - - fc = getEyesPos () + forward * frustum.MaxView; - nc = getEyesPos () + forward * frustum.MinView; - - fbl = fc + (up * frustum.farHeight * 0.5f) - (right * frustum.farWidth * 0.5f); - fbr = fc + (up * frustum.farHeight * 0.5f) + (right * frustum.farWidth * 0.5f); - ftl = fc - (up * frustum.farHeight * 0.5f) - (right * frustum.farWidth * 0.5f); - ftr = fc - (up * frustum.farHeight * 0.5f) + (right * frustum.farWidth * 0.5f); - nbl = nc + (up * frustum.nearHeight * 0.5f) - (right * frustum.nearWidth * 0.5f); - nbr = nc + (up * frustum.nearHeight * 0.5f) + (right * frustum.nearWidth * 0.5f); - ntl = nc - (up * frustum.nearHeight * 0.5f) - (right * frustum.nearWidth * 0.5f); - ntr = nc - (up * frustum.nearHeight * 0.5f) + (right * frustum.nearWidth * 0.5f); - - auto setPlane = [&] (FrustumSide side, const Vector &v1, const Vector &v2, const Vector &v3) { - auto &plane = m_frustum[side]; - - plane.normal = ((v2 - v1) ^ (v3 - v1)).normalize (); - plane.point = v2; - - plane.result = -(plane.normal | plane.point); - }; - - setPlane (FrustumSide::Top, ftl, ntl, ntr); - setPlane (FrustumSide::Bottom, fbr, nbr, nbl); - setPlane (FrustumSide::Left, fbl, nbl, ntl); - setPlane (FrustumSide::Right, ftr, ntr, nbr); - setPlane (FrustumSide::Near, nbr, ntr, ntl); - setPlane (FrustumSide::Far, fbl, ftl, ftr); -} - -bool Bot::isEnemyInFrustum (edict_t *enemy) { - const Vector &origin = enemy->v.origin - Vector (0.0f, 0.0f, 5.0f); - - for (auto &plane : m_frustum) { - if (!util.isObjectInsidePlane (plane, origin, 60.0f, 16.0f)) { - return false; - } - } - return true; -} - void Bot::updateBodyAngles () { // set the body angles to point the gun correctly pev->angles.x = -pev->v_angle.x * (1.0f / 3.0f); @@ -333,21 +285,10 @@ void Bot::updateBodyAngles () { pev->angles.clampAngles (); // calculate frustum plane data here, since look angles update functions call this last one - calculateFrustum (); + frustum.calculate (m_viewFrustum, pev->v_angle, getEyesPos ()); } void Bot::updateLookAngles () { - worker.enqueue ([this] () { - syncUpdateLookAngles (); - }); -} - -void Bot::syncUpdateLookAngles () { - if (!m_lookAnglesLock.tryLock ()) { - return; // allow only single instance of syncUpdateLookAngles per-bot - } - ScopedUnlock unlock (m_lookAnglesLock); - const float delta = cr::clamp (game.time () - m_lookUpdateTime, cr::kFloatEqualEpsilon, kViewFrameUpdate); m_lookUpdateTime = game.time (); @@ -364,10 +305,11 @@ void Bot::syncUpdateLookAngles () { return; } + const float aimSkill = cr::clamp (static_cast (m_difficulty), 1.0f, 4.0f) * 25.0f; - float accelerate = 3000.0f; - float stiffness = 200.0f; - float damping = 25.0f; + float accelerate = aimSkill * 30.0f; + float stiffness = aimSkill * 2.0f; + float damping = aimSkill * 0.25f; if (((m_aimFlags & (AimFlags::Enemy | AimFlags::Entity | AimFlags::Grenade)) || m_wantsToFire) && m_difficulty > Difficulty::Normal) { if (m_difficulty == Difficulty::Expert) { @@ -381,7 +323,7 @@ void Bot::syncUpdateLookAngles () { const float angleDiffPitch = cr::anglesDifference (direction.x, m_idealAngles.x); const float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y); - if (angleDiffYaw < 1.0f && angleDiffYaw > -1.0f) { + if (cr::abs (angleDiffYaw) < 1.0f) { m_lookYawVel = 0.0f; m_idealAngles.y = direction.y; } @@ -480,3 +422,60 @@ void Bot::updateLookAnglesNewbie (const Vector &direction, float delta) { pev->v_angle = pev->v_angle + delta * Vector (m_aimSpeed.x, m_aimSpeed.y, 0.0f); pev->v_angle.clampAngles (); } + +bool Frustum::isObjectInsidePlane (const Plane &plane, const Vector ¢er, float height, float radius) const { + auto isPointInsidePlane = [&] (const Vector &point) -> bool { + return plane.result + (plane.normal | point) >= 0.0f; + }; + + const Vector &test = plane.normal.get2d (); + const Vector &top = center + Vector (0.0f, 0.0f, height * 0.5f) + test * radius; + const Vector &bottom = center - Vector (0.0f, 0.0f, height * 0.5f) + test * radius; + + return isPointInsidePlane (top) || isPointInsidePlane (bottom); +} + +void Frustum::calculate (Planes &planes, const Vector &viewAngle, const Vector &viewOffset) { + Vector forward, right, up; + viewAngle.angleVectors (&forward, &right, &up); + + auto fc = viewOffset + forward * kMaxViewDistance; + auto nc = viewOffset + forward * kMinViewDistance; + + auto fbl = fc + (up * m_farHeight * 0.5f) - (right * m_farWidth * 0.5f); + auto fbr = fc + (up * m_farHeight * 0.5f) + (right * m_farWidth * 0.5f); + auto ftl = fc - (up * m_farHeight * 0.5f) - (right * m_farWidth * 0.5f); + auto ftr = fc - (up * m_farHeight * 0.5f) + (right * m_farWidth * 0.5f); + auto nbl = nc + (up * m_nearHeight * 0.5f) - (right * m_nearWidth * 0.5f); + auto nbr = nc + (up * m_nearHeight * 0.5f) + (right * m_nearWidth * 0.5f); + auto ntl = nc - (up * m_nearHeight * 0.5f) - (right * m_nearWidth * 0.5f); + auto ntr = nc - (up * m_nearHeight * 0.5f) + (right * m_nearWidth * 0.5f); + + auto setPlane = [&] (PlaneSide side, const Vector &v1, const Vector &v2, const Vector &v3) { + auto &plane = planes[static_cast (side)]; + + plane.normal = ((v2 - v1) ^ (v3 - v1)).normalize (); + plane.point = v2; + + plane.result = -(plane.normal | plane.point); + }; + + setPlane (PlaneSide::Top, ftl, ntl, ntr); + setPlane (PlaneSide::Bottom, fbr, nbr, nbl); + setPlane (PlaneSide::Left, fbl, nbl, ntl); + setPlane (PlaneSide::Right, ftr, ntr, nbr); + setPlane (PlaneSide::Near, nbr, ntr, ntl); + setPlane (PlaneSide::Far, fbl, ftl, ftr); +} + +bool Frustum::check (const Planes &planes, edict_t *ent) const { + constexpr auto kOffset = Vector (0.0f, 0.0f, 5.0f); + const Vector &origin = ent->v.origin - kOffset; + + for (const auto &plane : planes) { + if (!isObjectInsidePlane (plane, origin, 60.0f, 16.0f)) { + return false; + } + } + return true; +} diff --git a/vc/yapb.vcxproj b/vc/yapb.vcxproj index 80bd7e1..9baa0ef 100644 --- a/vc/yapb.vcxproj +++ b/vc/yapb.vcxproj @@ -69,6 +69,7 @@ + diff --git a/vc/yapb.vcxproj.filters b/vc/yapb.vcxproj.filters index 2b1a838..03d434e 100644 --- a/vc/yapb.vcxproj.filters +++ b/vc/yapb.vcxproj.filters @@ -183,6 +183,9 @@ inc\ext\crlib\simd + + inc +