quakec/source/client/navmesh_editor.qc
blubs e463a671d2 Adds zombie lerp traversal type
Adds bezier vector lerp util function
Adds drawing of test ent in traversal editor
Adds framedefs for zombie lerp anim
Updates framedefs for zombie from removing frame 160
2023-08-11 23:08:01 -07:00

3712 lines
No EOL
128 KiB
C++

// ============================================================================
// Navmesh editing functions
// ============================================================================
#define NAV_MAX_SELECTED_VERTS 4
int *selected_verts;
float selected_vert_count;
navmesh_vertex *cl_navmesh_verts;
float cl_navmesh_vert_count;
navmesh_poly *cl_navmesh_polies;
float cl_navmesh_poly_count;
navmesh_traversal *cl_navmesh_traversals;
float cl_navmesh_traversal_count;
float cl_navmesh_selected_traversal;
float cl_navmesh_traversal_edit_mode;
float cl_navmesh_traversal_editor_cur_point;
// 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;
float(vector pos, float poly_index) cl_navmesh_is_inside_poly;
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, vector color, float alpha) {
cl_navmesh_draw_line(start,end,1,color,alpha);
}
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) {
vector border_color = [0.5, 0.5, 0.5];
float border_alpha = 0.2;
cl_navmesh_draw_edge(a,b, border_color, border_alpha);
cl_navmesh_draw_edge(b,c, border_color, border_alpha);
cl_navmesh_draw_edge(c,d, border_color, border_alpha);
cl_navmesh_draw_edge(d,a, border_color, border_alpha);
}
}
// Draws a 3D line using a box
void cl_navmesh_draw_line_3d(vector start, vector end, float edge_width, vector color, float alpha) {
// drawline(edge_width, start, end, color, alpha, drawflag???);
// return;
vector v_f = normalize(end - start);
vector v_r = normalize(crossproduct(v_f, '0 0 1'));
vector v_u = normalize(crossproduct(v_r, v_f));
// Special degenerate case: v_f is straight up
if(end.x == start.x && end.y == start.y) {
v_f = '0 0 1';
v_r = '1 0 0';
v_u = '0 1 0';
}
// 4 corners around start
vector start_bl = start + edge_width * (-v_r - v_u);
vector start_br = start + edge_width * ( v_r - v_u);
vector start_tr = start + edge_width * ( v_r + v_u);
vector start_tl = start + edge_width * (-v_r + v_u);
// 4 corners around end
vector end_bl = end + edge_width * (-v_r - v_u);
vector end_br = end + edge_width * ( v_r - v_u);
vector end_tr = end + edge_width * ( v_r + v_u);
vector end_tl = end + edge_width * (-v_r + v_u);
cl_navmesh_draw_quad(start_bl, start_br, start_tr, start_tl, color, alpha, false); // Start quad
cl_navmesh_draw_quad(start_tr, end_tr, end_tl, start_tl, color, alpha, false); // Top quad
cl_navmesh_draw_quad(start_br, end_br, end_bl, start_bl, color, alpha, false); // Bottom quad
cl_navmesh_draw_quad(start_br, end_br, end_tr, start_tr, color, alpha, false); // Right quad
cl_navmesh_draw_quad(start_bl, end_bl, end_tl, start_tl, color, alpha, false); // Left quad
cl_navmesh_draw_quad(end_bl, end_tl, end_tr, end_br, color, alpha, false); // End quad
}
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)
{
vector border_color = [0.5, 0.5, 0.5];
float border_alpha = 0.2;
cl_navmesh_draw_edge(a,b, border_color, border_alpha);
cl_navmesh_draw_edge(b,c, border_color, border_alpha);
cl_navmesh_draw_edge(c,a, border_color, border_alpha);
}
}
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);
}
}
// Calculates world-space coordinate of traversal endpoint
vector cl_navmesh_get_traversal_midpoint_pos(float traversal_index) {
vector start_pos = cl_navmesh_traversals[traversal_index].start_pos;
vector midpoint_pos = cl_navmesh_traversals[traversal_index].midpoint_pos;
// vector end_pos = cl_navmesh_traversals[traversal_index].end_pos;
float angle = cl_navmesh_traversals[traversal_index].angle;
// Midpoint / endpoint pos are relative to start_pos:
makevectors([0, angle, 0]);
midpoint_pos = start_pos + (v_right * midpoint_pos.x) + (v_forward * midpoint_pos.y) + (v_up * midpoint_pos.z);
// end_pos = start_pos + (v_right * end_pos.x) + (v_forward * end_pos.y) + (v_up * end_pos.z);
return midpoint_pos;
}
// Calculates world-space coordinate of traversal midpoint
vector cl_navmesh_get_traversal_end_pos(float traversal_index) {
vector start_pos = cl_navmesh_traversals[traversal_index].start_pos;
// vector midpoint_pos = cl_navmesh_traversals[traversal_index].midpoint_pos;
vector end_pos = cl_navmesh_traversals[traversal_index].end_pos;
float angle = cl_navmesh_traversals[traversal_index].angle;
// Midpoint / endpoint pos are relative to start_pos:
makevectors([0, angle, 0]);
// midpoint_pos = start_pos + (v_right * midpoint_pos.x) + (v_forward * midpoint_pos.y) + (v_up * midpoint_pos.z);
end_pos = start_pos + (v_right * end_pos.x) + (v_forward * end_pos.y) + (v_up * end_pos.z);
return end_pos;
}
void cl_navmesh_draw_traversal(float traversal_index) {
vector start_pos = cl_navmesh_traversals[traversal_index].start_pos;
vector midpoint_pos = cl_navmesh_traversals[traversal_index].midpoint_pos;
vector end_pos = cl_navmesh_traversals[traversal_index].end_pos;
float angle = cl_navmesh_traversals[traversal_index].angle;
// Midpoint / endpoint pos are relative to start_pos:
makevectors([0, angle, 0]);
midpoint_pos = start_pos + (v_right * midpoint_pos.x) + (v_forward * midpoint_pos.y) + (v_up * midpoint_pos.z);
end_pos = start_pos + (v_right * end_pos.x) + (v_forward * end_pos.y) + (v_up * end_pos.z);
vector active_color = '0 1 1';
vector inactive_color = '0.5 0 0';
vector edge_color = '0.8 0.8 0.8';
float active_alpha = 0.4;
float inactive_alpha = 0.2;
float edge_alpha = 0.3;
float edge_width = 0.1;
if(cl_navmesh_selected_traversal != traversal_index) {
active_alpha = 0.4;
inactive_alpha = 0.0;
edge_alpha = 0.4;
edge_color = '0 0.2 0.5';
active_color = '0 0.2 0.5';
}
cl_navmesh_draw_box(start_pos, [4,4,4], active_color, 0.8);
cl_navmesh_draw_box(end_pos, [4,4,4], active_color, 0.8);
if(cl_navmesh_traversals[traversal_index].use_midpoint) {
cl_navmesh_draw_box(midpoint_pos, [4,4,4], active_color, active_alpha);
cl_navmesh_draw_line_3d(start_pos, midpoint_pos, edge_width, edge_color, active_alpha);
cl_navmesh_draw_line_3d(midpoint_pos, end_pos, edge_width, edge_color, edge_alpha);
}
else {
cl_navmesh_draw_box(midpoint_pos, [4,4,4], inactive_color, inactive_alpha);
cl_navmesh_draw_line_3d(start_pos, end_pos, edge_width, edge_color, edge_alpha);
}
// Draw arrows along edge:
vector arrow_pos;
float arrowhead_length = 2;
vector v_f;
vector v_r;
vector v_u;
float inc = 0.09;
float start = inc * (time % 1.0);
for(float t = start; t <= 1.0; t += inc) {
if(cl_navmesh_traversals[traversal_index].use_midpoint) {
if(t < 0.5) {
arrow_pos = start_pos + 2.0 * t * (midpoint_pos - start_pos);
v_f = normalize(midpoint_pos - start_pos);
v_r = normalize(crossproduct(v_f, '0 0 1'));
v_u = normalize(crossproduct(v_r, v_f));
}
else {
arrow_pos = midpoint_pos + 2.0 * (t - 0.5) * (end_pos - midpoint_pos);
v_f = normalize(end_pos - midpoint_pos);
v_r = normalize(crossproduct(v_f, '0 0 1'));
v_u = normalize(crossproduct(v_r, v_f));
}
}
else {
arrow_pos = start_pos + t * (end_pos - start_pos);
v_f = normalize(end_pos - start_pos);
v_r = normalize(crossproduct(v_f, '0 0 1'));
v_u = normalize(crossproduct(v_r, v_f));
}
cl_navmesh_draw_line_3d(arrow_pos, arrow_pos + arrowhead_length * (-v_f - v_r), edge_width, edge_color, edge_alpha);
cl_navmesh_draw_line_3d(arrow_pos, arrow_pos + arrowhead_length * (-v_f + v_r), edge_width, edge_color, edge_alpha);
}
// Traversal edit mode modifications:
if(cl_navmesh_selected_traversal == traversal_index) {
if(cl_navmesh_traversal_edit_mode) {
vector pos = '0 0 0';
if(cl_navmesh_traversal_editor_cur_point == 0) {
pos = start_pos;
}
else if(cl_navmesh_traversal_editor_cur_point == 1) {
pos = midpoint_pos;
}
else if(cl_navmesh_traversal_editor_cur_point == 2) {
pos = end_pos;
}
float g = 2; // gap from center
float l = 1.5; // line length
vector select_color = '1 1 0';
float select_width = 0.1; // line width
float select_alpha = 0.8;
// FTL
cl_navmesh_draw_line_3d(pos + [-g, -g, g], pos + [-g, -g, g] + [l,0,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [-g, -g, g], pos + [-g, -g, g] + [0,l,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [-g, -g, g], pos + [-g, -g, g] + [0,0,-l] , select_width, select_color, select_alpha);
// FTR
cl_navmesh_draw_line_3d(pos + [g, -g, g], pos + [g, -g, g] + [-l,0,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [g, -g, g], pos + [g, -g, g] + [0,l,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [g, -g, g], pos + [g, -g, g] + [0,0,-l] , select_width, select_color, select_alpha);
// BTL
cl_navmesh_draw_line_3d(pos + [-g, g, g], pos + [-g, g, g] + [l,0,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [-g, g, g], pos + [-g, g, g] + [0,-l,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [-g, g, g], pos + [-g, g, g] + [0,0,-l] , select_width, select_color, select_alpha);
// BTR
cl_navmesh_draw_line_3d(pos + [g, g, g], pos + [g, g, g] + [-l,0,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [g, g, g], pos + [g, g, g] + [0,-l,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [g, g, g], pos + [g, g, g] + [0,0,-l] , select_width, select_color, select_alpha);
// FBL
cl_navmesh_draw_line_3d(pos + [-g, -g, -g], pos + [-g, -g, -g] + [l,0,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [-g, -g, -g], pos + [-g, -g, -g] + [0,l,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [-g, -g, -g], pos + [-g, -g, -g] + [0,0,l] , select_width, select_color, select_alpha);
// FBR
cl_navmesh_draw_line_3d(pos + [g, -g, -g], pos + [g, -g, -g] + [-l,0,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [g, -g, -g], pos + [g, -g, -g] + [0,l,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [g, -g, -g], pos + [g, -g, -g] + [0,0,l] , select_width, select_color, select_alpha);
// BBL
cl_navmesh_draw_line_3d(pos + [-g, g, -g], pos + [-g, g, -g] + [l,0,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [-g, g, -g], pos + [-g, g, -g] + [0,-l,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [-g, g, -g], pos + [-g, g, -g] + [0,0,l] , select_width, select_color, select_alpha);
// BBR
cl_navmesh_draw_line_3d(pos + [g, g, -g], pos + [g, g, -g] + [-l,0,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [g, g, -g], pos + [g, g, -g] + [0,-l,0] , select_width, select_color, select_alpha);
cl_navmesh_draw_line_3d(pos + [g, g, -g], pos + [g, g, -g] + [0,0,l] , select_width, select_color, select_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_path;
void() cl_navmesh_pathfind_draw_result_portals;
void() cl_navmesh_pathfind_draw_result_point_path;
entity cl_navmesh_traversal_test_ent;
void cl_navmesh_draw_traversal_test_ent() {
if(cl_navmesh_selected_traversal == -1) {
return;
}
if(cl_navmesh_traversal_test_ent == world) {
cl_navmesh_traversal_test_ent = spawn();
setmodel(cl_navmesh_traversal_test_ent, "models/ai/zfull.mdl");
print("woo\n");
}
float traversal_speed_scale = 1.0; // 4x slower
float lerp_frac = (time * traversal_speed_scale) % 1.0;
cl_navmesh_traversal_test_ent.angles.y = cl_navmesh_traversals[cl_navmesh_selected_traversal].angle;
makevectors([0, cl_navmesh_traversals[cl_navmesh_selected_traversal].angle, 0]);
cl_navmesh_traversal_test_ent.drawmask = MASK_ENGINE;
vector start_pos = cl_navmesh_traversals[cl_navmesh_selected_traversal].start_pos;
vector end_pos = cl_navmesh_get_traversal_end_pos(cl_navmesh_selected_traversal);
// cl_navmesh_traversal_test_ent.origin = start_pos + lerp_frac * (end_pos - start_pos);
float trav_state_a_dur;
float trav_state_b_dur;
float trav_state_c_dur;
float lerp_subfrac;
float ent_framenum;
float ent_frametime = 0.1;
// TODO - Detect traversal type
// string traversal_type = "ledge";
string traversal_type = "leap";
if(traversal_type == "ledge") {
vector ledge_pos;
float traversal_height = end_pos.z - start_pos.z;
if(traversal_height < 0) {
trav_state_a_dur = min(-traversal_height * (0.35 / 100.0), 2.0);
trav_state_b_dur = 6*ent_frametime * 0.5; // double-speed
lerp_frac *= (trav_state_a_dur + trav_state_b_dur);
if(lerp_frac < trav_state_a_dur) {
lerp_subfrac = lerp_frac / (trav_state_a_dur);
cl_navmesh_traversal_test_ent.origin = lerp_vector(start_pos, end_pos, lerp_subfrac * lerp_subfrac);
ent_framenum = 150 + lerp_subfrac*4;
}
else {
lerp_subfrac = (lerp_frac - (trav_state_a_dur)) / trav_state_b_dur;
cl_navmesh_traversal_test_ent.origin = end_pos;
ent_framenum = 154 + lerp_subfrac*6;
}
}
else if(traversal_height < 98) {
ledge_pos = end_pos - '0 0 85' - v_forward * 28;
trav_state_a_dur = 3*ent_frametime;
trav_state_b_dur = 11*ent_frametime;
lerp_frac *= trav_state_a_dur + trav_state_b_dur;
// Play low jump anim (frames 163-165)
if(lerp_frac < trav_state_a_dur) {
lerp_subfrac = lerp_frac / (trav_state_a_dur);
cl_navmesh_traversal_test_ent.origin = lerp_vector(start_pos, ledge_pos, lerp_subfrac);
ent_framenum = 163 + lerp_subfrac * 6;
}
// Lerp to endpos, play low climb anim (frames 170-180)
else {
lerp_subfrac = (lerp_frac - (trav_state_a_dur)) / trav_state_b_dur;
cl_navmesh_traversal_test_ent.origin = lerp_vector(ledge_pos, end_pos, lerp_subfrac);
ent_framenum = 170 + lerp_subfrac * 10;
}
}
else {
// Traversal states:
ledge_pos = end_pos - '0 0 98' - v_forward * 28;
trav_state_a_dur = 6*ent_frametime;
trav_state_b_dur = 0.15;
trav_state_c_dur = 14*ent_frametime;
lerp_frac *= trav_state_a_dur + trav_state_b_dur + trav_state_c_dur;
// Play jump anim (frames 160-166)
if(lerp_frac < trav_state_a_dur) {
lerp_subfrac = lerp_frac / (trav_state_a_dur);
cl_navmesh_traversal_test_ent.origin = start_pos;
ent_framenum = 160 + lerp_subfrac * 6;
}
// Lerp to ledge pos
else if(lerp_frac < trav_state_a_dur + trav_state_b_dur) {
lerp_subfrac = (lerp_frac - (trav_state_a_dur)) / trav_state_b_dur;
cl_navmesh_traversal_test_ent.origin = lerp_vector(start_pos, ledge_pos, lerp_subfrac);
ent_framenum = 166 + lerp_subfrac * 1;
}
// Lerp to endpos, play climb anim (frames 167-180)
else {
lerp_subfrac = (lerp_frac - (trav_state_a_dur + trav_state_b_dur)) / trav_state_c_dur;
cl_navmesh_traversal_test_ent.origin = lerp_vector(ledge_pos, end_pos, lerp_subfrac);
ent_framenum = 167 + lerp_subfrac * 14;
}
}
}
else if(traversal_type == "leap") {
trav_state_a_dur = 5*ent_frametime; // Play anim (218-233)
trav_state_b_dur = 8*ent_frametime; // Play anim (233-241), lerp across gap
trav_state_c_dur = 5*ent_frametime; // Play anim (242-247)
lerp_frac *= (trav_state_a_dur + trav_state_b_dur + trav_state_c_dur);
lerp_subfrac = lerp_frac;
if(lerp_frac < trav_state_a_dur) {
lerp_subfrac = lerp_frac / (trav_state_a_dur);
cl_navmesh_traversal_test_ent.origin = start_pos;
ent_framenum = 228 + lerp_subfrac * 5;
}
else if(lerp_frac < trav_state_a_dur + trav_state_b_dur) {
lerp_subfrac = (lerp_frac - (trav_state_a_dur)) / trav_state_b_dur;
vector midpoint_pos = cl_navmesh_get_traversal_midpoint_pos(cl_navmesh_selected_traversal);
cl_navmesh_traversal_test_ent.origin = lerp_vector_bezier(start_pos, midpoint_pos, end_pos, lerp_subfrac);
ent_framenum = 233 + lerp_subfrac * 8;
}
else {
lerp_subfrac = (lerp_frac - (trav_state_a_dur + trav_state_b_dur)) / trav_state_c_dur;
cl_navmesh_traversal_test_ent.origin = end_pos;
ent_framenum = 242 + lerp_subfrac * 5;
}
}
cl_navmesh_traversal_test_ent.frame = floor(ent_framenum);
cl_navmesh_traversal_test_ent.frame2 = floor(ent_framenum+ 1);
cl_navmesh_traversal_test_ent.lerpfrac = ent_framenum % 1.0;
}
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);
}
for(float i = 0; i < cl_navmesh_traversal_count; i++) {
cl_navmesh_draw_traversal(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_path();
cl_navmesh_pathfind_draw_result_portals();
cl_navmesh_pathfind_draw_result_point_path();
// If a traversal is selected, draw a zombie being animated
if(cl_navmesh_selected_traversal != -1) {
cl_navmesh_draw_traversal_test_ent();
}
//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;
cl_navmesh_selected_traversal = -1;
cl_navmesh_traversal_edit_mode = false;
cl_navmesh_traversal_editor_cur_point = 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_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].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].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;
}
cl_navmesh_polies[i].connected_traversals_count = 0;
for(float j = 0; j < NAV_MAX_POLY_TRAVERSALS; j++) {
cl_navmesh_polies[i].connected_traversals[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");
}
}
}
for(float i = 0; i < cl_navmesh_traversal_count; i++) {
print("Checking traversal for connected polies\n");
makevectors([0, cl_navmesh_traversals[i].angle, 0]);
// Get start_pos and end_pos in world-space
vector start_pos = cl_navmesh_traversals[i].start_pos;
vector end_pos = start_pos + (v_right * cl_navmesh_traversals[i].end_pos.x) + (v_forward * cl_navmesh_traversals[i].end_pos.y) + (v_up * cl_navmesh_traversals[i].end_pos.z);
float start_pos_poly = -1;
float end_pos_poly = -1;
for(float j = 0; j < cl_navmesh_poly_count; j++) {
if(start_pos_poly == -1 && cl_navmesh_is_inside_poly(start_pos, j)) {
start_pos_poly = j;
}
if(end_pos_poly == -1 && cl_navmesh_is_inside_poly(end_pos, j)) {
end_pos_poly = j;
}
if(start_pos_poly != -1 && end_pos_poly != -1) {
break;
}
}
print("Traversal ");
print(ftos(i));
print(" starts in poly ");
print(ftos(start_pos_poly));
print(" and ends in poly ");
print(ftos(end_pos_poly));
print("\n");
if(start_pos_poly == -1) {
print("Warning: Traversal ");
print(ftos(i));
print(" does not start inside of a navmesh polygon. This traversal will not be used.\n");
continue;
}
else if(end_pos_poly == -1) {
print("Warning: Traversal ");
print(ftos(i));
print(" does not end inside of a navmesh polygon. This traversal will not be used.\n");
continue;
}
else if(start_pos_poly == end_pos_poly) {
print("Warning: Traversal ");
print(ftos(i));
print(" starts and ends inside of the same navmesh polygon. This traversal will not be used.\n");
continue;
}
if(cl_navmesh_polies[start_pos_poly].connected_traversals_count >= NAV_MAX_POLY_TRAVERSALS) {
print("Warning: Traversal ");
print(ftos(i));
print(" starts inside of a polygon that is already connected to the maximum number of traversals allowed (");
print(ftos(NAV_MAX_POLY_TRAVERSALS));
print("). This traversal will not be used.\n");
continue;
}
// Add to polygon's list of connected traversals
cl_navmesh_polies[start_pos_poly].connected_traversals[cl_navmesh_polies[start_pos_poly].connected_traversals_count] = i;
cl_navmesh_polies[start_pos_poly].connected_traversals_count += 1;
cl_navmesh_traversals[i].end_poly = end_pos_poly;
// TODO - Add traversal to end polygon list of traversals?
}
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 = 1;
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 connected traversals
fputs(file,ftos(cl_navmesh_polies[i].connected_traversals_count),"\n");
for(float j = 0; j < cl_navmesh_polies[i].connected_traversals_count; j++) {
fputs(file,ftos(cl_navmesh_polies[i].connected_traversals[j]),"\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, "\"" );
}
}
// -----------------------------------------------------------------------
// Write Traversals
// -----------------------------------------------------------------------
print("Writing ");
print((ftos(cl_navmesh_traversal_count)));
print(" traversals.\n");
fputs(file,ftos(cl_navmesh_traversal_count),"\n");
for(float i = 0; i < cl_navmesh_traversal_count; i++) {
fputs(file,vtos(cl_navmesh_traversals[i].start_pos),"\n");
fputs(file,vtos(cl_navmesh_traversals[i].midpoint_pos),"\n");
fputs(file,vtos(cl_navmesh_traversals[i].end_pos),"\n");
fputs(file,ftos(cl_navmesh_traversals[i].angle),"\n");
fputs(file,ftos(cl_navmesh_traversals[i].use_midpoint),"\n");
fputs(file,ftos(cl_navmesh_traversals[i].end_poly),"\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_deselect_all();
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;
for(float j = 0; j < NAV_MAX_POLY_TRAVERSALS; j++) {
cl_navmesh_polies[i].connected_traversals[j] = -1;
}
cl_navmesh_polies[i].connected_traversals_count = 0;
cl_navmesh_polies[i].doortarget = "";
}
cl_navmesh_traversal_count = 0;
for(float i = 0; i < NAV_MAX_TRAVERSALS; i++) {
cl_navmesh_traversals[i].start_pos.x = 0;
cl_navmesh_traversals[i].start_pos.y = 0;
cl_navmesh_traversals[i].start_pos.z = 0;
cl_navmesh_traversals[i].midpoint_pos.x = 0;
cl_navmesh_traversals[i].midpoint_pos.y = 0;
cl_navmesh_traversals[i].midpoint_pos.z = 0;
cl_navmesh_traversals[i].end_pos.x = 0;
cl_navmesh_traversals[i].end_pos.y = 0;
cl_navmesh_traversals[i].end_pos.z = 0;
cl_navmesh_traversals[i].angle = 0;
cl_navmesh_traversals[i].use_midpoint = false;
cl_navmesh_traversals[i].end_poly = -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;
}
// 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
float vert_count = stof(fgets(file));
if(vert_count > NAV_MAX_VERTS) {
print("Error: navmesh file \"",filepath,"\" has an invalid vert count. (" , vert_count, " > ", 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: ",vert_count,"\n");
//Reading all of the vertex positions
for(float i = 0; i < cl_navmesh_vert_count; i++) {
cl_navmesh_verts[i].pos = stov(fgets(file));
}
//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
// If navmesh file version 0.0.0, no traversals
// FIXME - Need a better way to do this..
if(nav_file_version != "0.0.0") {
// Don't care about connected traversals
float n_traversals = stof(fgets(file));
for(float j = 0; j < n_traversals; j++) {
fgets(file); // discard the j-th traversal index
}
}
// 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, "\"\n" );
}
// cl_navmesh_polies[i].doortarget = ""; // Temp fix to load old files
// If v0.0.0, discard entrance edge (no longer used)
if(nav_file_version == "0.0.0") {
fgets(file);
}
}
// -----------------------------------------------------------------------
// Load Traversals
// -----------------------------------------------------------------------
// If navmesh file version 0.0.0, no traversals
if(nav_file_version != "0.0.0") {
// Next line contains the number of traverals
cl_navmesh_traversal_count = stof(fgets(file));
// Next lines are each traversal
for(float i = 0; i < cl_navmesh_traversal_count; i++) {
cl_navmesh_traversals[i].start_pos = stov(fgets(file));
cl_navmesh_traversals[i].midpoint_pos = stov(fgets(file));
cl_navmesh_traversals[i].end_pos = stov(fgets(file));
cl_navmesh_traversals[i].angle = stof(fgets(file));
cl_navmesh_traversals[i].use_midpoint = stof(fgets(file));
// Don't care about traversal end polygon index
fgets(file);
}
}
// -----------------------------------------------------------------------
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 for a navmesh polygon (Our best guess for how far this node is from the goal node)
float cl_pathfind_calc_poly_h_score(float current_poly_idx, float goal_poly_idx) {
//FIXME: we could just as easily return vlen()^2 for comparisons... (saves a sqrt operation)
return vlen(cl_navmesh_polies[goal_poly_idx].center - cl_navmesh_polies[current_poly_idx].center);
}
//Calculates the heuristic h score value for a navmesh traversal (Our best guess for how far this node is from the goal node)
float cl_pathfind_calc_traversal_h_score(float current_traversal_idx, float goal_poly_idx) {
//FIXME: we could just as easily return vlen()^2 for comparisons... (saves a sqrt operation)
vector traversal_end_pos = cl_navmesh_get_traversal_end_pos(current_traversal_idx);
return vlen(cl_navmesh_polies[goal_poly_idx].center - traversal_end_pos);
}
//Struct containing all arrays that the pathfind code requires to operate
navmesh_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_poly() {
//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;
}
//Returns the traversal with the lowest f score from traversals the open set
float cl_pathfind_get_lowest_f_score_traversal() {
//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_traversal_count; i++) {
if(cl_test_pathfind_result->traversal_set[i] == PATHFIND_POLY_SET_OPEN) {
if(cl_test_pathfind_result->traversal_f_score[i] < best_score) {
best_score = cl_test_pathfind_result->traversal_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(float i = 0; i < NAV_MAX_POLIES; i++) {
cl_test_pathfind_result->poly_set[i] = PATHFIND_POLY_SET_NONE;
cl_test_pathfind_result->poly_prev_poly[i] = -1;
cl_test_pathfind_result->poly_prev_traversal[i] = -1;
cl_test_pathfind_result->poly_g_score[i] = 0;
cl_test_pathfind_result->poly_f_score[i] = 0;
cl_test_pathfind_result->path_polygons[i] = -1;
cl_test_pathfind_result->path_traversals[i] = -1;
cl_test_pathfind_result->portals_left_pos[i] = '0 0 0';
cl_test_pathfind_result->portals_right_pos[i] = '0 0 0';
cl_test_pathfind_result->portals_traversal[i] = -1;
cl_test_pathfind_result->point_path_points[i] = '0 0 0';
cl_test_pathfind_result->point_path_traversals[i] = -1;
}
for(float i = 0;i < NAV_MAX_TRAVERSALS; i++) {
cl_test_pathfind_result->traversal_set[i] = PATHFIND_POLY_SET_NONE;
cl_test_pathfind_result->traversal_prev_poly[i] = -1;
cl_test_pathfind_result->traversal_g_score[i] = 0;
cl_test_pathfind_result->traversal_f_score[i] = 0;
}
cl_test_pathfind_result->path_length = 0;
cl_test_pathfind_result->portals_length = 0;
cl_test_pathfind_result->point_path_length = 0;
}
// vector cl_pathfind_get_left_portal_corner(navmesh_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 cl_pathfind_get_right_portal_corner(navmesh_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) {
float cur_poly;
float cur_traversal;
float prev_poly;
float prev_traversal;
vector cur_portal_left_pos = '0 0 0';
vector cur_portal_right_pos = '0 0 0';
float cur_portal_traversal = -1;
vector traversal_start_pos;
vector traversal_end_pos;
// Build the list of portals
for(float i = 1; i < cl_test_pathfind_result->path_length; i++) {
cur_poly = cl_test_pathfind_result->path_polygons[i];
cur_traversal = cl_test_pathfind_result->path_traversals[i];
prev_poly = cl_test_pathfind_result->path_polygons[i-1];
prev_traversal = cl_test_pathfind_result->path_traversals[i-1];
// If polygon and came from polygon, portal = shared edge
if(cur_poly != -1 && prev_poly != -1) {
// Find which of prev_poly's edges connects to cur_poly
float prev_poly_edge_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] == cur_poly) {
prev_poly_edge_index = j;
break;
}
}
cur_portal_left_pos = cl_navmesh_verts[cl_navmesh_polies[prev_poly].connected_polies_left_vert[prev_poly_edge_index]].pos;
cur_portal_right_pos = cl_navmesh_verts[cl_navmesh_polies[prev_poly].connected_polies_right_vert[prev_poly_edge_index]].pos;
cur_portal_traversal = -1;
// ----------------------------------------------------------------
// Narrow the portal from each side by 20% or 20qu, whichever is smaller
// ----------------------------------------------------------------
float edge_len = vlen(cur_portal_right_pos - cur_portal_left_pos);
vector edge_dir = normalize(cur_portal_right_pos - cur_portal_left_pos);
float ofs = min(edge_len * 0.2, 20);
cur_portal_left_pos = cur_portal_left_pos + edge_dir * ofs;
cur_portal_right_pos = cur_portal_right_pos - edge_dir * ofs;
// ----------------------------------------------------------------
}
// If polygon and came from traversal, portal = traversal exit
else if(cur_poly != -1 && prev_traversal != -1) {
traversal_end_pos = cl_navmesh_get_traversal_end_pos(prev_traversal);
cur_portal_left_pos = traversal_end_pos;
cur_portal_right_pos = traversal_end_pos;
cur_portal_traversal = -1;
// TODO - Denote that it came from traversal? maybe we don't care?
}
// If traversal and came from polygon, portal = traversal start
else if(cur_traversal != -1 && prev_poly != -1) {
traversal_start_pos = cl_navmesh_traversals[cur_traversal].start_pos;
cur_portal_left_pos = traversal_start_pos;
cur_portal_right_pos = traversal_start_pos;
cur_portal_traversal = cur_traversal;
}
cl_test_pathfind_result->portals_left_pos[cl_test_pathfind_result->portals_length] = cur_portal_left_pos;
cl_test_pathfind_result->portals_right_pos[cl_test_pathfind_result->portals_length] = cur_portal_right_pos;
cl_test_pathfind_result->portals_traversal[cl_test_pathfind_result->portals_length] = cur_portal_traversal;
cl_test_pathfind_result->portals_length++;
}
// TODO - Should I add goal_pos as the final portal?
cl_test_pathfind_result->portals_left_pos[cl_test_pathfind_result->portals_length] = goal_point;
cl_test_pathfind_result->portals_right_pos[cl_test_pathfind_result->portals_length] = goal_point;
cl_test_pathfind_result->portals_traversal[cl_test_pathfind_result->portals_length] = -1;
cl_test_pathfind_result->portals_length++;
vector cur_funnel_corner = start_point;
vector cur_funnel_left = cl_test_pathfind_result->portals_left_pos[0];
vector cur_funnel_right = cl_test_pathfind_result->portals_right_pos[0];
float cur_funnel_left_portal_idx = 0;
float cur_funnel_right_portal_idx = 0;
// Loop through all portals, adding necessary funnel corners to final path
for(float i = 0; i < cl_test_pathfind_result->portals_length; i++) {
cur_portal_left_pos = cl_test_pathfind_result->portals_left_pos[i];
cur_portal_right_pos = cl_test_pathfind_result->portals_right_pos[i];
cur_portal_traversal = cl_test_pathfind_result->portals_traversal[i];
// TODO - Project portal points to axis made by cur funnel points
// Then we can check
// TODO - Compute value for a point:
// TODO 0: On funnel center
// TODO < -1 outside of funnel to the left
// TODO > 1 outside of funnel to the right
float cur_portal_left_in_funnel = pathfind_point_in_funnel( cur_portal_left_pos, cur_funnel_corner, cur_funnel_left, cur_funnel_right);
float cur_portal_right_in_funnel = pathfind_point_in_funnel(cur_portal_right_pos, cur_funnel_corner, cur_funnel_left, cur_funnel_right);
print("{'portal_idx': ", ftos(i), ", 'portal': ");
print(" [(", ftos(cur_portal_left_pos.x), ",", ftos(cur_portal_left_pos.y), "), (", ftos(cur_portal_right_pos.x), ",", ftos(cur_portal_right_pos.y));
print(")], 'traversal': ", ftos(cur_portal_traversal), ", ");
print("'funnel_result': (", ftos(cur_portal_left_in_funnel), ",", ftos(cur_portal_right_in_funnel), "), ");
print("'funnel': [");
print("(", ftos(cur_funnel_left.x), ",", ftos(cur_funnel_left.y), "), ");
print("(", ftos(cur_funnel_corner.x), ",", ftos(cur_funnel_corner.y), "), ");
print("(", ftos(cur_funnel_right.x), ",", ftos(cur_funnel_right.y), ")");
print("]},\n");
// print(vtos(cur_funnel_left));
// If cur portal left point is in cur funnel, narrow the cur funnel
if(cur_portal_left_in_funnel >= -1 && cur_portal_left_in_funnel <= 1) {
cur_funnel_left = cur_portal_left_pos;
cur_funnel_left_portal_idx = i;
}
// If cur portal right point is in cur funnel, narrow the cur funnel
if(cur_portal_right_in_funnel >= -1 && cur_portal_right_in_funnel <= 1) {
cur_funnel_right = cur_portal_right_pos;
cur_funnel_right_portal_idx = i;
}
// If cur portal is completely outside (to left of) current funnel
if(cur_portal_left_in_funnel < -1 && cur_portal_right_in_funnel < -1) {
// Add cur funnel left point to final path:
cl_test_pathfind_result->point_path_points[cl_test_pathfind_result->point_path_length] = cur_funnel_left;
cl_test_pathfind_result->point_path_traversals[cl_test_pathfind_result->point_path_length] = -1;
cl_test_pathfind_result->point_path_length++;
// Update funnel to start at the current funnel left endpoint, pointing to the next portal in the list
// Set next funnel to start at cur funnel left point
cur_funnel_corner = cur_funnel_left;
// Advance the funnel's left point to the next portal
cur_funnel_left_portal_idx += 1;
cur_funnel_right = cl_test_pathfind_result->portals_right_pos[cur_funnel_left_portal_idx];
cur_funnel_left = cl_test_pathfind_result->portals_left_pos[cur_funnel_left_portal_idx];
cur_funnel_right_portal_idx = cur_funnel_left_portal_idx;
// Restart funnel algorithm from the funnel's endpoint
i = cur_funnel_left_portal_idx - 1;
continue;
}
// If cur portal is completely outside (to right of) current funnel
else if(cur_portal_left_in_funnel > 1 && cur_portal_right_in_funnel > 1) {
// Add cur funnel right point to final path:
cl_test_pathfind_result->point_path_points[cl_test_pathfind_result->point_path_length] = cur_funnel_right;
cl_test_pathfind_result->point_path_traversals[cl_test_pathfind_result->point_path_length] = -1;
cl_test_pathfind_result->point_path_length++;
// Update funnel to start at the current funnel right endpoint, pointing to the next portal in the list
// Set next funnel to start at cur funnel right point
cur_funnel_corner = cur_funnel_right;
// Advance the funnel's right point to the next portal
cur_funnel_right_portal_idx += 1;
cur_funnel_right = cl_test_pathfind_result->portals_right_pos[cur_funnel_right_portal_idx];
cur_funnel_left = cl_test_pathfind_result->portals_left_pos[cur_funnel_right_portal_idx];
cur_funnel_left_portal_idx = cur_funnel_right_portal_idx;
// Restart funnel algorithm from the funnel's endpoint
i = cur_funnel_right_portal_idx - 1;
continue;
}
// If cur portal is a traversal
if(cur_portal_traversal != -1) {
vector start_pos = cl_navmesh_traversals[cur_portal_traversal].start_pos;
vector end_pos = cl_navmesh_get_traversal_end_pos(cur_portal_traversal);
// Add traversal start point to final path:
cl_test_pathfind_result->point_path_points[cl_test_pathfind_result->point_path_length] = start_pos;
cl_test_pathfind_result->point_path_traversals[cl_test_pathfind_result->point_path_length] = cur_portal_traversal;
cl_test_pathfind_result->point_path_length++;
// Add traversal end point to final path:
// FIXME - Should I remove this?
cl_test_pathfind_result->point_path_points[cl_test_pathfind_result->point_path_length] = end_pos;
cl_test_pathfind_result->point_path_traversals[cl_test_pathfind_result->point_path_length] = -1;
cl_test_pathfind_result->point_path_length++;
// Set funnel to start at traversal endpoint and use the next portal
cur_funnel_corner = end_pos;
cur_funnel_left = cl_test_pathfind_result->portals_left_pos[i+1];
cur_funnel_right = cl_test_pathfind_result->portals_right_pos[i+1];
cur_funnel_left_portal_idx = i+1;
cur_funnel_right_portal_idx = i+1;
}
}
// Always add goal point to the path
cl_test_pathfind_result->point_path_points[cl_test_pathfind_result->point_path_length] = goal_point;
cl_test_pathfind_result->point_path_traversals[cl_test_pathfind_result->point_path_length] = -1;
cl_test_pathfind_result->point_path_length++;
}
//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 cl_pathfind_trace_path(float start_poly, float goal_poly) {
// print("Polygon from poly: ");
// for(float i = 0; i < cl_navmesh_poly_count; i++) {
// print(ftos(i));
// print(": ");
// print(ftos(cl_test_pathfind_result->poly_prev_poly[i]));
// print(", ");
// }
// print("\n");
// print("Polygon from traversal: ");
// for(float i = 0; i < cl_navmesh_poly_count; i++) {
// print(ftos(i));
// print(": ");
// print(ftos(cl_test_pathfind_result->poly_prev_traversal[i]));
// print(", ");
// }
// print("\n");
// print("Traversal from polygon:");
// for(float i = 0; i < cl_navmesh_traversal_count; i++) {
// print(ftos(i));
// print(": ");
// print(ftos(cl_test_pathfind_result->traversal_prev_poly[i]));
// print(", ");
// }
// print("\n");
//Count the length of the path (how many polygons the path traverses)
float current_poly = goal_poly;
float current_traversal = -1;
cl_test_pathfind_result->path_length = 1;
do {
// Current node is a polygon
if(current_poly != -1) {
current_traversal = cl_test_pathfind_result->poly_prev_traversal[current_poly];
current_poly = cl_test_pathfind_result->poly_prev_poly[current_poly];
}
// Current node is a traversal
else if(current_traversal != -1) {
current_poly = cl_test_pathfind_result->traversal_prev_poly[current_traversal];
current_traversal = -1;
}
cl_test_pathfind_result->path_length++;
} while(current_poly != start_poly);
// Starting at goal_poly, add the traversed polygons / traversals to the result path in reverse order
current_poly = goal_poly;
current_traversal = -1;
for(float i = 0; i < cl_test_pathfind_result->path_length; i++) {
// print("Current node (P=");
// print(ftos(current_poly));
// print(",T=");
// print(ftos(current_traversal));
// print(") came from ");
cl_test_pathfind_result->path_polygons[cl_test_pathfind_result->path_length - 1 - i] = current_poly;
cl_test_pathfind_result->path_traversals[cl_test_pathfind_result->path_length - 1 - i] = current_traversal;
// Current node is a polygon
if(current_poly != -1) {
current_traversal = cl_test_pathfind_result->poly_prev_traversal[current_poly];
current_poly = cl_test_pathfind_result->poly_prev_poly[current_poly];
}
// Current node is a traversal
else if(current_traversal != -1) {
current_poly = cl_test_pathfind_result->traversal_prev_poly[current_traversal];
current_traversal = -1;
}
// print(" (P=");
// print(ftos(current_poly));
// print(",T=");
// print(ftos(current_traversal));
// print(")\n");
}
print("Pathfind success, path length: ");
print(ftos(cl_test_pathfind_result->path_length));
print(".\n");
print("Path: ");
for(float i = 0; i < cl_test_pathfind_result->path_length; i++) {
if(i > 0) {
print(" , ");
}
if(cl_test_pathfind_result->path_polygons[i] != -1) {
print("P");
print(ftos(cl_test_pathfind_result->path_polygons[i]));
}
else if(cl_test_pathfind_result->path_traversals[i] != -1) {
print("T");
print(ftos(cl_test_pathfind_result->path_traversals[i]));
}
}
print(" .\n");
}
//Accepts start polygon and goal polygon
//Returns 1 on success.
//Returns 0 on fail.
float cl_navmesh_pathfind_start(float start_poly, float goal_poly, vector start_pos, vector end_pos) {
//Clearing previous data
cl_pathfind_clear_result_data();
if(start_poly == -1) {
print("Error: pathfind start node invalid.\n");
return 0;
}
if(goal_poly == -1) {
print("Error: pathfind goal node invalid.\n");
return 0;
}
if(start_poly == goal_poly) {
//Calculating node path
cl_test_pathfind_result->path_polygons[0] = start_poly;
cl_test_pathfind_result->path_traversals[0] = -1;
cl_test_pathfind_result->path_length = 1;
print("Pathind success: trivial case (start = goal).\n");
//Calculating vector based path (go directly to goal)
cl_test_pathfind_result->point_path_points[0].x = end_pos.x;
cl_test_pathfind_result->point_path_points[0].y = end_pos.y;
cl_test_pathfind_result->point_path_points[0].z = end_pos.z;
// Indicate non-traversal
cl_test_pathfind_result->point_path_traversals[0] = -1;
cl_test_pathfind_result->point_path_length = 1;
return 1;
}
//Adding start polygon to the open set
cl_test_pathfind_result->poly_set[start_poly] = PATHFIND_POLY_SET_OPEN;
cl_test_pathfind_result->poly_g_score[start_poly] = 0;
cl_test_pathfind_result->poly_f_score[start_poly] = 0 + cl_pathfind_calc_poly_h_score(start_poly , goal_poly);
// print("Pathfind init. Start: ");
// print(ftos(start_poly));
// print(" , Goal: ");
// print(ftos(goal_poly));
// print(".\n");
float current_poly = start_poly;
float current_traversal = -1;
float pathfind_success = FALSE;
// While we have a traversal OR a polygon
while(current_poly != -1 || current_traversal != -1) {
// If we are currently evaluating a polygon:
if(current_poly != -1) {
if(current_poly == goal_poly) {
//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_poly] = PATHFIND_POLY_SET_CLOSED;
//Add connected neighbor polygons to the open set
for(float i = 0; i < cl_navmesh_polies[current_poly].connected_polies_count; i++) {
float neighbor_poly = cl_navmesh_polies[current_poly].connected_polies[i];
// print("Checking poly ");
// print(ftos(current_poly));
// print("'s neighbor poly ");
// print(ftos(neighbor_poly));
// 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.
// ----------------------------------------------------------------
if(cl_test_pathfind_result->poly_set[neighbor_poly] != 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_poly] + vlen(cl_navmesh_polies[neighbor_poly].center - cl_navmesh_polies[current_poly].center);
// If not in open-set, or this is a better score, set score for this polygon
if(cl_test_pathfind_result->poly_set[neighbor_poly] != PATHFIND_POLY_SET_OPEN || tentative_g_score < cl_test_pathfind_result->poly_g_score[neighbor_poly]) {
//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_poly] = PATHFIND_POLY_SET_OPEN;
//Updating scores for neighbor node
float tentative_f_score = tentative_g_score + cl_pathfind_calc_poly_h_score(neighbor_poly , goal_poly);
cl_test_pathfind_result->poly_g_score[neighbor_poly] = tentative_g_score;
cl_test_pathfind_result->poly_f_score[neighbor_poly] = tentative_f_score;
//Linking neighbor node to current node (for tracing path)
cl_test_pathfind_result->poly_prev_poly[neighbor_poly] = current_poly;
cl_test_pathfind_result->poly_prev_traversal[neighbor_poly] = -1;
// print("Assigning Poly ");
// print(ftos(neighbor_poly));
// print(" came from poly ");
// print(ftos(current_poly));
// print(".\n");
}
}
}
//Add connected neighbor traversals to the open set
for(float i = 0; i < cl_navmesh_polies[current_poly].connected_traversals_count; i++) {
float neighbor_traversal = cl_navmesh_polies[current_poly].connected_traversals[i];
// print("Checking poly ");
// print(ftos(current_poly));
// print("'s neighbor traversal ");
// print(ftos(neighbor_traversal));
// print(".\n");
if(cl_test_pathfind_result->traversal_set[neighbor_traversal] != PATHFIND_POLY_SET_CLOSED) {
//Calculate tentative f score
//Calculate tentative g score (distance from start to current + distance from current to neighbor)
// vector traversal_start_pos = cl_navmesh_traversals[neighbor].start_pos;
vector traversal_end_pos = cl_navmesh_get_traversal_end_pos(neighbor_traversal);
// TODO - Should I cache traversal length?
float tentative_g_score = cl_test_pathfind_result->poly_g_score[current_poly] + vlen(traversal_end_pos - cl_navmesh_polies[current_poly].center);
// If not in open-set, or this is a better score, set score for this traversal
if(cl_test_pathfind_result->traversal_set[neighbor_traversal] != PATHFIND_POLY_SET_OPEN || tentative_g_score < cl_test_pathfind_result->traversal_g_score[neighbor_traversal]) {
//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->traversal_set[neighbor_traversal] = PATHFIND_POLY_SET_OPEN;
//Updating scores for neighbor node
float tentative_f_score = tentative_g_score + cl_pathfind_calc_traversal_h_score(neighbor_traversal, goal_poly);
cl_test_pathfind_result->traversal_g_score[neighbor_traversal] = tentative_g_score;
cl_test_pathfind_result->traversal_f_score[neighbor_traversal] = tentative_f_score;
//Linking neighbor node to current node (for tracing path)
cl_test_pathfind_result->traversal_prev_poly[neighbor_traversal] = current_poly;
// print("Assigning traversal ");
// print(ftos(neighbor_traversal));
// print(" came from poly ");
// print(ftos(current_poly));
// print(".\n");
}
}
}
}
else if(current_traversal != -1) {
// print("Checking traversal: ");
// print(ftos(current_traversal));
// print(" with end polygon: ");
// print(ftos(cl_navmesh_traversals[current_traversal].end_poly));
// print("\n");
//Add current traversal to the closed set
cl_test_pathfind_result->traversal_set[current_traversal] = PATHFIND_POLY_SET_CLOSED;
// Traversals have single neighbor polygons (at the traversal endpoint)
float neighbor_poly = cl_navmesh_traversals[current_traversal].end_poly;
if(neighbor_poly != -1) {
// print("Checking traversal ");
// print(ftos(current_traversal));
// print("'s neighbor poly ");
// print(ftos(neighbor_poly));
// print(".\n");
if(cl_test_pathfind_result->poly_set[neighbor_poly] != 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)
vector traversal_end_pos = cl_navmesh_get_traversal_end_pos(current_traversal);
float tentative_g_score = cl_test_pathfind_result->traversal_g_score[current_traversal] + vlen(cl_navmesh_polies[neighbor_poly].center - traversal_end_pos);
// If not in open-set, or this is a better score, set score for this polygon
if(cl_test_pathfind_result->poly_set[neighbor_poly] != PATHFIND_POLY_SET_OPEN || tentative_g_score < cl_test_pathfind_result->poly_g_score[neighbor_poly]) {
//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_poly] = PATHFIND_POLY_SET_OPEN;
//Updating scores for neighbor node
float tentative_f_score = tentative_g_score + cl_pathfind_calc_poly_h_score(neighbor_poly , goal_poly);
cl_test_pathfind_result->poly_g_score[neighbor_poly] = tentative_g_score;
cl_test_pathfind_result->poly_f_score[neighbor_poly] = tentative_f_score;
//Linking neighbor node to current node (for tracing path)
cl_test_pathfind_result->poly_prev_poly[neighbor_poly] = -1;
cl_test_pathfind_result->poly_prev_traversal[neighbor_poly] = current_traversal;
// print("Assigning Poly ");
// print(ftos(neighbor_poly));
// print(" came from traversal ");
// print(ftos(current_traversal));
// print(".\n");
}
}
}
}
float best_openset_poly = cl_pathfind_get_lowest_f_score_poly();
float best_openset_traversal = cl_pathfind_get_lowest_f_score_traversal();
// print("Best openset poly: ");
// print(ftos(best_openset_poly));
// print("\n");
// print("Best openset traversal: ");
// print(ftos(best_openset_traversal));
// print("\n");
// If we have both, compare their f-scores
if(best_openset_poly != -1 && best_openset_traversal != -1) {
// If traversal score is better, don't consider polygon
if(cl_test_pathfind_result->traversal_f_score[best_openset_poly] < cl_test_pathfind_result->poly_f_score[best_openset_poly]) {
best_openset_poly = -1;
}
// If polygon score is better, don't consider traversal
else {
best_openset_traversal = -1;
}
}
// Assign what we found, loop will terminate if we have neither
current_traversal = best_openset_traversal;
current_poly = best_openset_poly;
// --------------------------------------------------------------------
}
//Tracing the pathfind results
if(pathfind_success == TRUE) {
cl_pathfind_trace_path(start_poly, goal_poly);
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_path() {
if(cl_test_pathfind_result->path_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 ofs = [0,0,-5];
vector size = [5,5,5];
vector color = [1,0.1,0.5];
float alpha = 0.2;//0.3
float edge_width = 2;
float edge_alpha = 0.2;//0.3
vector cur_point = '0 0 0';
vector cur_end_point = '0 0 0';
vector prev_point = '0 0 0';
for(float i = 0; i < cl_test_pathfind_result->path_length; i++) {
// Current path entry is a polygon, draw box at polygon center
if(cl_test_pathfind_result->path_polygons[i] != -1) {
cur_point = cl_navmesh_polies[cl_test_pathfind_result->path_polygons[i]].center + ofs;
cur_end_point = cur_point;
cl_navmesh_draw_box(cur_point, size, color, alpha);
}
// Current path entry is a traversal, draw box at start and end
else if(cl_test_pathfind_result->path_traversals[i] != -1) {
cur_point = cl_navmesh_traversals[cl_test_pathfind_result->path_traversals[i]].start_pos + ofs;
cur_end_point = cl_navmesh_get_traversal_end_pos(cl_test_pathfind_result->path_traversals[i]) + ofs;
cl_navmesh_draw_box(cur_point, size, color, alpha);
cl_navmesh_draw_box(cur_end_point, size, color, alpha);
cl_navmesh_draw_line(cur_end_point, cur_point, edge_width, color, edge_alpha);
}
// Draw an edge connecting i-th node to previous node
if(i > 0) {
cl_navmesh_draw_line(prev_point, cur_point, edge_width, color, edge_alpha);
}
prev_point = cur_end_point;
}
}
void cl_navmesh_pathfind_draw_result_portals() {
// TODO - Update this to account for traversals (at least once I implement funnel algorithm for traversals)
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
vector portal_left;
vector portal_right;
for(float i = 0; i < cl_test_pathfind_result->portals_length; i++) {
portal_left = cl_test_pathfind_result->portals_left_pos[i];
portal_right = cl_test_pathfind_result->portals_right_pos[i];
cl_navmesh_draw_vert(portal_left + [0,0,-10],[0.5,0.5,1],0.4);
cl_navmesh_draw_vert(portal_right + [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
cl_navmesh_draw_line(portal_left + [0,0,-10], portal_right + [0,0,-8], edge_width, color, edge_alpha);
}
}
void cl_navmesh_pathfind_draw_result_point_path() {
// TODO - Update this to account for traversals
if(cl_test_pathfind_result->point_path_length < 1)
return;
vector color;
color = [1.0,0.0,0.0];
float edge_width = 4;
float alpha = 0.4;
float edge_alpha = 0.2;//0.3
vector ofs = [0,0,10];
//Drawing the a line connecting the start and first path node
cl_navmesh_draw_vert(startent_pos + ofs, color, alpha);
cl_navmesh_draw_line(startent_pos + ofs, cl_test_pathfind_result->point_path_points[0] + ofs, edge_width, color, edge_alpha);
vector cur_point = '0 0 0';
vector prev_point = '0 0 0';
for(float i = 0; i < cl_test_pathfind_result->point_path_length; i++) {
cur_point = cl_test_pathfind_result->point_path_points[i];
cl_navmesh_draw_vert(cur_point + ofs, color, alpha);
// TODO - if traversal, draw endpoint and line?
if(i > 0) {
//Draw an edge connecting path points
cl_navmesh_draw_line(cur_point + ofs, prev_point + ofs, edge_width, color, edge_alpha);
}
prev_point = cur_point;
}
}
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);
}
if(cl_navmesh_traversals == 0) {
cl_navmesh_traversals = memalloc(sizeof(navmesh_traversal) * NAV_MAX_TRAVERSALS);
}
// Allocate memory for pathfind result
if(cl_test_pathfind_result == 0) {
cl_test_pathfind_result = memalloc(sizeof(navmesh_pathfind_result));
cl_pathfind_clear_result_data();
}
cl_navmesh_deselect_all();
cl_navmesh_selected_traversal = -1;
cl_navmesh_traversal_edit_mode = false;
cl_navmesh_traversal_editor_cur_point = 0;
}
}
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_print_poly_door");
// --- Traversal Commands ---
registercommand("nav_place_traversal");
registercommand("nav_select_traversal");
registercommand("nav_clone_traversal");
registercommand("nav_delete_traversal");
registercommand("nav_edit_traversal");
registercommand("nav_next_traversal_type");
registercommand("nav_prev_traversal_type");
// --- Traversal Edit Mode Commands ---
registercommand("nav_trav_next_point");
registercommand("nav_trav_toggle_point");
registercommand("nav_trav_get_point_pos");
registercommand("nav_trav_set_point_pos");
registercommand("nav_trav_get_angle");
registercommand("nav_trav_set_angle");
registercommand("nav_trav_auto_adjust");
}
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) {
if(cl_navmesh_polies[selected_polygon].doortarget != editor_active_door) {
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_place_traversal() {
cl_navmesh_traversal_edit_mode = false;
cl_navmesh_traversal_editor_cur_point = 0;
if(cl_navmesh_traversal_count >= NAV_MAX_TRAVERSALS) {
print("Can't add traversal, max traversal count has been reached.\n");
return;
}
// Place the traversal at the player origin
vector player_pos = getentity(player_localentnum, GE_ORIGIN);
vector player_angles = getentity(player_localentnum, GE_ANGLES);
cl_navmesh_traversals[cl_navmesh_traversal_count].start_pos.x = player_pos.x;
cl_navmesh_traversals[cl_navmesh_traversal_count].start_pos.y = player_pos.y;
cl_navmesh_traversals[cl_navmesh_traversal_count].start_pos.z = player_pos.z;
// Make traversal face same direction as player
cl_navmesh_traversals[cl_navmesh_traversal_count].angle = player_angles.y;
// -------------------------------
// Set up default traversal (teleport)
// TODO - Move this to a separate function
// -------------------------------
// X - Right, Y - Forward, Z - up
cl_navmesh_traversals[cl_navmesh_traversal_count].midpoint_pos = '0 24 24';
cl_navmesh_traversals[cl_navmesh_traversal_count].use_midpoint = false;
cl_navmesh_traversals[cl_navmesh_traversal_count].use_midpoint = true;
cl_navmesh_traversals[cl_navmesh_traversal_count].end_pos = '0 48 0';
// TODO - somehow set traversal type ID?
// -------------------------------
print("Traveral ");
print(ftos(cl_navmesh_traversal_count));
print(" created at ", vtos(player_pos),"\n");
print("Angles:");
print(vtos(player_angles));
// Immediately select it
cl_navmesh_selected_traversal = cl_navmesh_traversal_count;
cl_navmesh_traversal_count++;
};
float cl_navmesh_get_nearest_traversal() {
if(cl_navmesh_traversal_count <= 0) {
return -1;
}
vector player_pos = getentity(player_localentnum, GE_ORIGIN);
float closest_dist = vlen(player_pos - cl_navmesh_traversals[0].start_pos);
float closest_index = 0;
float temp_dist;
for(float i = 1; i < cl_navmesh_traversal_count; i++) {
// Check traversal start position
temp_dist = vlen(player_pos - cl_navmesh_traversals[i].start_pos);
if(temp_dist < closest_dist) {
closest_dist = temp_dist;
closest_index = i;
}
// Also check traversal end position
temp_dist = vlen(player_pos - cl_navmesh_get_traversal_end_pos(i));
if(temp_dist < closest_dist) {
closest_dist = temp_dist;
closest_index = i;
}
}
return closest_index;
}
// Copies a traversal from index src to index dest
void cl_navmesh_editor_copy_traversal(float src, float dest, float copy_pos, float copy_angle) {
if(copy_pos) {
cl_navmesh_traversals[dest].start_pos = cl_navmesh_traversals[src].start_pos;
}
if(copy_angle) {
cl_navmesh_traversals[dest].angle = cl_navmesh_traversals[src].angle;
}
cl_navmesh_traversals[dest].end_pos = cl_navmesh_traversals[src].end_pos;
cl_navmesh_traversals[dest].midpoint_pos = cl_navmesh_traversals[src].midpoint_pos;
cl_navmesh_traversals[dest].use_midpoint = cl_navmesh_traversals[src].use_midpoint;
}
// Selects the nearest traversal (if nearest traversal is selcted, deselects it)
void cl_navmesh_editor_select_traversal() {
cl_navmesh_traversal_edit_mode = false;
cl_navmesh_traversal_editor_cur_point = 0;
int trav_idx = cl_navmesh_get_nearest_traversal();
if(trav_idx == -1) {
print("No traversals to select");
return;
}
if(trav_idx == cl_navmesh_selected_traversal) {
print("Travseral deselected.\n");
cl_navmesh_selected_traversal = -1;
return;
}
print("Traversal ");
print(ftos(trav_idx));
print(" selected.\n");
cl_navmesh_selected_traversal = trav_idx;
};
void cl_navmesh_editor_clone_traversal() {
cl_navmesh_traversal_edit_mode = false;
cl_navmesh_traversal_editor_cur_point = 0;
if(cl_navmesh_traversal_count >= NAV_MAX_TRAVERSALS) {
print("Can't clone traversal, max traversal count has been reached.\n");
return;
}
float prev_trav = cl_navmesh_selected_traversal;
cl_navmesh_editor_place_traversal();
// place_traversal implicitly updates `cl_navmesh_selected_traversal` to the new one
cl_navmesh_editor_copy_traversal(prev_trav, cl_navmesh_selected_traversal, false, false);
};
void cl_navmesh_editor_delete_traversal() {
cl_navmesh_traversal_edit_mode = false;
cl_navmesh_traversal_editor_cur_point = 0;
if(cl_navmesh_selected_traversal == -1) {
print("No traversal selected.\n");
return;
}
print("Deleting traversal: ");
print(ftos(cl_navmesh_selected_traversal));
print(".\n");
//Bringing all traversals down to not leave any holes in the array
//Moving down every index to the right of what is selected to not leave any holes
for(float i = cl_navmesh_selected_traversal; i < cl_navmesh_traversal_count - 1; i++) {
// Copy traversal (i+1) to traversal (i)
cl_navmesh_editor_copy_traversal(i+1, i, true, true);
}
// TOOD - Clear the last one?
cl_navmesh_traversal_count--;
// TODO - Fix any references from polygons -> traversals?
cl_navmesh_selected_traversal = -1;
};
void cl_navmesh_editor_edit_traversal() {
if(cl_navmesh_selected_traversal == -1) {
print("No traversal selected.\n");
return;
}
if(cl_navmesh_traversal_edit_mode) {
cl_navmesh_traversal_edit_mode = false;
}
else {
cl_navmesh_traversal_edit_mode = true;
}
cl_navmesh_traversal_editor_cur_point = 0;
};
void cl_navmesh_editor_next_traversal_type() {
// TODO - Scroll through list once traversals are defined
};
void cl_navmesh_editor_prev_traversal_type() {
// TODO - Scroll through list once traversals are defined
};
void cl_navmesh_traversal_editor_next_point() {
if(cl_navmesh_selected_traversal == -1) {
print("No traversal selected.\n");
return;
}
if(cl_navmesh_traversal_edit_mode == false) {
print("Not in traversal edit mode.\n");
return;
}
cl_navmesh_traversal_editor_cur_point = (cl_navmesh_traversal_editor_cur_point + 1) % 3;
};
void cl_navmesh_traversal_editor_toggle_point() {
if(cl_navmesh_selected_traversal == -1) {
print("No traversal selected.\n");
return;
}
if(cl_navmesh_traversal_edit_mode == false) {
print("Not in traversal edit mode.\n");
return;
}
if(cl_navmesh_traversal_editor_cur_point != 1){
print("Can only toggle midpoint.\n");
return;
}
if(cl_navmesh_traversals[cl_navmesh_selected_traversal].use_midpoint) {
cl_navmesh_traversals[cl_navmesh_selected_traversal].use_midpoint = false;
}
else {
cl_navmesh_traversals[cl_navmesh_selected_traversal].use_midpoint = true;
}
};
void cl_navmesh_traversal_editor_get_point_pos() {
if(cl_navmesh_selected_traversal == -1) {
print("No traversal selected.\n");
return;
}
if(cl_navmesh_traversal_edit_mode == false) {
print("Not in traversal edit mode.\n");
return;
}
if(cl_navmesh_traversal_editor_cur_point == 0) {
print("Traversal start point: ");
print(vtos(cl_navmesh_traversals[cl_navmesh_selected_traversal].start_pos));
print(".\n");
}
else if(cl_navmesh_traversal_editor_cur_point == 1) {
print("Traversal mid point: ");
print(vtos(cl_navmesh_traversals[cl_navmesh_selected_traversal].midpoint_pos));
print(". (relative to start point)\n");
}
else if(cl_navmesh_traversal_editor_cur_point == 2) {
print("Traversal end point: ");
print(vtos(cl_navmesh_traversals[cl_navmesh_selected_traversal].end_pos));
print(". (relative to start point)\n");
}
};
void cl_navmesh_traversal_editor_set_point_pos(vector pos) {
if(cl_navmesh_selected_traversal == -1) {
print("No traversal selected.\n");
return;
}
if(cl_navmesh_traversal_edit_mode == false) {
print("Not in traversal edit mode.\n");
return;
}
if(cl_navmesh_traversal_editor_cur_point == 0) {
cl_navmesh_traversals[cl_navmesh_selected_traversal].start_pos = pos;
print("Traversal start pos set to: ");
print(vtos(cl_navmesh_traversals[cl_navmesh_selected_traversal].start_pos));
print(".\n");
}
else if(cl_navmesh_traversal_editor_cur_point == 1) {
cl_navmesh_traversals[cl_navmesh_selected_traversal].midpoint_pos = pos;
print("Traversal mid pos set to: ");
print(vtos(cl_navmesh_traversals[cl_navmesh_selected_traversal].midpoint_pos));
print(". (relative to start pos)\n");
}
else if(cl_navmesh_traversal_editor_cur_point == 2) {
cl_navmesh_traversals[cl_navmesh_selected_traversal].end_pos = pos;
print("Traversal end pos set to: ");
print(vtos(cl_navmesh_traversals[cl_navmesh_selected_traversal].end_pos));
print(". (relative to start pos)\n");
}
};
void cl_navmesh_traversal_editor_get_angle() {
if(cl_navmesh_selected_traversal == -1) {
print("No traversal selected.\n");
return;
}
if(cl_navmesh_traversal_edit_mode == false) {
print("Not in traversal edit mode.\n");
return;
}
print("Traversal angle: ");
print(ftos(cl_navmesh_traversals[cl_navmesh_selected_traversal].angle));
print(".\n");
};
void cl_navmesh_traversal_editor_set_angle(float angle) {
if(cl_navmesh_selected_traversal == -1) {
print("No traversal selected.\n");
return;
}
if(cl_navmesh_traversal_edit_mode == false) {
print("Not in traversal edit mode.\n");
return;
}
cl_navmesh_traversals[cl_navmesh_selected_traversal].angle = angle;
print("Traversal angle set to: ");
print(ftos(cl_navmesh_traversals[cl_navmesh_selected_traversal].angle));
print(".\n");
};
void cl_navmesh_traversal_editor_auto_adjust_traversal() {
if(cl_navmesh_selected_traversal == -1) {
return;
}
vector start_pos = cl_navmesh_traversals[cl_navmesh_selected_traversal].start_pos;
vector midpoint_pos = cl_navmesh_get_traversal_midpoint_pos(cl_navmesh_selected_traversal);
vector end_pos = cl_navmesh_get_traversal_end_pos(cl_navmesh_selected_traversal);
vector ofs = '0 0 16';
tracebox(start_pos + ofs, VEC_HULL_MIN, VEC_HULL_MAX, start_pos - ofs, TRUE, player);
cl_navmesh_traversals[cl_navmesh_selected_traversal].start_pos.z = trace_endpos.z;
tracebox(end_pos + ofs, VEC_HULL_MIN, VEC_HULL_MAX, end_pos - ofs, TRUE, player);
cl_navmesh_traversals[cl_navmesh_selected_traversal].end_pos.z = trace_endpos.z - start_pos.z;
// FIXME - Pull from traversal struct
string traversal_type = "ledge";
// If "ledge" traversal,
if(traversal_type == "ledge") {
start_pos = cl_navmesh_traversals[cl_navmesh_selected_traversal].start_pos;
end_pos = cl_navmesh_get_traversal_end_pos(cl_navmesh_selected_traversal);
vector top_point;
vector bottom_point;
if(start_pos.z < end_pos.z) {
bottom_point = start_pos;
top_point = end_pos;
}
else {
top_point = start_pos;
bottom_point = end_pos;
}
// --------------------------------------------------------------------
// Binary search to find the ledge location
// --------------------------------------------------------------------
vector search_start = top_point;
vector search_midpoint;
vector search_end = bottom_point;
search_end.z = search_start.z;
// What if there's a wall above the bottom point?
for(float i = 0; i < 6; i++) {
search_midpoint = (search_start + search_end) * 0.5;
tracebox(search_midpoint + ofs, VEC_HULL_MIN, VEC_HULL_MAX, search_midpoint - ofs, TRUE, player);
print("--------------------------------\n");
print("iter: ", ftos(i), " start: ", vtos(search_start), " end: ", vtos(search_end), " mid: ");
print(vtos(search_midpoint), " frac: ", ftos(trace_fraction), "\n");
if(trace_fraction >= 1.0 || trace_startsolid) {
search_end = search_midpoint;
}
else {
search_start = search_midpoint;
}
}
vector ledge_pos = (search_start + search_end) * 0.5;
// ledge_pos is the center of the bbox, offset to find the corner near the ledge
// Push 16 qu towards the top point
// NOTE: This won't be accurate for sloped ledges, but we're just
// trying to get close-enough so the animations look half-decent
ledge_pos += normalize(top_point - [bottom_point.x, bottom_point.y, top_point.z]) * 16;
// Lower by the bbox height to the center
ledge_pos.z += VEC_HULL_MIN.z;
// --------------------------------------------------------------------
// --------------------------------------------------------------------
// Offset the traversal such that the top point is 10qu away from the ledge
// --------------------------------------------------------------------
float traversal_length = 50; // qu
float start_to_ledge = 10; // qu
// First, make sure the end_pos is exactly 50qu away from the start
cl_navmesh_traversals[cl_navmesh_selected_traversal].end_pos.x = 0;
cl_navmesh_traversals[cl_navmesh_selected_traversal].end_pos.y = traversal_length;
// Compute vector pointing from start towards ledge:
vector delta_pos = start_pos - ledge_pos;
delta_pos.z = 0;
float cur_dist = vlen(delta_pos);
delta_pos = normalize(delta_pos);
// If traversal start is at the bottom, move traversal start 40qu away from ledge
if(start_pos.z < end_pos.z) {
cl_navmesh_traversals[cl_navmesh_selected_traversal].start_pos += delta_pos * ((traversal_length - start_to_ledge) - cur_dist);
}
// If traversal start is at the top, move traversal start 10qu away from ledge
else {
cl_navmesh_traversals[cl_navmesh_selected_traversal].start_pos += delta_pos * (start_to_ledge - cur_dist);
}
// Next, adjust distance between the traversal start and the ledge
// so that it's exactly 25 qu away.
// --------------------------------------------------------------------
}
}
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_place_traversal":
cl_navmesh_editor_place_traversal();
return TRUE;
case "nav_select_traversal":
cl_navmesh_editor_select_traversal();
return TRUE;
case "nav_clone_traversal":
cl_navmesh_editor_clone_traversal();
return TRUE;
case "nav_delete_traversal":
cl_navmesh_editor_delete_traversal();
return TRUE;
case "nav_edit_traversal":
cl_navmesh_editor_edit_traversal();
return TRUE;
case "nav_next_traversal_type":
cl_navmesh_editor_next_traversal_type();
return TRUE;
case "nav_prev_traversal_type":
cl_navmesh_editor_prev_traversal_type();
return TRUE;
case "nav_trav_next_point":
cl_navmesh_traversal_editor_next_point();
return TRUE;
case "nav_trav_toggle_point":
cl_navmesh_traversal_editor_toggle_point();
return TRUE;
case "nav_trav_get_point_pos":
cl_navmesh_traversal_editor_get_point_pos();
return TRUE;
case "nav_trav_set_point_pos":
cl_navmesh_traversal_editor_set_point_pos(stov(argv(1)));
return TRUE;
case "nav_trav_get_angle":
cl_navmesh_traversal_editor_get_angle();
return TRUE;
case "nav_trav_set_angle":
cl_navmesh_traversal_editor_set_angle(stof(argv(1)));
return TRUE;
case "nav_trav_auto_adjust":
cl_navmesh_traversal_editor_auto_adjust_traversal();
return TRUE;
default:
break;
}
return FALSE;
}