diff --git a/rwengine/src/ai/AIGraph.cpp b/rwengine/src/ai/AIGraph.cpp index 29a5b7d0..91781a61 100644 --- a/rwengine/src/ai/AIGraph.cpp +++ b/rwengine/src/ai/AIGraph.cpp @@ -1,5 +1,6 @@ #include "ai/AIGraph.hpp" +#include #include #include @@ -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& nodes) { + std::vector& 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; + }); } } } diff --git a/rwengine/src/ai/AIGraph.hpp b/rwengine/src/ai/AIGraph.hpp index e600c1fb..9bbe4cbe 100644 --- a/rwengine/src/ai/AIGraph.hpp +++ b/rwengine/src/ai/AIGraph.hpp @@ -5,6 +5,8 @@ #include +#include "ai/AIGraphNode.hpp" + #include struct AIGraphNode; @@ -32,7 +34,7 @@ public: PathData& path); void gatherExternalNodesNear(const glm::vec3& center, const float radius, - std::vector& nodes); + std::vector& nodes, AIGraphNode::NodeType type); }; #endif diff --git a/rwengine/src/ai/AIGraphNode.hpp b/rwengine/src/ai/AIGraphNode.hpp index db478b71..dce0b9d7 100644 --- a/rwengine/src/ai/AIGraphNode.hpp +++ b/rwengine/src/ai/AIGraphNode.hpp @@ -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; diff --git a/rwengine/src/ai/CharacterController.cpp b/rwengine/src/ai/CharacterController.cpp index 9e10aa89..f174a8e3 100644 --- a/rwengine/src/ai/CharacterController.cpp +++ b/rwengine/src/ai/CharacterController.cpp @@ -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(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 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(true)); } else { diff --git a/rwengine/src/ai/CharacterController.hpp b/rwengine/src/ai/CharacterController.hpp index b67bd032..9a161b2f 100644 --- a/rwengine/src/ai/CharacterController.hpp +++ b/rwengine/src/ai/CharacterController.hpp @@ -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) diff --git a/rwengine/src/ai/DefaultAIController.cpp b/rwengine/src/ai/DefaultAIController.cpp index b63f0a00..33e55bb7 100644 --- a/rwengine/src/ai/DefaultAIController.cpp +++ b/rwengine/src/ai/DefaultAIController.cpp @@ -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::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()); + 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::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( + targetNode, false)); + } + } + else { + // We need to pick an initial node + auto& graph = getCharacter()->engine->aigraph; + AIGraphNode* node = nullptr; + float mindist = std::numeric_limits::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( + targetNode, false)); + } + } + } break; default: break; } diff --git a/rwengine/src/ai/TrafficDirector.cpp b/rwengine/src/ai/TrafficDirector.cpp index 589f099c..da911703 100644 --- a/rwengine/src/ai/TrafficDirector.cpp +++ b/rwengine/src/ai/TrafficDirector.cpp @@ -5,6 +5,9 @@ #include #include +#include +#include +#include #include "ai/AIGraph.hpp" #include "ai/AIGraphNode.hpp" @@ -17,7 +20,6 @@ #include "objects/VehicleObject.hpp" #include "render/ViewCamera.hpp" -#include #ifdef RW_WINDOWS #include #endif @@ -32,20 +34,28 @@ std::vector TrafficDirector::findAvailableNodes( std::vector 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 TrafficDirector::populateNearby( const ViewCamera& camera, float radius, int maxSpawn) { + + auto& random = world->randomEngine; + std::vector created; + int availablePeds = maximumPedestrians - world->pedestrianPool.objects.size(); - std::vector created; + int availableCars = + maximumCars - world->vehiclePool.objects.size(); /// @todo Check how "in player view" should be determined. @@ -121,16 +136,9 @@ std::vector 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 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 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 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(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 diff --git a/rwengine/src/data/PathData.hpp b/rwengine/src/data/PathData.hpp index edd569ce..788e7675 100644 --- a/rwengine/src/data/PathData.hpp +++ b/rwengine/src/data/PathData.hpp @@ -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 { diff --git a/rwengine/src/loaders/LoaderIDE.cpp b/rwengine/src/loaders/LoaderIDE.cpp index d3de28ba..9ab0eaad 100644 --- a/rwengine/src/loaders/LoaderIDE.cpp +++ b/rwengine/src/loaders/LoaderIDE.cpp @@ -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); } diff --git a/rwengine/src/objects/VehicleObject.cpp b/rwengine/src/objects/VehicleObject.cpp index 61074774..7d0fea85 100644 --- a/rwengine/src/objects/VehicleObject.cpp +++ b/rwengine/src/objects/VehicleObject.cpp @@ -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(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; +} + diff --git a/rwengine/src/objects/VehicleObject.hpp b/rwengine/src/objects/VehicleObject.hpp index a526976c..89bee9dd 100644 --- a/rwengine/src/objects/VehicleObject.hpp +++ b/rwengine/src/objects/VehicleObject.hpp @@ -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: diff --git a/rwengine/src/script/ScriptFunctions.hpp b/rwengine/src/script/ScriptFunctions.hpp index 9bed45c9..6c4cde1a 100644 --- a/rwengine/src/script/ScriptFunctions.hpp +++ b/rwengine/src/script/ScriptFunctions.hpp @@ -240,22 +240,20 @@ inline void getClosestNode(const ScriptArguments& args, ScriptVec3& coord, AIGra coord = script::getGround(args, coord); float closest = 10000.f; std::vector 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; } } } diff --git a/rwengine/src/script/modules/GTA3ModuleImpl.inl b/rwengine/src/script/modules/GTA3ModuleImpl.inl index 59895f35..ff95b29d 100644 --- a/rwengine/src/script/modules/GTA3ModuleImpl.inl +++ b/rwengine/src/script/modules/GTA3ModuleImpl.inl @@ -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); + } } /** diff --git a/rwgame/RWGame.cpp b/rwgame/RWGame.cpp index 9704f7dc..605911a1 100644 --- a/rwgame/RWGame.cpp +++ b/rwgame/RWGame.cpp @@ -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(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); }