// ============================================================================ // 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; }