From 69e7d32f3a877520455d192728b52fe5432130ce Mon Sep 17 00:00:00 2001 From: Daniel Evans Date: Sun, 26 Jun 2016 03:55:31 +0100 Subject: [PATCH] Use view frustum for traffic spawning and cleanup --- rwengine/include/ai/TrafficDirector.hpp | 9 +++-- rwengine/include/engine/GameWorld.hpp | 22 ++++++++-- rwengine/src/ai/TrafficDirector.cpp | 53 ++++++++++++++++--------- rwengine/src/engine/GameWorld.cpp | 39 ++++++++++-------- rwgame/RWGame.cpp | 12 +++--- tests/test_lifetime.cpp | 5 ++- tests/test_trafficdirector.cpp | 5 ++- 7 files changed, 93 insertions(+), 52 deletions(-) diff --git a/rwengine/include/ai/TrafficDirector.hpp b/rwengine/include/ai/TrafficDirector.hpp index 56b8e5a5..205618b3 100644 --- a/rwengine/include/ai/TrafficDirector.hpp +++ b/rwengine/include/ai/TrafficDirector.hpp @@ -9,6 +9,7 @@ class GameObject; class GameWorld; +class ViewCamera; class TrafficDirector { @@ -16,17 +17,17 @@ public: TrafficDirector(AIGraph* graph, GameWorld* world); - std::vector< AIGraphNode* > findAvailableNodes(AIGraphNode::NodeType type, const glm::vec3& near, float radius); + std::vector< AIGraphNode* > findAvailableNodes(AIGraphNode::NodeType type, const ViewCamera& camera, float radius); void setDensity(AIGraphNode::NodeType type, float density); /** * Creates new traffic at available locations. - * @param center The area to spawn around + * @param camera The camera to spawn around * @param radius the maximum distance to spawn in * @param max The maximum number of traffic to create. */ - std::vector populateNearby( const glm::vec3& center, float radius, int maxSpawn = -1 ); + std::vector populateNearby( const ViewCamera& camera, float radius, int maxSpawn = -1 ); /** * Sets the maximum number of pedestrians and cars in the traffic system @@ -40,4 +41,4 @@ private: float carDensity; int maximumPedestrians; int maximumCars; -}; \ No newline at end of file +}; diff --git a/rwengine/include/engine/GameWorld.hpp b/rwengine/include/engine/GameWorld.hpp index f0e670cd..2093a7f7 100644 --- a/rwengine/include/engine/GameWorld.hpp +++ b/rwengine/include/engine/GameWorld.hpp @@ -21,6 +21,7 @@ class CharacterObject; class InstanceObject; class VehicleObject; +class ViewCamera; #include #include @@ -76,10 +77,23 @@ public: * @param name The name of the IPL as it appears in the games' gta.dat */ bool placeItems(const std::string& name); - - void createTraffic(const glm::vec3& near); - void cleanupTraffic(const glm::vec3& focus); - + + /** + * @brief createTraffic spawn transitory peds and vehicles + * @param viewCamera The camera to create traffic near + * + * The position and frustum of the passed in camera is used to determine + * the radius where traffic can be spawned, and the frustum is used to avoid + * spawning traffic in view of the player. + */ + void createTraffic(const ViewCamera& viewCamera); + + /** + * @brief cleanupTraffic Cleans up traffic too far away from the given camera + * @param viewCamera + */ + void cleanupTraffic(const ViewCamera& viewCamera); + /** * Creates an instance */ diff --git a/rwengine/src/ai/TrafficDirector.cpp b/rwengine/src/ai/TrafficDirector.cpp index 7740ec96..ee6ea21a 100644 --- a/rwengine/src/ai/TrafficDirector.cpp +++ b/rwengine/src/ai/TrafficDirector.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -18,36 +19,39 @@ maximumPedestrians(20), maximumCars(10) } -std::vector< AIGraphNode* > TrafficDirector::findAvailableNodes(AIGraphNode::NodeType type, const glm::vec3& near, float radius) +std::vector< AIGraphNode* > TrafficDirector::findAvailableNodes(AIGraphNode::NodeType type, const ViewCamera& camera, float radius) { std::vector available; available.reserve(20); - graph->gatherExternalNodesNear(near, radius, available); + graph->gatherExternalNodesNear(camera.position, radius, available); float density = type == AIGraphNode::Vehicle ? carDensity : pedDensity; float minDist = (10.f / density) * (10.f / density); + float halfRadius2 = std::pow(radius / 2.f, 2.f); - // Determine if anything in the open set is blocked - for ( auto it = available.begin(); it != available.end(); ) - { + // Check if any of the nearby nodes are blocked by a pedestrian standing on it + // or because it's inside the view frustum + for (auto it = available.begin(); it != available.end();) { bool blocked = false; - for ( auto obj : world->pedestrianPool.objects ) - { - // Sanity check - if ( glm::distance2( (*it)->position, obj.second->getPosition() ) <= minDist ) - { + float dist2 = glm::distance2(camera.position, (*it)->position); + + for (auto& obj : world->pedestrianPool.objects) { + if (glm::distance2( (*it)->position, obj.second->getPosition() ) <= minDist) { blocked = true; break; } } + + // Check that we're not going to spawn something right where the player is looking + if (dist2 <= halfRadius2 && camera.frustum.intersects((*it)->position, 1.f)) { + blocked = true; + } - if ( blocked ) - { + if (blocked) { it = available.erase( it ); } - else - { + else { it++; } } @@ -68,17 +72,28 @@ void TrafficDirector::setDensity(AIGraphNode::NodeType type, float density) } } -std::vector TrafficDirector::populateNearby(const glm::vec3& center, float radius, int maxSpawn) +std::vector TrafficDirector::populateNearby(const ViewCamera& camera, float radius, int maxSpawn) { int availablePeds = maximumPedestrians - world->pedestrianPool.objects.size(); std::vector created; - // Spawn vehicles at vehicle generators - /// @todo avoid spawning in view frustum unless alwaysSpawn == true + /// @todo Check how "in player view" should be determined. + // Don't check the frustum for things more than 1/2 of the radius away + // so that things will spawn as you drive towards them + float halfRadius2 = std::pow(radius/ 2.f, 2.f); + + // Spawn vehicles at vehicle generators for (auto& gen : world->state->vehicleGenerators) { - if (glm::distance2(center, gen.position) < radius * radius) { + float dist2 = glm::distance2(camera.position, gen.position); + if (dist2 < radius * radius) { + if (dist2 <= halfRadius2 && camera.frustum.intersects(gen.position, 1.f)) { + if (! gen.alwaysSpawn) { + // Don't spawn in the view frustum unless we're forced to + continue; + } + } auto spawned = world->tryToSpawnVehicle(gen); if (spawned) { created.push_back(spawned); @@ -87,7 +102,7 @@ std::vector TrafficDirector::populateNearby(const glm::vec3& center } auto type = AIGraphNode::Pedestrian; - auto available = findAvailableNodes(type, center, radius); + auto available = findAvailableNodes(type, camera, radius); if( availablePeds <= 0 ) { diff --git a/rwengine/src/engine/GameWorld.cpp b/rwengine/src/engine/GameWorld.cpp index 03a16640..786e4c72 100644 --- a/rwengine/src/engine/GameWorld.cpp +++ b/rwengine/src/engine/GameWorld.cpp @@ -23,6 +23,12 @@ #include #include +#include + +// Behaviour Tuning +constexpr float kMaxTrafficSpawnRadius = 100.f; +constexpr float kMaxTrafficCleanupRadius = kMaxTrafficSpawnRadius * 1.25f; + class WorldCollisionDispatcher : public btCollisionDispatcher { public: @@ -218,39 +224,38 @@ InstanceObject *GameWorld::createInstance(const uint16_t id, const glm::vec3& po return nullptr; } -void GameWorld::createTraffic(const glm::vec3& near) +void GameWorld::createTraffic(const ViewCamera& viewCamera) { TrafficDirector director(&aigraph, this); - director.populateNearby( near, 100, 5 ); + director.populateNearby( viewCamera, kMaxTrafficSpawnRadius, 5 ); } -void GameWorld::cleanupTraffic(const glm::vec3& focus) +void GameWorld::cleanupTraffic(const ViewCamera& focus) { - for ( auto& p : pedestrianPool.objects ) - { - if ( p.second->getLifetime() != GameObject::TrafficLifetime ) - { + for (auto& p : pedestrianPool.objects) { + if (p.second->getLifetime() != GameObject::TrafficLifetime) { continue; } - if ( glm::distance( focus, p.second->getPosition() ) >= 100.f ) - { - destroyObjectQueued( p.second ); + if (glm::distance( focus.position, p.second->getPosition() ) >= kMaxTrafficCleanupRadius) { + if (! focus.frustum.intersects(p.second->getPosition(), 1.f)) { + destroyObjectQueued( p.second ); + } } } - for ( auto& p : vehiclePool.objects ) - { - if ( p.second->getLifetime() != GameObject::TrafficLifetime ) - { + for ( auto& p : vehiclePool.objects ) { + if (p.second->getLifetime() != GameObject::TrafficLifetime) { continue; } - if ( glm::distance( focus, p.second->getPosition() ) >= 100.f ) - { - destroyObjectQueued( p.second ); + if (glm::distance( focus.position, p.second->getPosition() ) >= kMaxTrafficCleanupRadius) { + if (! focus.frustum.intersects(p.second->getPosition(), 1.f)) { + destroyObjectQueued( p.second ); + } } } + destroyQueuedObjects(); } diff --git a/rwgame/RWGame.cpp b/rwgame/RWGame.cpp index a858f4c6..c17ee05d 100644 --- a/rwgame/RWGame.cpp +++ b/rwgame/RWGame.cpp @@ -463,13 +463,13 @@ void RWGame::tick(float dt) throw; } } - - if ( state->playerObject ) - { + + /// @todo this doesn't make sense as the condition + if ( state->playerObject ) { + nextCam.frustum.update(nextCam.frustum.projection() * nextCam.getView()); // Use the current camera position to spawn pedestrians. - auto p = nextCam.position; - world->cleanupTraffic(p); - world->createTraffic(p); + world->cleanupTraffic(nextCam); + world->createTraffic(nextCam); } } diff --git a/tests/test_lifetime.cpp b/tests/test_lifetime.cpp index 537acd92..0a8de643 100644 --- a/tests/test_lifetime.cpp +++ b/tests/test_lifetime.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "test_globals.hpp" BOOST_AUTO_TEST_SUITE(LifetimeTests) @@ -19,7 +20,9 @@ BOOST_AUTO_TEST_CASE(test_cleanup) BOOST_CHECK( search != objects.end() ); } - Global::get().e->cleanupTraffic(glm::vec3(0.f, 0.f, 0.f)); + ViewCamera testCamera; + testCamera.position = glm::vec3(0.f, 0.f, 0.f); + Global::get().e->cleanupTraffic(testCamera); { auto search = objects.find(id); diff --git a/tests/test_trafficdirector.cpp b/tests/test_trafficdirector.cpp index a8364b30..8d794eca 100644 --- a/tests/test_trafficdirector.cpp +++ b/tests/test_trafficdirector.cpp @@ -5,6 +5,7 @@ #include #include #include +#include bool operator!=(const AIGraphNode* lhs, const glm::vec3 &rhs) { return lhs->position != rhs; } std::ostream &operator<<(std::ostream &os, const AIGraphNode* yt) { os << glm::to_string(yt->position); return os; } @@ -68,8 +69,10 @@ BOOST_AUTO_TEST_CASE(test_available_nodes) graph.createPathNodes(glm::vec3(), glm::quat(), path); TrafficDirector director(&graph, Global::get().e); + + ViewCamera testCamera(glm::vec3(-5.f, -5.f, 0.f)); - auto open = director.findAvailableNodes(AIGraphNode::Pedestrian, glm::vec3(-5.f, -5.f, 0.f), 10.f); + auto open = director.findAvailableNodes(AIGraphNode::Pedestrian, testCamera, 10.f); std::vector expected { { 0.f,-10.f, 0.f },