quakec/source/server/ai/navmesh_core.qc
blubs cf0ec43631 Fixes funnel algorithm for various edge cases
Adds server-side navmesh v0.1.0 loading
Refactors AI_Chase code
2023-08-02 01:27:40 -07:00

343 lines
11 KiB
C++

//List of all navmesh verts loaded on the server
navmesh_vertex sv_navmesh_verts[NAV_MAX_VERTS];
float sv_navmesh_vert_count;
//List of all navmesh polies loaded on the server
navmesh_poly sv_navmesh_polies[NAV_MAX_POLIES];
float sv_navmesh_poly_count;
// List of all navmesh traversals
navmesh_traversal sv_navmesh_traversals[NAV_MAX_TRAVERSALS];
float sv_navmesh_traversal_count;
void () Navmesh_Editor_Logic =
{
if (!cl_navmesh_edit_mode) {
cl_navmesh_edit_mode = 1;
// ------------------------------------
// Remove and disable all zombies
// ------------------------------------
entity zent;
zent = find (world, classname, "ai_zombie");
while (zent)
{
remove(zent.goaldummy);
remove(zent.larm);
remove(zent.rarm);
remove(zent.head);
remove (zent);
zent = find (zent, classname, "ai_zombie");
}
// -----------------------------------------
// Reset all doors and rename to "door_nzp"
// -----------------------------------------
zent = find (world, classname, "door_nzp_cost");
while (zent)
{
zent.solid = SOLID_NOT;
zent.touch = SUB_Null;
zent = find (zent, classname, "door_nzp_cost");
}
zent = find (world, classname, "door_nzp");
while (zent)
{
zent.solid = SOLID_NOT;
zent.touch = SUB_Null;
zent.solid = SOLID_NOT;
zent = find (zent, classname, "door_nzp");
}
zent = find (world, classname, "window");
while (zent)
{
zent.solid = SOLID_NOT;
zent.touch = SUB_Null;
zent = find (zent, classname, "window");
}
}
// FIXME - Does navmesh need this?
// We do need to modify the server, as doors need to be set to nonsolid,
// but the client-side navmesh editor / functions is pretty nifty...
// Waypoint_Functions();
};
void sv_load_navmesh_data() {
string filepath;
float file;
filepath = strcat(mappath, ".nav");
print("Loading server-side navmesh from file \"", filepath, "\"\n");
file = fopen(filepath,FILE_READ);
if(file == -1) {
print("Error: file \"",filepath,"\" not found.\n");
return;
}
//First line contains navmesh file semver
string nav_file_version = fgets(file);
//Next line contains vertex count
string line = fgets(file);
float vert_count = stof(line);
if(vert_count > sv_navmesh_verts.length) {
print("Error: navmesh file \"",filepath,"\" has an invalid vert count. (" , line, " > ", ftos(sv_navmesh_verts.length),").\n");
fclose(file);
return;
}
//The navmesh appears to be valid (we shouldn't need to clear the current navmesh values)
sv_navmesh_vert_count = vert_count;
//Temp vector to assign component-wise
vector temp;
//Reading all of the vertex positions
for(float i = 0; i < sv_navmesh_vert_count; i++) {
line = fgets(file);
temp = stov(line);
sv_navmesh_verts[i].pos.x = temp.x;
sv_navmesh_verts[i].pos.y = temp.y;
sv_navmesh_verts[i].pos.z = temp.z;
}
//Next line contains the number of polygons
sv_navmesh_poly_count = stof(fgets(file));
//The next lines are each polygon
for(float i = 0; i < sv_navmesh_poly_count; i++) {
// Get vertex count
sv_navmesh_polies[i].vert_count = stof(fgets(file));
// Get vertices
sv_navmesh_polies[i].verts[0] = stof(fgets(file));
sv_navmesh_polies[i].verts[1] = stof(fgets(file));
sv_navmesh_polies[i].verts[2] = stof(fgets(file));
sv_navmesh_polies[i].verts[3] = stof(fgets(file));
// Get polygon center
temp = stov(fgets(file));
sv_navmesh_polies[i].center.x = temp.x;
sv_navmesh_polies[i].center.y = temp.y;
sv_navmesh_polies[i].center.z = temp.z;
// Get link count
sv_navmesh_polies[i].connected_polies_count = stof(fgets(file));
// Get links
sv_navmesh_polies[i].connected_polies[0] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies[1] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies[2] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies[3] = stof(fgets(file));
// Get link edge left vertex
sv_navmesh_polies[i].connected_polies_left_vert[0] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_left_vert[1] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_left_vert[2] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_left_vert[3] = stof(fgets(file));
// Get link edge right vertex
sv_navmesh_polies[i].connected_polies_right_vert[0] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_right_vert[1] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_right_vert[2] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_right_vert[3] = stof(fgets(file));
// Get link edge neighbor entrance edge index
sv_navmesh_polies[i].connected_polies_neighbor_edge_index[0] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_neighbor_edge_index[1] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_neighbor_edge_index[2] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_neighbor_edge_index[3] = stof(fgets(file));
// If navmesh file version 0.0.0, no traversals
// FIXME - Need a better way to do this..
if(strcmp(nav_file_version, "0.0.0") != 0) {
// Get connected traversals
sv_navmesh_polies[i].connected_traversals_count = stof(fgets(file));
for(float j = 0; j < sv_navmesh_polies[i].connected_traversals_count; j++) {
sv_navmesh_polies[i].connected_traversals[j] = stof(fgets(file));
}
}
//Get polygon doortarget
sv_navmesh_polies[i].doortarget = fgets(file);
//Get entrance edge
sv_navmesh_polies[i].entrance_edge = stoi(fgets(file));
}
// -----------------------------------------------------------------------
// Load Traversals
// -----------------------------------------------------------------------
// If navmesh file version 0.0.0, no traversals
// FIXME - Need a better way to do this..
if(strcmp(nav_file_version, "0.0.0") != 0) {
//Next line contains the number of traverals
sv_navmesh_traversal_count = stof(fgets(file));
//The next lines are each traversal
for(float i = 0; i < sv_navmesh_traversal_count; i++) {
line = fgets(file);
temp = stov(line);
sv_navmesh_traversals[i].start_pos.x = temp.x;
sv_navmesh_traversals[i].start_pos.y = temp.y;
sv_navmesh_traversals[i].start_pos.z = temp.z;
line = fgets(file);
temp = stov(line);
sv_navmesh_traversals[i].midpoint_pos.x = temp.x;
sv_navmesh_traversals[i].midpoint_pos.y = temp.y;
sv_navmesh_traversals[i].midpoint_pos.z = temp.z;
line = fgets(file);
temp = stov(line);
sv_navmesh_traversals[i].end_pos.x = temp.x;
sv_navmesh_traversals[i].end_pos.y = temp.y;
sv_navmesh_traversals[i].end_pos.z = temp.z;
sv_navmesh_traversals[i].angle = stof(fgets(file));
sv_navmesh_traversals[i].use_midpoint = stof(fgets(file));
sv_navmesh_traversals[i].end_poly = stof(fgets(file));
}
}
// -----------------------------------------------------------------------
fclose(file);
}
//Navmesh functions used in pathfinding
//Returns 1 if pos is inside poly at index poly_index, 0 otherwise
float sv_navmesh_is_inside_poly(vector pos, float poly_index)
{
//TODO: check if z coord is close enough to poly
local vector vert_to_pos;//points from vert to pos
local vector vert_to_next;//points from vert to the next vertex
local vector vert;
local vector next_vert;
float vert_count = sv_navmesh_polies[poly_index].vert_count;
//We are considered to be in the polygon, if pos is on the left of all edges of the polygon (if verts are ordered in CW order)
for(float i = 0; i < vert_count; i++)
{
vert = sv_navmesh_verts[sv_navmesh_polies[poly_index].verts[i]].pos;
next_vert = sv_navmesh_verts[sv_navmesh_polies[poly_index].verts[(i + 1) % vert_count]].pos;
vert_to_pos = pos - vert;
vert_to_next = next_vert - vert;
//Check if vert_to_pos is to the left of vert_to_next
if(vert_to_next.x * vert_to_pos.y - vert_to_next.y * vert_to_pos.x < 0)
return 0;
}
return 1;
}
//Returns distance from pos to an edge of the poly if pos is very close to an edge of the poly at index poly_index, -1 otherwise
float sv_navmesh_dist_to_poly(vector pos, float poly_index)
{
float leeway = 30;//Must be within 30 qu of edge to be considered close
//TODO: check if close enough on z-axis
local vector vert;
local vector next_vert;
float vert_count = sv_navmesh_polies[poly_index].vert_count;
float shortest_dist = 100000;
//Only considering 2D
//pos.z = 0;
//We are considered to be in the polygon, if pos is on the left of all edges of the polygon (if verts are ordered in CW order)
for(float i = 0; i < vert_count; i++)
{
vert = sv_navmesh_verts[sv_navmesh_polies[poly_index].verts[i]].pos;
next_vert = sv_navmesh_verts[sv_navmesh_polies[poly_index].verts[(i + 1) % vert_count]].pos;
//Calculating in 2D:
//vert.z = 0;
//next_vert.z = 0;
float temp_dist = navmesh_2D_line_point_dist(vert, next_vert, pos);
if(temp_dist < shortest_dist)
{
shortest_dist = temp_dist;
}
}
if(shortest_dist < leeway)
return shortest_dist;
return -1;
}
//Returns polygon that pos is inside of.
//If we are in no polygon, returns a polygon whose edge we are sufficiently close to (might be in but are not due to a small error)
//Otherwise, returns -1
float sv_navmesh_get_containing_poly(vector pos)
{
//Get nearest polygon, and check if pos is in that polygon
float closest_poly = -1;
float closest_poly_dist = 1000000;
//In reality, there's no need to try ALL polygons, the one we are in should be within the nearest 10 or so, but checking all to be safe
for(float i = 0; i < sv_navmesh_poly_count; i++)
{
//Check if we are sufficiently close enough to polygon i in the z-axis
//Current method of checking: if pos is within 30 qu of polygon z-axis bounds
float poly_min_z = sv_navmesh_verts[sv_navmesh_polies[i].verts[0]].pos.z;
float poly_max_z = poly_min_z;
for(float j = 0; j < sv_navmesh_polies[i].vert_count; j++)
{
float vert_z = sv_navmesh_verts[sv_navmesh_polies[i].verts[j]].pos.z;
poly_min_z = min(poly_min_z, vert_z);
poly_max_z = max(poly_max_z, vert_z);
}
// If we NOT between [min - 30, max + 30], skip this polygon
// if(fabs(sv_navmesh_polies[0].center.z - pos.z) >= 30)
if(pos.z < poly_min_z - 30 || pos.z > poly_max_z + 30)
{
continue;
}
//Check if we are in polygon i
if(sv_navmesh_is_inside_poly(pos,i))
{
// NOTE - Because is_inside_poly only checks if we are in it on X & Y axes,
// NOTE we may have found a polygon on a floor below us or above us.
// NOTE However, this may not be an issue. A polygon on a floor below
// NOTE us would be at least about 80-ish qu away, and the checks done for
// NOTE z proximity might be enough to stop us from choosing polygons on
// NOTE different floors. I shall assume this works fine, and wait for a
// NOTE counter-example that shows we'll need a more sophisticated strategy.
//print("Ent pos is inside poly: ",ftos(i),".\n");
return i;
}
//If we are not in polygon i, check if we arse very close to one of its edges
float dist = sv_navmesh_dist_to_poly(pos,i);
if(dist >= 0)
{
if(dist < closest_poly_dist)
{
closest_poly = i;
closest_poly_dist = dist;
}
}
}
//============================================
/*if(closest_poly == -1)
{
print("Ent pos is not in or near any polygons.\n");
}
else
{
print("Ent pos is near but not in poly: closest_poly.\n");
}*/
return closest_poly;
}