mirror of
https://github.com/rwengine/openrw.git
synced 2024-09-18 16:32:32 +02:00
Merge pull request #537 from mole99/basic-traffic
[Ready] Basic traffic
This commit is contained in:
commit
a620de0807
@ -1,5 +1,6 @@
|
||||
#include "ai/AIGraph.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
|
||||
#include <glm/gtx/norm.hpp>
|
||||
@ -46,8 +47,8 @@ void AIGraph::createPathNodes(const glm::vec3& position,
|
||||
ainode->nextIndex = node.next >= 0 ? startIndex + node.next : -1;
|
||||
ainode->flags = AIGraphNode::None;
|
||||
ainode->size = node.size;
|
||||
ainode->other_thing = node.other_thing;
|
||||
ainode->other_thing2 = node.other_thing2;
|
||||
ainode->leftLanes = node.leftLanes;
|
||||
ainode->rightLanes = node.rightLanes;
|
||||
ainode->position = nodePosition;
|
||||
ainode->external = node.type == PathNode::EXTERNAL;
|
||||
ainode->disabled = false;
|
||||
@ -96,7 +97,8 @@ glm::ivec2 worldToGrid(const glm::vec2& world) {
|
||||
|
||||
void AIGraph::gatherExternalNodesNear(const glm::vec3& center,
|
||||
const float radius,
|
||||
std::vector<AIGraphNode*>& nodes) {
|
||||
std::vector<AIGraphNode*>& nodes,
|
||||
AIGraphNode::NodeType type) {
|
||||
// the bounds end up covering more than might fit
|
||||
auto planecoords = glm::vec2(center);
|
||||
auto minWorld = planecoords - glm::vec2(radius);
|
||||
@ -111,11 +113,12 @@ void AIGraph::gatherExternalNodesNear(const glm::vec3& center,
|
||||
continue;
|
||||
}
|
||||
auto& external = gridNodes[i];
|
||||
for (AIGraphNode* node : external) {
|
||||
if (glm::distance2(center, node->position) < radius * radius) {
|
||||
nodes.push_back(node);
|
||||
}
|
||||
}
|
||||
copy_if(external.begin(), external.end(), back_inserter(nodes),
|
||||
[¢er, &radius, &type](const AIGraphNode* node) {
|
||||
return node->type == type &&
|
||||
glm::distance2(center, node->position) <
|
||||
radius * radius;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "ai/AIGraphNode.hpp"
|
||||
|
||||
#include <rw/types.hpp>
|
||||
|
||||
struct AIGraphNode;
|
||||
@ -32,7 +34,7 @@ public:
|
||||
PathData& path);
|
||||
|
||||
void gatherExternalNodesNear(const glm::vec3& center, const float radius,
|
||||
std::vector<AIGraphNode*>& nodes);
|
||||
std::vector<AIGraphNode*>& nodes, AIGraphNode::NodeType type);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -16,8 +16,8 @@ struct AIGraphNode {
|
||||
NodeType type;
|
||||
glm::vec3 position{};
|
||||
float size;
|
||||
int other_thing;
|
||||
int other_thing2;
|
||||
int leftLanes;
|
||||
int rightLanes;
|
||||
bool external;
|
||||
uint8_t flags;
|
||||
|
||||
|
@ -99,6 +99,7 @@ void CharacterController::update(float dt) {
|
||||
_currentActivity = nullptr;
|
||||
if (_nextActivity) {
|
||||
setActivity(std::move(_nextActivity));
|
||||
_nextActivity = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,6 +144,255 @@ bool Activities::GoTo::update(CharacterObject *character,
|
||||
return false;
|
||||
}
|
||||
|
||||
glm::vec3 CharacterController::calculateRoadTarget(const glm::vec3 &target,
|
||||
const glm::vec3 &start,
|
||||
const glm::vec3 &end) {
|
||||
// @todo set the real value
|
||||
static constexpr float roadWidth = 5.f;
|
||||
|
||||
static const glm::vec3 up = glm::vec3(0.f, 0.f, 1.f);
|
||||
const glm::vec3 dir = glm::normalize(start - end);
|
||||
|
||||
// Calculate the strafe vector
|
||||
glm::vec3 strafe = glm::cross(up, dir);
|
||||
|
||||
const glm::vec3 laneOffset =
|
||||
strafe *
|
||||
(roadWidth / 2 + roadWidth * static_cast<float>(getLane() - 1));
|
||||
|
||||
return target + laneOffset;
|
||||
}
|
||||
|
||||
void CharacterController::steerTo(const glm::vec3 &target) {
|
||||
// We can't drive without a vehicle
|
||||
VehicleObject* vehicle = character->getCurrentVehicle();
|
||||
if (vehicle == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the steeringAngle
|
||||
float steeringAngle = 0.0f;
|
||||
float deviation = glm::abs(vehicle->isOnSide(target)) / 5.f;
|
||||
|
||||
// If we are almost at the right angle, decrease the deviation to reduce wiggling
|
||||
if (deviation < 1.f) deviation = deviation / 5.f;
|
||||
|
||||
// Make sure to normalize the value
|
||||
deviation = glm::clamp(deviation, 0.f, 1.f);
|
||||
|
||||
// Set the right sign
|
||||
steeringAngle = std::copysign(deviation, -vehicle->isOnSide(target));
|
||||
|
||||
vehicle->setSteeringAngle(steeringAngle, true);
|
||||
}
|
||||
|
||||
// @todo replace this by raytest/raycast logic
|
||||
bool CharacterController::checkForObstacles()
|
||||
{
|
||||
// We can't drive without a vehicle
|
||||
VehicleObject* vehicle = character->getCurrentVehicle();
|
||||
if (vehicle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The minimal distance we test for objects
|
||||
static constexpr float minColDist = 20.f;
|
||||
|
||||
// Try to stop before pedestrians
|
||||
for (const auto &obj : character->engine->pedestrianPool.objects) {
|
||||
// Verify that the character isn't the driver and is walking
|
||||
if (obj.second != character && ((CharacterObject*)obj.second)->getCurrentVehicle() == nullptr) {
|
||||
// Only check characters that are near our vehicle
|
||||
if (glm::distance(vehicle->getPosition(), obj.second->getPosition()) <= minColDist) {
|
||||
// Check if the character is in front of us and in our way
|
||||
if ( vehicle->isInFront(obj.second->getPosition()) > -3.f
|
||||
&& vehicle->isInFront(obj.second->getPosition()) < 10.f
|
||||
&& glm::abs(vehicle->isOnSide(obj.second->getPosition())) < 3.f ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Brake when a car is in front of us and change lanes when possible
|
||||
for (const auto &obj : character->engine->vehiclePool.objects) {
|
||||
// Verify that the vehicle isn't our vehicle
|
||||
if (obj.second != vehicle) {
|
||||
// Only check vehicles that are near our vehicle
|
||||
if (glm::distance(vehicle->getPosition(), obj.second->getPosition()) <= minColDist) {
|
||||
// Check if the vehicle is in front of us and in our way
|
||||
if ( vehicle->isInFront(obj.second->getPosition()) > 0.f
|
||||
&& vehicle->isInFront(obj.second->getPosition()) < 10.f
|
||||
&& glm::abs(vehicle->isOnSide(obj.second->getPosition())) < 2.5f ) {
|
||||
// Check if the road has more than one lane
|
||||
// @todo we don't know the direction of the road, so for now, choose the bigger value
|
||||
int maxLanes = targetNode->rightLanes > targetNode->leftLanes ?
|
||||
targetNode->rightLanes : targetNode->leftLanes;
|
||||
if (maxLanes > 1) {
|
||||
// Change the lane, firstly check if there is an occupant
|
||||
if (((VehicleObject *)obj.second)->getDriver() !=
|
||||
nullptr) {
|
||||
// @todo for now we don't know the lane where the player is currently driving
|
||||
// so just slow down, in the future calculate the lane
|
||||
if (((VehicleObject *)obj.second)
|
||||
->getDriver()
|
||||
->isPlayer()) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
int avoidLane = ((VehicleObject *)obj.second)
|
||||
->getDriver()
|
||||
->controller->getLane();
|
||||
|
||||
// @todo for now just two lanes
|
||||
if (avoidLane == 1) character->controller->setLane(2);
|
||||
else character->controller->setLane(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Activities::DriveTo::update(CharacterObject *character,
|
||||
CharacterController *controller) {
|
||||
|
||||
// We can't drive without a vehicle
|
||||
VehicleObject* vehicle = character->getCurrentVehicle();
|
||||
if (vehicle == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the nodes from the controller
|
||||
AIGraphNode* lastTargetNode = controller->lastTargetNode;
|
||||
AIGraphNode* nextTargetNode = controller->nextTargetNode;
|
||||
|
||||
|
||||
// That's the position the vehicle is actually targeting
|
||||
// depending on the lane, we have to shift its position
|
||||
glm::vec3 roadTarget;
|
||||
|
||||
// A list of nodes we can choose from
|
||||
std::vector<AIGraphNode*> potentialNodes = targetNode->connections;
|
||||
|
||||
// Make sure that we have a lastTargetNode
|
||||
if (lastTargetNode == nullptr) {
|
||||
for (const auto &node : potentialNodes) {
|
||||
if (vehicle->isInFront(node->position) < 0.f) {
|
||||
lastTargetNode = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastTargetNode == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove unwanted nodes
|
||||
for( auto i = potentialNodes.begin(); i != potentialNodes.end(); ) {
|
||||
// @todo we don't know the direction of the road, so for now, choose the bigger value
|
||||
int maxLanes = (*i)->rightLanes > (*i)->leftLanes ? (*i)->rightLanes : (*i)->leftLanes;
|
||||
|
||||
// We don't want roads with lanes <= 0, also ignore the lastTargetNode
|
||||
if ( (*i) == lastTargetNode || maxLanes <= 0) {
|
||||
i = potentialNodes.erase(i);
|
||||
}
|
||||
else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// That's a dead end, try to turn around
|
||||
if (potentialNodes.empty()) {
|
||||
//@todo try to turn around
|
||||
}
|
||||
// Just a normal road
|
||||
if (potentialNodes.size() == 1) {
|
||||
roadTarget = controller->calculateRoadTarget(
|
||||
targetNode->position, lastTargetNode->position,
|
||||
potentialNodes.at(0)->position);
|
||||
}
|
||||
// Intersection, choose a direction
|
||||
else if (potentialNodes.size() > 1) {
|
||||
// Choose the next node randomly
|
||||
if(nextTargetNode == nullptr) {
|
||||
auto& random = character->engine->randomEngine;
|
||||
int i = std::uniform_int_distribution<>(0, potentialNodes.size() - 1)(random);
|
||||
nextTargetNode = potentialNodes.at(i);
|
||||
}
|
||||
|
||||
// Set the nextTargetNode to make sure we go this direction
|
||||
controller->nextTargetNode = nextTargetNode;
|
||||
|
||||
roadTarget = controller->calculateRoadTarget(targetNode->position,
|
||||
lastTargetNode->position,
|
||||
nextTargetNode->position);
|
||||
}
|
||||
// Otherwise set the target to the current node
|
||||
else {
|
||||
roadTarget = targetNode->position;
|
||||
}
|
||||
|
||||
// Check whether we reached the node
|
||||
const auto targetDistance = glm::vec2(vehicle->getPosition() - roadTarget);
|
||||
|
||||
static constexpr float reachDistance = 5.0f;
|
||||
|
||||
if (glm::length(targetDistance) <= reachDistance) {
|
||||
// Finish the activity
|
||||
return true;
|
||||
}
|
||||
|
||||
// @todo set real values
|
||||
static constexpr float maximumSpeed = 10.f;
|
||||
static constexpr float intersectionSpeed = 3.5f;
|
||||
|
||||
float currentSpeed = 0.f;
|
||||
|
||||
// Set the speed depending on where we are driving
|
||||
if ( potentialNodes.size() == 1 ) {
|
||||
currentSpeed = maximumSpeed;
|
||||
}
|
||||
else {
|
||||
currentSpeed = intersectionSpeed;
|
||||
}
|
||||
|
||||
// Check whether a pedestrian or vehicle is in our way
|
||||
if (controller->checkForObstacles() == true) {
|
||||
currentSpeed = 0.f;
|
||||
}
|
||||
|
||||
// Is the vehicle slower than it should be
|
||||
if (vehicle->getVelocity() < currentSpeed) {
|
||||
vehicle->setHandbraking(false);
|
||||
|
||||
// The vehicle is driving backwards, accelerate
|
||||
if (vehicle->getVelocity() < 0) {
|
||||
vehicle->setThrottle(1.f);
|
||||
}
|
||||
// Slowly accelerate until we reach the designated speed
|
||||
else {
|
||||
vehicle->setThrottle(1.f - (vehicle->getVelocity() / currentSpeed));
|
||||
}
|
||||
}
|
||||
// We are to fast, activate the handbrake - works better
|
||||
else {
|
||||
vehicle->setHandbraking(true);
|
||||
}
|
||||
|
||||
// Steer to the target
|
||||
controller->steerTo(roadTarget);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Activities::Jump::update(CharacterObject *character,
|
||||
CharacterController *controller) {
|
||||
RW_UNUSED(controller);
|
||||
@ -276,6 +526,11 @@ bool Activities::EnterVehicle::update(CharacterObject *character,
|
||||
// Play the pullout animation and tell the other character to get
|
||||
// out.
|
||||
character->playCycle(cycle_pullout);
|
||||
|
||||
if (currentOccupant->controller->getCurrentActivity() != nullptr) {
|
||||
currentOccupant->controller->skipActivity();
|
||||
}
|
||||
|
||||
currentOccupant->controller->setNextActivity(
|
||||
std::make_unique<Activities::ExitVehicle>(true));
|
||||
} else {
|
||||
|
@ -51,7 +51,11 @@ public:
|
||||
/**
|
||||
* Wander randomly around the map
|
||||
*/
|
||||
TrafficWander
|
||||
TrafficWander,
|
||||
/**
|
||||
* Drive randomly around the map
|
||||
*/
|
||||
TrafficDriver
|
||||
};
|
||||
|
||||
protected:
|
||||
@ -68,12 +72,19 @@ protected:
|
||||
|
||||
float m_closeDoorTimer{0.f};
|
||||
|
||||
// When driving a vehicle
|
||||
int m_lane;
|
||||
|
||||
// Goal related variables
|
||||
Goal currentGoal{None};
|
||||
CharacterObject* leader = nullptr;
|
||||
AIGraphNode* targetNode = nullptr;
|
||||
|
||||
public:
|
||||
|
||||
AIGraphNode* targetNode;
|
||||
AIGraphNode* lastTargetNode;
|
||||
AIGraphNode* nextTargetNode;
|
||||
|
||||
CharacterController() = default;
|
||||
|
||||
virtual ~CharacterController() = default;
|
||||
@ -132,6 +143,30 @@ public:
|
||||
void setMoveDirection(const glm::vec3& movement);
|
||||
void setLookDirection(const glm::vec2& look);
|
||||
|
||||
/**
|
||||
* @brief createRoadTarget When driving on a road, the targetNode must be shifted to a specific lane
|
||||
*/
|
||||
glm::vec3 calculateRoadTarget(const glm::vec3& target,
|
||||
const glm::vec3& start, const glm::vec3& end);
|
||||
|
||||
/**
|
||||
* @brief steerTo When owning a vehicle, set the steering angle to drive to a target
|
||||
*/
|
||||
void steerTo(const glm::vec3& target);
|
||||
|
||||
/**
|
||||
* @brief checkForObstacles Check whether a pedestrian or vehicle is the way
|
||||
*/
|
||||
bool checkForObstacles();
|
||||
|
||||
void setLane(int lane) {
|
||||
m_lane = lane;
|
||||
}
|
||||
|
||||
int getLane() const {
|
||||
return m_lane;
|
||||
}
|
||||
|
||||
void setRunning(bool run);
|
||||
|
||||
void setGoal(Goal goal) {
|
||||
@ -182,6 +217,25 @@ struct GoTo : public CharacterController::Activity {
|
||||
}
|
||||
};
|
||||
|
||||
struct DriveTo : public CharacterController::Activity {
|
||||
DECL_ACTIVITY(DriveTo)
|
||||
|
||||
AIGraphNode* targetNode = nullptr;
|
||||
bool rampant = false; // Drive fast, ignore traffic rules @todo
|
||||
|
||||
DriveTo() = default;
|
||||
|
||||
DriveTo(AIGraphNode* targetNode, bool _rampant = false)
|
||||
: targetNode(targetNode), rampant(_rampant) {
|
||||
}
|
||||
|
||||
bool update(CharacterObject* character, CharacterController* controller) override;
|
||||
|
||||
bool canSkip(CharacterObject*, CharacterController*) const override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct Jump : public CharacterController::Activity {
|
||||
DECL_ACTIVITY(Jump)
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include "engine/GameWorld.hpp"
|
||||
#include "objects/CharacterObject.hpp"
|
||||
#include "objects/VehicleObject.hpp"
|
||||
|
||||
glm::vec3 DefaultAIController::getTargetPosition() {
|
||||
/*if(targetNode) {
|
||||
@ -80,7 +81,11 @@ void DefaultAIController::update(float dt) {
|
||||
auto& graph = getCharacter()->engine->aigraph;
|
||||
AIGraphNode* node = nullptr;
|
||||
float mindist = std::numeric_limits<float>::max();
|
||||
for (auto n : graph.nodes) {
|
||||
for (const auto& n : graph.nodes) {
|
||||
if (n->type != AIGraphNode::Pedestrian) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float d = glm::distance(n->position,
|
||||
getCharacter()->getPosition());
|
||||
if (d < mindist) {
|
||||
@ -91,6 +96,123 @@ void DefaultAIController::update(float dt) {
|
||||
targetNode = node;
|
||||
}
|
||||
} break;
|
||||
case TrafficDriver: {
|
||||
|
||||
// If we don't own a car, become a pedestrian
|
||||
if (getCharacter()->getCurrentVehicle() == nullptr)
|
||||
{
|
||||
targetNode = nullptr;
|
||||
nextTargetNode = nullptr;
|
||||
lastTargetNode = nullptr;
|
||||
currentGoal = TrafficWander;
|
||||
|
||||
// Try to skip the current activity
|
||||
if (getCharacter()->controller->getCurrentActivity() != nullptr) {
|
||||
getCharacter()->controller->skipActivity();
|
||||
}
|
||||
|
||||
setNextActivity(std::make_unique<Activities::ExitVehicle>());
|
||||
break;
|
||||
}
|
||||
|
||||
// We have a target
|
||||
if (targetNode) {
|
||||
// Either we reached the node or we started new, therefore set the next activity
|
||||
if (getCurrentActivity() == nullptr) {
|
||||
// Assign the last target node
|
||||
lastTargetNode = targetNode;
|
||||
|
||||
// Assign the next target node, either it is already set,
|
||||
// or we have to find one by ourselves
|
||||
if (nextTargetNode != nullptr) {
|
||||
targetNode = nextTargetNode;
|
||||
nextTargetNode = nullptr;
|
||||
}
|
||||
else {
|
||||
float mindist = std::numeric_limits<float>::max();
|
||||
for (const auto& node : lastTargetNode->connections) {
|
||||
const float distance =
|
||||
getCharacter()->getCurrentVehicle()->isInFront(
|
||||
node->position);
|
||||
const float lastDistance =
|
||||
getCharacter()->getCurrentVehicle()->isInFront(
|
||||
lastTargetNode->position);
|
||||
|
||||
// Make sure, that the next node is in front of us, and farther away then the last node
|
||||
if (distance > 0.f && distance > lastDistance) {
|
||||
const float d = glm::distance(
|
||||
node->position, getCharacter()
|
||||
->getCurrentVehicle()
|
||||
->getPosition());
|
||||
|
||||
if (d < mindist) {
|
||||
targetNode = node;
|
||||
mindist = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we haven't found a node, choose one randomly
|
||||
if (!targetNode) {
|
||||
auto& random = getCharacter()->engine->randomEngine;
|
||||
int nodeIndex = std::uniform_int_distribution<>(0, lastTargetNode->connections.size() - 1)(random);
|
||||
targetNode = lastTargetNode->connections.at(nodeIndex);
|
||||
}
|
||||
|
||||
// Check whether the maximum amount of lanes changed and adjust our lane
|
||||
// @todo we don't know the direction of the street, so for now, choose the bigger value
|
||||
int maxLanes = targetNode->rightLanes > targetNode->leftLanes ? targetNode->rightLanes : targetNode->leftLanes;
|
||||
|
||||
if (maxLanes < getLane()) {
|
||||
if(maxLanes <= 0) {
|
||||
setLane(1);
|
||||
}
|
||||
else {
|
||||
setLane(maxLanes);
|
||||
}
|
||||
}
|
||||
|
||||
setNextActivity(std::make_unique<Activities::DriveTo>(
|
||||
targetNode, false));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// We need to pick an initial node
|
||||
auto& graph = getCharacter()->engine->aigraph;
|
||||
AIGraphNode* node = nullptr;
|
||||
float mindist = std::numeric_limits<float>::max();
|
||||
|
||||
for (const auto& n : graph.nodes) {
|
||||
// No vehicle node, continue
|
||||
if (n->type != AIGraphNode::Vehicle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The node must be ahead of the vehicle
|
||||
if (getCharacter()->getCurrentVehicle()->isInFront(n->position) < 0.f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float d = glm::distance(
|
||||
n->position,
|
||||
getCharacter()->getCurrentVehicle()->getPosition());
|
||||
|
||||
if (d < mindist) {
|
||||
node = n;
|
||||
mindist = d;
|
||||
}
|
||||
}
|
||||
|
||||
targetNode = node;
|
||||
|
||||
// Set the next activity
|
||||
if (targetNode) {
|
||||
setNextActivity(std::make_unique<Activities::DriveTo>(
|
||||
targetNode, false));
|
||||
}
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -5,6 +5,9 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/norm.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
#include "ai/AIGraph.hpp"
|
||||
#include "ai/AIGraphNode.hpp"
|
||||
@ -17,7 +20,6 @@
|
||||
#include "objects/VehicleObject.hpp"
|
||||
#include "render/ViewCamera.hpp"
|
||||
|
||||
#include <glm/gtx/norm.hpp>
|
||||
#ifdef RW_WINDOWS
|
||||
#include <rw_mingw.hpp>
|
||||
#endif
|
||||
@ -32,20 +34,28 @@ std::vector<AIGraphNode*> TrafficDirector::findAvailableNodes(
|
||||
std::vector<AIGraphNode*> available;
|
||||
available.reserve(20);
|
||||
|
||||
graph->gatherExternalNodesNear(camera.position, radius, available);
|
||||
graph->gatherExternalNodesNear(camera.position, radius, available, type);
|
||||
|
||||
float density = type == AIGraphNode::Vehicle ? carDensity : pedDensity;
|
||||
float minDist = (10.f / density) * (10.f / density);
|
||||
float minDist = (15.f / density) * (15.f / density);
|
||||
float halfRadius2 = std::pow(radius / 2.f, 2.f);
|
||||
|
||||
// Check if any of the nearby nodes are blocked by a pedestrian standing on
|
||||
// Check if any of the nearby nodes are blocked by a pedestrian or vehicle standing on
|
||||
// it
|
||||
// or because it's inside the view frustum
|
||||
for (auto it = available.begin(); it != available.end();) {
|
||||
bool blocked = false;
|
||||
float dist2 = glm::distance2(camera.position, (*it)->position);
|
||||
|
||||
for (auto& obj : world->pedestrianPool.objects) {
|
||||
for (const auto& obj : world->pedestrianPool.objects) {
|
||||
if (glm::distance2((*it)->position, obj.second->getPosition()) <=
|
||||
minDist) {
|
||||
blocked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& obj : world->vehiclePool.objects) {
|
||||
if (glm::distance2((*it)->position, obj.second->getPosition()) <=
|
||||
minDist) {
|
||||
blocked = true;
|
||||
@ -83,10 +93,15 @@ void TrafficDirector::setDensity(AIGraphNode::NodeType type, float density) {
|
||||
|
||||
std::vector<GameObject*> TrafficDirector::populateNearby(
|
||||
const ViewCamera& camera, float radius, int maxSpawn) {
|
||||
|
||||
auto& random = world->randomEngine;
|
||||
std::vector<GameObject*> created;
|
||||
|
||||
int availablePeds =
|
||||
maximumPedestrians - world->pedestrianPool.objects.size();
|
||||
|
||||
std::vector<GameObject*> created;
|
||||
int availableCars =
|
||||
maximumCars - world->vehiclePool.objects.size();
|
||||
|
||||
/// @todo Check how "in player view" should be determined.
|
||||
|
||||
@ -121,16 +136,9 @@ std::vector<GameObject*> TrafficDirector::populateNearby(
|
||||
}
|
||||
}
|
||||
|
||||
auto type = AIGraphNode::Pedestrian;
|
||||
auto available = findAvailableNodes(type, camera, radius);
|
||||
|
||||
if (availablePeds <= 0) {
|
||||
// We have already reached the limit of spawned traffic
|
||||
return {};
|
||||
}
|
||||
|
||||
/// Hardcoded cop Pedestrian
|
||||
// Hardcoded cop Pedestrian
|
||||
std::vector<uint16_t> peds = {1};
|
||||
|
||||
// Determine which zone the viewpoint is in
|
||||
auto zone = world->data->findZoneAt(camera.position);
|
||||
bool day = (world->state->basic.gameHour >= 8 &&
|
||||
@ -139,34 +147,124 @@ std::vector<GameObject*> TrafficDirector::populateNearby(
|
||||
const auto& group = world->data->pedgroups.at(groupid);
|
||||
peds.insert(peds.end(), group.cbegin(), group.cend());
|
||||
|
||||
std::random_device rd;
|
||||
std::default_random_engine re(rd());
|
||||
std::uniform_int_distribution<> d(0, peds.size() - 1);
|
||||
const glm::vec3 kSpawnOffset{0.f, 0.f, 1.f};
|
||||
// Vehicles for normal traffic @todo create correct vehicle list
|
||||
static constexpr std::array<uint16_t, 32> cars = {90, 91, 92, 94, 95, 97, 98, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
|
||||
111, 112, 116, 119, 128, 129, 130, 134, 135, 136, 138, 139, 144, 146};
|
||||
|
||||
int counter = availablePeds;
|
||||
// maxSpawn can be -1 for "as many as possible"
|
||||
if (maxSpawn > -1) {
|
||||
counter = std::min(availablePeds, maxSpawn);
|
||||
auto availablePedsNodes = findAvailableNodes(AIGraphNode::Pedestrian, camera, radius);
|
||||
|
||||
// We have not reached the limit of spawned pedestrians
|
||||
if (availablePeds > 0) {
|
||||
static const glm::vec3 kSpawnOffset{0.f, 0.f, 1.f};
|
||||
|
||||
int counter = availablePeds;
|
||||
// maxSpawn can be -1 for "as many as possible"
|
||||
if (maxSpawn > -1) {
|
||||
counter = std::min(availablePeds, maxSpawn);
|
||||
}
|
||||
|
||||
for (AIGraphNode* spawn : availablePedsNodes) {
|
||||
if (spawn->type != AIGraphNode::Pedestrian) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (counter > -1) {
|
||||
if (counter <= 0) {
|
||||
break;
|
||||
}
|
||||
counter--;
|
||||
}
|
||||
|
||||
// Spawn a pedestrian from the available pool
|
||||
const uint16_t pedId = peds[std::uniform_int_distribution<>(
|
||||
0, peds.size() - 1)(random)];
|
||||
auto ped = world->createPedestrian(pedId,
|
||||
spawn->position + kSpawnOffset);
|
||||
ped->setLifetime(GameObject::TrafficLifetime);
|
||||
ped->controller->setGoal(CharacterController::TrafficWander);
|
||||
created.push_back(ped);
|
||||
}
|
||||
}
|
||||
|
||||
for (AIGraphNode* spawn : available) {
|
||||
if (spawn->type != AIGraphNode::Pedestrian) {
|
||||
continue;
|
||||
}
|
||||
if (counter > -1) {
|
||||
if (counter == 0) {
|
||||
break;
|
||||
}
|
||||
counter--;
|
||||
auto availableVehicleNodes = findAvailableNodes(AIGraphNode::Vehicle, camera, radius);
|
||||
|
||||
// We have not reached the limit of spawned vehicles
|
||||
if (availableCars > 0) {
|
||||
static const glm::vec3 kSpawnOffset{0.f, 0.f, 1.f};
|
||||
|
||||
int counter = availableCars;
|
||||
// maxSpawn can be -1 for "as many as possible"
|
||||
if (maxSpawn > -1) {
|
||||
counter = std::min(availableCars, maxSpawn);
|
||||
}
|
||||
|
||||
// Spawn a pedestrian from the available pool
|
||||
auto ped = world->createPedestrian(peds[d(re)],
|
||||
spawn->position + kSpawnOffset);
|
||||
ped->setLifetime(GameObject::TrafficLifetime);
|
||||
ped->controller->setGoal(CharacterController::TrafficWander);
|
||||
created.push_back(ped);
|
||||
|
||||
for (AIGraphNode* spawn : availableVehicleNodes) {
|
||||
if (spawn->type != AIGraphNode::Vehicle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (counter > -1) {
|
||||
if (counter <= 0) {
|
||||
break;
|
||||
}
|
||||
counter--;
|
||||
}
|
||||
|
||||
// Get the next node, to spawn in between
|
||||
AIGraphNode* next = spawn->connections.at(0);
|
||||
|
||||
// Set the spawn point to the middle of the two nodes
|
||||
const glm::vec3 diff = (spawn->position - next->position) / 2.f;
|
||||
|
||||
// Calculate the orientation of the vehicle
|
||||
glm::mat4 rotMat = glm::lookAt(next->position, spawn->position, glm::vec3(0,0,1));
|
||||
const glm::mat4 rotate =
|
||||
glm::rotate(glm::radians(90.f), glm::vec3(1, 0, 0));
|
||||
rotMat = rotate * rotMat;
|
||||
|
||||
const glm::quat orientation = glm::conjugate(glm::toQuat(rotMat));
|
||||
|
||||
const glm::vec3 up = glm::vec3(0, 0, 1);
|
||||
const glm::vec3 dir =
|
||||
glm::normalize(next->position - spawn->position);
|
||||
|
||||
// Calculate the strafe vector
|
||||
const glm::vec3 strafe = glm::cross(up, dir);
|
||||
|
||||
// @todo we don't know the direction of the street, so for now, choose the smaller value
|
||||
int maxLanes = spawn->rightLanes < spawn->leftLanes ? spawn->rightLanes : spawn->leftLanes;
|
||||
|
||||
// This street has no lanes, continue
|
||||
if( maxLanes <= 0 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Choose a random lane
|
||||
const int lane =
|
||||
std::uniform_int_distribution<>(1, maxLanes)(random);
|
||||
const glm::vec3 laneOffset =
|
||||
strafe * (2.5f + 5.f * static_cast<float>(lane - 1));
|
||||
|
||||
// Spawn a vehicle from the available pool
|
||||
const uint16_t carId = cars[std::uniform_int_distribution<>(
|
||||
0, cars.size() - 1)(random)];
|
||||
auto vehicle = world->createVehicle(carId, next->position + kSpawnOffset + diff + laneOffset, orientation);
|
||||
vehicle->setLifetime(GameObject::TrafficLifetime);
|
||||
vehicle->setHandbraking(false);
|
||||
|
||||
// Spawn a pedestrian and put it into the vehicle
|
||||
int pedId = peds[std::uniform_int_distribution<>(0, peds.size() - 1)(random)];
|
||||
CharacterObject* character = world->createPedestrian(pedId, vehicle->getPosition());
|
||||
character->setLifetime(GameObject::TrafficLifetime);
|
||||
character->setCurrentVehicle(vehicle, 0);
|
||||
character->controller->setGoal(CharacterController::TrafficDriver);
|
||||
character->controller->setLane(lane);
|
||||
vehicle->setOccupant(0, character);
|
||||
|
||||
created.push_back(character);
|
||||
created.push_back(vehicle);
|
||||
}
|
||||
}
|
||||
|
||||
// Find places it's legal to spawn things
|
||||
|
@ -16,8 +16,8 @@ struct PathNode {
|
||||
int32_t next;
|
||||
glm::vec3 position{};
|
||||
float size;
|
||||
int other_thing;
|
||||
int other_thing2;
|
||||
int leftLanes;
|
||||
int rightLanes;
|
||||
};
|
||||
|
||||
struct PathData {
|
||||
|
@ -232,10 +232,10 @@ bool LoaderIDE::load(const std::string &filename, const PedStatsList &stats) {
|
||||
node.size = strtof(buff.c_str(), nullptr) / 16.f;
|
||||
|
||||
getline(buffstream, buff, ',');
|
||||
node.other_thing = atoi(buff.c_str());
|
||||
node.leftLanes = atoi(buff.c_str());
|
||||
|
||||
getline(buffstream, buff, ',');
|
||||
node.other_thing2 = atoi(buff.c_str());
|
||||
node.rightLanes = atoi(buff.c_str());
|
||||
|
||||
path.nodes.push_back(node);
|
||||
}
|
||||
|
@ -551,8 +551,18 @@ float VehicleObject::getHealth() const {
|
||||
return health;
|
||||
}
|
||||
|
||||
void VehicleObject::setSteeringAngle(float a) {
|
||||
void VehicleObject::setSteeringAngle(float a, bool force) {
|
||||
steerAngle = a;
|
||||
|
||||
if (force && physVehicle) {
|
||||
for (int w = 0; w < physVehicle->getNumWheels(); ++w) {
|
||||
btWheelInfo& wi = physVehicle->getWheelInfo(w);
|
||||
|
||||
if (wi.m_bIsFrontWheel) {
|
||||
physVehicle->setSteeringValue(a, w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float VehicleObject::getSteeringAngle() const {
|
||||
@ -595,7 +605,7 @@ void VehicleObject::ejectAll() {
|
||||
}
|
||||
}
|
||||
|
||||
GameObject* VehicleObject::getOccupant(size_t seat) {
|
||||
GameObject* VehicleObject::getOccupant(size_t seat) const {
|
||||
auto it = seatOccupants.find(seat);
|
||||
if (it != seatOccupants.end()) {
|
||||
return it->second;
|
||||
@ -623,6 +633,10 @@ bool VehicleObject::isOccupantDriver(size_t seat) const {
|
||||
return seat == 0;
|
||||
}
|
||||
|
||||
CharacterObject* VehicleObject::getDriver() const {
|
||||
return static_cast<CharacterObject*>(getOccupant(0));
|
||||
}
|
||||
|
||||
VehicleObject::Part* VehicleObject::getSeatEntryDoor(size_t seat) {
|
||||
auto pos = info->seats[seat].offset + glm::vec3(0.f, 0.5f, 0.f);
|
||||
Part* nearestDoor = nullptr;
|
||||
@ -905,3 +919,66 @@ void VehicleObject::grantOccupantRewards(CharacterObject* character) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float VehicleObject::isInFront(const glm::vec3& point) {
|
||||
// The point we need to test
|
||||
glm::vec3 testPoint;
|
||||
|
||||
testPoint = point - getPosition();
|
||||
|
||||
// The two endpoints of the line
|
||||
glm::vec3 v1;
|
||||
glm::vec3 v2;
|
||||
|
||||
static const glm::vec3 up = glm::vec3(0.f, 0.f, 1.f);
|
||||
const glm::vec3 dir =
|
||||
glm::normalize(getRotation() * glm::vec3(0.f, 1.f, 0.f));
|
||||
|
||||
// Calculate the strafe vector
|
||||
glm::vec3 strafe = glm::cross(up, dir);
|
||||
|
||||
v1 = strafe;
|
||||
v2 = -strafe;
|
||||
|
||||
// Check if the point is behind or in front the car
|
||||
|
||||
glm::vec3 normal(v1.y - v2.y, 0, v2.x - v1.x);
|
||||
normal = glm::normalize(normal);
|
||||
|
||||
const glm::vec3 vecTemp(testPoint.x - v1.x, 0, testPoint.y - v1.y);
|
||||
double distance = glm::dot(vecTemp, normal);
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
float VehicleObject::isOnSide(const glm::vec3& point) {
|
||||
// The point we need to test
|
||||
glm::vec3 testPoint;
|
||||
|
||||
testPoint = point - getPosition();
|
||||
|
||||
// The two endpoints of the line
|
||||
glm::vec3 v1;
|
||||
glm::vec3 v2;
|
||||
|
||||
static const glm::vec3 up = glm::vec3(0.f, 0.f, 1.f);
|
||||
const glm::vec3 dir =
|
||||
glm::normalize(getRotation() * glm::vec3(1.f, 0.f, 0.f));
|
||||
|
||||
// Calculate the strafe vector
|
||||
glm::vec3 strafe = glm::cross(up, dir);
|
||||
|
||||
v1 = strafe;
|
||||
v2 = -strafe;
|
||||
|
||||
// Check if the point is behind or in front the car
|
||||
|
||||
glm::vec3 normal(v1.y - v2.y, 0, v2.x - v1.x);
|
||||
normal = glm::normalize(normal);
|
||||
|
||||
const glm::vec3 vecTemp(testPoint.x - v1.x, 0, testPoint.y - v1.y);
|
||||
double distance = glm::dot(vecTemp, normal);
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,7 @@ public:
|
||||
|
||||
void setExtraEnabled(size_t extra, bool enabled);
|
||||
|
||||
void setSteeringAngle(float);
|
||||
void setSteeringAngle(float, bool=false);
|
||||
|
||||
float getSteeringAngle() const;
|
||||
|
||||
@ -134,7 +134,7 @@ public:
|
||||
|
||||
void ejectAll();
|
||||
|
||||
GameObject* getOccupant(size_t seat);
|
||||
GameObject* getOccupant(size_t seat) const;
|
||||
|
||||
void setOccupant(size_t seat, GameObject* occupant);
|
||||
|
||||
@ -151,6 +151,12 @@ public:
|
||||
*/
|
||||
bool isOccupantDriver(size_t seat) const;
|
||||
|
||||
/**
|
||||
* @brief getDriver
|
||||
* @return CharacterObject* if there is a driver
|
||||
*/
|
||||
CharacterObject* getDriver() const;
|
||||
|
||||
glm::vec3 getSeatEntryPosition(size_t seat) const {
|
||||
auto pos = info->seats[seat].offset;
|
||||
pos -= glm::vec3(glm::sign(pos.x) * -0.81756252f, 0.34800607f,
|
||||
@ -192,6 +198,21 @@ public:
|
||||
*/
|
||||
bool collectSpecial();
|
||||
|
||||
/**
|
||||
* @brief isInFront
|
||||
* @return a positive distance when the point is in front of the car
|
||||
* and a negative distance when the point is behind the car
|
||||
*/
|
||||
float isInFront(const glm::vec3& point);
|
||||
|
||||
/**
|
||||
* @brief collectSpecial
|
||||
* @return a positive distance when the point is at the right side of the car
|
||||
* and a negative distance when the point is at the left side of the car
|
||||
*/
|
||||
float isOnSide(const glm::vec3& point);
|
||||
|
||||
|
||||
void grantOccupantRewards(CharacterObject* character);
|
||||
|
||||
private:
|
||||
|
@ -240,22 +240,20 @@ inline void getClosestNode(const ScriptArguments& args, ScriptVec3& coord, AIGra
|
||||
coord = script::getGround(args, coord);
|
||||
float closest = 10000.f;
|
||||
std::vector<AIGraphNode*> nodes;
|
||||
args.getWorld()->aigraph.gatherExternalNodesNear(coord, closest, nodes);
|
||||
args.getWorld()->aigraph.gatherExternalNodesNear(coord, closest, nodes, type);
|
||||
|
||||
for (const auto &node : nodes) {
|
||||
if (node->type == type) {
|
||||
// This is how the original game calculates distance,
|
||||
// weighted manhattan-distance where the vertical distance
|
||||
// has to be 3x as close to be considered.
|
||||
float dist = std::abs(coord.x - node->position.x);
|
||||
dist += std::abs(coord.y - node->position.y);
|
||||
dist += std::abs(coord.z - node->position.z) * 3.f;
|
||||
if (dist < closest) {
|
||||
closest = dist;
|
||||
xCoord = node->position.x;
|
||||
yCoord = node->position.y;
|
||||
zCoord = node->position.z;
|
||||
}
|
||||
// This is how the original game calculates distance,
|
||||
// weighted manhattan-distance where the vertical distance
|
||||
// has to be 3x as close to be considered.
|
||||
const float dist = std::abs(coord.x - node->position.x) +
|
||||
std::abs(coord.y - node->position.y) +
|
||||
std::abs(coord.z - node->position.z) * 3.f;
|
||||
if (dist < closest) {
|
||||
closest = dist;
|
||||
xCoord = node->position.x;
|
||||
yCoord = node->position.y;
|
||||
zCoord = node->position.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2057,9 +2057,16 @@ void opcode_00ad(const ScriptArguments& args, const ScriptVehicle vehicle, const
|
||||
*/
|
||||
void opcode_00ae(const ScriptArguments& args, const ScriptVehicle vehicle, const ScriptDrivingMode arg2) {
|
||||
RW_UNIMPLEMENTED_OPCODE(0x00ae);
|
||||
RW_UNUSED(vehicle);
|
||||
RW_UNUSED(arg2);
|
||||
RW_UNUSED(args);
|
||||
|
||||
// Check whether we have a driver
|
||||
if (vehicle->getDriver() != nullptr)
|
||||
{
|
||||
// @todo set the right driving style and lane
|
||||
vehicle->getDriver()->controller->setGoal(CharacterController::TrafficDriver);
|
||||
vehicle->getDriver()->controller->setLane(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -754,6 +754,23 @@ void RWGame::renderDebugPaths(float time) {
|
||||
debug.drawLine(position, position + left, color);
|
||||
}
|
||||
|
||||
// Draw the targetNode if a character is driving a vehicle
|
||||
for (auto& p : world->pedestrianPool.objects) {
|
||||
auto v = static_cast<CharacterObject*>(p.second);
|
||||
|
||||
static const btVector3 color(1.f, 1.f, 0.f);
|
||||
|
||||
if (v->controller->targetNode && v->getCurrentVehicle()) {
|
||||
const glm::vec3 pos1 = v->getPosition();
|
||||
const glm::vec3 pos2 = v->controller->targetNode->position;
|
||||
|
||||
btVector3 position1(pos1.x, pos1.y, pos1.z);
|
||||
btVector3 position2(pos2.x, pos2.y, pos2.z);
|
||||
|
||||
debug.drawLine(position1, position2, color);
|
||||
}
|
||||
}
|
||||
|
||||
debug.flush(&renderer);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user