#include #include "crowd.h" #include "navmesh_query.h" #include #include #define MAX_AGENTS 20 DetourCrowdManager::DetourCrowdManager() : Node(), crowd(0), dirty(false), initialized(false), query(memnew(DetourNavigationQuery)), max_agents(20), max_agent_radius(0.0f) {} DetourCrowdManager::~DetourCrowdManager() { if (crowd) { if (initialized) dtFreeCrowd(crowd); else dtFree(crowd); } } void DetourCrowdManager::set_navigation_mesh( const Ref &navmesh, const Transform &xform) { this->navmesh = navmesh; this->transform = xform; query->init(navmesh, xform); create_crowd(); } Ref DetourCrowdManager::get_navigation_mesh() const { return navmesh; } int DetourCrowdManager::add_agent(Object *agent, int mode, bool signals, PoolVector inparams) { AgentData *new_agent = memnew(AgentData); int aid = -1, id; uint64_t pref; new_agent->obj_id = agent->get_instance_id(); new_agent->mode = mode; if (inparams.size() != 4) { inparams.resize(4); inparams.write()[0] = 1.0f; inparams.write()[1] = 2.0f; inparams.write()[2] = 0.5f; inparams.write()[3] = 100.0f; } new_agent->radius = inparams[0]; new_agent->height = inparams[1]; new_agent->max_accel = inparams[2]; new_agent->max_speed = inparams[3]; new_agent->filter_id = 0; new_agent->oa_id = 0; new_agent->mode = mode; new_agent->send_signals = signals; Vector3 pos; Spatial *obj = Object::cast_to(agent); if (obj) pos = obj->get_global_transform().origin; pos = _nearest_point(pos, 0, &pref); assert(pref > 0); dtCrowdAgentParams params; memset(¶ms, 0, sizeof(params)); params.radius = new_agent->radius; params.height = new_agent->height; params.maxAcceleration = new_agent->max_accel; params.maxSpeed = new_agent->max_speed; params.pathOptimizationRange = params.radius * 30.0f; params.queryFilterType = (unsigned char)new_agent->filter_id; params.obstacleAvoidanceType = (unsigned char)new_agent->oa_id; if (agents.size() == 0) { agents.push_back(new_agent); aid = 0; } else { bool ok = false; for (aid = 0; aid < agents.size(); aid++) if (agents[aid] == NULL) { agents.write[aid] = new_agent; ok = true; break; } if (!ok) aid = -1; } if (aid == -1) { agents.push_back(new_agent); aid = 0; } params.userData = new_agent; id = crowd->addAgent(&pos.coord[0], ¶ms); assert(id >= 0); // printf("agent id = %d\n", id); new_agent->id = id; dtCrowdAgent *ag = crowd->getEditableAgent(id); assert(ag); assert(ag->state != DT_CROWDAGENT_STATE_INVALID); ag->state = DT_CROWDAGENT_STATE_WALKING; if (max_agents < agents.size()) { max_agents = agents.size() + 20; create_crowd(); } if (signals && obj) obj->emit_signal("agent_added", id); obj->set_meta("agent_id", aid); obj->set_meta("agent_crowd_id", id); return agents.size() - 1; } void DetourCrowdManager::remove_agent(Object *agent) { int agent_id = agent->get_instance_id(); for (int i = 0; i < agents.size(); i++) { if (!agents[i]) continue; if (agents[i]->obj_id == agent_id) { crowd->removeAgent(agents[i]->id); memfree(agents[i]); agents.write[i] = NULL; break; } } } void DetourCrowdManager::clear_agent_list() { agents.clear(); } void DetourCrowdManager::set_target(const Vector3 &position) {} void DetourCrowdManager::set_velocity(const Vector3 &position) {} void DetourCrowdManager::reset_target() {} void DetourCrowdManager::set_max_agents(int max_agents) { this->max_agents = max_agents; create_crowd(); } void DetourCrowdManager::set_max_agent_radius(float radius) { max_agent_radius = radius; create_crowd(); } DetourCrowdManager::AgentData::AgentData() : obj_id(-1), mode(0), id(0), radius(1.0f), height(2.0f), max_accel(10.5f), max_speed(30.0f), filter_id(0), oa_id(0) {} DetourCrowdManager::AgentData::~AgentData() {} void DetourCrowdManager::process_agent(dtCrowdAgent *agent) { if (!agent || !agent->params.userData) return; AgentData *data = (AgentData *)agent->params.userData; bool update_params = false; Object * obj = ObjectDB::get_instance(data->obj_id); if (!obj) return; #if 0 Node *node = Object::cast_to(data->obj); if (!node) returnl if (!node->is_inside_tree()) return; #endif dtCrowdAgentParams params = agent->params; #if 0 if (!params.radius > 0.0f) { params.radius = data->radius; params.height = data->height; params.maxAcceleration = data->max_accel; params.pathOptimizationRange = data->radius * 30.0f; params.queryFilterType = data->filter_id; params.obstacleAvoidanceType = data->oa_id; update_params = true; } #endif if (!params.updateFlags) { params.updateFlags = DT_CROWD_OPTIMIZE_TOPO | DT_CROWD_OPTIMIZE_VIS | DT_CROWD_ANTICIPATE_TURNS | DT_CROWD_SEPARATION | DT_CROWD_OBSTACLE_AVOIDANCE; update_params = true; } if (!(params.separationWeight > 0.0f)) { params.separationWeight = 4.0f; params.collisionQueryRange = 16.0f * data->radius; /* *radius */ update_params = true; } if (update_params) crowd->updateAgentParameters(data->id, ¶ms); Vector3 position; Vector3 velocity; Vector3 desired_velocity; memcpy(&position, agent->npos, sizeof(float) * 3); memcpy(&velocity, agent->vel, sizeof(float) * 3); memcpy(&desired_velocity, agent->dvel, sizeof(float) * 3); #if 0 printf("process_agent: %p: position: %ls, velocity: %ls, desired_velocity: %ls, state: %d\n", agent, String(position).c_str(), String(velocity).c_str(), String(desired_velocity).c_str(), (int)agent->state); #endif #if 0 Transform transform = data->obj->get_global_transform(); if (velocity.length() == 0.0f) velocity = transform.basis[2]; if (data->mode == 0) data->obj->look_at_from_position(position, position + velocity, Vector3(0, 1, 0)); #endif if (data->send_signals) obj->emit_signal("agent_position", position, velocity, desired_velocity, (int)agent->state); obj->set_meta("agent_position", position); obj->set_meta("agent_velocity", velocity); obj->set_meta("agent_desired_velocity", desired_velocity); obj->set_meta("agent_state", (int)agent->state); } Vector3 DetourCrowdManager::_nearest_point(const Vector3 &point, int query_filter, polyref_t *nearest_ref) { if (!navmesh.is_valid() || !crowd) return point; polyref_t nearestRef = 0; Vector3 ret; if (!query) { if (nearest_ref) *nearest_ref = nearestRef; return point; } ret = query->nearest_point_(point, Vector3(*reinterpret_cast(crowd->getQueryExtents())), crowd->getFilter(query_filter), &nearestRef); if (nearest_ref) *nearest_ref = nearestRef; return ret; } bool DetourCrowdManager::has_arrived(int id) { bool ret; dtCrowdAgent* ag = crowd->getEditableAgent(agents[id]->id); assert(ag); ret = !ag->ncorners || (ag->cornerFlags[ag->ncorners - 1] & DT_STRAIGHTPATH_END && dtVdist2D(ag->npos, &ag->cornerVerts[(ag->ncorners - 1) * 3]) <= ag->params.radius); return ret; } void DetourCrowdManager::clear_agent_target(int id) { if (!agents[id]) { printf("!!bad agent %d\n", id); return; } assert(agents[id]); crowd->resetMoveTarget(agents[id]->id); } void DetourCrowdManager::set_agent_target_position(int id, const Vector3 &position) { uint64_t pref; if (!agents[id]) { printf("!!bad agent %d\n", id); return; } assert(agents[id]); Vector3 close = _nearest_point(position, 0, &pref); dtPolyRef nearestRef = (dtPolyRef)pref; // printf("set target position %d %d %ls\n", id, agents[id]->id, String(close).c_str()); dtCrowdAgent *ag = crowd->getEditableAgent(agents[id]->id); if (ag->state == DT_CROWDAGENT_STATE_INVALID) ag->state = DT_CROWDAGENT_STATE_WALKING; crowd->requestMoveTarget(agents[id]->id, nearestRef, &close.coord[0]); } void DetourCrowdManager::reset_agent(int id) { dtCrowdAgent *ag = crowd->getEditableAgent(agents[id]->id); dtCrowdAgentParams params; Vector3 pos, target; uint64_t tref; Object *obj = ObjectDB::get_instance(agents[id]->obj_id); assert(obj); Spatial *sp = Object::cast_to(obj); assert(ag); memcpy(¶ms, &ag->params, sizeof(params)); if (!sp) memcpy(&pos.coord[0], ag->npos, sizeof(pos.coord)); else pos = sp->get_global_transform().origin; memcpy(&target.coord[0], ag->targetPos, sizeof(target.coord)); tref = ag->targetRef; crowd->removeAgent(agents[id]->id); int new_agent = crowd->addAgent(&pos.coord[0], ¶ms); assert(new_agent >= 0); agents[id]->id = new_agent; crowd->requestMoveTarget(agents[id]->id, tref, &target.coord[0]); } void DetourCrowdManager::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: create_crowd(); set_process(true); break; case NOTIFICATION_ENTER_TREE: create_crowd(); break; case NOTIFICATION_EXIT_TREE: agents.clear(); break; case NOTIFICATION_PROCESS: float delta = get_process_delta_time(); // update_crowd(delta); if (crowd && navmesh.is_valid() && agents.size() > 0) { int i; crowd->update(delta, NULL); Vector active_agents; active_agents.resize(agents.size()); int nactive = crowd->getActiveAgents(&active_agents.write[0], agents.size()); for (i = 0; i < nactive; i++) process_agent(active_agents[i]); for (i = 0; i < agents.size(); i++) { if (!agents[i]) continue; Object *obj = ObjectDB::get_instance(agents[i]->obj_id); assert(obj); if (!obj->has_meta("agent_position")) continue; Vector3 pos = obj->get_meta("agent_position"); Spatial *sp = Object::cast_to(obj); Vector3 opos = sp->get_global_transform().origin; if (fabs(pos.y - opos.y) < 1.5f) pos.y = opos.y; /* resetting agent position */ dtCrowdAgent *ag = crowd->getEditableAgent(agents[i]->id); if (!ag->active) continue; // printf("%d:%d active: %d state: %d tstate: %d vel: %f %f %f\n", i, agents[i]->id, ag->active, ag->state, ag->targetState, ag->vel[0], ag->vel[1], ag->vel[2]); if (opos.distance_squared_to(pos) > 0.01f || ag->state == DT_CROWDAGENT_STATE_INVALID) { uint64_t pref; Vector3 close = _nearest_point(opos, 0, &pref); dtPolyRef nearestRef = (dtPolyRef)pref; memcpy(ag->npos, &close.coord[0], sizeof(float) * 3); if (ag->targetState != DT_CROWDAGENT_TARGET_NONE) crowd->requestMoveTarget(agents[i]->id, ag->targetRef, ag->targetPos); } if (ag->state == DT_CROWDAGENT_STATE_INVALID) { ag->state = DT_CROWDAGENT_STATE_WALKING; if (ag->targetState != DT_CROWDAGENT_TARGET_NONE) crowd->requestMoveTarget(agents[i]->id, ag->targetRef, ag->targetPos); } } } break; } } void DetourCrowdManager::update_crowd(float delta) { if (dirty) create_crowd(); } bool DetourCrowdManager::create_crowd() { dirty = true; if (!navmesh.is_valid()) return false; if (crowd) { if (initialized) dtFreeCrowd(crowd); else dtFree(crowd); initialized = false; } crowd = dtAllocCrowd(); if (max_agent_radius == 0.0f) max_agent_radius = navmesh->get_agent_radius(); if (navmesh->get_navmesh() != NULL) print_line("good navmesh"); else print_line("bad navmesh"); if (!crowd->init(max_agents, max_agent_radius, navmesh->get_navmesh())) return false; dirty = false; initialized = true; return true; } void DetourCrowdManager::set_area_cost(int filter_id, int area_id, float cost) { dtQueryFilter *filter = crowd->getEditableFilter(filter_id); if (filter) filter->setAreaCost(area_id, cost); } float DetourCrowdManager::get_area_cost(int filter_id, int area_id) { const dtQueryFilter *filter = crowd->getFilter(filter_id); if (!filter) return 1.0f; return filter->getAreaCost(area_id); } void DetourCrowdManager::set_include_flags(int filter_id, unsigned short flags) { dtQueryFilter *filter = crowd->getEditableFilter(filter_id); if (filter) filter->setIncludeFlags(flags); } void DetourCrowdManager::set_exclude_flags(int filter_id, unsigned short flags) { dtQueryFilter *filter = crowd->getEditableFilter(filter_id); if (filter) filter->setExcludeFlags(flags); } unsigned short DetourCrowdManager::get_include_flags(int filter_id) { const dtQueryFilter *filter = crowd->getFilter(filter_id); if (!filter) return 0U; return filter->getIncludeFlags(); } unsigned short DetourCrowdManager::get_exclude_flags(int filter_id) { const dtQueryFilter *filter = crowd->getFilter(filter_id); if (!filter) return 0U; return filter->getExcludeFlags(); } void DetourCrowdManager::_bind_methods() { ClassDB::bind_method(D_METHOD("set_navigation_mesh", "navmesh", "xform"), &DetourCrowdManager::set_navigation_mesh); ClassDB::bind_method(D_METHOD("get_navigation_mesh"), &DetourCrowdManager::get_navigation_mesh); ClassDB::bind_method(D_METHOD("add_agent", "agent", "mode", "signals", "params"), &DetourCrowdManager::add_agent); ClassDB::bind_method(D_METHOD("remove_agent", "agent"), &DetourCrowdManager::remove_agent); ClassDB::bind_method(D_METHOD("clear_agent_list"), &DetourCrowdManager::clear_agent_list); ClassDB::bind_method(D_METHOD("get_agent_obj", "id"), &DetourCrowdManager::get_agent_obj); ClassDB::bind_method(D_METHOD("get_agent_mode", "id"), &DetourCrowdManager::get_agent_mode); ClassDB::bind_method(D_METHOD("get_agent_count"), &DetourCrowdManager::get_agent_count); ClassDB::bind_method(D_METHOD("set_target", "position"), &DetourCrowdManager::set_target); ClassDB::bind_method(D_METHOD("set_velocity", "velocity"), &DetourCrowdManager::set_velocity); ClassDB::bind_method(D_METHOD("reset_target"), &DetourCrowdManager::reset_target); ClassDB::bind_method(D_METHOD("set_max_agents", "max_agents"), &DetourCrowdManager::set_max_agents); ClassDB::bind_method(D_METHOD("get_max_agents"), &DetourCrowdManager::get_max_agents); ClassDB::bind_method(D_METHOD("set_max_agent_radius", "max_agent_radius"), &DetourCrowdManager::set_max_agent_radius); ClassDB::bind_method(D_METHOD("get_max_agent_radius"), &DetourCrowdManager::get_max_agent_radius); ClassDB::bind_method( D_METHOD("set_area_cost", "filter_id", "area_id", "cost"), &DetourCrowdManager::set_area_cost); ClassDB::bind_method(D_METHOD("get_area_cost", "filter_id", "area_id"), &DetourCrowdManager::get_area_cost); ClassDB::bind_method(D_METHOD("set_include_flags", "filter_id", "flags"), &DetourCrowdManager::set_include_flags); ClassDB::bind_method(D_METHOD("get_include_flags", "filter_id"), &DetourCrowdManager::get_include_flags); ClassDB::bind_method(D_METHOD("set_exclude_flags", "filter_id", "flags"), &DetourCrowdManager::set_exclude_flags); ClassDB::bind_method(D_METHOD("get_exclude_flags", "filter_id"), &DetourCrowdManager::get_exclude_flags); ClassDB::bind_method(D_METHOD("set_agent_target_position", "id", "position"), &DetourCrowdManager::set_agent_target_position); } #if 0 // void DetourCrowdManager::agent_update_cb(dtCrowdAgent *ag, float dt) //{ //} bool Crowd::_set(const StringName &p_name, const Variant &p_value) { print_line("_set"); if (!manager) return false; String name = p_name; print_line(String() + "setting " + name); if (name == "add_object") { if (p_value.get_type() == Variant::NIL) return false; String path = p_value; NodePath npname = p_value; print_line(String() + "setting spatial " + path); agent_paths.push_back(npname); modes.push_back(0); print_line(String() + "agent count: " + itos(agent_paths.size())); update_agent_list(); _change_notify(); return true; } else if (name == "nav_mesh") { if (p_value.get_type() == Variant::NODE_PATH) { NodePath path = p_value; DetourNavigationMeshInstance *nmi = (DetourNavigationMeshInstance*)get_node(path); if (nmi) { manager->set_navigation_mesh(nmi->get_navmesh()); _change_notify(); print_line("navmesh set from path"); return true; } } else if (p_value.get_type() == Variant::OBJECT) { Ref ov = p_value; if (ov.is_valid()) { manager->set_navigation_mesh((Ref)ov); print_line("navmesh set from resource"); _change_notify(); return true; } } else print_line(String() + "type: " + itos(p_value.get_type())); return false; } else if (name.begins_with("agents")) { int idx = name.get_slice("/", 1).to_int(); String what = name.get_slice("/", 2); if (what == "path") { if (agent_paths.size() > idx) { NodePath path = p_value; agent_paths.write[idx] = path; } else { NodePath path = p_value; agent_paths.push_back(path); } } else if (what == "mode") { int mode = p_value; if (modes.size() > idx) modes.write[idx] = mode; else modes.push_back(mode); } else if (what == "remove") { bool rm = p_value; if (rm) { agent_paths.remove(idx); modes.remove(idx); } } _change_notify(); update_agent_list(); return true; } return false; } bool Crowd::_get(const StringName &p_name, Variant &r_ret) const { print_line("_get"); if (!manager) return false; String name = p_name; if (name == "nav_mesh") { r_ret = manager->get_navigation_mesh(); return true; } else if (name.begins_with("agents")) { int idx = name.get_slice("/", 1).to_int(); String what = name.get_slice("/", 2); if (what == "path") { r_ret = agent_paths[idx]; return true; } else if (what == "mode") { if (modes.size() > idx) r_ret = modes[idx]; else r_ret = 0; return true; } else if (what == "remove") { r_ret = false; return true; } } return false; } void Crowd::_get_property_list(List *p_list) const { print_line("_get_property_list"); if (!manager) return; if (manager->get_agent_count() > 0) { for (int i = 0; i < agent_paths.size(); i++) { p_list->push_back(PropertyInfo(Variant::NODE_PATH, "agents/" + itos(i) + "/path", PROPERTY_HINT_NONE, "")); p_list->push_back(PropertyInfo(Variant::INT, "agents/" + itos(i) + "/mode", PROPERTY_HINT_ENUM, "normal,signal")); p_list->push_back(PropertyInfo(Variant::BOOL, "agents/" + itos(i) + "/remove", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); } } p_list->push_back(PropertyInfo(Variant::NODE_PATH, "add_object", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Spatial")); if (!manager->get_navigation_mesh().is_valid()) p_list->push_back(PropertyInfo(Variant::NODE_PATH, "nav_mesh", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "DetourNavigationMeshInstance")); else p_list->push_back(PropertyInfo(Variant::OBJECT, "nav_mesh", PROPERTY_HINT_RESOURCE_TYPE, "DetourNavigationMesh")); } Crowd::Crowd() : manager(0) { } Crowd::~Crowd() { } void Crowd::_notification(int p_what) { switch(p_what) { case NOTIFICATION_READY: print_line("a"); if (!manager) return; else update_agent_list(); break; case NOTIFICATION_ENTER_TREE: print_line("b"); manager = Object::cast_to(get_parent()); if (!manager) return; else { print_line("manager set"); update_agent_list(); } break; case NOTIFICATION_EXIT_TREE: print_line("c"); manager = NULL; break; case NOTIFICATION_PROCESS: float delta = get_process_delta_time(); // update_crowd(delta); break; } } String Crowd::get_configuration_warning() { String ret; print_line("get_configuration_warning"); if (!is_inside_tree()) return ret; if (!manager) ret += TTR("Incorrect instancing. "); if (!Object::cast_to(get_parent())) ret += TTR("Should be parented to DetourCrowdManager. "); if (manager && !manager->get_navigation_mesh().is_valid()) ret += TTR("No navmesh data are set to function. "); return ret; } void Crowd::update_agent_list() { if (!is_inside_tree()) return; if (!manager) return; print_line("update_agent_list"); manager->clear_agent_list(); print_line("update_agent_list 1"); for (int i = 0; i < agent_paths.size(); i++) { print_line("update_agent_list 2: " + itos(i)); if (String(agent_paths[i]).length() > 0) { print_line("update_agent_list 3: " + itos(i)); Spatial *obj = (Spatial *)get_node(agent_paths[i]); print_line("update_agent_list 4: " + itos(i)); if (obj) { print_line("update_agent_list 5: " + itos(i)); manager->add_agent(obj, modes[i]); print_line("object added ok 0"); } print_line("update_agent_list 6: " + itos(i)); } else { print_line("update_agent_list 7: " + itos(i)); manager->add_agent(NULL, modes[i]); } } print_line("update_agent_list done"); } void Crowd::_bind_methods() { } #endif