Adds FTE navmesh editor from navmesh branch

This commit is contained in:
blubs 2023-02-05 02:07:35 -08:00
parent 30a0895d45
commit 316a8b3372
13 changed files with 3497 additions and 12 deletions

View file

@ -4,8 +4,10 @@
../source/shared/defs/custom.qc
../source/shared/sound_enhanced.qc
../source/shared/weapon_defines.qc
../source/shared/navmesh_defines.qc
../source/client/defs/custom.qc
../source/client/menu.qc
../source/client/achievements.qc
../source/client/hud.qc
../source/client/main.qc
../source/client/navmesh_editor.qc

View file

@ -3,6 +3,7 @@
../source/server/defs/fte.qc
../source/shared/defs/custom.qc
../source/shared/weapon_defines.qc
../source/shared/navmesh_defines.qc
../source/server/defs/custom.qc
../source/server/clientfuncs.qc
@ -33,7 +34,9 @@
../source/server/entities/powerups.qc
../source/server/ai/navmesh_core.qc
../source/server/ai/pathfind_code.qc
../source/server/ai/chase_ai.qc
../source/server/ai/ai_core.qc
../source/server/ai/fte/waypoints_core.qc
../source/server/ai/zombie_core.qc

View file

@ -28,6 +28,13 @@
#pragma warning disable Q302
#define VEC_HULL_MIN '-16 -16 -32'
#define VEC_HULL_MAX '16 16 40'
#define VEC_HULL2_MIN '-32 -32 -24'
#define VEC_HULL2_MAX '32 32 64'
string mappath;
vector cursor_pos; /* Current mouse cursor position, updated in csqc_inputevent */
float g_width, g_height; /* Globals for screen width and height */
@ -143,6 +150,11 @@ float achievement_pages;
float K_LEFTDOWN, K_RIGHTDOWN, K_BACKDOWN, K_FORWARDDOWN;
//Navmesh defs
void() cl_register_navmesh_commands;
float(string cmd) cl_navmesh_console_commands;
void() cl_navmesh_editor_draw;
#define P_JUG 1
#define P_DOUBLE 2
#define P_SPEED 4

View file

@ -78,6 +78,8 @@ noref void(float apiver, string enginename, float enginever) CSQC_Init =
else
platform_is_web = false;
cl_register_navmesh_commands();
//print("CSQC Started\n");
dummy = spawn();
if(serverkey("constate") == "disconnected")
@ -219,6 +221,9 @@ noref void() CSQC_WorldLoaded =
playerpoints[2] = -1;
playerpoints[3] = -1;
mappath = strcat("maps/", mapname);
mappath = strzone(mappath);
Achievement_Init();
// Dummy so that our other point particles work!
@ -582,6 +587,10 @@ noref void(float width, float height, float menushown) CSQC_UpdateView =
deltalisten("models/weapons/pistol/pistol.iqm", add_outline, 0);*/
//deltalisten("models/humanoid_simplerig.iqm", add_outline, 0);
if(cvar("navmesh_edit_mode")) {
cl_navmesh_editor_draw();
}
//does what you think it does
renderscene();
@ -634,6 +643,8 @@ noref float(string cmd) CSQC_ConsoleCommand =
return TRUE;
break;
default:
if(cl_navmesh_console_commands(cmd))
return TRUE;
return FALSE;
}
return FALSE;

File diff suppressed because it is too large Load diff

View file

@ -216,17 +216,17 @@ void create_framegroups() {
// Performs 1D linear interpolation between x1 and x2 on t=0 to t=1
// NOTE - the value of t is clamped to always be 0<=t<=1
float(float x1, float x2, float t) lerp = {
// t = min(max(t,0.0), 1.0);
// Thanks, QC
if(t < 0.0) {
t = 0.0;
}
else if(t > 1.0) {
t = 1.0;
}
return t * (x2 - x1) + x1;
};
// float(float x1, float x2, float t) lerp = {
// // t = min(max(t,0.0), 1.0);
// // Thanks, QC
// if(t < 0.0) {
// t = 0.0;
// }
// else if(t > 1.0) {
// t = 1.0;
// }
// return t * (x2 - x1) + x1;
// };
class AI_Zombie : AI_Chase {

View file

@ -0,0 +1,299 @@
//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;
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");
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++)
{
//Getting vertex count
sv_navmesh_polies[i].vert_count = stof(fgets(file));
//Getting 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));
//Getting 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;
//Getting link count
sv_navmesh_polies[i].connected_polies_count = stof(fgets(file));
//Getting 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));
//Getting 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));
//Getting 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));
//Get polygon doortarget
sv_navmesh_polies[i].doortarget = fgets(file);
//Get entrance edge
sv_navmesh_polies[i].entrance_edge = stoi(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;
}

View file

@ -429,6 +429,7 @@ float rounds_change;
void () Waypoint_Logic;
entity current_way;
float waypoint_mode;
float cl_navmesh_edit_mode;
entity active_way;
#define MAX_WAY_TARGETS 10
@ -472,6 +473,18 @@ string world_fog;
#define UT_ZOOM2 4
#define UT_CROSSHAIR 5
//======================= Navmesh defs ============================
#ifdef PC
void() Navmesh_Editor_Logic;
void() sv_load_navmesh_data;
#endif // PC
// //One struct for temp pathfinding calculations per zombie
// //pathfind_result sv_zombie_pathfind_result[MAX_ZOMBIES];
// pathfind_result *sv_zombie_pathfind_result;
//=================================================================
//Misc patch definitions
.string teddyremovetarget;

View file

@ -346,6 +346,18 @@ void() door_trigger_touch =
return;
}
#ifdef PC
if(cvar("navmesh_edit_mode")) {
if(strcmp(cvar_string("nav_editor_active_door"), self.owner.wayTarget) != 0) {
bprint(PRINT_HIGH, "Current Door for navmesh editor set to \"");
bprint(PRINT_HIGH, self.owner.wayTarget); // naiveil (FIXME)
bprint(PRINT_HIGH, "\"\n");
cvar_set("nav_editor_active_door", self.owner.wayTarget);
}
return;
}
#endif // PC
if (other.health <= 20)
return;

View file

@ -98,6 +98,11 @@ void() StartFrame =
return;
}
if (cl_navmesh_edit_mode) {
return;
}
if (roundinit) {
Round_Core();
@ -335,6 +340,13 @@ void() worldspawn =
mappath = strzone(mappath);
LoadWaypointData();
// sv_load_navmesh_data();
// //Allocating memory for the zombie's pathfinding data
// sv_zombie_pathfind_result = memalloc(sizeof(pathfind_result) * MAX_ZOMBIES);
//I should technically deallocate the memory, but I've nowhere to put this, and it SHOULD be cleared automatically upon vm reset
//memfree(sv_zombie_pathfind_result);
//set game elements
G_STARTPOINTS = 500;

View file

@ -309,7 +309,13 @@ void() PlayerPreThink =
if (cvar("waypoint_mode")) {
Waypoint_Logic();
} else {
}
#ifdef PC
else if (cvar("navmesh_edit_mode")) {
Navmesh_Editor_Logic();
}
#endif // PC
else {
Weapon_Logic();
}

View file

@ -61,6 +61,13 @@ const float EVENT_REVIVECHANGE = 39;
#endif
#endif
// NAVMESH BEGIN - Increases stringtable size, adds support for memalloc.
#ifdef PC
#pragma target FTE
#endif
// NAVMESH END
// Weapon Firetype definitions
#define FIRETYPE_FULLAUTO 0
#define FIRETYPE_SEMIAUTO 1

View file

@ -0,0 +1,109 @@
//A single navmesh vertex
struct navmesh_vertex
{
vector pos;
};
//Either a tri or a quad
struct navmesh_poly
{
float verts[4];
float vert_count;
string doortarget; // "" or matches the .wayTarget field of a door entity. Polygon is only used when door is open (i.e. door.state == STATE_BOTTOM)
// TODO - Remove this, will be replaced by traversals
int entrance_edge; // If != -1, specifies which edge index this polygon can be entered from. (0,1,2, or 3)
//The following fields are only used for actually pathfinding on the navmesh
//The values are calculated in the editor when the navmesh is saved, but are not assigned in the editor before that.
//These values are saved in the navmesh file, and loaded into the server's navmesh
//FIXME: Unhandled edge case when a polygon shares a single edge with more than one polygon (like dropping down from a ledge)
float connected_polies_count;//How many polygons we share an edge with
float connected_polies[4];//Index of the polygons that we share an edge with (similar to links), more than 4 should be impossible (FIXME: highly unlikely)
float connected_polies_left_vert[4];//The left vertex of the shared edge
float connected_polies_right_vert[4];//The right vertex of the shared edge
float connected_polies_neighbor_edge_index[4]; // conected_polies_neighbor_edge_index[i] holds the ith neighbor's edge index we are traversing to enter the neighbor polygon. This is used for polygons that may only be entered from a specific direction.
vector center;//The center of the polygon in 3D space (pre-calculated because why calculate it at runtime)
};
#define NAV_MAX_VERTS 1024
#define NAV_MAX_POLIES 512
// ============================================================================
// Shared Navmesh functions used by both client and server
// ============================================================================
//Returns the distance between the 2d line l1->l2 and pos
float navmesh_2D_line_point_dist(vector l1, vector l2, vector pos)
{
float dot = (l2 - l1) * (pos - l2);
if(dot > 0)
return vlen(l2 - pos);
dot = (l1 - l2) * (pos - l1);
if(dot > 0)
return vlen(l1 - pos);
//2D cross product between (next_vert-vert) and (pos-vert)
//float dist = (l2.x - l1.x) * (pos.y - l1.y) - (l2.y - l1.y) * (pos.y - l1.y);
//dist = dist / vlen(l1 - l2);
float dist = vlen(crossproduct(l2-l1,l1-pos))/vlen(l1-l2);
return fabs(dist);
}
//============= Navmesh Pathfind Defs =============
#define PATHFIND_POLY_SET_NONE 0
#define PATHFIND_POLY_SET_OPEN 1
#define PATHFIND_POLY_SET_CLOSED 2
struct pathfind_result
{
//Contains the set that the ith polygon is in
float poly_set[NAV_MAX_POLIES];
//Contains the index of the last polygon that we used to get to the ith polygon
float poly_prev[NAV_MAX_POLIES];
//Contains the g-score of the ith polygon (distance from start node to ith node along the node path)
float poly_g_score[NAV_MAX_POLIES];
//f score = g score + h score
float poly_f_score[NAV_MAX_POLIES];
//Holds list of polygons in the path (indices of polygons from start to goal)
float result_node_path[NAV_MAX_POLIES];
//How many polies are in the path
float result_node_length;
//Together, these two arrays hold the list of polygon edges we cross on the path
float portals_left_vert[NAV_MAX_POLIES];
float portals_right_vert[NAV_MAX_POLIES];
//How many portals are in the pathfind_portals_left_vert (same as pathfind_result_node_length - 1)
float portals_length;
// The traversal index of the ith polygon's traversal list that was used. -1 if no traversal used.
float traversal_index[NAV_MAX_POLIES];
//Final resultant path:
//Contains a list of points that makes up the path
vector result_path[NAV_MAX_POLIES];
//How many points are in the path
float result_length;
};
//Returns some number > 0 if point p is to the left of line a->b
//Returns some number < 0 if point is to the right of line a->b
//Returns 0 if point is on the line
//(Left / Right is defined in the xy plane, z=0)
float pathfind_point_is_to_left(vector a, vector b, vector p)
{
return (b.x - a.x)*(p.y - a.y) - (b.y - a.y)*(p.x - a.x);
}
// ============================================================================