1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-09-03 17:19:46 +02:00

Implement basic traffic

This includes spawning vehicles on the road and rudimentary traffic control with changing lanes and braking in front of characters
This commit is contained in:
mole99 2018-07-03 22:11:04 +02:00
parent c4ad3d9846
commit 5f2fe96167
14 changed files with 729 additions and 75 deletions

View File

@ -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),
[&center, &radius, &type](const AIGraphNode* node) {
return node->type == type &&
glm::distance2(center, node->position) <
radius * radius;
});
}
}
}

View File

@ -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

View File

@ -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;

View File

@ -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 {

View File

@ -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)

View File

@ -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;
}

View File

@ -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

View File

@ -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 {

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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:

View File

@ -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;
}
}
}

View File

@ -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);
}
}
/**

View File

@ -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);
}