mirror of
https://github.com/nzp-team/quakec.git
synced 2025-02-24 20:51:22 +00:00
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
3712 lines
No EOL
128 KiB
C++
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;
|
|
} |