// Copyright (c) 2019 Computer Vision Center (CVC) at the Universitat Autonoma // de Barcelona (UAB). // // This work is licensed under the terms of the MIT license. // For a copy, see . #define _USE_MATH_DEFINES // to avoid undefined error of M_PI (bug in Visual // Studio 2015 and 2017) #include #include "carla/Logging.h" #include "carla/nav/Navigation.h" #include "carla/nav/WalkerManager.h" #include "carla/geom/Math.h" #include #include #include namespace carla { namespace nav { enum UpdateFlags { DT_CROWD_ANTICIPATE_TURNS = 1, DT_CROWD_OBSTACLE_AVOIDANCE = 2, DT_CROWD_SEPARATION = 4, DT_CROWD_OPTIMIZE_VIS = 8, DT_CROWD_OPTIMIZE_TOPO = 16 }; // these settings are the same than in RecastBuilder, so if you change the height of the agent, // you should do the same in RecastBuilder static const int MAX_POLYS = 256; static const int MAX_AGENTS = 500; static const int MAX_QUERY_SEARCH_NODES = 2048; static const float AGENT_HEIGHT = 1.8f; static const float AGENT_RADIUS = 0.3f; static const float AGENT_UNBLOCK_DISTANCE = 0.5f; static const float AGENT_UNBLOCK_DISTANCE_SQUARED = AGENT_UNBLOCK_DISTANCE * AGENT_UNBLOCK_DISTANCE; static const float AGENT_UNBLOCK_TIME = 4.0f; static const float AREA_GRASS_COST = 1.0f; static const float AREA_ROAD_COST = 10.0f; // return a random float static float frand() { return static_cast(rand()) / static_cast(RAND_MAX); } Navigation::Navigation() { // assign walker manager _walker_manager.SetNav(this); } Navigation::~Navigation() { _ready = false; _time_to_unblock = 0.0f; _mapped_walkers_id.clear(); _mapped_vehicles_id.clear(); _mapped_by_index.clear(); _walkers_blocked_position.clear(); _yaw_walkers.clear(); _binary_mesh.clear(); dtFreeCrowd(_crowd); dtFreeNavMeshQuery(_nav_query); dtFreeNavMesh(_nav_mesh); } // reference to the simulator to access API functions void Navigation::SetSimulator(std::weak_ptr simulator) { _simulator = simulator; _walker_manager.SetSimulator(simulator); } // set the seed to use with random numbers void Navigation::SetSeed(unsigned int seed) { srand(seed); } // load navigation data bool Navigation::Load(const std::string &filename) { std::ifstream f; std::istream_iterator start(f), end; // read the whole file f.open(filename, std::ios::binary); if (!f.is_open()) { return false; } std::vector content(start, end); f.close(); // parse the content return Load(std::move(content)); } // load navigation data from memory bool Navigation::Load(std::vector content) { const int NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; // 'MSET'; const int NAVMESHSET_VERSION = 1; #pragma pack(push, 1) struct NavMeshSetHeader { int magic; int version; int num_tiles; dtNavMeshParams params; } header; struct NavMeshTileHeader { dtTileRef tile_ref; int data_size; }; #pragma pack(pop) // check size for header if (content.size() < sizeof(header)) { logging::log("Nav: failed loading binary"); return false; } // read the file header unsigned long pos = 0; memcpy(&header, &content[pos], sizeof(header)); pos += sizeof(header); // check file magic and version if (header.magic != NAVMESHSET_MAGIC || header.version != NAVMESHSET_VERSION) { return false; } // allocate object dtNavMesh *mesh = dtAllocNavMesh(); if (!mesh) { return false; } // set number of tiles and origin dtStatus status = mesh->init(&header.params); if (dtStatusFailed(status)) { return false; } // read the tiles data for (int i = 0; i < header.num_tiles; ++i) { NavMeshTileHeader tile_header; // read the tile header memcpy(&tile_header, &content[pos], sizeof(tile_header)); pos += sizeof(tile_header); if (pos >= content.size()) { dtFreeNavMesh(mesh); return false; } // check for valid tile if (!tile_header.tile_ref || !tile_header.data_size) { break; } // allocate the buffer char *data = static_cast(dtAlloc(static_cast(tile_header.data_size), DT_ALLOC_PERM)); if (!data) { break; } // read the tile memcpy(data, &content[pos], static_cast(tile_header.data_size)); pos += static_cast(tile_header.data_size); if (pos > content.size()) { dtFree(data); dtFreeNavMesh(mesh); return false; } // add the tile data mesh->addTile(reinterpret_cast(data), tile_header.data_size, DT_TILE_FREE_DATA, tile_header.tile_ref, 0); } // exchange dtFreeNavMesh(_nav_mesh); _nav_mesh = mesh; // prepare the query object dtFreeNavMeshQuery(_nav_query); _nav_query = dtAllocNavMeshQuery(); _nav_query->init(_nav_mesh, MAX_QUERY_SEARCH_NODES); // copy _binary_mesh = std::move(content); _ready = true; // create and init the crowd manager CreateCrowd(); return true; } void Navigation::CreateCrowd(void) { // check if all is ready if (!_ready) { return; } DEBUG_ASSERT(_crowd == nullptr); // create and init _crowd = dtAllocCrowd(); // these radius should be the maximum size of the vehicles (CarlaCola for Carla) const float max_agent_radius = AGENT_RADIUS * 20; if (!_crowd->init(MAX_AGENTS, max_agent_radius, _nav_mesh)) { logging::log("Nav: failed to create crowd"); return; } // set different filters // filter 0 can not walk on roads _crowd->getEditableFilter(0)->setIncludeFlags(CARLA_TYPE_WALKABLE); _crowd->getEditableFilter(0)->setExcludeFlags(CARLA_TYPE_ROAD); _crowd->getEditableFilter(0)->setAreaCost(CARLA_AREA_ROAD, AREA_ROAD_COST); _crowd->getEditableFilter(0)->setAreaCost(CARLA_AREA_GRASS, AREA_GRASS_COST); // filter 1 can walk on roads _crowd->getEditableFilter(1)->setIncludeFlags(CARLA_TYPE_WALKABLE); _crowd->getEditableFilter(1)->setExcludeFlags(CARLA_TYPE_NONE); _crowd->getEditableFilter(1)->setAreaCost(CARLA_AREA_ROAD, AREA_ROAD_COST); _crowd->getEditableFilter(1)->setAreaCost(CARLA_AREA_GRASS, AREA_GRASS_COST); // Setup local avoidance params to different qualities. dtObstacleAvoidanceParams params; // Use mostly default settings, copy from dtCrowd. memcpy(¶ms, _crowd->getObstacleAvoidanceParams(0), sizeof(dtObstacleAvoidanceParams)); // Low (11) params.velBias = 0.5f; params.adaptiveDivs = 5; params.adaptiveRings = 2; params.adaptiveDepth = 1; _crowd->setObstacleAvoidanceParams(0, ¶ms); // Medium (22) params.velBias = 0.5f; params.adaptiveDivs = 5; params.adaptiveRings = 2; params.adaptiveDepth = 2; _crowd->setObstacleAvoidanceParams(1, ¶ms); // Good (45) params.velBias = 0.5f; params.adaptiveDivs = 7; params.adaptiveRings = 2; params.adaptiveDepth = 3; _crowd->setObstacleAvoidanceParams(2, ¶ms); // High (66) params.velBias = 0.5f; params.adaptiveDivs = 7; params.adaptiveRings = 3; params.adaptiveDepth = 3; _crowd->setObstacleAvoidanceParams(3, ¶ms); } // return the path points to go from one position to another bool Navigation::GetPath(carla::geom::Location from, carla::geom::Location to, dtQueryFilter * filter, std::vector &path, std::vector &area) { // path found float straight_path[MAX_POLYS * 3]; unsigned char straight_path_flags[MAX_POLYS]; dtPolyRef straight_path_polys[MAX_POLYS]; int num_straight_path; int straight_path_options = DT_STRAIGHTPATH_AREA_CROSSINGS; // polys in path dtPolyRef polys[MAX_POLYS]; int num_polys; // check if all is ready if (!_ready) { return false; } DEBUG_ASSERT(_nav_query != nullptr); // point extension float poly_pick_ext[3]; poly_pick_ext[0] = 2; poly_pick_ext[1] = 4; poly_pick_ext[2] = 2; // filter dtQueryFilter filter2; if (filter == nullptr) { filter2.setAreaCost(CARLA_AREA_ROAD, AREA_ROAD_COST); filter2.setAreaCost(CARLA_AREA_GRASS, AREA_GRASS_COST); filter2.setIncludeFlags(CARLA_TYPE_WALKABLE); filter2.setExcludeFlags(CARLA_TYPE_NONE); filter = &filter2; } // set the points dtPolyRef start_ref = 0; dtPolyRef end_ref = 0; float start_pos[3] = { from.x, from.z, from.y }; float end_pos[3] = { to.x, to.z, to.y }; { // critical section, force single thread running this std::lock_guard lock(_mutex); _nav_query->findNearestPoly(start_pos, poly_pick_ext, filter, &start_ref, 0); _nav_query->findNearestPoly(end_pos, poly_pick_ext, filter, &end_ref, 0); } if (!start_ref || !end_ref) { return false; } { // critical section, force single thread running this std::lock_guard lock(_mutex); // get the path of nodes _nav_query->findPath(start_ref, end_ref, start_pos, end_pos, filter, polys, &num_polys, MAX_POLYS); } // get the path of points num_straight_path = 0; if (num_polys == 0) { return false; } // in case of partial path, make sure the end point is clamped to the last // polygon float end_pos2[3]; dtVcopy(end_pos2, end_pos); if (polys[num_polys - 1] != end_ref) { // critical section, force single thread running this std::lock_guard lock(_mutex); _nav_query->closestPointOnPoly(polys[num_polys - 1], end_pos, end_pos2, 0); } // get the points { // critical section, force single thread running this std::lock_guard lock(_mutex); _nav_query->findStraightPath(start_pos, end_pos2, polys, num_polys, straight_path, straight_path_flags, straight_path_polys, &num_straight_path, MAX_POLYS, straight_path_options); } // copy the path to the output buffer path.clear(); path.reserve(static_cast(num_straight_path)); unsigned char area_type; for (int i = 0, j = 0; j < num_straight_path; i += 3, ++j) { // save coordinate for Unreal axis (x, z, y) path.emplace_back(straight_path[i], straight_path[i + 2], straight_path[i + 1]); // save area type { // critical section, force single thread running this std::lock_guard lock(_mutex); _nav_mesh->getPolyArea(straight_path_polys[j], &area_type); } area.emplace_back(area_type); } return true; } bool Navigation::GetAgentRoute(ActorId id, carla::geom::Location from, carla::geom::Location to, std::vector &path, std::vector &area) { // path found float straight_path[MAX_POLYS * 3]; unsigned char straight_path_flags[MAX_POLYS]; dtPolyRef straight_path_polys[MAX_POLYS]; int num_straight_path = 0; int straight_path_options = DT_STRAIGHTPATH_AREA_CROSSINGS; // polys in path dtPolyRef polys[MAX_POLYS]; int num_polys; // check if all is ready if (!_ready) { return false; } DEBUG_ASSERT(_nav_query != nullptr); // point extension float poly_pick_ext[3] = {2,4,2}; // get current filter from agent auto it = _mapped_walkers_id.find(id); if (it == _mapped_walkers_id.end()) return false; const dtQueryFilter *filter; { // critical section, force single thread running this std::lock_guard lock(_mutex); filter = _crowd->getFilter(_crowd->getAgent(it->second)->params.queryFilterType); } // set the points dtPolyRef start_ref = 0; dtPolyRef end_ref = 0; float start_pos[3] = { from.x, from.z, from.y }; float end_pos[3] = { to.x, to.z, to.y }; { // critical section, force single thread running this std::lock_guard lock(_mutex); _nav_query->findNearestPoly(start_pos, poly_pick_ext, filter, &start_ref, 0); _nav_query->findNearestPoly(end_pos, poly_pick_ext, filter, &end_ref, 0); } if (!start_ref || !end_ref) { return false; } // get the path of nodes { // critical section, force single thread running this std::lock_guard lock(_mutex); _nav_query->findPath(start_ref, end_ref, start_pos, end_pos, filter, polys, &num_polys, MAX_POLYS); } // get the path of points if (num_polys == 0) { return false; } // in case of partial path, make sure the end point is clamped to the last // polygon float end_pos2[3]; dtVcopy(end_pos2, end_pos); if (polys[num_polys - 1] != end_ref) { // critical section, force single thread running this std::lock_guard lock(_mutex); _nav_query->closestPointOnPoly(polys[num_polys - 1], end_pos, end_pos2, 0); } // get the points { // critical section, force single thread running this std::lock_guard lock(_mutex); _nav_query->findStraightPath(start_pos, end_pos2, polys, num_polys, straight_path, straight_path_flags, straight_path_polys, &num_straight_path, MAX_POLYS, straight_path_options); } // copy the path to the output buffer path.clear(); path.reserve(static_cast(num_straight_path)); unsigned char area_type; for (int i = 0, j = 0; j < num_straight_path; i += 3, ++j) { // save coordinate for Unreal axis (x, z, y) path.emplace_back(straight_path[i], straight_path[i + 2], straight_path[i + 1]); // save area type { // critical section, force single thread running this std::lock_guard lock(_mutex); _nav_mesh->getPolyArea(straight_path_polys[j], &area_type); } area.emplace_back(area_type); } return true; } // create a new walker in crowd bool Navigation::AddWalker(ActorId id, carla::geom::Location from) { dtCrowdAgentParams params; // check if all is ready if (!_ready) { return false; } DEBUG_ASSERT(_crowd != nullptr); // set parameters memset(¶ms, 0, sizeof(params)); params.radius = AGENT_RADIUS; params.height = AGENT_HEIGHT; params.maxAcceleration = 160.0f; params.maxSpeed = 1.47f; params.collisionQueryRange = 10; params.obstacleAvoidanceType = 3; params.separationWeight = 0.5f; // set if the agent can cross roads or not if (frand() <= _probability_crossing) { params.queryFilterType = 1; } else { params.queryFilterType = 0; } // flags params.updateFlags = 0; params.updateFlags |= DT_CROWD_ANTICIPATE_TURNS; params.updateFlags |= DT_CROWD_OBSTACLE_AVOIDANCE; params.updateFlags |= DT_CROWD_SEPARATION; // from Unreal coordinates (subtract half height to move pivot from center // (unreal) to bottom (recast)) float point_from[3] = { from.x, from.z - (AGENT_HEIGHT / 2.0f), from.y }; // add walker int index; { // critical section, force single thread running this std::lock_guard lock(_mutex); index = _crowd->addAgent(point_from, ¶ms); if (index == -1) { return false; } } // save the id _mapped_walkers_id[id] = index; _mapped_by_index[index] = id; // init yaw _yaw_walkers[id] = 0.0f; // add walker for the route planning _walker_manager.AddWalker(id); return true; } // create a new vehicle in crowd to be avoided by walkers bool Navigation::AddOrUpdateVehicle(VehicleCollisionInfo &vehicle) { namespace cg = carla::geom; dtCrowdAgentParams params; // check if all is ready if (!_ready) { return false; } DEBUG_ASSERT(_crowd != nullptr); // get the bounding box extension plus some space around float marge = 0.8f; float hx = vehicle.bounding.extent.x + marge; float hy = vehicle.bounding.extent.y + marge; // define the 4 corners of the bounding box cg::Vector3D box_corner1 {-hx, -hy, 0}; cg::Vector3D box_corner2 { hx + 0.2f, -hy, 0}; cg::Vector3D box_corner3 { hx + 0.2f, hy, 0}; cg::Vector3D box_corner4 {-hx, hy, 0}; // rotate the points float angle = cg::Math::ToRadians(vehicle.transform.rotation.yaw); box_corner1 = cg::Math::RotatePointOnOrigin2D(box_corner1, angle); box_corner2 = cg::Math::RotatePointOnOrigin2D(box_corner2, angle); box_corner3 = cg::Math::RotatePointOnOrigin2D(box_corner3, angle); box_corner4 = cg::Math::RotatePointOnOrigin2D(box_corner4, angle); // translate to world position box_corner1 += vehicle.transform.location; box_corner2 += vehicle.transform.location; box_corner3 += vehicle.transform.location; box_corner4 += vehicle.transform.location; // check if this actor exists auto it = _mapped_vehicles_id.find(vehicle.id); if (it != _mapped_vehicles_id.end()) { // get the index found int index = it->second; if (index != -1) { // get the agent dtCrowdAgent *agent; { // critical section, force single thread running this std::lock_guard lock(_mutex); agent = _crowd->getEditableAgent(index); } if (agent) { // update its position agent->npos[0] = vehicle.transform.location.x; agent->npos[1] = vehicle.transform.location.z; agent->npos[2] = vehicle.transform.location.y; // update its oriented bounding box agent->params.obb[0] = box_corner1.x; agent->params.obb[1] = box_corner1.z; agent->params.obb[2] = box_corner1.y; agent->params.obb[3] = box_corner2.x; agent->params.obb[4] = box_corner2.z; agent->params.obb[5] = box_corner2.y; agent->params.obb[6] = box_corner3.x; agent->params.obb[7] = box_corner3.z; agent->params.obb[8] = box_corner3.y; agent->params.obb[9] = box_corner4.x; agent->params.obb[10] = box_corner4.z; agent->params.obb[11] = box_corner4.y; } return true; } } // set parameters memset(¶ms, 0, sizeof(params)); params.radius = 2; params.height = AGENT_HEIGHT; params.maxAcceleration = 0.0f; params.maxSpeed = 1.47f; params.collisionQueryRange = 0; params.obstacleAvoidanceType = 0; params.separationWeight = 100.0f; // flags params.updateFlags = 0; params.updateFlags |= DT_CROWD_SEPARATION; // update its oriented bounding box // data: [x][y][z] [x][y][z] [x][y][z] [x][y][z] params.useObb = true; params.obb[0] = box_corner1.x; params.obb[1] = box_corner1.z; params.obb[2] = box_corner1.y; params.obb[3] = box_corner2.x; params.obb[4] = box_corner2.z; params.obb[5] = box_corner2.y; params.obb[6] = box_corner3.x; params.obb[7] = box_corner3.z; params.obb[8] = box_corner3.y; params.obb[9] = box_corner4.x; params.obb[10] = box_corner4.z; params.obb[11] = box_corner4.y; // from Unreal coordinates (vertical is Z) to Recast coordinates (vertical is Y) float point_from[3] = { vehicle.transform.location.x, vehicle.transform.location.z, vehicle.transform.location.y }; // add walker int index; { // critical section, force single thread running this std::lock_guard lock(_mutex); index = _crowd->addAgent(point_from, ¶ms); if (index == -1) { logging::log("Vehicle agent not added to the crowd by some problem!"); return false; } // mark as valid dtCrowdAgent *agent = _crowd->getEditableAgent(index); if (agent) { agent->state = DT_CROWDAGENT_STATE_WALKING; } } // save the id _mapped_vehicles_id[vehicle.id] = index; _mapped_by_index[index] = vehicle.id; return true; } // remove an agent bool Navigation::RemoveAgent(ActorId id) { // check if all is ready if (!_ready) { return false; } DEBUG_ASSERT(_crowd != nullptr); // get the internal walker index auto it = _mapped_walkers_id.find(id); if (it != _mapped_walkers_id.end()) { // remove from crowd { // critical section, force single thread running this std::lock_guard lock(_mutex); _crowd->removeAgent(it->second); } _walker_manager.RemoveWalker(id); // remove from mapping _mapped_walkers_id.erase(it); _mapped_by_index.erase(it->second); return true; } // get the internal vehicle index it = _mapped_vehicles_id.find(id); if (it != _mapped_vehicles_id.end()) { // remove from crowd { // critical section, force single thread running this std::lock_guard lock(_mutex); _crowd->removeAgent(it->second); } // remove from mapping _mapped_vehicles_id.erase(it); _mapped_by_index.erase(it->second); return true; } return false; } // add/update/delete vehicles in crowd bool Navigation::UpdateVehicles(std::vector vehicles) { std::unordered_set updated; // add all current mapped vehicles in the set for (auto &&entry : _mapped_vehicles_id) { updated.insert(entry.first); } // add all vehicles (if already exists, it gets updated only) for (auto &&entry : vehicles) { // try to add or update the vehicle AddOrUpdateVehicle(entry); // mark as updated (to avoid removing it in this frame) updated.erase(entry.id); } // remove all vehicles not updated (they don't exist in this frame) for (auto &&entry : updated) { // remove agent not updated RemoveAgent(entry); } return true; } // set new max speed bool Navigation::SetWalkerMaxSpeed(ActorId id, float max_speed) { // check if all is ready if (!_ready) { return false; } DEBUG_ASSERT(_crowd != nullptr); // get the internal index auto it = _mapped_walkers_id.find(id); if (it == _mapped_walkers_id.end()) { return false; } // get the agent { // critical section, force single thread running this std::lock_guard lock(_mutex); dtCrowdAgent *agent = _crowd->getEditableAgent(it->second); if (agent) { agent->params.maxSpeed = max_speed; return true; } } return false; } // set a new target point to go bool Navigation::SetWalkerTarget(ActorId id, carla::geom::Location to) { // check if all is ready if (!_ready) { return false; } // get the internal index auto it = _mapped_walkers_id.find(id); if (it == _mapped_walkers_id.end()) { return false; } return _walker_manager.SetWalkerRoute(id, to); } // set a new target point to go directly without events bool Navigation::SetWalkerDirectTarget(ActorId id, carla::geom::Location to) { // check if all is ready if (!_ready) { return false; } // get the internal index auto it = _mapped_walkers_id.find(id); if (it == _mapped_walkers_id.end()) { return false; } return SetWalkerDirectTargetIndex(it->second, to); } // set a new target point to go directly without events bool Navigation::SetWalkerDirectTargetIndex(int index, carla::geom::Location to) { // check if all is ready if (!_ready) { return false; } DEBUG_ASSERT(_crowd != nullptr); DEBUG_ASSERT(_nav_query != nullptr); if (index == -1) { return false; } // set target position float point_to[3] = { to.x, to.z, to.y }; float nearest[3]; bool res; { // critical section, force single thread running this std::lock_guard lock(_mutex); const dtQueryFilter *filter = _crowd->getFilter(0); dtPolyRef target_ref; _nav_query->findNearestPoly(point_to, _crowd->getQueryHalfExtents(), filter, &target_ref, nearest); if (!target_ref) { return false; } res = _crowd->requestMoveTarget(index, target_ref, point_to); } return res; } // update all walkers in crowd void Navigation::UpdateCrowd(const client::detail::EpisodeState &state) { // check if all is ready if (!_ready) { return; } DEBUG_ASSERT(_crowd != nullptr); // update crowd agents _delta_seconds = state.GetTimestamp().delta_seconds; { // critical section, force single thread running this std::lock_guard lock(_mutex); _crowd->update(static_cast(_delta_seconds), nullptr); } // update the walkers route _walker_manager.Update(_delta_seconds); // update the time to check for blocked agents _time_to_unblock += _delta_seconds; // check all active agents int total_unblocked = 0; int total_agents; { // critical section, force single thread running this std::lock_guard lock(_mutex); total_agents = _crowd->getAgentCount(); } const dtCrowdAgent *ag; for (int i = 0; i < total_agents; ++i) { { // critical section, force single thread running this std::lock_guard lock(_mutex); ag = _crowd->getAgent(i); } if (!ag->active || ag->paused || ag->dead) { continue; } // check only pedestrians not paused, and no vehicles if (!ag->params.useObb && !ag->paused) { bool reset_target_pos = false; bool use_same_filter = false; // check for unblocking actors if (_time_to_unblock >= AGENT_UNBLOCK_TIME) { // get the distance moved by each actor carla::geom::Vector3D previous = _walkers_blocked_position[i]; carla::geom::Vector3D current = carla::geom::Vector3D(ag->npos[0], ag->npos[1], ag->npos[2]); carla::geom::Vector3D distance = current - previous; float d = distance.SquaredLength(); if (d < AGENT_UNBLOCK_DISTANCE_SQUARED) { ++total_unblocked; reset_target_pos = true; use_same_filter = true; } // update with current position _walkers_blocked_position[i] = current; // check to assign a new target position if (reset_target_pos) { // set if the agent can cross roads or not if (!use_same_filter) { if (frand() <= _probability_crossing) { SetAgentFilter(i, 1); } else { SetAgentFilter(i, 0); } } // set a new random target carla::geom::Location location; GetRandomLocation(location, nullptr); _walker_manager.SetWalkerRoute(_mapped_by_index[i], location); } } } } // check for resetting time if (_time_to_unblock >= AGENT_UNBLOCK_TIME) { _time_to_unblock = 0.0f; } } // get the walker current transform bool Navigation::GetWalkerTransform(ActorId id, carla::geom::Transform &trans) { // check if all is ready if (!_ready) { return false; } DEBUG_ASSERT(_crowd != nullptr); // get the internal index auto it = _mapped_walkers_id.find(id); if (it == _mapped_walkers_id.end()) { return false; } // get the index found int index = it->second; if (index == -1) { return false; } // get the walker const dtCrowdAgent *agent; { // critical section, force single thread running this std::lock_guard lock(_mutex); agent = _crowd->getAgent(index); } if (!agent->active) { return false; } // set its position in Unreal coordinates trans.location.x = agent->npos[0]; trans.location.y = agent->npos[2]; trans.location.z = agent->npos[1]; // set its rotation float yaw; float speed = 0.0f; float min = 0.1f; if (agent->vel[0] < -min || agent->vel[0] > min || agent->vel[2] < -min || agent->vel[2] > min) { yaw = atan2f(agent->vel[2], agent->vel[0]) * (180.0f / static_cast(M_PI)); speed = sqrtf(agent->vel[0] * agent->vel[0] + agent->vel[1] * agent->vel[1] + agent->vel[2] * agent->vel[2]); } else { yaw = atan2f(agent->dvel[2], agent->dvel[0]) * (180.0f / static_cast(M_PI)); speed = sqrtf(agent->dvel[0] * agent->dvel[0] + agent->dvel[1] * agent->dvel[1] + agent->dvel[2] * agent->dvel[2]); } // interpolate current and target angle float shortest_angle = fmod(yaw - _yaw_walkers[id] + 540.0f, 360.0f) - 180.0f; float per = (speed / 1.5f); if (per > 1.0f) per = 1.0f; float rotation_speed = per * 6.0f; trans.rotation.yaw = _yaw_walkers[id] + (shortest_angle * rotation_speed * static_cast(_delta_seconds)); _yaw_walkers[id] = trans.rotation.yaw; return true; } // get the walker current location bool Navigation::GetWalkerPosition(ActorId id, carla::geom::Location &location) { // check if all is ready if (!_ready) { return false; } DEBUG_ASSERT(_crowd != nullptr); // get the internal index auto it = _mapped_walkers_id.find(id); if (it == _mapped_walkers_id.end()) { return false; } // get the index found int index = it->second; if (index == -1) { return false; } // get the walker const dtCrowdAgent *agent; { // critical section, force single thread running this std::lock_guard lock(_mutex); agent = _crowd->getAgent(index); } if (!agent->active) { return false; } // set its position in Unreal coordinates location.x = agent->npos[0]; location.y = agent->npos[2]; location.z = agent->npos[1]; return true; } float Navigation::GetWalkerSpeed(ActorId id) { // check if all is ready if (!_ready) { return 0.0f; } DEBUG_ASSERT(_crowd != nullptr); // get the internal index auto it = _mapped_walkers_id.find(id); if (it == _mapped_walkers_id.end()) { return 0.0f; } // get the index found int index = it->second; if (index == -1) { return 0.0f; } // get the walker const dtCrowdAgent *agent; { // critical section, force single thread running this std::lock_guard lock(_mutex); agent = _crowd->getAgent(index); } return sqrt(agent->vel[0] * agent->vel[0] + agent->vel[1] * agent->vel[1] + agent->vel[2] * agent->vel[2]); } // get a random location for navigation bool Navigation::GetRandomLocation(carla::geom::Location &location, dtQueryFilter * filter) const { // check if all is ready if (!_ready) { return false; } DEBUG_ASSERT(_nav_query != nullptr); // filter dtQueryFilter filter2; if (filter == nullptr) { filter2.setIncludeFlags(CARLA_TYPE_SIDEWALK); filter2.setExcludeFlags(CARLA_TYPE_NONE); filter = &filter2; } // we will try up to 10 rounds, otherwise we failed to find a good location dtPolyRef random_ref { 0 }; float point[3] { 0.0f, 0.0f, 0.0f }; int rounds = 10; { dtStatus status; // critical section, force single thread running this std::lock_guard lock(_mutex); do { status = _nav_query->findRandomPoint(filter, frand, &random_ref, point); // set the location in Unreal coords if (status == DT_SUCCESS) { location.x = point[0]; location.y = point[2]; location.z = point[1]; } --rounds; } while (status != DT_SUCCESS && rounds > 0); } return (rounds > 0); } // assign a filter index to an agent void Navigation::SetAgentFilter(int agent_index, int filter_index) { // get the walker dtCrowdAgent *agent; { // critical section, force single thread running this std::lock_guard lock(_mutex); agent = _crowd->getEditableAgent(agent_index); } agent->params.queryFilterType = static_cast(filter_index); } // set the probability that an agent could cross the roads in its path following // percentage of 0.0f means no pedestrian can cross roads // percentage of 0.5f means 50% of all pedestrians can cross roads // percentage of 1.0f means all pedestrians can cross roads if needed void Navigation::SetPedestriansCrossFactor(float percentage) { _probability_crossing = percentage; } // set an agent as paused for the crowd void Navigation::PauseAgent(ActorId id, bool pause) { // check if all is ready if (!_ready) { return; } DEBUG_ASSERT(_crowd != nullptr); // get the internal index auto it = _mapped_walkers_id.find(id); if (it == _mapped_walkers_id.end()) { return; } // get the index found int index = it->second; if (index == -1) { return; } // get the walker dtCrowdAgent *agent; { // critical section, force single thread running this std::lock_guard lock(_mutex); agent = _crowd->getEditableAgent(index); } // mark agent->paused = pause; } bool Navigation::HasVehicleNear(ActorId id, float distance, carla::geom::Location direction) { // get the internal index (walker or vehicle) auto it = _mapped_walkers_id.find(id); if (it == _mapped_walkers_id.end()) { it = _mapped_vehicles_id.find(id); if (it == _mapped_vehicles_id.end()) { return false; } } float dir[3] = { direction.x, direction.z, direction.y }; bool result; { // critical section, force single thread running this std::lock_guard lock(_mutex); result = _crowd->hasVehicleNear(it->second, distance * distance, dir, false); } return result; } /// make agent look at some location bool Navigation::SetWalkerLookAt(ActorId id, carla::geom::Location location) { // get the internal index (walker or vehicle) auto it = _mapped_walkers_id.find(id); if (it == _mapped_walkers_id.end()) { it = _mapped_vehicles_id.find(id); if (it == _mapped_vehicles_id.end()) { return false; } } dtCrowdAgent *agent; { // critical section, force single thread running this std::lock_guard lock(_mutex); agent = _crowd->getEditableAgent(it->second); } // get the position float x = (location.x - agent->npos[0]) * 0.0001f; float y = (location.y - agent->npos[2]) * 0.0001f; float z = (location.z - agent->npos[1]) * 0.0001f; // set its velocity agent->vel[0] = x; agent->vel[2] = y; agent->vel[1] = z; agent->nvel[0] = x; agent->nvel[2] = y; agent->nvel[1] = z; agent->dvel[0] = x; agent->dvel[2] = y; agent->dvel[1] = z; return true; } bool Navigation::IsWalkerAlive(ActorId id, bool &alive) { // check if all is ready if (!_ready) { return false; } DEBUG_ASSERT(_crowd != nullptr); // get the internal index auto it = _mapped_walkers_id.find(id); if (it == _mapped_walkers_id.end()) { return false; } // get the index found int index = it->second; if (index == -1) { return false; } // get the walker const dtCrowdAgent *agent; { // critical section, force single thread running this std::lock_guard lock(_mutex); agent = _crowd->getAgent(index); } // mark alive = !agent->dead; return true; } } // namespace nav } // namespace carla