mirror of
https://github.com/nzp-team/quakec.git
synced 2025-02-24 12:42:27 +00:00
2999 lines
No EOL
99 KiB
C++
2999 lines
No EOL
99 KiB
C++
|
|
// ============================================================================
|
|
// Navmesh editing functions
|
|
// ============================================================================
|
|
|
|
#define NAV_MAX_SELECTED_VERTS 4
|
|
|
|
int *selected_verts;
|
|
float selected_vert_count;
|
|
navmesh_vertex *cl_navmesh_verts;
|
|
float cl_navmesh_vert_count;
|
|
navmesh_poly *cl_navmesh_polies;
|
|
float cl_navmesh_poly_count;
|
|
|
|
|
|
|
|
|
|
// float selected_verts[NAV_MAX_SELECTED_VERTS];
|
|
// float selected_vert_count;
|
|
|
|
// navmesh_vertex cl_navmesh_verts[NAV_MAX_VERTS];
|
|
// float cl_navmesh_vert_count;
|
|
|
|
// navmesh_poly cl_navmesh_polies[NAV_MAX_POLIES];
|
|
// float cl_navmesh_poly_count;
|
|
|
|
|
|
float cl_navmesh_place_corner_state;
|
|
#define NAVMESH_PLACE_CORNER_PLACING 1
|
|
#define NAVMESH_PLACE_CORNER_CONFIRM 2
|
|
|
|
// Declarations
|
|
void(vector pos, vector scale, vector color, float alpha) cl_navmesh_draw_test_ent;
|
|
void() cl_navmesh_delete_verts;
|
|
void(float poly_index) cl_navmesh_delete_poly_at_index;
|
|
float(vector pos) cl_navmesh_get_containing_poly;
|
|
|
|
|
|
//This is for placing a temp entity to test the pathfinding
|
|
vector goalent_pos;
|
|
float goalent_set;
|
|
vector startent_pos;
|
|
float startent_set;
|
|
|
|
void cl_navmesh_draw_box(vector pos,vector size, vector color, float alpha) {
|
|
//Assigning the shader as something else so that fte doesn't batch the calls (leading to colors not changing between draw calls)
|
|
R_BeginPolygon("debug/wireframe",0);
|
|
R_BeginPolygon("debug/solid_nocull",0);
|
|
|
|
// Size is radius:
|
|
size = size * 0.5;
|
|
|
|
//bottom face
|
|
R_PolygonVertex(pos + [-size.x,size.y,-size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [size.x,size.y,-size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [size.x,-size.y,-size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [-size.x,-size.y,-size.z], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
//Top face
|
|
R_PolygonVertex(pos + [-size.x,size.y,size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [size.x,size.y,size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [size.x,-size.y,size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [-size.x,-size.y,size.z], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
//Front face
|
|
R_PolygonVertex(pos + [-size.x,-size.y,-size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [size.x,-size.y,-size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [size.x,-size.y,size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [-size.x,-size.y,size.z], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
//Back face
|
|
R_PolygonVertex(pos + [-size.x,size.y,-size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [size.x,size.y,-size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [size.x,size.y,size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [-size.x,size.y,size.z], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
//Left face
|
|
R_PolygonVertex(pos + [-size.x,-size.y,-size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [-size.x,size.y,-size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [-size.x,size.y,size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [-size.x,-size.y,size.z], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
//Right face
|
|
R_PolygonVertex(pos + [size.x,-size.y,-size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [size.x,size.y,-size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [size.x,size.y,size.z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [size.x,-size.y,size.z], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
}
|
|
|
|
void cl_navmesh_draw_vert(vector pos,vector color, float alpha) {
|
|
cl_navmesh_draw_box(pos, [4,4,4], color, alpha);
|
|
}
|
|
|
|
|
|
void cl_navmesh_draw_plane(vector pos, vector nor, float size, vector color, float alpha) {
|
|
//Assigning the shader as something else so that fte doesn't batch the calls (leading to colors not changing between draw calls)
|
|
R_BeginPolygon("debug/wireframe",0);
|
|
R_BeginPolygon("debug/solid_nocull",0);
|
|
|
|
vector nor_angles = vectoangles(nor);
|
|
makevectors(nor_angles);
|
|
vector tl = pos + (-v_right + v_up) * size;
|
|
vector tr = pos + ( v_right + v_up) * size;
|
|
vector bl = pos + (-v_right - v_up) * size;
|
|
vector br = pos + ( v_right - v_up) * size;
|
|
|
|
// Front face
|
|
R_PolygonVertex(bl, [0,0,0], color,alpha);
|
|
R_PolygonVertex(br, [0,0,0], color,alpha);
|
|
R_PolygonVertex(tr, [0,0,0], color,alpha);
|
|
R_PolygonVertex(tl, [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
|
|
// Back face -- not needed when backface culling is disabled
|
|
// R_PolygonVertex(bl, [0,0,0], color,alpha);
|
|
// R_PolygonVertex(tl, [0,0,0], color,alpha);
|
|
// R_PolygonVertex(tr, [0,0,0], color,alpha);
|
|
// R_PolygonVertex(br, [0,0,0], color,alpha);
|
|
// R_EndPolygon();
|
|
}
|
|
|
|
|
|
|
|
|
|
void cl_navmesh_draw_line(vector start, vector end, float edge_width, vector color, float alpha) {
|
|
R_BeginPolygon("debug/wireframe",0);
|
|
R_BeginPolygon("debug/solid_nocull",0);
|
|
|
|
R_PolygonVertex(end + [0,0,-edge_width], [0,0,0], color, alpha);
|
|
R_PolygonVertex(end + [0,0,edge_width], [0,0,0], color, alpha);
|
|
R_PolygonVertex(start + [0,0,edge_width], [0,0,0], color, alpha);
|
|
R_PolygonVertex(start + [0,0,-edge_width], [0,0,0], color, alpha);
|
|
R_EndPolygon();
|
|
}
|
|
|
|
void cl_navmesh_draw_edge(vector start, vector end) {
|
|
cl_navmesh_draw_line(start,end,1,[0.5,0.5,0.5],0.2);
|
|
}
|
|
|
|
void cl_navmesh_draw_quad(vector a, vector b, vector c, vector d, vector color, float alpha, int draw_edges) {
|
|
//Assigning the shader as something else so that fte doesn't batch the calls (leading to colors not changing between draw calls)
|
|
R_BeginPolygon("debug/wireframe",0);
|
|
R_BeginPolygon("debug/solid_nocull",0);
|
|
|
|
R_PolygonVertex(a, [0,0,0], color, alpha);
|
|
R_PolygonVertex(b, [0,0,0], color, alpha);
|
|
R_PolygonVertex(c, [0,0,0], color, alpha);
|
|
R_PolygonVertex(d, [0,0,0], color, alpha);
|
|
R_EndPolygon();
|
|
|
|
//Drawing polygon border
|
|
if(draw_edges == TRUE)
|
|
{
|
|
cl_navmesh_draw_edge(a,b);
|
|
cl_navmesh_draw_edge(b,c);
|
|
cl_navmesh_draw_edge(c,d);
|
|
cl_navmesh_draw_edge(d,a);
|
|
}
|
|
}
|
|
|
|
void cl_navmesh_draw_tri(vector a, vector b, vector c, vector color, float alpha, int draw_edges) {
|
|
//Assigning the shader as something else so that fte doesn't batch the calls (leading to colors not changing between draw calls)
|
|
R_BeginPolygon("debug/wireframe",0);
|
|
R_BeginPolygon("debug/solid_nocull",0);
|
|
|
|
R_PolygonVertex(a, [0,0,0], color, alpha);
|
|
R_PolygonVertex(b, [0,0,0], color, alpha);
|
|
R_PolygonVertex(c, [0,0,0], color, alpha);
|
|
R_EndPolygon();
|
|
|
|
//Drawing polygon border
|
|
if(draw_edges == TRUE)
|
|
{
|
|
cl_navmesh_draw_edge(a,b);
|
|
cl_navmesh_draw_edge(b,c);
|
|
cl_navmesh_draw_edge(c,a);
|
|
}
|
|
}
|
|
|
|
void cl_navmesh_draw_poly(float poly_index) {
|
|
vector face_color = [0.2,0.8,0.2];
|
|
float face_alpha = 0.1;
|
|
|
|
vector a = cl_navmesh_verts[cl_navmesh_polies[poly_index].verts[0]].pos;
|
|
vector b = cl_navmesh_verts[cl_navmesh_polies[poly_index].verts[1]].pos;
|
|
vector c = cl_navmesh_verts[cl_navmesh_polies[poly_index].verts[2]].pos;
|
|
|
|
// Draw door polygons as red
|
|
if(cl_navmesh_polies[poly_index].doortarget != "") {
|
|
face_color = [0.8,0.2,0.2];
|
|
}
|
|
|
|
if(cl_navmesh_polies[poly_index].vert_count == 3)
|
|
{
|
|
cl_navmesh_draw_tri(a,b,c,face_color,face_alpha,TRUE);
|
|
}
|
|
else
|
|
{
|
|
vector d = cl_navmesh_verts[cl_navmesh_polies[poly_index].verts[3]].pos;
|
|
cl_navmesh_draw_quad(a,b,c,d,face_color,face_alpha,TRUE);
|
|
}
|
|
|
|
// If polygon's entrance edge is set, draw other edges as red.
|
|
if(cl_navmesh_polies[poly_index].entrance_edge != -1) {
|
|
for(int i = 0; i < cl_navmesh_polies[poly_index].vert_count; i++) {
|
|
if(i == cl_navmesh_polies[poly_index].entrance_edge)
|
|
continue;
|
|
|
|
// Draw this edge as solid red
|
|
int edge_vert_a_idx = i;
|
|
int edge_vert_b_idx = (i + 1) % cl_navmesh_polies[poly_index].vert_count;
|
|
int edge_vert_a = cl_navmesh_polies[poly_index].verts[edge_vert_a_idx];
|
|
int edge_vert_b = cl_navmesh_polies[poly_index].verts[edge_vert_b_idx];
|
|
vector start = cl_navmesh_verts[edge_vert_a].pos;
|
|
vector end = cl_navmesh_verts[edge_vert_b].pos;
|
|
|
|
vector edge_color = [0.9,0.2,0.2];
|
|
float edge_alpha = 0.7;
|
|
cl_navmesh_draw_line(start,end,2,edge_color,edge_alpha);
|
|
}
|
|
}
|
|
}
|
|
|
|
float cl_navmesh_is_vert_selected(float vert_index)
|
|
{
|
|
for(float i = 0; i < selected_vert_count; i++)
|
|
{
|
|
if(selected_verts[i] == vert_index)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
void() cl_navmesh_pathfind_draw_result_node_path;
|
|
void() cl_navmesh_pathfind_draw_result_portals;
|
|
void() cl_navmesh_pathfind_draw_result_path;
|
|
|
|
|
|
void cl_navmesh_editor_draw()
|
|
{
|
|
for(float i = 0; i < cl_navmesh_vert_count; i++)
|
|
{
|
|
vector color = [0,0,1];
|
|
if(cl_navmesh_is_vert_selected(i))
|
|
color = [1,1,0];
|
|
|
|
cl_navmesh_draw_vert(cl_navmesh_verts[i].pos,color,0.4 );
|
|
}
|
|
|
|
for(float i = 0; i < cl_navmesh_poly_count; i++)
|
|
{
|
|
cl_navmesh_draw_poly(i);
|
|
}
|
|
|
|
cl_navmesh_draw_test_ent(startent_pos, [1,1,1], [0,1,0], 0.4);
|
|
cl_navmesh_draw_test_ent(goalent_pos, [1,1,1], [1,0,0], 0.4);
|
|
|
|
cl_navmesh_pathfind_draw_result_node_path();
|
|
cl_navmesh_pathfind_draw_result_portals();
|
|
cl_navmesh_pathfind_draw_result_path();
|
|
|
|
|
|
//The following code block is for detecting and placing waypoints at bsp map corners
|
|
if(cl_navmesh_place_corner_state == NAVMESH_PLACE_CORNER_PLACING || cl_navmesh_place_corner_state == NAVMESH_PLACE_CORNER_CONFIRM)
|
|
{
|
|
|
|
//vector vorg = getentity(player_localentnum, GE_ORIGIN);
|
|
//vector vorg = getviewprop(VF_ORIGIN) - VEC_VIEW_OFS;
|
|
vector vorg = getviewprop(VF_ORIGIN);
|
|
vector vang = getviewprop(VF_ANGLES);
|
|
|
|
vector vang_left1 = vang + [0,6,0];//Two degrees to the left
|
|
vector vang_left2 = vang + [0,5,0];//Three degrees to the left
|
|
vector vang_right1 = vang + [0,-6,0];//Two degrees to the right
|
|
vector vang_right2 = vang + [0,-5,0];//Three degrees to the right
|
|
|
|
makevectors(vang);
|
|
vector vang_fwd = v_forward;
|
|
|
|
makevectors(vang_left1);
|
|
vector vang_left1_fwd = v_forward;
|
|
//Trace out to wall
|
|
tracebox(vorg,VEC_HULL_MIN,VEC_HULL_MAX,vorg+(vang_left1_fwd * 2000),1,self);
|
|
vector wall_hit_left1 = vorg+(vang_left1_fwd * 2000)*trace_fraction;
|
|
//Trace down to ground
|
|
tracebox(wall_hit_left1,VEC_HULL_MIN,VEC_HULL_MAX,wall_hit_left1+([0,0,-1] * 2000),1,self);
|
|
|
|
vector hit_left1 = wall_hit_left1 + (([0,0,-1] * 2000) * trace_fraction);
|
|
|
|
makevectors(vang_left2);
|
|
vector vang_left2_fwd = v_forward;
|
|
//Trace out to wall
|
|
tracebox(vorg,VEC_HULL_MIN,VEC_HULL_MAX,vorg+(vang_left2_fwd * 2000),1,self);
|
|
vector wall_hit_left2 = vorg+(vang_left2_fwd * 2000)*trace_fraction;
|
|
//Trace down to ground
|
|
tracebox(wall_hit_left2,VEC_HULL_MIN,VEC_HULL_MAX,wall_hit_left2+([0,0,-1] * 2000),1,self);
|
|
|
|
vector hit_left2 = wall_hit_left2 + (([0,0,-1] * 2000) * trace_fraction);
|
|
|
|
|
|
makevectors(vang_right1);
|
|
vector vang_right1_fwd = v_forward;
|
|
//Trace out to wall
|
|
tracebox(vorg,VEC_HULL_MIN,VEC_HULL_MAX,vorg+(vang_right1_fwd * 2000),1,self);
|
|
vector wall_hit_right1 = vorg+(vang_right1_fwd * 2000)*trace_fraction;
|
|
//Trace down to ground
|
|
tracebox(wall_hit_right1,VEC_HULL_MIN,VEC_HULL_MAX,wall_hit_right1+([0,0,-1] * 2000),1,self);
|
|
|
|
vector hit_right1 = wall_hit_right1 + (([0,0,-1] * 2000) * trace_fraction);
|
|
|
|
|
|
makevectors(vang_right2);
|
|
vector vang_right2_fwd = v_forward;
|
|
//Trace out to wall
|
|
tracebox(vorg,VEC_HULL_MIN,VEC_HULL_MAX,vorg+(vang_right2_fwd * 2000),1,self);
|
|
vector wall_hit_right2 = vorg+(vang_right2_fwd * 2000)*trace_fraction;
|
|
//Trace down to ground
|
|
tracebox(wall_hit_right2,VEC_HULL_MIN,VEC_HULL_MAX,wall_hit_right2+([0,0,-1] * 2000),1,self);
|
|
|
|
vector hit_right2 = wall_hit_right2 + (([0,0,-1] * 2000) * trace_fraction);
|
|
|
|
cl_navmesh_draw_vert(hit_left1, [0,0,1],0.4 );
|
|
cl_navmesh_draw_vert(hit_left2, [0,0,1],0.4 );
|
|
cl_navmesh_draw_vert(hit_right1, [0,0,1],0.4 );
|
|
cl_navmesh_draw_vert(hit_right2, [0,0,1],0.4 );
|
|
|
|
// Draw walls that we have hit (2 flat planes on the walls we hit)
|
|
vector up = [0,0,1];
|
|
vector left_wall_pos = (hit_left1 + hit_left2) * 0.5;
|
|
vector left_wall_nor = -1 * crossproduct(hit_left1 - hit_left2, up);
|
|
|
|
vector right_wall_pos = (hit_right1 + hit_right2) * 0.5;
|
|
vector right_wall_nor = -1 * crossproduct(hit_right1 - hit_right2, up);
|
|
|
|
cl_navmesh_draw_plane(left_wall_pos, left_wall_nor, 50, [0,1,1], 0.1);
|
|
cl_navmesh_draw_plane(right_wall_pos, right_wall_nor, 50, [1,0,1], 0.1);
|
|
|
|
|
|
//===================== Calculate the resolved vertex pos =======================
|
|
//This solution only works for 90 degree corners
|
|
|
|
//Line 1 extends from l1a in direction l1_dir
|
|
/*vector l1a;
|
|
l1a = hit_left2;
|
|
vector l1_dir = normalize(hit_left1 - l1a);
|
|
//The third point
|
|
vector p2 = hit_right1;
|
|
|
|
//Considering only l1_dir in the 2d plane of z=0
|
|
|
|
vector vert_loc;
|
|
vert_loc.z = l1a.z;
|
|
|
|
//Is the line vertical?
|
|
if(l1_dir.x == 0)//Is the line vertical?
|
|
{
|
|
vert_loc.x = l1a.x;
|
|
vert_loc.y = p2.y;
|
|
//FIXME
|
|
}
|
|
else if(l1_dir.y == 0)//Is the line horizontal?
|
|
{
|
|
vert_loc.x = p2.x;
|
|
vert_loc.y = l1a.y;
|
|
//FIXME
|
|
}
|
|
else
|
|
{
|
|
//Getting slope of line 1
|
|
float m = (hit_left1.y - hit_left2.y) / (hit_left1.x - hit_left2.x);
|
|
|
|
float x1 = p2.x - l1a.x;
|
|
float y1 = p2.y - l1a.y;
|
|
|
|
vert_loc.x = (x1 + m*y1)/(m*m + 1);
|
|
vert_loc.y = (-1/m) * (vert_loc.x-x1) + y1;
|
|
}
|
|
|
|
vert_loc.x += l1a.x;
|
|
vert_loc.y += l1a.y;*/
|
|
|
|
|
|
//===============================================================================
|
|
//This solution should work with any corner
|
|
//===============================================================================
|
|
|
|
//Line 1: hit_left1 -> hit_left2
|
|
//Line 2: hit_right1 -> hit_right2
|
|
vector vert_loc;
|
|
|
|
float skew = 0;
|
|
float horizontal = 1;
|
|
float vertical = 2;
|
|
|
|
float line_1_orient = skew;
|
|
float line_2_orient = skew;
|
|
|
|
//Check left wall is horizontal
|
|
if(hit_left1.y == hit_left2.y)
|
|
{
|
|
line_1_orient = horizontal;
|
|
}
|
|
//Check if the left wall is vertical
|
|
else if(hit_left1.x == hit_left2.x)
|
|
{
|
|
line_1_orient = vertical;
|
|
}
|
|
|
|
//Check if the right wall is horizontal
|
|
if(hit_right1.y == hit_right2.y)
|
|
{
|
|
line_2_orient = horizontal;
|
|
}
|
|
//Check if the right wall is vertical
|
|
else if(hit_right1.x == hit_right2.x)
|
|
{
|
|
line_2_orient = vertical;
|
|
}
|
|
|
|
//Checking if the lines are parallel and not skew
|
|
if(line_1_orient == line_2_orient && line_1_orient != skew)
|
|
{
|
|
if(cl_navmesh_place_corner_state == NAVMESH_PLACE_CORNER_CONFIRM)
|
|
{
|
|
print("Cannot place corner for parallel walls (there is no corner).\n");
|
|
cl_navmesh_place_corner_state = NAVMESH_PLACE_CORNER_PLACING;
|
|
}
|
|
return;
|
|
}
|
|
|
|
//Checking other special cases
|
|
else if(line_1_orient == horizontal && line_2_orient == vertical)
|
|
{
|
|
vert_loc.x = hit_right1.x;
|
|
vert_loc.y = hit_left1.y;
|
|
}
|
|
else if(line_1_orient == vertical && line_2_orient == horizontal)
|
|
{
|
|
vert_loc.x = hit_left1.x;
|
|
vert_loc.y = hit_right1.y;
|
|
}
|
|
else if(line_1_orient == vertical)//line 2 is skew
|
|
{
|
|
vert_loc.x = hit_left1.x;
|
|
//Plugging in vert_loc.x into equation of line 2 to get vert_loc.y
|
|
//Slope of line 2
|
|
float m2_a = (hit_right2.y - hit_right1.y)/(hit_right2.x - hit_right1.x);
|
|
vert_loc.y = m2_a*(vert_loc.x - hit_right1.x) + hit_right1.y;
|
|
}
|
|
|
|
else if(line_2_orient == vertical)//line 1 is skew
|
|
{
|
|
vert_loc.x = hit_right1.x;
|
|
//P;ugging in vert_loc.x into equation of line 1 to get vert_loc.y
|
|
//Slope of line 1
|
|
float m1_a = (hit_left2.y - hit_left1.y)/(hit_left2.x - hit_left1.x);
|
|
vert_loc.y = m1_a*(vert_loc.x - hit_left1.x) + hit_left1.y;
|
|
}
|
|
else//Both lines are skew or horizontal
|
|
{
|
|
//Slope of line 1
|
|
float m1 = (hit_left2.y - hit_left1.y)/(hit_left2.x - hit_left1.x);
|
|
//Slope of line 2
|
|
float m2 = (hit_right2.y - hit_right1.y)/(hit_right2.x - hit_right1.x);
|
|
|
|
//Checking if the walls are parallel
|
|
if(m1 == m2)
|
|
{
|
|
if(cl_navmesh_place_corner_state == NAVMESH_PLACE_CORNER_CONFIRM)
|
|
{
|
|
print("Cannot place corner for skew parallel walls (there is no corner).\n");
|
|
cl_navmesh_place_corner_state = NAVMESH_PLACE_CORNER_PLACING;
|
|
}
|
|
return;
|
|
}
|
|
|
|
//Resolving the corner vertex location (this is derived from the solution of the two lines)
|
|
vert_loc.x = ((m1 * hit_left1.x) - (m2 * hit_right1.x) + hit_right1.y - hit_left1.y) / (m1 - m2);
|
|
//Plugging in vert_loc.x to the equation of the first line to get vert_loc.y
|
|
vert_loc.y = m1*(vert_loc.x - hit_left1.x) + hit_left1.y;
|
|
}
|
|
|
|
//Placing the vert as the highest z-value
|
|
float highest_z = hit_left1.z;
|
|
|
|
if(hit_left2.z > highest_z)
|
|
highest_z = hit_left2.z;
|
|
if(hit_right1.z > highest_z)
|
|
highest_z = hit_right1.z;
|
|
if(hit_right2.z > highest_z)
|
|
highest_z = hit_right2.z;
|
|
vert_loc.z = highest_z;
|
|
|
|
//Traceboxing down from that vert position to get actual height
|
|
tracebox(vert_loc,VEC_HULL_MIN,VEC_HULL_MAX,vert_loc+([0,0,-1] * 2000),1,self);
|
|
vert_loc.z = vert_loc.z + (-2000 * trace_fraction);
|
|
|
|
|
|
//================================================================================
|
|
|
|
//Render the resolved vertex pos
|
|
cl_navmesh_draw_vert(vert_loc, [1,1,0],0.4 );
|
|
|
|
// Draw a vertical line at the resolved vert pos:
|
|
cl_navmesh_draw_box(vert_loc, [0.5, 0.5, 120], [1,1,1], 0.05);
|
|
|
|
|
|
|
|
|
|
if(cl_navmesh_place_corner_state == NAVMESH_PLACE_CORNER_CONFIRM)
|
|
{
|
|
cl_navmesh_verts[cl_navmesh_vert_count].pos.x = vert_loc.x;
|
|
cl_navmesh_verts[cl_navmesh_vert_count].pos.y = vert_loc.y;
|
|
cl_navmesh_verts[cl_navmesh_vert_count].pos.z = vert_loc.z;
|
|
|
|
cl_navmesh_vert_count++;
|
|
|
|
cl_navmesh_place_corner_state = 0;
|
|
}
|
|
|
|
}
|
|
|
|
//For using tracebox to place vertices
|
|
/*vector vorg = getviewprop(VF_ORIGIN);
|
|
vector vang = getviewprop(VF_ANGLES);
|
|
|
|
makevectors(vang);
|
|
|
|
//v_forward, v_right, v_up
|
|
getviewprop(VF_ACTIVESEAT);
|
|
|
|
//Tracebox out from there
|
|
//Zombie dimensions
|
|
tracebox(vorg,[-8,-8,-32],[8,8,30],vorg+(v_forward * 2000),1,self);
|
|
|
|
|
|
//Tracebox down from there
|
|
vector vpos1 = vorg+(v_forward*2000)*trace_fraction;
|
|
cl_navmesh_draw_vert(vpos1, [0,0,1],0.4 );
|
|
|
|
tracebox(vpos1,[-8,-8,-32],[8,8,30],vpos1+([0,0,-1] * 2000),1,self);
|
|
vector vpos2 = vpos1+(([0,0,-1] * 2000) * trace_fraction);
|
|
|
|
cl_navmesh_draw_vert(vpos2, [1,1,0],0.4 );*/
|
|
|
|
//Tracebox down from there
|
|
}
|
|
|
|
//Places a vertex at player position
|
|
void cl_navmesh_place_vert()
|
|
{
|
|
if(cl_navmesh_vert_count >= NAV_MAX_VERTS)
|
|
{
|
|
print("Can't add vertex, max vert count has been reached.\n");
|
|
return;
|
|
}
|
|
|
|
//Placing the vert at player origin
|
|
vector vert_pos = getentity(player_localentnum, GE_ORIGIN);
|
|
|
|
cl_navmesh_verts[cl_navmesh_vert_count].pos.x = vert_pos.x;
|
|
cl_navmesh_verts[cl_navmesh_vert_count].pos.y = vert_pos.y;
|
|
cl_navmesh_verts[cl_navmesh_vert_count].pos.z = vert_pos.z;
|
|
|
|
print("Vertex ");
|
|
print(ftos(cl_navmesh_vert_count));
|
|
print(" created at ", vtos(vert_pos),"\n");
|
|
|
|
cl_navmesh_vert_count++;
|
|
}
|
|
|
|
//Deletes all selected vertices
|
|
void cl_navmesh_delete_verts()
|
|
{
|
|
if(selected_vert_count <= 0)
|
|
{
|
|
print("No vertices selected.\n");
|
|
return;
|
|
}
|
|
|
|
for(float i = 0; i < selected_vert_count; i++)
|
|
{
|
|
float vert = selected_verts[i];
|
|
print("Deleting vert: ");
|
|
print(ftos(vert));
|
|
print(".\n");
|
|
//Check if vertex is in any polygons, if so delete that polygon
|
|
for(float j = 0; j < cl_navmesh_poly_count; j++)
|
|
{
|
|
for(float k = 0; k < cl_navmesh_polies[j].vert_count; k++)
|
|
{
|
|
if(cl_navmesh_polies[j].verts[k] == vert)
|
|
{
|
|
cl_navmesh_delete_poly_at_index(j);
|
|
j -= 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//Bringing all vertices down to not leave any holes in the array
|
|
//Moving down every index to the right of what we deselected to not leave any holes
|
|
for(float j = vert; j < cl_navmesh_vert_count - 1; j++)
|
|
{
|
|
cl_navmesh_verts[j].pos.x = cl_navmesh_verts[j+1].pos.x;
|
|
cl_navmesh_verts[j].pos.y = cl_navmesh_verts[j+1].pos.y;
|
|
cl_navmesh_verts[j].pos.z = cl_navmesh_verts[j+1].pos.z;
|
|
}
|
|
//Clearing the last one
|
|
cl_navmesh_verts[cl_navmesh_vert_count-1].pos = [0,0,0];
|
|
cl_navmesh_vert_count--;
|
|
|
|
|
|
//Fixing references to verts in all polygons
|
|
for(float j = 0; j < cl_navmesh_poly_count; j++)
|
|
{
|
|
for(float k = 0; k < cl_navmesh_polies[j].vert_count; k++)
|
|
{
|
|
if(cl_navmesh_polies[j].verts[k] >= vert)
|
|
cl_navmesh_polies[j].verts[k]--;
|
|
}
|
|
}
|
|
//Fixing references of selected verts
|
|
for(float j = i; j < selected_vert_count; j++)
|
|
{
|
|
if(selected_verts[j] >= vert)
|
|
selected_verts[j]--;
|
|
}
|
|
}
|
|
selected_vert_count = 0;
|
|
for(float i = 0; i < NAV_MAX_SELECTED_VERTS; i++)
|
|
{
|
|
selected_verts[i] = -1;
|
|
}
|
|
}
|
|
|
|
|
|
//=================================== Navmesh vertex selection functions ===================================
|
|
//Returns index of the vertex that is nearest to the player
|
|
float cl_navmesh_get_nearest_vert()
|
|
{
|
|
if(cl_navmesh_vert_count <= 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
vector player_pos = getentity(player_localentnum, GE_ORIGIN);
|
|
float closest_dist = vlen(player_pos - cl_navmesh_verts[0].pos);
|
|
float closest_index = 0;
|
|
|
|
float temp_dist;
|
|
|
|
for(float i = 1; i < cl_navmesh_vert_count; i++)
|
|
{
|
|
temp_dist = vlen(player_pos - cl_navmesh_verts[i].pos);
|
|
|
|
if(temp_dist < closest_dist)
|
|
{
|
|
closest_dist = temp_dist;
|
|
closest_index = i;
|
|
}
|
|
}
|
|
return closest_index;
|
|
}
|
|
|
|
//Selects the nearest vertex
|
|
void cl_navmesh_select_vert()
|
|
{
|
|
if(selected_vert_count >= NAV_MAX_SELECTED_VERTS)
|
|
{
|
|
print("Can't select another vertex, max vertices selected.\n");
|
|
return;
|
|
}
|
|
|
|
float vert = cl_navmesh_get_nearest_vert();
|
|
if(vert == -1)
|
|
{
|
|
print("No vertices to select");
|
|
return;
|
|
}
|
|
if(cl_navmesh_is_vert_selected(vert))
|
|
{
|
|
print("Vert is already selected.\n");
|
|
return;
|
|
}
|
|
|
|
print("Vertex ");
|
|
print(ftos(vert));
|
|
print(" selected.\n");
|
|
selected_verts[selected_vert_count++] = vert;
|
|
}
|
|
|
|
|
|
//Deselects the nearest vertex
|
|
void cl_navmesh_deselect_vert()
|
|
{
|
|
float vert = cl_navmesh_get_nearest_vert();
|
|
|
|
if(vert == -1)
|
|
{
|
|
print("No vertices to select.\n");
|
|
return;
|
|
}
|
|
|
|
for(float i = 0; i < selected_vert_count; i++)
|
|
{
|
|
if(selected_verts[i] == vert)
|
|
{
|
|
print("Vertex ");
|
|
print(ftos(selected_verts[i]));
|
|
print(" deselected.\n");
|
|
// Clear selected vert
|
|
selected_verts[i] = -1;
|
|
//Moving down every index to the right of what we deselected to not leave any holes
|
|
for(float j = i; j < selected_vert_count - 1; j++)
|
|
{
|
|
selected_verts[j] = selected_verts[j+1];
|
|
}
|
|
selected_verts[selected_vert_count - 1] = -1;
|
|
selected_vert_count--;
|
|
|
|
return;
|
|
}
|
|
}
|
|
print("Vertex is not selected.\n");
|
|
}
|
|
|
|
//Deselects all vertices
|
|
void cl_navmesh_deselect_all()
|
|
{
|
|
for(float i = 0; i < NAV_MAX_SELECTED_VERTS; i++)
|
|
{
|
|
selected_verts[i] = -1;
|
|
}
|
|
selected_vert_count = 0;
|
|
|
|
|
|
//print("All vertices deselected.\n");
|
|
}
|
|
|
|
//=============================================================================================================
|
|
|
|
// Returns the index of the currently selected polygon
|
|
// Otherwise, returns -1
|
|
int cl_navmesh_get_selected_poly()
|
|
{
|
|
//Check if this polygon already exists
|
|
float verts_selected[4];
|
|
|
|
for(float i = 0; i < cl_navmesh_poly_count; i++)
|
|
{
|
|
verts_selected[0] = FALSE;
|
|
verts_selected[1] = FALSE;
|
|
verts_selected[2] = FALSE;
|
|
verts_selected[3] = FALSE;
|
|
|
|
//Check each of the poly's verts
|
|
for(float j = 0; j < cl_navmesh_polies[i].vert_count; j++)
|
|
{
|
|
//Check each of our selected verts to see if they are in this polygon
|
|
for(float k = 0; k < selected_vert_count; k++)
|
|
{
|
|
if(cl_navmesh_polies[i].verts[j] == selected_verts[k])
|
|
{
|
|
verts_selected[j] = TRUE;
|
|
}
|
|
}
|
|
|
|
//This poly vert was not selected
|
|
if(verts_selected[j] == FALSE)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(cl_navmesh_polies[i].vert_count == 3)
|
|
{
|
|
if(verts_selected[0] == TRUE && verts_selected[1] == TRUE && verts_selected[2] == TRUE)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
else if(cl_navmesh_polies[i].vert_count == 4)
|
|
{
|
|
if(verts_selected[0] == TRUE && verts_selected[1] == TRUE && verts_selected[2] == TRUE && verts_selected[3] == TRUE)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
void cl_navmesh_make_poly()
|
|
{
|
|
if(selected_vert_count < 3)
|
|
{
|
|
print("Not enough selected vertices to make a polygon (need 3).\n");
|
|
return;
|
|
}
|
|
|
|
if(cl_navmesh_poly_count >= NAV_MAX_POLIES)
|
|
{
|
|
print("Max polygon count reached.\n");
|
|
return;
|
|
}
|
|
|
|
if(cl_navmesh_get_selected_poly() != -1) {
|
|
print("This polygon already exists.\n");
|
|
return;
|
|
}
|
|
|
|
|
|
//Sorting the verts so the polygon is build consecutively (i.e. vert 0 -> vert 1 is an edge, 1->2, 2->3, and 3->0 are all edges)
|
|
//Furthermore, this sorting will ensure the verts are sorted in a counter clockwise order, so we also apply it to tris
|
|
|
|
|
|
//Printing all polygon vertices
|
|
print("Selected Verts: [");
|
|
for(float j = 0; j < selected_vert_count; j++)
|
|
{
|
|
print("(",ftos(j),", ",ftos(selected_verts[j]),", ",vtos(cl_navmesh_verts[selected_verts[j]].pos),") , ");
|
|
}
|
|
print("]\n\n");
|
|
|
|
//Calculating center of the polygon
|
|
local vector center = [0,0,0];
|
|
for(float j = 0; j < selected_vert_count; j++)
|
|
{
|
|
center += cl_navmesh_verts[selected_verts[j]].pos;
|
|
}
|
|
center /= selected_vert_count;
|
|
|
|
print("Center of selected polygon: ",vtos(center),".\n\n");
|
|
|
|
float vert_angle[NAV_MAX_SELECTED_VERTS];
|
|
|
|
//For suppressing uninitialised vert_angle compiler warning
|
|
vert_angle[0] = 0;
|
|
|
|
//Calculating vertex angles:
|
|
for(float j = 0; j < selected_vert_count; j++) {
|
|
//Calculating angle of vert
|
|
vert_angle[j] = atan2(cl_navmesh_verts[selected_verts[j]].pos.y - center.y,cl_navmesh_verts[selected_verts[j]].pos.x - center.x);
|
|
}
|
|
|
|
//Sorting from least to greatest
|
|
for(float j = 0; j < selected_vert_count; j++) {
|
|
//Finding lowest value from j to selected_vert_count
|
|
float lowest_value = vert_angle[j];
|
|
float lowest_index = j;
|
|
for(float k = j; k < selected_vert_count; k++) {
|
|
if(vert_angle[k] < lowest_value) {
|
|
lowest_value = vert_angle[k];
|
|
lowest_index = k;
|
|
}
|
|
}
|
|
//Swapping the lowest value with index j
|
|
float temp_angle = vert_angle[j];
|
|
vert_angle[j] = vert_angle[lowest_index];
|
|
vert_angle[lowest_index] = temp_angle;
|
|
|
|
float temp_vert = selected_verts[j];
|
|
selected_verts[j] = selected_verts[lowest_index];
|
|
selected_verts[lowest_index] = temp_vert;
|
|
}
|
|
|
|
//Vert angles
|
|
print("Sorted Verts: [");
|
|
for(float j = 0; j < selected_vert_count; j++) {
|
|
print("(",ftos(j),", ",ftos(selected_verts[j]),", ",vtos(cl_navmesh_verts[selected_verts[j]].pos),") ,");
|
|
}
|
|
print("]\n\n");
|
|
|
|
print("Sorted Vert angles: [");
|
|
for(float j = 0; j < selected_vert_count; j++) {
|
|
print(", ",ftos(vert_angle[j]));
|
|
}
|
|
print("]\n\n");
|
|
|
|
// Verify that the polygon is concave.
|
|
for(int i = 0; i < selected_vert_count; i++) {
|
|
vector prev_vert_pos = cl_navmesh_verts[selected_verts[(i + selected_vert_count - 1) % selected_vert_count]].pos;
|
|
vector vert_pos = cl_navmesh_verts[selected_verts[i]].pos;
|
|
vector next_vert_pos = cl_navmesh_verts[selected_verts[(i + 1) % selected_vert_count]].pos;
|
|
|
|
// For the polygon to be concave, next_vert _must_ be to the left of the line prev_vert -> vert
|
|
if(pathfind_point_is_to_left(prev_vert_pos, vert_pos, next_vert_pos) <= 0) {
|
|
print("Specified polygon is not concave.\n");
|
|
print("\tprev vert: ", ftos(selected_verts[(i + selected_vert_count - 1) % selected_vert_count]), "\n");
|
|
print("\tvert: ", ftos(selected_verts[(i) % selected_vert_count]), "\n");
|
|
print("\tnext vert: ", ftos(selected_verts[(i + 1) % selected_vert_count]), "\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
for(float i = 0; i < selected_vert_count; i++)
|
|
{
|
|
cl_navmesh_polies[cl_navmesh_poly_count].verts[i] = selected_verts[i];
|
|
}
|
|
cl_navmesh_polies[cl_navmesh_poly_count].vert_count = selected_vert_count;
|
|
cl_navmesh_polies[cl_navmesh_poly_count].entrance_edge = -1;
|
|
cl_navmesh_poly_count++;
|
|
}
|
|
|
|
void cl_navmesh_delete_poly_at_index(float poly_index)
|
|
{
|
|
//Starting at this polygon, move every polygon after in the array down one (leave no holes in the array)
|
|
//Moving down every index to the right of what we deselected to not leave any holes
|
|
for(float i = poly_index; i < cl_navmesh_poly_count - 1; i++)
|
|
{
|
|
// Copy down the verts
|
|
cl_navmesh_polies[i].vert_count = cl_navmesh_polies[i+1].vert_count;
|
|
for(float j = 0; j < cl_navmesh_polies[i].verts.length; j++)
|
|
{
|
|
cl_navmesh_polies[i].verts[j] = cl_navmesh_polies[i+1].verts[j];
|
|
}
|
|
|
|
// Copy down other fields:
|
|
cl_navmesh_polies[i].entrance_edge = cl_navmesh_polies[i+1].entrance_edge;
|
|
cl_navmesh_polies[i].doortarget = cl_navmesh_polies[i+1].doortarget;
|
|
|
|
}
|
|
//Clear the last polygon in the list
|
|
cl_navmesh_polies[cl_navmesh_poly_count-1].vert_count = 0;
|
|
for(float j = 0; j < cl_navmesh_polies[cl_navmesh_poly_count-1].verts.length; j++)
|
|
{
|
|
cl_navmesh_polies[cl_navmesh_poly_count-1].verts[j] = -1;
|
|
}
|
|
cl_navmesh_polies[cl_navmesh_poly_count-1].entrance_edge = -1;
|
|
cl_navmesh_polies[cl_navmesh_poly_count-1].doortarget = "";
|
|
|
|
cl_navmesh_poly_count--;
|
|
}
|
|
|
|
void cl_navmesh_delete_poly()
|
|
{
|
|
float verts_selected[4];
|
|
float poly_index = -1;
|
|
//Find index of the polygon whose vertices are all selected
|
|
for(float i = 0; i < cl_navmesh_poly_count; i++)
|
|
{
|
|
verts_selected[0] = FALSE;
|
|
verts_selected[1] = FALSE;
|
|
verts_selected[2] = FALSE;
|
|
verts_selected[3] = FALSE;
|
|
|
|
//Check each of the poly's verts
|
|
for(float j = 0; j < cl_navmesh_polies[i].vert_count; j++)
|
|
{
|
|
//Check each of our selected verts to see if they are in this polygon
|
|
for(float k = 0; k < selected_vert_count; k++)
|
|
{
|
|
if(cl_navmesh_polies[i].verts[j] == selected_verts[k])
|
|
{
|
|
verts_selected[j] = TRUE;
|
|
}
|
|
}
|
|
|
|
//This poly vert was not selected
|
|
if(verts_selected[j] == FALSE)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(cl_navmesh_polies[i].vert_count == 3)
|
|
{
|
|
if(verts_selected[0] == TRUE && verts_selected[1] == TRUE && verts_selected[2] == TRUE)
|
|
{
|
|
poly_index = i;
|
|
break;
|
|
}
|
|
}
|
|
else if(cl_navmesh_polies[i].vert_count == 4)
|
|
{
|
|
if(verts_selected[0] == TRUE && verts_selected[1] == TRUE && verts_selected[2] == TRUE && verts_selected[3] == TRUE)
|
|
{
|
|
poly_index = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(poly_index == -1)
|
|
{
|
|
print("A polygon is not selected.\n");
|
|
return;
|
|
}
|
|
|
|
cl_navmesh_delete_poly_at_index(poly_index);
|
|
print("Deleted polygon.\n");
|
|
}
|
|
|
|
//Treats 1st and 2nd selected verts as line 1, 3rd and 4th verts as line 2, places a new vertex at the intersection of these lines
|
|
void cl_navmesh_resolve_corner()
|
|
{
|
|
if(selected_vert_count < 3)
|
|
{
|
|
print("You need to select at least 3 vertices to resolve a corner.\n");
|
|
return;
|
|
}
|
|
|
|
//Line 1 extends from l1a in direction l1_dir
|
|
vector l1a;
|
|
l1a = cl_navmesh_verts[selected_verts[0]].pos;
|
|
vector l1_dir = normalize(cl_navmesh_verts[selected_verts[1]].pos - l1a);
|
|
//The third point
|
|
vector p2 = cl_navmesh_verts[selected_verts[2]].pos;
|
|
|
|
|
|
//Considering only l1_dir in the 2d plane of z=0
|
|
|
|
vector vert_loc;
|
|
vert_loc.z = l1a.z;
|
|
|
|
//Is the line vertical?
|
|
if(l1_dir.x == 0)//Is the line vertical?
|
|
{
|
|
vert_loc.x = l1a.x;
|
|
vert_loc.y = p2.y;
|
|
}
|
|
else if(l1_dir.y == 0)//Is the line horizontal?
|
|
{
|
|
vert_loc.x = p2.x;
|
|
vert_loc.y = l1a.y;
|
|
}
|
|
else
|
|
{
|
|
//Getting slope of line 1
|
|
float m = l1_dir.y / l1_dir.x;
|
|
|
|
float x1 = p2.x - l1a.x;
|
|
float y1 = p2.y - l1a.y;
|
|
|
|
vert_loc.x = (x1 + m*y1)/(m*m + 1);
|
|
vert_loc.y = (-1/m) * (vert_loc.x-x1) + y1;
|
|
}
|
|
|
|
vert_loc.x += l1a.x;
|
|
vert_loc.y += l1a.y;
|
|
|
|
|
|
//Place a new vert at the location
|
|
cl_navmesh_verts[cl_navmesh_vert_count].pos.x = vert_loc.x;
|
|
cl_navmesh_verts[cl_navmesh_vert_count].pos.y = vert_loc.y;
|
|
cl_navmesh_verts[cl_navmesh_vert_count].pos.z = vert_loc.z;
|
|
|
|
print("Resolved corner with vertex ");
|
|
print(ftos(cl_navmesh_vert_count));
|
|
print(" at (");
|
|
print(ftos(vert_loc.x));
|
|
print(" , ");
|
|
print(ftos(vert_loc.y));
|
|
print(" , ");
|
|
print(ftos(vert_loc.z));
|
|
print(").\n");
|
|
|
|
|
|
cl_navmesh_vert_count++;
|
|
}
|
|
|
|
void cl_navmesh_place_corner()
|
|
{
|
|
cl_navmesh_place_corner_state = NAVMESH_PLACE_CORNER_PLACING;
|
|
}
|
|
void cl_navmesh_cancel_corner()
|
|
{
|
|
cl_navmesh_place_corner_state = 0;
|
|
}
|
|
void cl_navmesh_confirm_corner()
|
|
{
|
|
if(cl_navmesh_place_corner_state == NAVMESH_PLACE_CORNER_PLACING)
|
|
cl_navmesh_place_corner_state = NAVMESH_PLACE_CORNER_CONFIRM;
|
|
}
|
|
|
|
|
|
void cl_navmesh_clear_connected_polies()
|
|
{
|
|
for(float i = 0; i < cl_navmesh_poly_count; i++)
|
|
{
|
|
cl_navmesh_polies[i].connected_polies_count = 0;
|
|
|
|
for(float j = 0; j < 4; j++)
|
|
{
|
|
cl_navmesh_polies[i].connected_polies[j] = -1;
|
|
cl_navmesh_polies[i].connected_polies_left_vert[j] = -1;
|
|
cl_navmesh_polies[i].connected_polies_right_vert[j] = -1;
|
|
cl_navmesh_polies[i].connected_polies_neighbor_edge_index[j] = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void cl_navmesh_calc_connected_polies() {
|
|
cl_navmesh_clear_connected_polies();
|
|
|
|
for(float i = 0; i < cl_navmesh_poly_count; i++) {
|
|
for(float j = i + 1; j < cl_navmesh_poly_count; j++) {
|
|
|
|
//Check if poly j shares an edge with poly i
|
|
|
|
//Edge case: where two opposite corners (two corners not on the same edge) of a quad are shared with another poly
|
|
//but this edge case is a result of terrible topology, so I will not consider it
|
|
|
|
//Check if poly j shares at least 2 vertices with poly i
|
|
//I'm not sure what sharing more than 2 verts means... but it's a result of bad topology, so I will not consider it
|
|
// if two quads share 4: they are the same -> bad topology
|
|
// if two quads share 3: one of them is convex, one is concave -> bad topology
|
|
// if a quad shares 3 with a tri: the tri is within the quad -> bad topology
|
|
// if two tris share 3: they are the same -> bad topology
|
|
|
|
|
|
float shared_vert_ids[2];
|
|
shared_vert_ids[0] = -1;
|
|
shared_vert_ids[1] = -1;
|
|
|
|
float poly_i_shared_verts_index[2]; // The index of the vert in the ith polygon's vertex list
|
|
float poly_j_shared_verts_index[2]; // The index of the vert in the ith polygon's vertex list
|
|
|
|
poly_i_shared_verts_index[0] = -1;
|
|
poly_i_shared_verts_index[1] = -1;
|
|
poly_j_shared_verts_index[0] = -1;
|
|
poly_j_shared_verts_index[1] = -1;
|
|
float verts_found = 0;
|
|
|
|
for(float k = 0; k < cl_navmesh_polies[i].vert_count; k++) {
|
|
for(float l = 0; l < cl_navmesh_polies[j].vert_count; l++) {
|
|
if(cl_navmesh_polies[i].verts[k] == cl_navmesh_polies[j].verts[l]) {
|
|
//If we have found more verts than should be allowed, count them for error print
|
|
if(verts_found >= 2) {
|
|
verts_found++;
|
|
continue;
|
|
}
|
|
|
|
shared_vert_ids[verts_found] = cl_navmesh_polies[i].verts[k];
|
|
poly_i_shared_verts_index[verts_found] = k;
|
|
poly_j_shared_verts_index[verts_found] = l;
|
|
verts_found++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Print a warning if more than 2, should be impossible
|
|
if(verts_found > 2) {
|
|
print("Warning: polygon ");
|
|
print(ftos(i));
|
|
print(" shares ");
|
|
print(ftos(verts_found));
|
|
print(" vertices with polygon ");
|
|
print(ftos(j));
|
|
print(". There is an issue somewhere.\n");
|
|
}
|
|
else if(verts_found == 2) {
|
|
//Setting the polygons as connected to each other
|
|
if(cl_navmesh_polies[i].connected_polies_count >= 4) {
|
|
print("Warning: polygon ");
|
|
print(ftos(i));
|
|
print(" shares an edge with more than 4 other polygons.\n");
|
|
break;
|
|
}
|
|
if(cl_navmesh_polies[j].connected_polies_count >= 4) {
|
|
print("Warning: polygon ");
|
|
print(ftos(j));
|
|
print(" shares an edge with more than 4 other polygons.\n");
|
|
break;
|
|
}
|
|
cl_navmesh_polies[i].connected_polies[cl_navmesh_polies[i].connected_polies_count] = j;
|
|
cl_navmesh_polies[j].connected_polies[cl_navmesh_polies[j].connected_polies_count] = i;
|
|
|
|
|
|
//=======================================================================================
|
|
//Calculating shared edge left / right vertices
|
|
|
|
//Calculating from the perspective of moving from poly[i] to poly[j]
|
|
|
|
//Because polygon's vertices are stored in a ccw winding order:
|
|
//connected polygons must share two consecutive verts.
|
|
//the CCW winding of the triangle dictates that the next shared vertex is to the left of, and in the index after the first shared vertex
|
|
//i.e. of the two consecutive verts shard, the former is to the right, the latter is to the left
|
|
|
|
//if first one is a quad: shared edge must be, where the first index is right and second index is left
|
|
//3-0, 0-1, 1-2, 2-3
|
|
//if first one is a tri: shared edge must be, where the first index is right and second index is left
|
|
//2-0, 0-1, 1-2
|
|
|
|
//Two distinct cases:
|
|
//The index of second is one greater than the index of the first
|
|
//OR
|
|
//The index of the first is 0, and index of second is 2 or 3
|
|
|
|
|
|
// Sort the vertices to be from left to right when crossing from polygon i to j
|
|
//
|
|
// let a,b = poly_i_shared_verts_index
|
|
//
|
|
// 0 ---------- 3
|
|
// | |
|
|
// | |
|
|
// | |
|
|
// | |
|
|
// 1 ---------- 2
|
|
//
|
|
// Possible Values:
|
|
//
|
|
// b = 0 b = 1 b = 2 b = 3
|
|
// a = 0 X B F F
|
|
// a = 1 F X B X
|
|
// a = 2 B F X B
|
|
// a = 3 B X F X
|
|
//
|
|
// F -- Forward (a is left vert, b is right vert)
|
|
// B -- Backward (a is right vert, b is left vert)
|
|
// X -- Can't happen
|
|
|
|
// if(a == (b + 1) % poly_i_vert_count)
|
|
if(poly_i_shared_verts_index[0] == (poly_i_shared_verts_index[1] + 1) % cl_navmesh_polies[i].vert_count) {
|
|
// poly_i_shared_verts_index[0] denotes the left vertex when crossing from i to j
|
|
// poly_i_shared_verts_index[1] denotes the right vertex when crossing from i to j
|
|
// Do nothing
|
|
}
|
|
else {
|
|
// Flip the vert lists so the above is true.
|
|
float temp;
|
|
temp = shared_vert_ids[0];
|
|
shared_vert_ids[0] = shared_vert_ids[1];
|
|
shared_vert_ids[1] = temp;
|
|
|
|
temp = poly_i_shared_verts_index[0];
|
|
poly_i_shared_verts_index[0] = poly_i_shared_verts_index[1];
|
|
poly_i_shared_verts_index[1] = temp;
|
|
|
|
temp = poly_j_shared_verts_index[0];
|
|
poly_j_shared_verts_index[0] = poly_j_shared_verts_index[1];
|
|
poly_j_shared_verts_index[1] = temp;
|
|
}
|
|
|
|
|
|
//Assigning the link's left/right edges
|
|
cl_navmesh_polies[i].connected_polies_left_vert[cl_navmesh_polies[i].connected_polies_count] = shared_vert_ids[0];
|
|
cl_navmesh_polies[i].connected_polies_right_vert[cl_navmesh_polies[i].connected_polies_count] = shared_vert_ids[1];
|
|
|
|
//From j to i, left vert is on the right, and right vert is on the left
|
|
cl_navmesh_polies[j].connected_polies_left_vert[cl_navmesh_polies[j].connected_polies_count] = shared_vert_ids[1];
|
|
cl_navmesh_polies[j].connected_polies_right_vert[cl_navmesh_polies[j].connected_polies_count] = shared_vert_ids[0];
|
|
|
|
// When crossing from polygon i to polygon j, the right-hand vertex index is the edge index for polygon i
|
|
float poly_i_edge_index = poly_i_shared_verts_index[1];
|
|
// When crossing from polygon j to polygon i, the left-hand vertex index is the edge index for polygon j
|
|
float poly_j_edge_index = poly_j_shared_verts_index[0];
|
|
|
|
// For polygon i, record which of polygon's j edges we are crossing to get into polygon j
|
|
cl_navmesh_polies[i].connected_polies_neighbor_edge_index[cl_navmesh_polies[i].connected_polies_count] = poly_j_edge_index;
|
|
// For polygon j, record which of polygon's i edges we are crossing to get into polygon i
|
|
cl_navmesh_polies[j].connected_polies_neighbor_edge_index[cl_navmesh_polies[j].connected_polies_count] = poly_i_edge_index;
|
|
|
|
|
|
cl_navmesh_polies[i].connected_polies_count++;
|
|
cl_navmesh_polies[j].connected_polies_count++;
|
|
//=======================================================================================
|
|
|
|
print("Poly ");
|
|
print(ftos(i));
|
|
print(" is connected to ");
|
|
print(ftos(j));
|
|
print(" and poly ");
|
|
print(ftos(j));
|
|
print(" is connected to ");
|
|
print(ftos(i));
|
|
print(".\n");
|
|
}
|
|
}
|
|
}
|
|
print("Connected polygons calculated.\n");
|
|
}
|
|
|
|
//We calculate this outside of polygon creation / editing because the polygon centers are not important to creation, only to actually using in pathfinding
|
|
void cl_navmesh_calc_polies_centers()
|
|
{
|
|
for(float i = 0; i < cl_navmesh_poly_count; i++)
|
|
{
|
|
local vector cen = [0,0,0];
|
|
for(float j = 0; j < cl_navmesh_polies[i].vert_count; j++)
|
|
{
|
|
cen += cl_navmesh_verts[cl_navmesh_polies[i].verts[j]].pos;
|
|
}
|
|
cen /= cl_navmesh_polies[i].vert_count;
|
|
cl_navmesh_polies[i].center.x = cen.x;
|
|
cl_navmesh_polies[i].center.y = cen.y;
|
|
cl_navmesh_polies[i].center.z = cen.z;
|
|
}
|
|
print("Centers calculated.\n");
|
|
}
|
|
|
|
|
|
//Navmesh file specification for v0.0.0:
|
|
//Vertex count
|
|
//For each vert:
|
|
//Vertex position vector
|
|
//Polygon count
|
|
//For each polygon:
|
|
//vertex count (must be 3 or 4)
|
|
//vertex 0 index
|
|
//vertex 1 index
|
|
//vertex 2 index
|
|
//vertex 3 index (will be -1 for a tri)
|
|
//center (vector)
|
|
//link count
|
|
//link 0 index (will be set or -1 for a tri)
|
|
//link 1 index (will be set or -1 for a tri)
|
|
//link 2 index (will be set or -1 for a tri)
|
|
//link 3 index (will be set or -1 for a tri)
|
|
//link 0 left vert index (will be set or -1 for a tri)
|
|
//link 1 left vert index (will be set or -1 for a tri)
|
|
//link 2 left vert index (will be set or -1 for a tri)
|
|
//link 3 left vert index (will be set or -1 for a tri)
|
|
//link 0 right vert index (will be set or -1 for a tri)
|
|
//link 1 right vert index (will be set or -1 for a tri)
|
|
//link 2 right vert index (will be set or -1 for a tri)
|
|
//link 3 right vert index (will be set or -1 for a tri)
|
|
|
|
|
|
//Saves the current cl_navmesh to a file
|
|
void cl_navmesh_editor_save_navmesh()
|
|
{
|
|
int v_major = 0;
|
|
int v_minor = 0;
|
|
int v_patch = 0;
|
|
float file;
|
|
string h;
|
|
|
|
h = strcat(mappath, ".nav");
|
|
file = fopen(h,FILE_WRITE);
|
|
|
|
if(file == -1) {
|
|
print("Error: unable to write to file \"",h,"\".\n");
|
|
return;
|
|
}
|
|
// Write version number:
|
|
fputs( file, itos(v_major), ".", itos(v_minor), ".", itos(v_patch),"\n");
|
|
|
|
//Calculating all links and polygon centers
|
|
//===========================================
|
|
cl_navmesh_calc_polies_centers();
|
|
cl_navmesh_calc_connected_polies();
|
|
//===========================================
|
|
|
|
//Write vertex count
|
|
fputs(file,ftos(cl_navmesh_vert_count),"\n");
|
|
|
|
//Write all vertex positions
|
|
for(float i = 0; i < cl_navmesh_vert_count; i++)
|
|
{
|
|
fputs(file,vtos(cl_navmesh_verts[i].pos),"\n");
|
|
}
|
|
|
|
//Write polygon count
|
|
fputs(file,ftos(cl_navmesh_poly_count),"\n");
|
|
|
|
//Write all polygon data to file
|
|
for(float i = 0; i < cl_navmesh_poly_count; i++)
|
|
{
|
|
// Write vert count
|
|
fputs(file,ftos(cl_navmesh_polies[i].vert_count),"\n");
|
|
// Write vertices
|
|
fputs(file,ftos(cl_navmesh_polies[i].verts[0]),"\n");
|
|
fputs(file,ftos(cl_navmesh_polies[i].verts[1]),"\n");
|
|
fputs(file,ftos(cl_navmesh_polies[i].verts[2]),"\n");
|
|
fputs(file,ftos(cl_navmesh_polies[i].verts[3]),"\n");
|
|
fputs(file,vtos(cl_navmesh_polies[i].center),"\n");
|
|
|
|
// Write link count
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies_count),"\n");
|
|
// Write links
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies[0]),"\n");
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies[1]),"\n");
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies[2]),"\n");
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies[3]),"\n");
|
|
// Write left vertices of the links
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies_left_vert[0]),"\n");
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies_left_vert[1]),"\n");
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies_left_vert[2]),"\n");
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies_left_vert[3]),"\n");
|
|
// Write right vertices of the links
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies_right_vert[0]),"\n");
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies_right_vert[1]),"\n");
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies_right_vert[2]),"\n");
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies_right_vert[3]),"\n");
|
|
// Write the neighbor edge indices
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies_neighbor_edge_index[0]),"\n");
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies_neighbor_edge_index[1]),"\n");
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies_neighbor_edge_index[2]),"\n");
|
|
fputs(file,ftos(cl_navmesh_polies[i].connected_polies_neighbor_edge_index[3]),"\n");
|
|
// Write polygon doortarget
|
|
fputs(file,cl_navmesh_polies[i].doortarget,"\n");
|
|
|
|
if(cl_navmesh_polies[i].doortarget != "") {
|
|
print("CLSAVENAVMESH - Polygon at index ",ftos(i)," has doortarget: \"", cl_navmesh_polies[i].doortarget, "\"" );
|
|
}
|
|
|
|
|
|
// Getting polygon entrance edge index
|
|
fputs(file,itos(cl_navmesh_polies[i].entrance_edge),"\n");
|
|
}
|
|
|
|
fclose(file);
|
|
|
|
print("Navmesh saved to file \"",h,"\".\n");
|
|
}
|
|
//Overwrites current navmesh with an empty navmesh
|
|
void cl_navmesh_editor_clear_navmesh()
|
|
{
|
|
cl_navmesh_vert_count = 0;
|
|
for(float i = 0; i < NAV_MAX_VERTS; i++)
|
|
{
|
|
cl_navmesh_verts[i].pos.x = 0;
|
|
cl_navmesh_verts[i].pos.y = 0;
|
|
cl_navmesh_verts[i].pos.z = 0;
|
|
}
|
|
cl_navmesh_poly_count = 0;
|
|
for(float i = 0; i < NAV_MAX_POLIES; i++)
|
|
{
|
|
cl_navmesh_polies[i].vert_count = 0;
|
|
|
|
cl_navmesh_polies[i].verts[0] = -1;
|
|
cl_navmesh_polies[i].verts[1] = -1;
|
|
cl_navmesh_polies[i].verts[2] = -1;
|
|
cl_navmesh_polies[i].verts[3] = -1;
|
|
|
|
cl_navmesh_polies[i].center.x = 0;
|
|
cl_navmesh_polies[i].center.y = 0;
|
|
cl_navmesh_polies[i].center.z = 0;
|
|
|
|
cl_navmesh_polies[i].connected_polies_count = 0;
|
|
|
|
cl_navmesh_polies[i].connected_polies[0] = -1;
|
|
cl_navmesh_polies[i].connected_polies[1] = -1;
|
|
cl_navmesh_polies[i].connected_polies[2] = -1;
|
|
cl_navmesh_polies[i].connected_polies[3] = -1;
|
|
|
|
cl_navmesh_polies[i].connected_polies_left_vert[0] = -1;
|
|
cl_navmesh_polies[i].connected_polies_left_vert[1] = -1;
|
|
cl_navmesh_polies[i].connected_polies_left_vert[2] = -1;
|
|
cl_navmesh_polies[i].connected_polies_left_vert[3] = -1;
|
|
|
|
cl_navmesh_polies[i].connected_polies_right_vert[0] = -1;
|
|
cl_navmesh_polies[i].connected_polies_right_vert[1] = -1;
|
|
cl_navmesh_polies[i].connected_polies_right_vert[2] = -1;
|
|
cl_navmesh_polies[i].connected_polies_right_vert[3] = -1;
|
|
|
|
cl_navmesh_polies[i].connected_polies_neighbor_edge_index[0] = -1;
|
|
cl_navmesh_polies[i].connected_polies_neighbor_edge_index[1] = -1;
|
|
cl_navmesh_polies[i].connected_polies_neighbor_edge_index[2] = -1;
|
|
cl_navmesh_polies[i].connected_polies_neighbor_edge_index[3] = -1;
|
|
|
|
cl_navmesh_polies[i].doortarget = "";
|
|
|
|
cl_navmesh_polies[i].entrance_edge = -1;
|
|
}
|
|
}
|
|
//Overwrites currently loaded navmesh with navmesh stored in fail
|
|
void cl_navmesh_editor_load_navmesh()
|
|
{
|
|
string filepath;
|
|
float file;
|
|
|
|
filepath = strcat(mappath, ".nav");
|
|
file = fopen(filepath,FILE_READ);
|
|
|
|
if(file == -1) {
|
|
print("Error: file \"",filepath,"\" not found.\n");
|
|
return;
|
|
}
|
|
|
|
|
|
float v;
|
|
//First line contains navmesh file semver
|
|
string nav_file_version = fgets(file);
|
|
print("Loading v", nav_file_version, " navmesh file \"", filepath, "\"...\n");
|
|
|
|
// TODO - Add backwards compatibility for older navmesh file versions.
|
|
|
|
// Next line contains vertex count
|
|
string line = fgets(file);
|
|
float vert_count = stof(line);
|
|
|
|
if(vert_count > NAV_MAX_VERTS) {
|
|
print("Error: navmesh file \"",filepath,"\" has an invalid vert count. (" , line, " > ", ftos(NAV_MAX_VERTS),").\n");
|
|
fclose(file);
|
|
return;
|
|
}
|
|
|
|
//It appears to be valid, so clear the current navmesh before continuing.
|
|
cl_navmesh_editor_clear_navmesh();
|
|
|
|
cl_navmesh_vert_count = vert_count;
|
|
|
|
print("Vert count: ",line,"\n");
|
|
|
|
//Temp vector to assign component-wise
|
|
vector temp;
|
|
|
|
//Reading all of the vertex positions
|
|
for(float i = 0; i < cl_navmesh_vert_count; i++) {
|
|
line = fgets(file);
|
|
temp = stov(line);
|
|
cl_navmesh_verts[i].pos.x = temp.x;
|
|
cl_navmesh_verts[i].pos.y = temp.y;
|
|
cl_navmesh_verts[i].pos.z = temp.z;
|
|
//TODO: if something goes wrong, clear the partially loaded navmesh
|
|
}
|
|
|
|
//Next line contains the number of polygons
|
|
cl_navmesh_poly_count = stof(fgets(file));
|
|
|
|
//The next lines are each polygon
|
|
for(float i = 0; i < cl_navmesh_poly_count; i++) {
|
|
//Getting vertex count
|
|
cl_navmesh_polies[i].vert_count = stof(fgets(file));
|
|
//Getting vertices
|
|
cl_navmesh_polies[i].verts[0] = stof(fgets(file));
|
|
cl_navmesh_polies[i].verts[1] = stof(fgets(file));
|
|
cl_navmesh_polies[i].verts[2] = stof(fgets(file));
|
|
cl_navmesh_polies[i].verts[3] = stof(fgets(file));
|
|
//Don't care about polygon center
|
|
fgets(file);
|
|
//Don't care about link count
|
|
fgets(file);
|
|
//Don't care about links for now FIXME HANDLE DIRECTIONAL LINKS
|
|
fgets(file);//0
|
|
fgets(file);//1
|
|
fgets(file);//2
|
|
fgets(file);//3
|
|
//Don't care about link's left vertices
|
|
fgets(file);//0
|
|
fgets(file);//1
|
|
fgets(file);//2
|
|
fgets(file);//3
|
|
//Don't care about link's right vertices
|
|
fgets(file);//0
|
|
fgets(file);//1
|
|
fgets(file);//2
|
|
fgets(file);//3
|
|
// Don't care about neighbor entrance edge indices
|
|
fgets(file);//0
|
|
fgets(file);//1
|
|
fgets(file);//2
|
|
fgets(file);//3
|
|
// Load doortarget field
|
|
cl_navmesh_polies[i].doortarget = fgets(file);
|
|
|
|
if(cl_navmesh_polies[i].doortarget != "") {
|
|
print("CLLOADNAVMESH - Polygon at index ",ftos(i)," has doortarget: \"", cl_navmesh_polies[i].doortarget, "\"" );
|
|
}
|
|
// cl_navmesh_polies[i].doortarget = ""; // Temp fix to load old files
|
|
// Load entrance edge
|
|
cl_navmesh_polies[i].entrance_edge = stoi(fgets(file));
|
|
// cl_navmesh_polies[i].entrance_edge = -1; // Temp fix to load old files
|
|
}
|
|
|
|
fclose(file);
|
|
}
|
|
|
|
|
|
//==================================================================
|
|
|
|
|
|
//==========================================================================================================================
|
|
//============================ The following methods are methods required in the pathfinding code ==========================
|
|
//==========================================================================================================================
|
|
|
|
|
|
float(float start, float goal, vector start_pos, vector end_pos) cl_navmesh_pathfind_start;
|
|
|
|
void cl_navmesh_draw_test_ent(vector pos, vector scale, vector color, float alpha)
|
|
{
|
|
//Assigning the shader as something else so that fte doesn't batch the calls (leading to colors not changing between draw calls)
|
|
R_BeginPolygon("debug/wireframe",0);
|
|
R_BeginPolygon("debug/solid_nocull",0);
|
|
|
|
//vector VEC_HULL_MIN = '-16 -16 -32';
|
|
//vector VEC_HULL_MAX = '16 16 40';
|
|
|
|
float min_x = -16 * scale_x;
|
|
float min_y = -16 * scale_y;
|
|
float min_z = -32 * scale_z;
|
|
float max_x = 16 * scale_x;
|
|
float max_y = 16 * scale_y;
|
|
float max_z = 40 * scale_z;
|
|
|
|
|
|
|
|
//bottom face
|
|
R_PolygonVertex(pos + [min_x,max_y,min_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [max_x,max_y,min_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [max_x,min_y,min_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [min_x,min_y,min_z], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
//Top face
|
|
R_PolygonVertex(pos + [min_x,max_y,max_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [max_x,max_y,max_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [max_x,min_y,max_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [min_x,min_y,max_z], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
//Front face
|
|
R_PolygonVertex(pos + [min_x,min_y,min_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [max_x,min_y,min_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [max_x,min_y,max_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [min_x,min_y,max_z], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
//Back face
|
|
R_PolygonVertex(pos + [min_x,max_y,min_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [max_x,max_y,min_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [max_x,max_y,max_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [min_x,max_y,max_z], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
//Left face
|
|
R_PolygonVertex(pos + [min_x,min_y,min_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [min_x,max_y,min_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [min_x,max_y,max_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [min_x,min_y,max_z], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
//Right face
|
|
R_PolygonVertex(pos + [max_x,min_y,min_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [max_x,max_y,min_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [max_x,max_y,max_z], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [max_x,min_y,max_z], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
}
|
|
|
|
void cl_navmesh_place_test_goalent()
|
|
{
|
|
goalent_pos = getentity(player_localentnum, GE_ORIGIN);
|
|
|
|
goalent_set = TRUE;
|
|
|
|
|
|
//Calculate data that the navmesh exporer will calculate
|
|
cl_navmesh_calc_polies_centers();
|
|
cl_navmesh_calc_connected_polies();
|
|
|
|
if(goalent_set == TRUE && startent_set == TRUE)
|
|
{
|
|
print("Getting start poly.");
|
|
float start = cl_navmesh_get_containing_poly(startent_pos);
|
|
print("Getting goal poly.");
|
|
float goal = cl_navmesh_get_containing_poly(goalent_pos);
|
|
cl_navmesh_pathfind_start(start,goal,startent_pos,goalent_pos);
|
|
}
|
|
|
|
|
|
}
|
|
void cl_navmesh_place_test_startent()
|
|
{
|
|
startent_pos = getentity(player_localentnum, GE_ORIGIN);
|
|
|
|
startent_set = TRUE;
|
|
|
|
//Calculate data that the navmesh exporer will calculate
|
|
cl_navmesh_calc_polies_centers();
|
|
cl_navmesh_calc_connected_polies();
|
|
|
|
if(goalent_set == TRUE && startent_set == TRUE)
|
|
{
|
|
print("Getting start poly.");
|
|
float start = cl_navmesh_get_containing_poly(startent_pos);
|
|
print("Getting goal poly.");
|
|
float goal = cl_navmesh_get_containing_poly(goalent_pos);
|
|
cl_navmesh_pathfind_start(start,goal,startent_pos,goalent_pos);
|
|
}
|
|
}
|
|
|
|
//Returns 1 if pos is inside poly at index poly_index, 0 otherwise
|
|
float cl_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 = cl_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 = cl_navmesh_verts[cl_navmesh_polies[poly_index].verts[i]].pos;
|
|
next_vert = cl_navmesh_verts[cl_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;
|
|
}
|
|
|
|
|
|
//FOR DEBUG: select that polygon if succesfull
|
|
cl_navmesh_deselect_all();
|
|
selected_vert_count = cl_navmesh_polies[poly_index].vert_count;
|
|
for(float i = 0; i < selected_vert_count; i++)
|
|
{
|
|
selected_verts[i] = cl_navmesh_polies[poly_index].verts[i];
|
|
}
|
|
//============================================
|
|
|
|
|
|
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 cl_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_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 = cl_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 = cl_navmesh_verts[cl_navmesh_polies[poly_index].verts[i]].pos;
|
|
next_vert = cl_navmesh_verts[cl_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 cl_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 < cl_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 = cl_navmesh_verts[cl_navmesh_polies[i].verts[0]].pos.z;
|
|
float poly_max_z = poly_min_z;
|
|
|
|
for(float j = 0; j < cl_navmesh_polies[i].vert_count; j++)
|
|
{
|
|
float vert_z = cl_navmesh_verts[cl_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 the 2d plane of polygon i
|
|
if(cl_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: ");
|
|
print(ftos(i));
|
|
print(".\n");
|
|
return i;
|
|
}
|
|
|
|
//If we are not in polygon i, check if we are very close to one of its edges
|
|
float dist = cl_navmesh_dist_to_poly(pos,i);
|
|
|
|
if(dist >= 0)
|
|
{
|
|
if(dist < closest_poly_dist)
|
|
{
|
|
closest_poly = i;
|
|
closest_poly_dist = dist;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//FOR DEBUG: select that polygon if successful
|
|
if(closest_poly != -1)
|
|
{
|
|
cl_navmesh_deselect_all();
|
|
selected_vert_count = cl_navmesh_polies[closest_poly].vert_count;
|
|
for(float i = 0; i < selected_vert_count; i++)
|
|
{
|
|
selected_verts[i] = cl_navmesh_polies[closest_poly].verts[i];
|
|
}
|
|
}
|
|
//============================================
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
|
|
//================================================================================================================
|
|
//=============================================== Actual A* functions ============================================
|
|
//================================================================================================================
|
|
|
|
//Calculates the heuristic h score value (Our best guess for how far this node is from the goal node)
|
|
float cl_pathfind_calc_h_score(float current, float goal)
|
|
{
|
|
//FIXME: we could just as easily return vlen()^2 for comparisons... (saves a sqrt operation)
|
|
return vlen(cl_navmesh_polies[goal].center - cl_navmesh_polies[current].center);
|
|
}
|
|
|
|
//Struct containing all arrays that the pathfind code requires to operate
|
|
pathfind_result *cl_test_pathfind_result;
|
|
|
|
//Returns the polygon with the lowest f score from polygons the open set
|
|
float cl_pathfind_get_lowest_f_score()
|
|
{
|
|
//TODO: implement a better algorithm for finding the lowest score
|
|
|
|
float best_score = 100000;
|
|
float best_score_index = -1;
|
|
|
|
for(float i = 0; i < cl_navmesh_poly_count; i++)
|
|
{
|
|
if(cl_test_pathfind_result->poly_set[i] == PATHFIND_POLY_SET_OPEN)
|
|
{
|
|
if(cl_test_pathfind_result->poly_f_score[i] < best_score)
|
|
{
|
|
best_score = cl_test_pathfind_result->poly_f_score[i];
|
|
best_score_index = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
return best_score_index;
|
|
}
|
|
|
|
void cl_pathfind_clear_result_data() {
|
|
//Technically we only need to iterate over navmesh_poly_count...
|
|
for(int i = 0; i < NAV_MAX_POLIES; i++)
|
|
{
|
|
cl_test_pathfind_result->poly_set[i] = PATHFIND_POLY_SET_NONE;
|
|
cl_test_pathfind_result->poly_prev[i] = -1;
|
|
cl_test_pathfind_result->poly_g_score[i] = 0;
|
|
cl_test_pathfind_result->poly_f_score[i] = 0;
|
|
cl_test_pathfind_result->result_node_path[i] = -1;
|
|
|
|
cl_test_pathfind_result->result_path[i].x = 0;
|
|
cl_test_pathfind_result->result_path[i].y = 0;
|
|
cl_test_pathfind_result->result_path[i].z = 0;
|
|
|
|
cl_test_pathfind_result->portals_right_vert[i] = -1;
|
|
cl_test_pathfind_result->portals_left_vert[i] = -1;
|
|
}
|
|
cl_test_pathfind_result->result_node_length = 0;
|
|
cl_test_pathfind_result->result_length = 0;
|
|
cl_test_pathfind_result->portals_length = 0;
|
|
}
|
|
|
|
|
|
vector get_left_portal_corner(pathfind_result *res, int portal_idx) {
|
|
// return cl_navmesh_verts[res->portals_left_vert[portal_idx]].pos;
|
|
|
|
// Find the vertex opposite this portal edge
|
|
vector corner = cl_navmesh_verts[res->portals_left_vert[portal_idx]].pos;
|
|
vector opposite_corner = cl_navmesh_verts[res->portals_right_vert[portal_idx]].pos;
|
|
// Move 20 qu or 20% of the edge length, whichever is smaller:
|
|
float edge_len = vlen(opposite_corner - corner);
|
|
vector edge_dir = normalize(opposite_corner - corner);
|
|
return corner + edge_dir * min(edge_len * 0.2, 20);
|
|
}
|
|
|
|
vector get_right_portal_corner(pathfind_result *res, int portal_idx) {
|
|
// return cl_navmesh_verts[res->portals_right_vert[portal_idx]].pos;
|
|
|
|
// Find the vertex opposite this portal edge
|
|
vector corner = cl_navmesh_verts[res->portals_right_vert[portal_idx]].pos;
|
|
vector opposite_corner = cl_navmesh_verts[res->portals_left_vert[portal_idx]].pos;
|
|
// Move 20 qu or 20% of the edge length, whichever is smaller:
|
|
float edge_len = vlen(opposite_corner - corner);
|
|
vector edge_dir = normalize(opposite_corner - corner);
|
|
return corner + edge_dir * min(edge_len * 0.2, 20);
|
|
}
|
|
|
|
|
|
//Applies a funnel algorithm to the path defined by the array cl_test_pathfind_result->result_node_path
|
|
// and populates pathfind result path
|
|
void cl_pathfind_smooth_path(vector start_point, vector goal_point) {
|
|
//Evaluate portals and identify left / right portal vertices
|
|
for(float i = 1; i < cl_test_pathfind_result->result_node_length; i++) {
|
|
float prev_poly = cl_test_pathfind_result->result_node_path[i-1];
|
|
float poly = cl_test_pathfind_result->result_node_path[i];
|
|
|
|
// Find what index prev_poly is linked to poly
|
|
float link_index = -1;
|
|
for(float j = 0; j < cl_navmesh_polies[prev_poly].connected_polies_count; j++) {
|
|
if(cl_navmesh_polies[prev_poly].connected_polies[j] == poly) {
|
|
link_index = j;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Use that index to get left and right vertices
|
|
cl_test_pathfind_result->portals_left_vert[cl_test_pathfind_result->portals_length] = cl_navmesh_polies[prev_poly].connected_polies_left_vert[link_index];
|
|
cl_test_pathfind_result->portals_right_vert[cl_test_pathfind_result->portals_length++] = cl_navmesh_polies[prev_poly].connected_polies_right_vert[link_index];
|
|
}
|
|
|
|
//the last left edge / right edge is the goal position
|
|
|
|
print("Portals: ");
|
|
for(float i = 0; i < cl_test_pathfind_result->portals_length; i++)
|
|
{
|
|
print("[");
|
|
print(ftos(i));
|
|
print("] = (");
|
|
print(ftos(cl_test_pathfind_result->portals_left_vert[i]));
|
|
print(" , ");
|
|
print(ftos(cl_test_pathfind_result->portals_right_vert[i]));
|
|
print(") , ");
|
|
}
|
|
|
|
//starting at start_pos (not at start center point)
|
|
//get vectors that point to first left edge and first right edge
|
|
vector funnel_apex = start_point;
|
|
float funnel_left_index = 0;
|
|
//Index of the funnel's left vertex in the portal list
|
|
vector funnel_left = get_left_portal_corner(cl_test_pathfind_result, 0);
|
|
//Index of the funnel's right vertex in the portal list
|
|
float funnel_right_index = 0;
|
|
vector funnel_right = get_right_portal_corner(cl_test_pathfind_result, 0);
|
|
|
|
vector next_funnel_left;
|
|
vector next_funnel_right;
|
|
|
|
float inside_right_edge;
|
|
float inside_left_edge;
|
|
|
|
while(1) {
|
|
print("=============Funnel iteration.==========\n");
|
|
print("\tCurrent left edge: ");
|
|
print(ftos(cl_test_pathfind_result->portals_left_vert[funnel_left_index]));
|
|
print("\n");
|
|
print("\tCurrent right edge: ");
|
|
print(ftos(cl_test_pathfind_result->portals_right_vert[funnel_right_index]));
|
|
print("\n");
|
|
|
|
//Check if we have reached the end of the portals, consider the goal position as the last portal
|
|
//If we are not at the last index of the left portal
|
|
|
|
//Keeping track of whether or not advancing the left edge or right edge narrows the funnel
|
|
float advanced_left = FALSE;
|
|
float advanced_right = FALSE;
|
|
|
|
//Consider the end goal point as the last portal
|
|
//================ Checking left funnel edge =================
|
|
if(funnel_left_index < cl_test_pathfind_result->portals_length) {
|
|
print("\t-- Trying to advance left edge. --\n");
|
|
|
|
if(funnel_left_index < cl_test_pathfind_result->portals_length - 1) {
|
|
// next_funnel_left = cl_navmesh_verts[cl_test_pathfind_result->portals_left_vert[funnel_left_index+1]].pos;
|
|
next_funnel_left = get_left_portal_corner(cl_test_pathfind_result, funnel_left_index + 1);
|
|
print("\t\tNext left edge is vert: ");
|
|
print(ftos(cl_test_pathfind_result->portals_left_vert[funnel_left_index+1]));
|
|
print(".\n");
|
|
}
|
|
else {
|
|
print("\t\tTrying next left edge as goal point.\n");
|
|
//If funnel_left is pointing to the last portal in the array, consider the goal_point the last portal
|
|
next_funnel_left = goal_point;
|
|
}
|
|
|
|
inside_right_edge = pathfind_point_is_to_left(funnel_apex,funnel_right,next_funnel_left);
|
|
//If the next left edge crosses the current right edge
|
|
if(inside_right_edge < 0) {
|
|
print("\t\tnext left edge crosses current right edge.\n");
|
|
//Add funnel right point to path
|
|
// cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = funnel_right;
|
|
vector corner = funnel_right;
|
|
// // ------------------------------------------------------------
|
|
// // Instead of adding the funnel's corner to the path,
|
|
// // move away from the corner by some small amount
|
|
// // ------------------------------------------------------------
|
|
// // Find the first portal along the path with this corner
|
|
// int portal_idx = funnel_right_index;
|
|
// for(int i = funnel_right_index; i >= 0; i--) {
|
|
// if(cl_test_pathfind_result->portals_right_vert[i] == cl_test_pathfind_result->portals_right_vert[funnel_right_index]) {
|
|
// portal_idx = i;
|
|
// continue;
|
|
// }
|
|
// break;
|
|
// }
|
|
// // For every portal with this corner, add a point to the path
|
|
// for(int i = portal_idx; i <= funnel_right_index; i++) {
|
|
// // Find the vertex opposite this portal edge
|
|
// vector opposite_corner = cl_navmesh_verts[cl_test_pathfind_result->portals_left_vert[i]].pos;
|
|
// // Move 20 qu or 20% of the edge length, whichever is smaller:
|
|
// float edge_len = vlen(opposite_corner - corner);
|
|
// vector edge_dir = normalize(opposite_corner - corner);
|
|
// vector delta_corner = edge_dir * min(edge_len * 0.2, 20);
|
|
// cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = corner + delta_corner;
|
|
// }
|
|
// // ------------------------------------------------------------
|
|
cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = corner;
|
|
|
|
//Check if we are at the end of our portal list
|
|
if(funnel_right_index >= cl_test_pathfind_result->portals_length - 1) {
|
|
cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = goal_point;
|
|
return;
|
|
}
|
|
|
|
print("\t\tnew funnel apex is now vert: ");
|
|
print(ftos(cl_test_pathfind_result->portals_right_vert[funnel_right_index]));
|
|
print("\n");
|
|
|
|
//Restart algorithm with the portal after the right point
|
|
funnel_apex = funnel_right;
|
|
|
|
funnel_left_index = funnel_right_index + 1;
|
|
// funnel_left = cl_navmesh_verts[cl_test_pathfind_result->portals_left_vert[funnel_left_index]].pos;
|
|
funnel_left = get_left_portal_corner(cl_test_pathfind_result, funnel_left_index);
|
|
funnel_right_index = funnel_right_index + 1;
|
|
// funnel_right = cl_navmesh_verts[cl_test_pathfind_result->portals_right_vert[funnel_right_index]].pos;
|
|
funnel_right = get_right_portal_corner(cl_test_pathfind_result, funnel_right_index);
|
|
continue;
|
|
}
|
|
//If the next left edge is in the funnel
|
|
inside_left_edge = pathfind_point_is_to_left(funnel_left,funnel_apex,next_funnel_left);
|
|
if(inside_left_edge >= 0 && inside_right_edge >= 0) {
|
|
print("\t\tnext left edge is in the funnel, narrowing funnel.\n");
|
|
//Advance the left edge
|
|
funnel_left = next_funnel_left;
|
|
funnel_left_index++;
|
|
advanced_left = TRUE;
|
|
//Check if the portal we just added was the goal point (will be after the list)
|
|
if(funnel_left_index >= cl_test_pathfind_result->portals_length) {
|
|
cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = goal_point;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
//================ Checking right funnel edge =================
|
|
if(funnel_right_index < cl_test_pathfind_result->portals_length) {
|
|
print("\t -- Trying to advance right edge --\n");
|
|
|
|
if(funnel_right_index < cl_test_pathfind_result->portals_length - 1) {
|
|
next_funnel_right = get_right_portal_corner(cl_test_pathfind_result, funnel_right_index + 1);
|
|
// next_funnel_right = cl_navmesh_verts[cl_test_pathfind_result->portals_right_vert[funnel_right_index+1]].pos;
|
|
print("\t\tNext right edge is vert: ");
|
|
print(ftos(cl_test_pathfind_result->portals_right_vert[funnel_right_index+1]));
|
|
print(".\n");
|
|
}
|
|
else {
|
|
print("\t\tTrying next right edge as goal point.\n");
|
|
//If funnel_right is pointing to the last portal in the array, consider the goal_point the last portal
|
|
next_funnel_right = goal_point;
|
|
}
|
|
|
|
//If the next right edge crosses the current left edge
|
|
inside_left_edge = pathfind_point_is_to_left(funnel_left,funnel_apex,next_funnel_right);
|
|
if(inside_left_edge < 0) {
|
|
print("\t\tnext right edge crosses current left edge.\n");
|
|
//Add funnel left point to path
|
|
// cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = funnel_left;
|
|
vector corner = funnel_left;
|
|
// // ------------------------------------------------------------
|
|
// // Instead of adding the funnel's corner to the path,
|
|
// // move away from the corner by some small amount
|
|
// // ------------------------------------------------------------
|
|
// // Find the first portal along the path with this corner
|
|
// int portal_idx = funnel_left_index;
|
|
// for(int i = funnel_left_index; i >= 0; i--) {
|
|
// if(cl_test_pathfind_result->portals_left_vert[i] == cl_test_pathfind_result->portals_left_vert[funnel_left_index]) {
|
|
// portal_idx = i;
|
|
// continue;
|
|
// }
|
|
// break;
|
|
// }
|
|
// // For every portal with this corner, add a point to the path
|
|
// for(int i = portal_idx; i <= funnel_left_index; i++) {
|
|
// // Find the vertex opposite this portal edge
|
|
// vector opposite_corner = cl_navmesh_verts[cl_test_pathfind_result->portals_right_vert[i]].pos;
|
|
// // Move 20 qu or 20% of the edge length, whichever is smaller:
|
|
// float edge_len = vlen(opposite_corner - corner);
|
|
// vector edge_dir = normalize(opposite_corner - corner);
|
|
// vector delta_corner = edge_dir * min(edge_len * 0.2, 20);
|
|
// cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = corner + delta_corner;
|
|
// }
|
|
// // ------------------------------------------------------------
|
|
cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = corner;
|
|
|
|
//Check if we are at the end of our portal list
|
|
if(funnel_left_index >= cl_test_pathfind_result->portals_length - 1) {
|
|
cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = goal_point;
|
|
return;
|
|
}
|
|
|
|
//Restart algorithm with the portal after the right point
|
|
funnel_apex = funnel_left;
|
|
print("\t\tnew funnel apex is now vert: ");
|
|
print(ftos(cl_test_pathfind_result->portals_left_vert[funnel_left_index]));
|
|
print("\n");
|
|
|
|
funnel_right_index = funnel_left_index + 1;
|
|
funnel_right = get_right_portal_corner(cl_test_pathfind_result, funnel_right_index);
|
|
// funnel_right = cl_navmesh_verts[cl_test_pathfind_result->portals_right_vert[funnel_right_index]].pos;
|
|
funnel_left_index = funnel_left_index + 1;
|
|
funnel_left = get_left_portal_corner(cl_test_pathfind_result, funnel_left_index);
|
|
// funnel_left = cl_navmesh_verts[cl_test_pathfind_result->portals_left_vert[funnel_left_index]].pos;
|
|
continue;
|
|
}
|
|
//If the next right edge is in the funnel
|
|
inside_right_edge = pathfind_point_is_to_left(funnel_apex,funnel_right,next_funnel_right);
|
|
if(inside_left_edge >= 0 && inside_right_edge >= 0) {
|
|
print("\t\tnext right edge is in the funnel, narrowing funnel.\n");
|
|
//Advance the left edge
|
|
funnel_right = next_funnel_right;
|
|
funnel_right_index++;
|
|
advanced_right = TRUE;
|
|
|
|
//Check if the portal we just added was the goal point (will be after the list)
|
|
if(funnel_right_index >= cl_test_pathfind_result->portals_length) {
|
|
cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = goal_point;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if(advanced_left == FALSE && advanced_right == FALSE) {
|
|
print("Hit funnel freeze condition.\n");
|
|
|
|
float left_vert = -1;
|
|
float left_type;
|
|
float left_portal_index;//index of the vertex in the list of portals
|
|
|
|
float right_vert = -1;
|
|
float right_type;
|
|
float right_portal_index;//index of the vertex in the list of portals
|
|
|
|
float crossed = 1;//Indicates the vert crossed the funnel
|
|
float contained = 2;//Indicates a vert is in the funnel
|
|
|
|
float last_vert = -1;//used to skip calculating the same vertex more than once
|
|
|
|
|
|
//Find the closest vertex in the portal left with the lowest index that crosses the funnel or is in the funnel
|
|
for(float i = funnel_left_index + 1; i < cl_test_pathfind_result->portals_length + 1; i++) {
|
|
if(i < cl_test_pathfind_result->portals_length - 1) {
|
|
//Don't calculate the same vertex again
|
|
if(last_vert == cl_test_pathfind_result->portals_left_vert[i])
|
|
continue;
|
|
last_vert = cl_test_pathfind_result->portals_left_vert[i];
|
|
|
|
next_funnel_left = get_left_portal_corner(cl_test_pathfind_result, i);
|
|
// next_funnel_left = cl_navmesh_verts[last_vert].pos;
|
|
}
|
|
else {
|
|
//consider goal pos as last portal edge
|
|
next_funnel_left = goal_point;
|
|
}
|
|
|
|
inside_right_edge = pathfind_point_is_to_left(funnel_apex,funnel_right,next_funnel_left);
|
|
|
|
//If the left vertex crosses the funnel (left vertex is outside the funnel's right edge)
|
|
if(inside_right_edge < 0) {
|
|
left_vert = cl_test_pathfind_result->portals_left_vert[i];
|
|
left_type = crossed;
|
|
break;
|
|
}
|
|
|
|
inside_left_edge = pathfind_point_is_to_left(funnel_left,funnel_apex,next_funnel_left);
|
|
|
|
//If the left vertex is within the funnel
|
|
if(inside_left_edge >= 0 && inside_right_edge >= 0) {
|
|
left_vert = cl_test_pathfind_result->portals_left_vert[i];
|
|
left_type = contained;
|
|
left_portal_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
last_vert = -1;
|
|
|
|
//Find the closest vertex in the portal right with the lowest index that crosses the funnel or is in the funnel
|
|
for(float i = funnel_right_index + 1; i < cl_test_pathfind_result->portals_length; i++) {
|
|
if(i < cl_test_pathfind_result->portals_length - 1) {
|
|
//Don't calculate the same vertex again
|
|
if(last_vert == cl_test_pathfind_result->portals_right_vert[i])
|
|
continue;
|
|
last_vert = cl_test_pathfind_result->portals_right_vert[i];
|
|
|
|
next_funnel_right = get_right_portal_corner(cl_test_pathfind_result, i);
|
|
// next_funnel_right = cl_navmesh_verts[last_vert].pos;
|
|
}
|
|
//consider goal pos as last portal edge
|
|
else {
|
|
next_funnel_right = goal_point;
|
|
}
|
|
|
|
inside_left_edge = pathfind_point_is_to_left(funnel_left,funnel_apex,next_funnel_right);
|
|
|
|
//If the right vertex crosses the funnel (right vertex is outside the funnel's left edge)
|
|
if(inside_left_edge < 0) {
|
|
right_vert = cl_test_pathfind_result->portals_right_vert[i];
|
|
right_type = crossed;
|
|
break;
|
|
}
|
|
|
|
inside_right_edge = pathfind_point_is_to_left(funnel_apex,funnel_right,next_funnel_right);
|
|
|
|
//If the right vertex is within the funnel
|
|
if(inside_left_edge >= 0 && inside_right_edge >= 0) {
|
|
right_vert = cl_test_pathfind_result->portals_right_vert[i];
|
|
right_type = contained;
|
|
right_portal_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//If no vertices were found, goal is reachable from here
|
|
if(left_vert == -1 && right_vert == -1) {
|
|
cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = goal_point;
|
|
return;
|
|
}
|
|
|
|
float use_left;
|
|
//If both verts were found
|
|
//Find which vertex has a lower index
|
|
if(left_vert != -1 && right_vert != -1) {
|
|
if(left_portal_index < right_portal_index) {
|
|
use_left = TRUE;
|
|
}
|
|
else {
|
|
use_left = FALSE;
|
|
}
|
|
}
|
|
|
|
//if left vert was found
|
|
else if(left_vert != -1) {
|
|
use_left = TRUE;
|
|
}
|
|
//else right vert was found
|
|
else {
|
|
use_left = FALSE;
|
|
}
|
|
|
|
if(use_left) {
|
|
if(left_type == crossed) {
|
|
//Place corner at current right funnel edge
|
|
cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = funnel_right;
|
|
|
|
//Restart algorithm with the portal after the right point
|
|
funnel_apex = funnel_right;
|
|
|
|
//Check if portal after this portal is goal:
|
|
funnel_right_index++;
|
|
if(funnel_right_index >= cl_test_pathfind_result->portals_length) {
|
|
cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = goal_point;
|
|
return;
|
|
}
|
|
|
|
funnel_left_index = funnel_right_index;
|
|
// funnel_left = cl_navmesh_verts[cl_test_pathfind_result->portals_left_vert[funnel_left_index]].pos;
|
|
// funnel_right = cl_navmesh_verts[cl_test_pathfind_result->portals_right_vert[funnel_right_index]].pos;
|
|
funnel_left = get_left_portal_corner(cl_test_pathfind_result, funnel_left_index);
|
|
funnel_right = get_right_portal_corner(cl_test_pathfind_result, funnel_right_index);
|
|
continue;
|
|
}
|
|
//in funnel
|
|
else {
|
|
//If the next funnel right is the goal, we are done
|
|
//Check if portal after this portal is goal:
|
|
if(right_portal_index >= cl_test_pathfind_result->portals_length) {
|
|
cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = goal_point;
|
|
return;
|
|
}
|
|
//Update current left funnel edge to use this vertex
|
|
funnel_left = next_funnel_left;
|
|
funnel_left_index = left_portal_index;
|
|
continue;
|
|
}
|
|
}
|
|
//use right
|
|
else {
|
|
if(right_type == crossed)
|
|
{
|
|
//Place corner at current left funnel edge
|
|
cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = funnel_left;
|
|
|
|
//Restart algorithm with the portal after the left point
|
|
funnel_apex = funnel_left;
|
|
|
|
//Check if portal after this portal is goal:
|
|
funnel_left_index++;
|
|
if(funnel_left_index >= cl_test_pathfind_result->portals_length) {
|
|
cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = goal_point;
|
|
return;
|
|
}
|
|
|
|
funnel_right_index = funnel_left_index;
|
|
funnel_left = get_left_portal_corner(cl_test_pathfind_result, funnel_left_index);
|
|
funnel_right = get_right_portal_corner(cl_test_pathfind_result, funnel_right_index);
|
|
continue;
|
|
}
|
|
//in funnel
|
|
else {
|
|
//If the next funnel right is the goal, we are done
|
|
//Check if portal after this portal is goal:
|
|
if(right_portal_index >= cl_test_pathfind_result->portals_length) {
|
|
cl_test_pathfind_result->result_path[cl_test_pathfind_result->result_length++] = goal_point;
|
|
return;
|
|
}
|
|
//Update current right funnel edge to use this vertex
|
|
funnel_right = next_funnel_right;
|
|
funnel_right_index = right_portal_index;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
print("==========================\n");
|
|
}
|
|
}
|
|
|
|
//Evaluates the path that is found in the pathfinding algorithm and populates an array with the nodes in the path from start to goal
|
|
void pathfind_trace_path(float start, float goal)
|
|
{
|
|
float current = start;
|
|
//Count the length of the path (how many polygons the path traverses)
|
|
current = goal;
|
|
cl_test_pathfind_result->result_node_length = 1;
|
|
do
|
|
{
|
|
//print("Poly ");
|
|
//print(ftos(current));
|
|
//print(" came from ");
|
|
//print(ftos(cl_test_pathfind_result->poly_prev[current]));
|
|
//print(".\n");
|
|
current = cl_test_pathfind_result->poly_prev[current];
|
|
cl_test_pathfind_result->result_node_length++;
|
|
} while(current != start);
|
|
|
|
|
|
//Starting at goal waypoint, add the traversed waypoints to the result path in reverse order
|
|
current = goal;
|
|
for(float i = 0; i < cl_test_pathfind_result->result_node_length; i++)
|
|
{
|
|
cl_test_pathfind_result->result_node_path[cl_test_pathfind_result->result_node_length - 1 - i] = current;
|
|
current = cl_test_pathfind_result->poly_prev[current];
|
|
}
|
|
|
|
// print("Pathfind success, path length: ");
|
|
// print(ftos(cl_test_pathfind_result->result_node_length));
|
|
// print(".\n");
|
|
// print("Path: ");
|
|
// for(float i = 0; i < cl_test_pathfind_result->result_node_length; i++)
|
|
// {
|
|
// if(i > 0)
|
|
// print(" , ");
|
|
// print(ftos(cl_test_pathfind_result->result_node_path[i]));
|
|
// }
|
|
// print(" .\n");
|
|
}
|
|
|
|
//Accepts start polygon and goal polygon
|
|
//Returns 1 on success.
|
|
//Returns 0 on fail.
|
|
float cl_navmesh_pathfind_start(float start, float goal, vector start_pos, vector end_pos)
|
|
{
|
|
if(start == -1)
|
|
{
|
|
print("Error: pathfind start node invalid.\n");
|
|
return 0;
|
|
}
|
|
if(goal == -1)
|
|
{
|
|
print("Error: pathfind goal node invalid.\n");
|
|
return 0;
|
|
}
|
|
if(start == goal)
|
|
{
|
|
//Calculating node path
|
|
cl_test_pathfind_result->result_node_path[0] = start;
|
|
cl_test_pathfind_result->result_node_length = 1;
|
|
print("Pathind success: trivial case (start = goal).\n");
|
|
|
|
//Calculating vector based path (go directly to goal)
|
|
cl_test_pathfind_result->result_path[0].x = end_pos.x;
|
|
cl_test_pathfind_result->result_path[0].y = end_pos.y;
|
|
cl_test_pathfind_result->result_path[0].z = end_pos.z;
|
|
cl_test_pathfind_result->result_length = 1;
|
|
return 1;
|
|
}
|
|
|
|
//Clearing previous data
|
|
cl_pathfind_clear_result_data();
|
|
|
|
//Adding start polygon to the open set
|
|
cl_test_pathfind_result->poly_set[start] = PATHFIND_POLY_SET_OPEN;
|
|
cl_test_pathfind_result->poly_g_score[start] = 0;
|
|
cl_test_pathfind_result->poly_f_score[start] = 0 + cl_pathfind_calc_h_score(start , goal);
|
|
|
|
//Fields that need to be set:
|
|
//cl_test_pathfind_result->poly_set[NAV_MAX_POLIES];
|
|
//cl_test_pathfind_result->prev_poly[NAV_MAX_POLIES];
|
|
//cl_test_pathfind_result->poly_g_score[NAV_MAX_POLIES];
|
|
//cl_test_pathfind_result->poly_f_score[NAV_MAX_POLIES];
|
|
|
|
print("Pathfind init. Start: ");
|
|
print(ftos(start));
|
|
print(" , Goal: ");
|
|
print(ftos(goal));
|
|
print(".\n");
|
|
|
|
float current = start;
|
|
|
|
float pathfind_success = FALSE;
|
|
|
|
while(current != -1)
|
|
{
|
|
if(current == goal)
|
|
{
|
|
//print("Current is now goal. Breaking.\n");
|
|
pathfind_success = TRUE;
|
|
break;
|
|
}
|
|
//Add current node to the closed set
|
|
cl_test_pathfind_result->poly_set[current] = PATHFIND_POLY_SET_CLOSED;
|
|
//Add connected nodes to the open set
|
|
for(float i = 0; i < cl_navmesh_polies[current].connected_polies_count; i++)
|
|
{
|
|
float neighbor = cl_navmesh_polies[current].connected_polies[i];
|
|
|
|
/*print("Checking poly ");
|
|
print(ftos(current));
|
|
print("'s neighbor ");
|
|
print(ftos(neighbor));
|
|
print(".\n");*/
|
|
|
|
// ----------------------------------------------------------------
|
|
// Door check
|
|
// ----------------------------------------------------------------
|
|
// NOTE - If polygon's door hasn't been opened, don't consider it.
|
|
// NOTE - This check isn't done in the navmesh editor.
|
|
// ----------------------------------------------------------------
|
|
|
|
// ----------------------------------------------------------------
|
|
// Entrance edge
|
|
// ----------------------------------------------------------------
|
|
// Check if we can enter this polygon from this edge
|
|
// If entrance_edge != -1, we can only enter the polygon from the edge at index "entrance_edge"
|
|
if(cl_navmesh_polies[neighbor].entrance_edge != -1) {
|
|
// print("-- Pathfind loop -- Evaluating a neighbor whose entrance_edge = ",ftos(cl_navmesh_polies[neighbor].entrance_edge),"\n");
|
|
// print("Current polygon:",ftos(current),"\n");
|
|
// print("Neighbor polygon:",ftos(neighbor),"\n");
|
|
// print("Neighbor index:",ftos(i),"\n");
|
|
// print("Current vertices: ");
|
|
// for(float j = 0; j < cl_navmesh_polies[current].vert_count; j++) {
|
|
// print(ftos(cl_navmesh_polies[current].verts[j]),",");
|
|
// }
|
|
// print("\n");
|
|
// print("Neighbor vertices: ");
|
|
// for(float j = 0; j < cl_navmesh_polies[neighbor].vert_count; j++) {
|
|
// print(ftos(cl_navmesh_polies[neighbor].verts[j]),",");
|
|
// }
|
|
// print("\n");
|
|
// print("Current left portal vertices: ");
|
|
// for(float j = 0; j < cl_navmesh_polies[current].connected_polies_count; j++) {
|
|
// print(ftos(cl_navmesh_polies[current].connected_polies_left_vert[j]),",");
|
|
// }
|
|
// print("\n");
|
|
// print("Current right portal vertices: ");
|
|
// for(float j = 0; j < cl_navmesh_polies[current].connected_polies_count; j++) {
|
|
// print(ftos(cl_navmesh_polies[current].connected_polies_right_vert[j]),",");
|
|
// }
|
|
// print("\n");
|
|
// print("neighbor left portal vertices: ");
|
|
// for(float j = 0; j < cl_navmesh_polies[neighbor].connected_polies_count; j++) {
|
|
// print(ftos(cl_navmesh_polies[neighbor].connected_polies_left_vert[j]),",");
|
|
// }
|
|
// print("\n");
|
|
// print("neighbor right portal vertices: ");
|
|
// for(float j = 0; j < cl_navmesh_polies[neighbor].connected_polies_count; j++) {
|
|
// print(ftos(cl_navmesh_polies[neighbor].connected_polies_right_vert[j]),",");
|
|
// }
|
|
// print("\n");
|
|
// print("Current neighbor edge index: ");
|
|
// for(float j = 0; j < cl_navmesh_polies[current].connected_polies_count; j++) {
|
|
// print(ftos(cl_navmesh_polies[current].connected_polies_neighbor_edge_index[j]),",");
|
|
// }
|
|
// print("\n");
|
|
// print("Neighbor neighbor edge index: ");
|
|
// for(float j = 0; j < cl_navmesh_polies[neighbor].connected_polies_count; j++) {
|
|
// print(ftos(cl_navmesh_polies[neighbor].connected_polies_neighbor_edge_index[j]),",");
|
|
// }
|
|
// print("\n");
|
|
// print("According to current polygon, the we're traversing the neighbor's edge with index: ", ftos(cl_navmesh_polies[current].connected_polies_neighbor_edge_index[i])," to enter neighbor.\n");
|
|
|
|
// Check if the edge we're crossing from current to neighbor is the entrance edge
|
|
if(cl_navmesh_polies[neighbor].entrance_edge != cl_navmesh_polies[current].connected_polies_neighbor_edge_index[i]) {
|
|
// If it's not the entrance edge, skip this neighbor.
|
|
// We can't walk from current to neighbor.
|
|
continue;
|
|
}
|
|
}
|
|
// ----------------------------------------------------------------
|
|
|
|
|
|
if(cl_test_pathfind_result->poly_set[neighbor] != PATHFIND_POLY_SET_CLOSED)
|
|
{
|
|
//print("Neighbor is not in closed list.\n");
|
|
//Calculate tentative f score
|
|
//Calculate tentative g score (distance from start to current + distance from current to neighbor)
|
|
float tentative_g_score = cl_test_pathfind_result->poly_g_score[current] + vlen(cl_navmesh_polies[neighbor].center - cl_navmesh_polies[current].center);
|
|
|
|
if(cl_test_pathfind_result->poly_set[neighbor] != PATHFIND_POLY_SET_OPEN || tentative_g_score < cl_test_pathfind_result->poly_g_score[neighbor])
|
|
{
|
|
//print("Neighbor is not in open list, or a better g score has been found.\n");
|
|
//Adding neighbor to open set
|
|
cl_test_pathfind_result->poly_set[neighbor] = PATHFIND_POLY_SET_OPEN;
|
|
|
|
//Updating scores for neighbor node
|
|
float tentative_f_score = tentative_g_score + cl_pathfind_calc_h_score(neighbor , goal);
|
|
cl_test_pathfind_result->poly_g_score[neighbor] = tentative_g_score;
|
|
cl_test_pathfind_result->poly_f_score[neighbor] = tentative_f_score;
|
|
|
|
//Linking neighbor node to current node (for tracing path)
|
|
cl_test_pathfind_result->poly_prev[neighbor] = current;
|
|
|
|
/*print("Assigning Poly ");
|
|
print(ftos(neighbor));
|
|
print(" came from ");
|
|
print(ftos(current));
|
|
print(".\n");*/
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
current = cl_pathfind_get_lowest_f_score();
|
|
}
|
|
|
|
|
|
//Tracing the pathfind results
|
|
if(pathfind_success == TRUE)
|
|
{
|
|
pathfind_trace_path(start,goal);
|
|
|
|
cl_pathfind_smooth_path(start_pos,end_pos);
|
|
|
|
return 1;
|
|
}
|
|
print("Pathfind fail");
|
|
return 0;
|
|
}
|
|
|
|
|
|
//This renders the raw pathfind results (polygon center to polygon center)
|
|
void cl_navmesh_pathfind_draw_result_node_path()
|
|
{
|
|
//FIXME: don't call if length is 0
|
|
if(cl_test_pathfind_result->result_node_length < 1)
|
|
return;
|
|
//================= Drawing Polygon Center ======================
|
|
//=======================================
|
|
//Assigning the shader as something else so that fte doesn't batch the calls (leading to colors not changing between draw calls)
|
|
R_BeginPolygon("debug/wireframe",0);
|
|
R_BeginPolygon("debug/solid_nocull",0);
|
|
|
|
vector color;
|
|
color = [1,0.1,0.5];
|
|
float alpha = 0.2;//0.3
|
|
float edge_width = 2;
|
|
float edge_alpha = 0.2;//0.3
|
|
|
|
for(float i = 0; i < cl_test_pathfind_result->result_node_length; i++)
|
|
{
|
|
vector pos = cl_navmesh_polies[cl_test_pathfind_result->result_node_path[i]].center + [0,0,-5];
|
|
//bottom face
|
|
R_PolygonVertex(pos + [-5,5,-5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [5,5,-5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [5,-5,-5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [-5,-5,-5], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
//Top face
|
|
R_PolygonVertex(pos + [-5,5,5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [5,5,5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [5,-5,5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [-5,-5,5], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
//Front face
|
|
R_PolygonVertex(pos + [-5,-5,-5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [5,-5,-5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [5,-5,5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [-5,-5,5], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
//Back face
|
|
R_PolygonVertex(pos + [-5,5,-5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [5,5,-5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [5,5,5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [-5,5,5], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
//Left face
|
|
R_PolygonVertex(pos + [-5,-5,-5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [-5,5,-5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [-5,5,5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [-5,-5,5], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
//Right face
|
|
R_PolygonVertex(pos + [5,-5,-5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [5,5,-5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [5,5,5], [0,0,0], color,alpha);
|
|
R_PolygonVertex(pos + [5,-5,5], [0,0,0], color,alpha);
|
|
R_EndPolygon();
|
|
|
|
if(i > 0)
|
|
{
|
|
//Draw an edge connecting ith node to previous node
|
|
R_PolygonVertex(cl_navmesh_polies[cl_test_pathfind_result->result_node_path[i]].center + [0,0,-5] + [0,0,-edge_width], [0,0,0], color, edge_alpha);
|
|
R_PolygonVertex(cl_navmesh_polies[cl_test_pathfind_result->result_node_path[i]].center + [0,0,-5] + [0,0,edge_width], [0,0,0], color, edge_alpha);
|
|
R_PolygonVertex(cl_navmesh_polies[cl_test_pathfind_result->result_node_path[i-1]].center + [0,0,-5] + [0,0,edge_width], [0,0,0], color, edge_alpha);
|
|
R_PolygonVertex(cl_navmesh_polies[cl_test_pathfind_result->result_node_path[i-1]].center + [0,0,-5] + [0,0,-edge_width], [0,0,0], color, edge_alpha);
|
|
R_EndPolygon();
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
void cl_navmesh_pathfind_draw_result_portals()
|
|
{
|
|
if(cl_test_pathfind_result->portals_length < 1)
|
|
return;
|
|
vector color;
|
|
color = [0.5,0.5,1.0];
|
|
float edge_width = 4;
|
|
float edge_alpha = 0.2;//0.3
|
|
|
|
for(float i = 0; i < cl_test_pathfind_result->portals_length; i++)
|
|
{
|
|
cl_navmesh_draw_vert(cl_navmesh_verts[cl_test_pathfind_result->portals_left_vert[i]].pos + [0,0,-10],[0.5,0.5,1],0.4);
|
|
cl_navmesh_draw_vert(cl_navmesh_verts[cl_test_pathfind_result->portals_right_vert[i]].pos + [0,0,-8],[0.5,1,0.5],0.4);
|
|
|
|
R_BeginPolygon("debug/wireframe",0);
|
|
R_BeginPolygon("debug/solid_nocull",0);
|
|
|
|
//Draw an edge connecting portals
|
|
R_PolygonVertex(cl_navmesh_verts[cl_test_pathfind_result->portals_left_vert[i]].pos + [0,0,-10] + [0,0,-edge_width], [0,0,0], color, edge_alpha);
|
|
R_PolygonVertex(cl_navmesh_verts[cl_test_pathfind_result->portals_left_vert[i]].pos + [0,0,-10] + [0,0,edge_width], [0,0,0], color, edge_alpha);
|
|
R_PolygonVertex(cl_navmesh_verts[cl_test_pathfind_result->portals_right_vert[i]].pos + [0,0,-8] + [0,0,edge_width], [0,0,0], color, edge_alpha);
|
|
R_PolygonVertex(cl_navmesh_verts[cl_test_pathfind_result->portals_right_vert[i]].pos + [0,0,-8] + [0,0,-edge_width], [0,0,0], color, edge_alpha);
|
|
R_EndPolygon();
|
|
}
|
|
}
|
|
|
|
void cl_navmesh_pathfind_draw_result_path()
|
|
{
|
|
if(cl_test_pathfind_result->result_length < 1)
|
|
return;
|
|
|
|
vector color;
|
|
color = [1.0,0.0,0.0];
|
|
float edge_width = 4;
|
|
float edge_alpha = 0.2;//0.3
|
|
|
|
//Drawing the a line connecting the start and first path node
|
|
cl_navmesh_draw_vert(startent_pos + [0,0,10],[1,0,0],0.4);
|
|
R_BeginPolygon("debug/wireframe",0);
|
|
R_BeginPolygon("debug/solid_nocull",0);
|
|
|
|
//Draw an edge connecting portals
|
|
R_PolygonVertex(cl_test_pathfind_result->result_path[0] + [0,0,10] + [0,0,-edge_width], [0,0,0], color, edge_alpha);
|
|
R_PolygonVertex(cl_test_pathfind_result->result_path[0] + [0,0,10] + [0,0,edge_width], [0,0,0], color, edge_alpha);
|
|
R_PolygonVertex(startent_pos + [0,0,10] + [0,0,edge_width], [0,0,0], color, edge_alpha);
|
|
R_PolygonVertex(startent_pos + [0,0,10] + [0,0,-edge_width], [0,0,0], color, edge_alpha);
|
|
R_EndPolygon();
|
|
|
|
|
|
for(float i = 0; i < cl_test_pathfind_result->result_length; i++)
|
|
{
|
|
cl_navmesh_draw_vert(cl_test_pathfind_result->result_path[i] + [0,0,10],[1,0,0],0.4);
|
|
|
|
if(i > 0)
|
|
{
|
|
R_BeginPolygon("debug/wireframe",0);
|
|
R_BeginPolygon("debug/solid_nocull",0);
|
|
|
|
//Draw an edge connecting portals
|
|
R_PolygonVertex(cl_test_pathfind_result->result_path[i] + [0,0,10] + [0,0,-edge_width], [0,0,0], color, edge_alpha);
|
|
R_PolygonVertex(cl_test_pathfind_result->result_path[i] + [0,0,10] + [0,0,edge_width], [0,0,0], color, edge_alpha);
|
|
R_PolygonVertex(cl_test_pathfind_result->result_path[i-1] + [0,0,10] + [0,0,edge_width], [0,0,0], color, edge_alpha);
|
|
R_PolygonVertex(cl_test_pathfind_result->result_path[i-1] + [0,0,10] + [0,0,-edge_width], [0,0,0], color, edge_alpha);
|
|
R_EndPolygon();
|
|
}
|
|
}
|
|
}
|
|
|
|
void cl_toggle_navmesh_editor()
|
|
{
|
|
|
|
|
|
if(cvar("navmesh_edit_mode")) {
|
|
cvar_set("navmesh_edit_mode", "0");
|
|
print("cl_navmesh editor: 0\n");
|
|
}
|
|
else {
|
|
print("cl_navmesh editor: 1\n");
|
|
cvar_set("navmesh_edit_mode", "1");
|
|
|
|
if(selected_verts == 0) {
|
|
selected_verts = memalloc(sizeof(float) * NAV_MAX_SELECTED_VERTS);
|
|
}
|
|
if(cl_navmesh_verts == 0) {
|
|
cl_navmesh_verts = memalloc(sizeof(navmesh_vertex) * NAV_MAX_VERTS);
|
|
}
|
|
if(cl_navmesh_polies == 0) {
|
|
cl_navmesh_polies = memalloc(sizeof(navmesh_poly) * NAV_MAX_POLIES);
|
|
}
|
|
// Allocate memory for pathfind result
|
|
if(cl_test_pathfind_result == 0) {
|
|
cl_test_pathfind_result = memalloc(sizeof(pathfind_result));
|
|
cl_pathfind_clear_result_data();
|
|
}
|
|
|
|
|
|
cl_navmesh_deselect_all();
|
|
}
|
|
}
|
|
|
|
|
|
void cl_register_navmesh_commands() =
|
|
{
|
|
registercommand("nav_editor");
|
|
registercommand("nav_place_vert");
|
|
registercommand("nav_delete_verts");
|
|
registercommand("nav_select_vert");
|
|
registercommand("nav_deselect_vert");
|
|
registercommand("nav_deselect_all");
|
|
registercommand("nav_make_poly");
|
|
registercommand("nav_delete_poly");
|
|
registercommand("nav_resolve_corner");
|
|
registercommand("nav_place_corner");
|
|
registercommand("nav_cancel_corner");
|
|
registercommand("nav_confirm_corner");
|
|
registercommand("nav_calc_connected_polies");
|
|
registercommand("nav_calc_poly_centers");
|
|
registercommand("navtest_place_goal");
|
|
registercommand("navtest_place_start");
|
|
registercommand("nav_save_navmesh");
|
|
registercommand("nav_clear_navmesh");
|
|
registercommand("y");
|
|
registercommand("yes");
|
|
registercommand("nav_load_navmesh");
|
|
registercommand("nav_toggle_poly_door");
|
|
registercommand("nav_toggle_poly_entrance_edge");
|
|
registercommand("nav_print_poly_door");
|
|
|
|
}
|
|
|
|
float cl_confirm_clear_navmesh;
|
|
|
|
void cl_navmesh_editor_toggle_poly_door() {
|
|
int selected_polygon = cl_navmesh_get_selected_poly();
|
|
if(selected_polygon == -1) {
|
|
print("Can't make door polygon. No polygon selected.\n");
|
|
return;
|
|
}
|
|
|
|
// strcat will copy the string
|
|
string editor_active_door = strcat(cvar_string("nav_editor_active_door"));
|
|
|
|
// If current polygon doortarget isn't set to the editor's currently active door, set it
|
|
if(strcmp(cl_navmesh_polies[selected_polygon].doortarget, editor_active_door) != 0) {
|
|
cl_navmesh_polies[selected_polygon].doortarget = editor_active_door;
|
|
}
|
|
// Otherwise, clear it.
|
|
else {
|
|
cl_navmesh_polies[selected_polygon].doortarget = "";
|
|
}
|
|
|
|
print("Selected Polygon doortarget set to: \"");
|
|
print(cl_navmesh_polies[selected_polygon].doortarget);
|
|
print("\"\n");
|
|
}
|
|
|
|
|
|
// Print the currently selected polygon's doortarget field
|
|
void cl_navmesh_editor_print_poly_door() {
|
|
int selected_polygon = cl_navmesh_get_selected_poly();
|
|
if(selected_polygon == -1) {
|
|
print("Can't print door polygon. No polygon selected.\n");
|
|
return;
|
|
}
|
|
|
|
print("Current selected polygon (");
|
|
print(itos(selected_polygon));
|
|
print(") doortarget: \"");
|
|
print(cl_navmesh_polies[selected_polygon].doortarget);
|
|
print("\"\n");
|
|
}
|
|
|
|
|
|
void cl_navmesh_editor_toggle_entrance_edge() {
|
|
int selected_polygon = cl_navmesh_get_selected_poly();
|
|
if(selected_polygon == -1) {
|
|
print("Can't make door polygon. No polygon selected.\n");
|
|
return;
|
|
}
|
|
|
|
cl_navmesh_polies[selected_polygon].entrance_edge += 1;
|
|
// If entrance edge index is no longer valid, reset it to -1
|
|
if(cl_navmesh_polies[selected_polygon].entrance_edge >= cl_navmesh_polies[selected_polygon].vert_count) {
|
|
cl_navmesh_polies[selected_polygon].entrance_edge = -1;
|
|
}
|
|
|
|
print("Selected Polygon entrance edge set to: ");
|
|
print(itos(cl_navmesh_polies[selected_polygon].entrance_edge));
|
|
print("\n");
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float(string cmd) cl_navmesh_console_commands =
|
|
{
|
|
tokenize(cmd);
|
|
switch(argv(0))
|
|
{
|
|
case "nav_editor":
|
|
cl_toggle_navmesh_editor();
|
|
return TRUE;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//nav_clear_navmesh must be run twice consecutively to execute it (for safety)
|
|
switch(argv(0))
|
|
{
|
|
case "nav_clear_navmesh":
|
|
cl_confirm_clear_navmesh = 1;
|
|
print("Are you sure you want to clear the navmesh? All unsaved changes will be lost.\n");
|
|
return TRUE;
|
|
case "y":
|
|
case "yes":
|
|
if(cl_confirm_clear_navmesh == 1)
|
|
{
|
|
print("Navmesh cleared.\n");
|
|
cl_navmesh_editor_clear_navmesh();
|
|
}
|
|
cl_confirm_clear_navmesh = 0;
|
|
return TRUE;
|
|
default:
|
|
cl_confirm_clear_navmesh = 0;
|
|
break;
|
|
}
|
|
|
|
if(!cvar("navmesh_edit_mode"))
|
|
return FALSE;
|
|
switch(argv(0))
|
|
{
|
|
case "nav_place_vert":
|
|
cl_navmesh_place_vert();
|
|
return TRUE;
|
|
case "nav_delete_verts":
|
|
cl_navmesh_delete_verts();
|
|
return TRUE;
|
|
case "nav_select_vert":
|
|
cl_navmesh_select_vert();
|
|
return TRUE;
|
|
case "nav_deselect_vert":
|
|
cl_navmesh_deselect_vert();
|
|
return TRUE;
|
|
case "nav_deselect_all":
|
|
cl_navmesh_deselect_all();
|
|
print("All vertices deselected.\n");
|
|
return TRUE;
|
|
case "nav_make_poly":
|
|
cl_navmesh_make_poly();
|
|
return TRUE;
|
|
case "nav_delete_poly":
|
|
cl_navmesh_delete_poly();
|
|
return TRUE;
|
|
case "nav_resolve_corner":
|
|
cl_navmesh_resolve_corner();
|
|
return TRUE;
|
|
case "nav_place_corner":
|
|
cl_navmesh_place_corner();
|
|
return TRUE;
|
|
case "nav_cancel_corner":
|
|
cl_navmesh_cancel_corner();
|
|
return TRUE;
|
|
case "nav_confirm_corner":
|
|
cl_navmesh_confirm_corner();
|
|
return TRUE;
|
|
case "nav_calc_connected_polies":
|
|
cl_navmesh_calc_connected_polies();
|
|
return TRUE;
|
|
case "nav_calc_poly_centers":
|
|
cl_navmesh_calc_polies_centers();
|
|
return TRUE;
|
|
case "navtest_place_goal":
|
|
cl_navmesh_place_test_goalent();
|
|
return TRUE;
|
|
case "navtest_place_start":
|
|
cl_navmesh_place_test_startent();
|
|
return TRUE;
|
|
case "nav_save_navmesh":
|
|
cl_navmesh_editor_save_navmesh();
|
|
return TRUE;
|
|
case "nav_load_navmesh":
|
|
cl_navmesh_editor_load_navmesh();
|
|
return TRUE;
|
|
case "nav_toggle_poly_door":
|
|
cl_navmesh_editor_toggle_poly_door();
|
|
return TRUE;
|
|
case "nav_print_poly_door":
|
|
cl_navmesh_editor_print_poly_door();
|
|
return TRUE;
|
|
case "nav_toggle_poly_entrance_edge":
|
|
cl_navmesh_editor_toggle_entrance_edge();
|
|
// TODO - Make currently selected polygon only be traversible from
|
|
// TODO one direction to another.
|
|
// TODO
|
|
// TODO - If no polygon selected, stop.
|
|
// TODO - If Quad, assume verts sorted CCW from BL
|
|
// TODO - if not one-way, make one-way poly from 01 to 23
|
|
// TODO - if one-way from 01 to 23, make one-way from 12 to 03
|
|
// TODO - if one-way from 12 to 03, make one-way from 23 to 01
|
|
// TODO - if one-way from 12 to 03, make one-way from 03 to 12
|
|
// TODO - if one-way from 03 to 12, make normal polygon
|
|
// TODO - If tri, should I support one-way? (probably not, tbh)
|
|
// TODO
|
|
return TRUE;
|
|
default:
|
|
break;
|
|
}
|
|
return FALSE;
|
|
} |