mirror of
https://github.com/nzp-team/quakec.git
synced 2025-02-24 20:51:22 +00:00
Fixes funnel algorithm for various edge cases
Adds server-side navmesh v0.1.0 loading Refactors AI_Chase code
This commit is contained in:
parent
66bb8a8702
commit
cf0ec43631
8 changed files with 1170 additions and 339 deletions
|
@ -1479,15 +1479,15 @@ void cl_navmesh_calc_connected_polies() {
|
|||
cl_navmesh_polies[j].connected_polies_count++;
|
||||
//=======================================================================================
|
||||
|
||||
print("Poly ");
|
||||
print(ftos(i));
|
||||
print(" is connected to ");
|
||||
print(ftos(j));
|
||||
print(" and poly ");
|
||||
print(ftos(j));
|
||||
print(" is connected to ");
|
||||
print(ftos(i));
|
||||
print(".\n");
|
||||
// print("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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1616,7 +1616,7 @@ void cl_navmesh_calc_polies_centers()
|
|||
//Saves the current cl_navmesh to a file
|
||||
void cl_navmesh_editor_save_navmesh() {
|
||||
int v_major = 0;
|
||||
int v_minor = 0;
|
||||
int v_minor = 1;
|
||||
int v_patch = 0;
|
||||
float file;
|
||||
string h;
|
||||
|
@ -1878,10 +1878,15 @@ void cl_navmesh_editor_load_navmesh() {
|
|||
fgets(file);//1
|
||||
fgets(file);//2
|
||||
fgets(file);//3
|
||||
// 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
|
||||
|
||||
// If navmesh file version 0.0.0, no traversals
|
||||
// FIXME - Need a better way to do this..
|
||||
if(strcmp(nav_file_version, "0.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
|
||||
|
@ -1899,31 +1904,33 @@ void cl_navmesh_editor_load_navmesh() {
|
|||
// -----------------------------------------------------------------------
|
||||
// Load Traversals
|
||||
// -----------------------------------------------------------------------
|
||||
// TODO - Increment navmesh version count, if 0.0.0, load file without traversals
|
||||
//Next line contains the number of traverals
|
||||
cl_navmesh_traversal_count = stof(fgets(file));
|
||||
|
||||
//The next lines are each traversal
|
||||
for(float i = 0; i < cl_navmesh_traversal_count; i++) {
|
||||
line = fgets(file);
|
||||
temp = stov(line);
|
||||
cl_navmesh_traversals[i].start_pos.x = temp.x;
|
||||
cl_navmesh_traversals[i].start_pos.y = temp.y;
|
||||
cl_navmesh_traversals[i].start_pos.z = temp.z;
|
||||
line = fgets(file);
|
||||
temp = stov(line);
|
||||
cl_navmesh_traversals[i].midpoint_pos.x = temp.x;
|
||||
cl_navmesh_traversals[i].midpoint_pos.y = temp.y;
|
||||
cl_navmesh_traversals[i].midpoint_pos.z = temp.z;
|
||||
line = fgets(file);
|
||||
temp = stov(line);
|
||||
cl_navmesh_traversals[i].end_pos.x = temp.x;
|
||||
cl_navmesh_traversals[i].end_pos.y = temp.y;
|
||||
cl_navmesh_traversals[i].end_pos.z = temp.z;
|
||||
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);
|
||||
// If navmesh file version 0.0.0, no traversals
|
||||
if(strcmp(nav_file_version, "0.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++) {
|
||||
line = fgets(file);
|
||||
temp = stov(line);
|
||||
cl_navmesh_traversals[i].start_pos.x = temp.x;
|
||||
cl_navmesh_traversals[i].start_pos.y = temp.y;
|
||||
cl_navmesh_traversals[i].start_pos.z = temp.z;
|
||||
line = fgets(file);
|
||||
temp = stov(line);
|
||||
cl_navmesh_traversals[i].midpoint_pos.x = temp.x;
|
||||
cl_navmesh_traversals[i].midpoint_pos.y = temp.y;
|
||||
cl_navmesh_traversals[i].midpoint_pos.z = temp.z;
|
||||
line = fgets(file);
|
||||
temp = stov(line);
|
||||
cl_navmesh_traversals[i].end_pos.x = temp.x;
|
||||
cl_navmesh_traversals[i].end_pos.y = temp.y;
|
||||
cl_navmesh_traversals[i].end_pos.z = temp.z;
|
||||
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);
|
||||
}
|
||||
}
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
|
@ -2397,6 +2404,8 @@ void cl_pathfind_smooth_path(vector start_point, vector goal_point) {
|
|||
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;
|
||||
|
||||
float next_portal_left_in_funnel;
|
||||
float next_portal_right_in_funnel;
|
||||
|
@ -2415,51 +2424,30 @@ void cl_pathfind_smooth_path(vector start_point, vector goal_point) {
|
|||
// 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("Current portal: ");
|
||||
print(ftos(i));
|
||||
print(" (l: ");
|
||||
print(ftos(cur_portal_left_pos.x));
|
||||
print(",");
|
||||
print(ftos(cur_portal_left_pos.y));
|
||||
print(", r: ");
|
||||
print(ftos(cur_portal_right_pos.x));
|
||||
print(",");
|
||||
print(ftos(cur_portal_right_pos.y));
|
||||
print(", t: ");
|
||||
print(ftos(cur_portal_traversal));
|
||||
print(") -");
|
||||
print(" In funnel? (");
|
||||
print(ftos(cur_portal_left_in_funnel));
|
||||
print(",");
|
||||
print(ftos(cur_portal_right_in_funnel));
|
||||
print(") - ");
|
||||
print("Funnel: (");
|
||||
print(ftos(cur_funnel_left.x));
|
||||
print(",");
|
||||
print(ftos(cur_funnel_left.y));
|
||||
print(" -> ");
|
||||
print(ftos(cur_funnel_corner.x));
|
||||
print(",");
|
||||
print(ftos(cur_funnel_corner.y));
|
||||
print(" -> ");
|
||||
print(ftos(cur_funnel_right.x));
|
||||
print(",");
|
||||
print(ftos(cur_funnel_right.y));
|
||||
print(")\n");
|
||||
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
|
||||
|
@ -2469,12 +2457,18 @@ void cl_pathfind_smooth_path(vector start_point, vector goal_point) {
|
|||
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;
|
||||
// Set next funnel left point to next portal left
|
||||
cur_funnel_left = cl_test_pathfind_result->portals_left_pos[i+1];
|
||||
// Set next funnel right point to next portal right
|
||||
cur_funnel_right = cl_test_pathfind_result->portals_right_pos[i+1];
|
||||
|
||||
// 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) {
|
||||
|
@ -2483,12 +2477,18 @@ void cl_pathfind_smooth_path(vector start_point, vector goal_point) {
|
|||
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;
|
||||
// Set next funnel left point to next portal left
|
||||
cur_funnel_left = cl_test_pathfind_result->portals_left_pos[i+1];
|
||||
// Set next funnel right point to next portal right
|
||||
cur_funnel_right = cl_test_pathfind_result->portals_right_pos[i+1];
|
||||
|
||||
// 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
|
||||
|
@ -2510,6 +2510,8 @@ void cl_pathfind_smooth_path(vector start_point, vector goal_point) {
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,36 +1,9 @@
|
|||
var struct framegroup {
|
||||
float start_frame;
|
||||
float end_frame;
|
||||
float duration;
|
||||
// void(entity) start_callback; // Called at the first frame of the range
|
||||
void(entity) frame_callback; // Called at every frame of the range (after start / finish callbacks)
|
||||
// void(entity) end_callback; // Called at the last frame of the range
|
||||
// FIXME - Increasing the size of this framegroup by one more function pointer causes chaos...
|
||||
// I assume I run out of space to pass framegroups along as arguments at any point...
|
||||
// It starts to write into weird memory locations, like overriding self / world... things just break down.
|
||||
// TODO - How can I refactor this to work?
|
||||
void(entity) think_callback; // Called at each think invocation
|
||||
// -- Maybe I can get rid of start / end callbacks? idk
|
||||
// I should think about how I actually plan on using these...
|
||||
|
||||
// TODO - Get rid of start / end callback, add fields to the AI_Chase class that denotes
|
||||
// TODO - These callbacks will be executed at the same time as frame anyways.
|
||||
};
|
||||
|
||||
|
||||
// FIXME - This framegroup nonsense is causing chaos... the struct is too large apparently... what can I cut out?
|
||||
|
||||
|
||||
framegroup make_empty_framegroup(__out framegroup fg) {
|
||||
fg.start_frame = -1;
|
||||
fg.end_frame = -1;
|
||||
fg.duration = 1.0;
|
||||
// fg.start_callback = SUB_Null;
|
||||
fg.frame_callback = SUB_Null;
|
||||
// fg.end_callback = SUB_Null;
|
||||
fg.think_callback = SUB_Null;
|
||||
}
|
||||
|
||||
void init_framegroup( float start_frame, float end_frame, float duration,
|
||||
void(entity) frame_callback,
|
||||
void(entity) think_callback,
|
||||
|
@ -44,214 +17,157 @@ void init_framegroup( float start_frame, float end_frame, float duration,
|
|||
|
||||
|
||||
|
||||
// Constructor. Called when calling `spawn(AI_Chase);`
|
||||
void() AI_Chase::AI_Chase = {
|
||||
this.path_target = world;
|
||||
this.path_pos = world.origin;
|
||||
this.think_delta_time = 0.1; // Call `this.think();` 10x per second
|
||||
this.cur_fg_idx = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class AI_Chase : entity {
|
||||
// framegroup_registry *th_reg;
|
||||
entity path_target; // If specified, path towards entity
|
||||
vector path_pos; // Otherwise, path towards location specified
|
||||
float think_delta_time; // Delta time (seconds) between each `this.think();`
|
||||
|
||||
// ------------------------------
|
||||
// Current Framegroup value vars
|
||||
// ------------------------------
|
||||
float cur_fg_start_time;
|
||||
float cur_fg_start_frame;
|
||||
float cur_fg_end_frame;
|
||||
float cur_fg_duration;
|
||||
// void() endanimfunc = SUB_Null;
|
||||
// virtual void(entity) cur_fg_start_callback = SUB_Null;
|
||||
virtual void(entity) cur_fg_frame_callback = SUB_Null;
|
||||
// virtual void(entity) cur_fg_end_callback = SUB_Null;
|
||||
virtual void(entity) cur_fg_think_callback = SUB_Null;
|
||||
// ------------------------------
|
||||
// Framegroup state vars
|
||||
// ------------------------------
|
||||
float cur_fg_idx; // Counter for checking when the framegroup has been changed
|
||||
float cur_fg_frame_callback_next_frame;
|
||||
float cur_fg_frame_callback_is_start_frame; // Is set to true when performing cur_fg_frame_callback for the first framegroup frame
|
||||
float cur_fg_frame_callback_is_end_frame; // Is set to true when performing cur_fg_frame_callback for the final framegroup frame
|
||||
|
||||
// ------------------------------
|
||||
|
||||
|
||||
// Constructor. Called when calling `spawn(AI_Chase);`
|
||||
virtual void() AI_Chase = {
|
||||
this.path_target = world;
|
||||
this.path_pos = world.origin;
|
||||
this.think_delta_time = 0.1; // Call `this.think();` 10x per second
|
||||
this.cur_fg_idx = 0;
|
||||
};
|
||||
|
||||
|
||||
virtual void() think = {
|
||||
if(this.cur_fg_start_time >= 0) {
|
||||
float cur_framegroup_idx = this.cur_fg_idx;
|
||||
float dt = time - this.cur_fg_start_time;
|
||||
float lerp_frac = (dt / this.cur_fg_duration);
|
||||
// Frame ranges are inclusive, do an un-clamped interpolation to include the final frame
|
||||
float cur_frame = unclamped_lerp(this.cur_fg_start_frame, this.cur_fg_end_frame, lerp_frac);
|
||||
// For rendering, keep frame number always within current animation
|
||||
if(this.cur_fg_end_frame > this.cur_fg_start_frame) {
|
||||
this.frame = clamp(cur_frame, this.cur_fg_start_frame, this.cur_fg_end_frame);
|
||||
}
|
||||
else {
|
||||
this.frame = clamp(cur_frame, this.cur_fg_end_frame, this.cur_fg_start_frame);
|
||||
}
|
||||
|
||||
// print("Cur frame: ");
|
||||
// print(ftos(this.frame));
|
||||
// print(", start_frame: ");
|
||||
// print(ftos(this.cur_fg_start_frame));
|
||||
// print(", end_frame: ");
|
||||
// print(ftos(this.cur_fg_end_frame));
|
||||
// print(", cur_frame: ");
|
||||
// print(ftos(cur_frame));
|
||||
// print(", next frame callback: ");
|
||||
// print(ftos(this.cur_fg_frame_callback_next_frame));
|
||||
// print(", duration: ");
|
||||
// print(ftos(this.cur_fg_duration));
|
||||
// print("\n");
|
||||
|
||||
// TODO - round frame to nearest frame?
|
||||
|
||||
// If the animation is playing forward (low frame num to high frame num)
|
||||
if(this.cur_fg_end_frame > this.cur_fg_start_frame) {
|
||||
if(cur_frame >= this.cur_fg_frame_callback_next_frame) {
|
||||
if(cur_frame >= this.cur_fg_end_frame + 1) {
|
||||
this.cur_fg_frame_callback_is_end_frame = true;
|
||||
}
|
||||
this.cur_fg_frame_callback(this); // FIXME - This can override and invoke new things, need to be careful
|
||||
// Callback may have assigned new framegroup
|
||||
if(this.cur_fg_idx == cur_framegroup_idx) {
|
||||
this.cur_fg_frame_callback_is_start_frame = false;
|
||||
this.cur_fg_frame_callback_is_end_frame = false;
|
||||
this.cur_fg_frame_callback_next_frame = floor(cur_frame) + 1;
|
||||
}
|
||||
// print("\tSet next frame callback frame to: ");
|
||||
// print(ftos(this.cur_fg_frame_callback_next_frame));
|
||||
// print("\n");
|
||||
}
|
||||
}
|
||||
// else the animation is playing in reverse (high frame num to low frame num)
|
||||
else {
|
||||
if(cur_frame <= this.cur_fg_frame_callback_next_frame) {
|
||||
if(cur_frame <= this.cur_fg_end_frame - 1) {
|
||||
this.cur_fg_frame_callback_is_end_frame = true;
|
||||
}
|
||||
this.cur_fg_frame_callback(this);
|
||||
// Callback may have assigned new framegroup
|
||||
if(this.cur_fg_idx == cur_fg_idx) {
|
||||
this.cur_fg_frame_callback_is_start_frame = false;
|
||||
this.cur_fg_frame_callback_is_end_frame = false;
|
||||
this.cur_fg_frame_callback_next_frame = ceil(cur_frame) - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Callback may have assigned new framegroup
|
||||
// if(this.cur_fg_idx == cur_fg_idx) {
|
||||
this.cur_fg_think_callback(this);
|
||||
// }
|
||||
}
|
||||
this.nextthink = time + this.think_delta_time;
|
||||
};
|
||||
|
||||
virtual void (float duration) set_framegroup_duration = {
|
||||
// If no current framegroup, stop
|
||||
if(this.cur_fg_start_time < 0) {
|
||||
print("Warning: Attempted to set framegroup duration without an active framegroup.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
this.cur_fg_duration = duration;
|
||||
// --------------------------------------------------------------------
|
||||
// Adjust think_delta_time so no frames are skipped
|
||||
// --------------------------------------------------------------------
|
||||
float n_anim_frames = fabs(this.cur_fg_end_frame - this.cur_fg_start_frame);
|
||||
float time_per_frame = duration / n_anim_frames;
|
||||
// If faster than 10FPS, make thinks be called more often to not miss a frame callback
|
||||
if(time_per_frame < 0.10) {
|
||||
this.think_delta_time = time_per_frame * 0.5;
|
||||
void() AI_Chase::think = {
|
||||
if(this.cur_fg_start_time >= 0) {
|
||||
float cur_framegroup_idx = this.cur_fg_idx;
|
||||
float dt = time - this.cur_fg_start_time;
|
||||
float lerp_frac = (dt / this.cur_fg_duration);
|
||||
// Frame ranges are inclusive, do an un-clamped interpolation to include the final frame
|
||||
float cur_frame = unclamped_lerp(this.cur_fg_start_frame, this.cur_fg_end_frame, lerp_frac);
|
||||
// For rendering, keep frame number always within current animation
|
||||
if(this.cur_fg_end_frame > this.cur_fg_start_frame) {
|
||||
this.frame = clamp(cur_frame, this.cur_fg_start_frame, this.cur_fg_end_frame);
|
||||
}
|
||||
else {
|
||||
this.think_delta_time = 0.1;
|
||||
this.frame = clamp(cur_frame, this.cur_fg_end_frame, this.cur_fg_start_frame);
|
||||
}
|
||||
// --------------------------------------------------------------------
|
||||
};
|
||||
|
||||
virtual void (framegroup fgroup) set_framegroup = {
|
||||
// Increment framegroup counter. Reset every 100, we only use this to check when it has changed
|
||||
this.cur_fg_idx = (this.cur_fg_idx + 1) % 100;
|
||||
|
||||
// FIXME - frame callback isn't guaranteed to be called for every frame because sometimes we skip over frames
|
||||
// FIXME - I should modify the think delta time to run at 80% of the time between frames, or 0.10 seconds, whichever is greater.
|
||||
// FIXME - That way, if are playing an animation really fast, it'll call think more often
|
||||
this.cur_fg_start_frame = fgroup.start_frame;
|
||||
this.cur_fg_end_frame = fgroup.end_frame;
|
||||
this.cur_fg_frame_callback = fgroup.frame_callback;
|
||||
this.cur_fg_think_callback = fgroup.think_callback;
|
||||
this.set_framegroup_duration(fgroup.duration);
|
||||
|
||||
// Start the animation now
|
||||
this.cur_fg_frame_callback_is_start_frame = true;
|
||||
this.cur_fg_frame_callback_is_end_frame = false;
|
||||
this.frame = this.cur_fg_start_frame;
|
||||
this.cur_fg_start_time = time;
|
||||
this.cur_fg_frame_callback_next_frame = this.cur_fg_start_frame;
|
||||
// print("Done setting framegroup! Start frame: ");
|
||||
// print(ftos(fgroup.start_frame));
|
||||
// print(", End frame: ");
|
||||
// print(ftos(fgroup.end_frame));
|
||||
// print("Cur frame: ");
|
||||
// print(ftos(this.frame));
|
||||
// print(", start_frame: ");
|
||||
// print(ftos(this.cur_fg_start_frame));
|
||||
// print(", end_frame: ");
|
||||
// print(ftos(this.cur_fg_end_frame));
|
||||
// print(", cur_frame: ");
|
||||
// print(ftos(cur_frame));
|
||||
// print(", next frame callback: ");
|
||||
// print(ftos(this.cur_fg_frame_callback_next_frame));
|
||||
// print(", duration: ");
|
||||
// print(ftos(this.cur_fg_duration));
|
||||
// print("\n");
|
||||
};
|
||||
|
||||
// TODO - round frame to nearest frame?
|
||||
|
||||
virtual void () fg_die = {};
|
||||
virtual void () fg_walk = {};
|
||||
virtual void () fg_attack = {};
|
||||
virtual void () fg_idle = {};
|
||||
|
||||
|
||||
virtual void (float dist) do_walk_to_goal = {
|
||||
if(dist == 0) {
|
||||
return;
|
||||
// If the animation is playing forward (low frame num to high frame num)
|
||||
if(this.cur_fg_end_frame > this.cur_fg_start_frame) {
|
||||
if(cur_frame >= this.cur_fg_frame_callback_next_frame) {
|
||||
if(cur_frame >= this.cur_fg_end_frame + 1) {
|
||||
this.cur_fg_frame_callback_is_end_frame = true;
|
||||
}
|
||||
this.cur_fg_frame_callback(this); // FIXME - This can override and invoke new things, need to be careful
|
||||
// Callback may have assigned new framegroup
|
||||
if(this.cur_fg_idx == cur_framegroup_idx) {
|
||||
this.cur_fg_frame_callback_is_start_frame = false;
|
||||
this.cur_fg_frame_callback_is_end_frame = false;
|
||||
this.cur_fg_frame_callback_next_frame = floor(cur_frame) + 1;
|
||||
}
|
||||
// print("\tSet next frame callback frame to: ");
|
||||
// print(ftos(this.cur_fg_frame_callback_next_frame));
|
||||
// print("\n");
|
||||
}
|
||||
}
|
||||
|
||||
vector goal_pos = path_pos;
|
||||
if(this.path_target != world) {
|
||||
goal_pos = this.path_target.origin;
|
||||
// else the animation is playing in reverse (high frame num to low frame num)
|
||||
else {
|
||||
if(cur_frame <= this.cur_fg_frame_callback_next_frame) {
|
||||
if(cur_frame <= this.cur_fg_end_frame - 1) {
|
||||
this.cur_fg_frame_callback_is_end_frame = true;
|
||||
}
|
||||
this.cur_fg_frame_callback(this);
|
||||
// Callback may have assigned new framegroup
|
||||
if(this.cur_fg_idx == cur_fg_idx) {
|
||||
this.cur_fg_frame_callback_is_start_frame = false;
|
||||
this.cur_fg_frame_callback_is_end_frame = false;
|
||||
this.cur_fg_frame_callback_next_frame = ceil(cur_frame) - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Callback may have assigned new framegroup
|
||||
// if(this.cur_fg_idx == cur_fg_idx) {
|
||||
this.cur_fg_think_callback(this);
|
||||
// }
|
||||
}
|
||||
this.nextthink = time + this.think_delta_time;
|
||||
};
|
||||
|
||||
this.ideal_yaw = vectoyaw(goal_pos - this.origin);
|
||||
ChangeYaw();
|
||||
vector new_velocity;
|
||||
float dist_to_goal = vlen(goal_pos - this.origin);
|
||||
if(dist > dist_to_goal) {
|
||||
dist = dist_to_goal;
|
||||
}
|
||||
new_velocity = normalize(goal_pos - this.origin) * dist;
|
||||
new_velocity_z = this.velocity_z;
|
||||
this.velocity = new_velocity;
|
||||
};
|
||||
void (float duration) AI_Chase::set_framegroup_duration = {
|
||||
// If no current framegroup, stop
|
||||
if(this.cur_fg_start_time < 0) {
|
||||
print("Warning: Attempted to set framegroup duration without an active framegroup.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
this.cur_fg_duration = duration;
|
||||
// --------------------------------------------------------------------
|
||||
// Adjust think_delta_time so no frames are skipped
|
||||
// --------------------------------------------------------------------
|
||||
float n_anim_frames = fabs(this.cur_fg_end_frame - this.cur_fg_start_frame);
|
||||
float time_per_frame = duration / n_anim_frames;
|
||||
// If faster than 10FPS, make thinks be called more often to not miss a frame callback
|
||||
if(time_per_frame < 0.10) {
|
||||
this.think_delta_time = time_per_frame * 0.5;
|
||||
}
|
||||
else {
|
||||
this.think_delta_time = 0.1;
|
||||
}
|
||||
// --------------------------------------------------------------------
|
||||
};
|
||||
|
||||
void (framegroup fgroup) AI_Chase::set_framegroup = {
|
||||
// Increment framegroup counter. Reset every 100, we only use this to check when it has changed
|
||||
this.cur_fg_idx = (this.cur_fg_idx + 1) % 100;
|
||||
|
||||
// FIXME - frame callback isn't guaranteed to be called for every frame because sometimes we skip over frames
|
||||
// FIXME - I should modify the think delta time to run at 80% of the time between frames, or 0.10 seconds, whichever is greater.
|
||||
// FIXME - That way, if are playing an animation really fast, it'll call think more often
|
||||
this.cur_fg_start_frame = fgroup.start_frame;
|
||||
this.cur_fg_end_frame = fgroup.end_frame;
|
||||
this.cur_fg_frame_callback = fgroup.frame_callback;
|
||||
this.cur_fg_think_callback = fgroup.think_callback;
|
||||
this.set_framegroup_duration(fgroup.duration);
|
||||
|
||||
// Start the animation now
|
||||
this.cur_fg_frame_callback_is_start_frame = true;
|
||||
this.cur_fg_frame_callback_is_end_frame = false;
|
||||
this.frame = this.cur_fg_start_frame;
|
||||
this.cur_fg_start_time = time;
|
||||
this.cur_fg_frame_callback_next_frame = this.cur_fg_start_frame;
|
||||
// print("Done setting framegroup! Start frame: ");
|
||||
// print(ftos(fgroup.start_frame));
|
||||
// print(", End frame: ");
|
||||
// print(ftos(fgroup.end_frame));
|
||||
// print("\n");
|
||||
};
|
||||
|
||||
|
||||
void (float dist) AI_Chase::do_walk_to_goal = {
|
||||
if(dist == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
vector goal_pos = path_pos;
|
||||
if(this.path_target != world) {
|
||||
goal_pos = this.path_target.origin;
|
||||
}
|
||||
|
||||
|
||||
class AI_Zombie : AI_Chase {
|
||||
entity enemy; // If near, attack
|
||||
|
||||
// Constructor. Called when calling `spawn(AI_Zombie);`
|
||||
// virtual void() AI_Zombie = {};
|
||||
// This should be called explicitly:
|
||||
virtual void(vector org) init;
|
||||
|
||||
virtual void () fg_die;
|
||||
virtual void () fg_walk;
|
||||
virtual void () fg_attack;
|
||||
virtual void () fg_idle;
|
||||
this.ideal_yaw = vectoyaw(goal_pos - this.origin);
|
||||
ChangeYaw();
|
||||
vector new_velocity;
|
||||
float dist_to_goal = vlen(goal_pos - this.origin);
|
||||
if(dist > dist_to_goal) {
|
||||
dist = dist_to_goal;
|
||||
}
|
||||
new_velocity = normalize(goal_pos - this.origin) * dist;
|
||||
new_velocity_z = this.velocity_z;
|
||||
this.velocity = new_velocity;
|
||||
};
|
||||
|
||||
|
||||
|
@ -264,6 +180,7 @@ class AI_Zombie : AI_Chase {
|
|||
|
||||
|
||||
|
||||
|
||||
framegroup fg_zombie_walk_0;
|
||||
framegroup fg_zombie_walk_1;
|
||||
framegroup fg_zombie_walk_2;
|
||||
|
@ -410,18 +327,17 @@ void create_framegroups() {
|
|||
// death2 139 148
|
||||
|
||||
// In blender, it's frames 38 to 53, inclusive
|
||||
// make_empty_framegroup(fg_zombie_walk_0);
|
||||
init_framegroup( 38, 53, 1.0, zombie_walk_frame_callback, zombie_walk_think_callback, fg_zombie_walk_0);
|
||||
init_framegroup( 53, 66, 10.0, SUB_Null, SUB_Null, fg_zombie_walk_1);
|
||||
init_framegroup( 67, 82, 10.0, SUB_Null, SUB_Null, fg_zombie_walk_2);
|
||||
init_framegroup( 116, 129, 10.0, SUB_Null, SUB_Null, fg_zombie_jog_0);
|
||||
init_framegroup( 78, 85, 10.0, SUB_Null, SUB_Null, fg_zombie_run_0);
|
||||
init_framegroup( 0, 12, 10.0, SUB_Null, SUB_Null, fg_zombie_idle_0);
|
||||
init_framegroup( 86, 90, 10.0, SUB_Null, SUB_Null, fg_zombie_attack_0);
|
||||
init_framegroup( 91, 96, 10.0, SUB_Null, SUB_Null, fg_zombie_attack_1);
|
||||
init_framegroup( 123, 133, 10.0, SUB_Null, SUB_Null, fg_zombie_die_0);
|
||||
init_framegroup( 134, 138, 10.0, SUB_Null, SUB_Null, fg_zombie_die_1);
|
||||
init_framegroup( 139, 148, 10.0, SUB_Null, SUB_Null, fg_zombie_die_2);
|
||||
init_framegroup( 53, 66, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_walk_1);
|
||||
init_framegroup( 67, 82, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_walk_2);
|
||||
init_framegroup( 116, 129, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_jog_0);
|
||||
init_framegroup( 78, 85, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_run_0);
|
||||
init_framegroup( 0, 12, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_idle_0);
|
||||
init_framegroup( 86, 90, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_attack_0);
|
||||
init_framegroup( 91, 96, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_attack_1);
|
||||
init_framegroup( 123, 133, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_die_0);
|
||||
init_framegroup( 134, 138, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_die_1);
|
||||
init_framegroup( 139, 148, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_die_2);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@ float sv_navmesh_vert_count;
|
|||
navmesh_poly sv_navmesh_polies[NAV_MAX_POLIES];
|
||||
float sv_navmesh_poly_count;
|
||||
|
||||
// List of all navmesh traversals
|
||||
navmesh_traversal sv_navmesh_traversals[NAV_MAX_TRAVERSALS];
|
||||
float sv_navmesh_traversal_count;
|
||||
|
||||
void () Navmesh_Editor_Logic =
|
||||
{
|
||||
if (!cl_navmesh_edit_mode) {
|
||||
|
@ -62,16 +66,16 @@ void () Navmesh_Editor_Logic =
|
|||
};
|
||||
|
||||
|
||||
void sv_load_navmesh_data()
|
||||
{
|
||||
void sv_load_navmesh_data() {
|
||||
string filepath;
|
||||
float file;
|
||||
|
||||
filepath = strcat(mappath, ".nav");
|
||||
print("Loading server-side navmesh from file \"", filepath, "\"\n");
|
||||
|
||||
file = fopen(filepath,FILE_READ);
|
||||
|
||||
if(file == -1)
|
||||
{
|
||||
|
||||
if(file == -1) {
|
||||
print("Error: file \"",filepath,"\" not found.\n");
|
||||
return;
|
||||
}
|
||||
|
@ -83,8 +87,7 @@ void sv_load_navmesh_data()
|
|||
string line = fgets(file);
|
||||
float vert_count = stof(line);
|
||||
|
||||
if(vert_count > sv_navmesh_verts.length)
|
||||
{
|
||||
if(vert_count > sv_navmesh_verts.length) {
|
||||
print("Error: navmesh file \"",filepath,"\" has an invalid vert count. (" , line, " > ", ftos(sv_navmesh_verts.length),").\n");
|
||||
fclose(file);
|
||||
return;
|
||||
|
@ -98,8 +101,7 @@ void sv_load_navmesh_data()
|
|||
|
||||
|
||||
//Reading all of the vertex positions
|
||||
for(float i = 0; i < sv_navmesh_vert_count; i++)
|
||||
{
|
||||
for(float i = 0; i < sv_navmesh_vert_count; i++) {
|
||||
line = fgets(file);
|
||||
temp = stov(line);
|
||||
sv_navmesh_verts[i].pos.x = temp.x;
|
||||
|
@ -111,47 +113,89 @@ void sv_load_navmesh_data()
|
|||
sv_navmesh_poly_count = stof(fgets(file));
|
||||
|
||||
//The next lines are each polygon
|
||||
for(float i = 0; i < sv_navmesh_poly_count; i++)
|
||||
{
|
||||
//Getting vertex count
|
||||
for(float i = 0; i < sv_navmesh_poly_count; i++) {
|
||||
// Get vertex count
|
||||
sv_navmesh_polies[i].vert_count = stof(fgets(file));
|
||||
//Getting vertices
|
||||
// Get vertices
|
||||
sv_navmesh_polies[i].verts[0] = stof(fgets(file));
|
||||
sv_navmesh_polies[i].verts[1] = stof(fgets(file));
|
||||
sv_navmesh_polies[i].verts[2] = stof(fgets(file));
|
||||
sv_navmesh_polies[i].verts[3] = stof(fgets(file));
|
||||
//Getting polygon center
|
||||
// Get polygon center
|
||||
temp = stov(fgets(file));
|
||||
sv_navmesh_polies[i].center.x = temp.x;
|
||||
sv_navmesh_polies[i].center.y = temp.y;
|
||||
sv_navmesh_polies[i].center.z = temp.z;
|
||||
//Getting link count
|
||||
// Get link count
|
||||
sv_navmesh_polies[i].connected_polies_count = stof(fgets(file));
|
||||
//Getting links
|
||||
// Get links
|
||||
sv_navmesh_polies[i].connected_polies[0] = stof(fgets(file));
|
||||
sv_navmesh_polies[i].connected_polies[1] = stof(fgets(file));
|
||||
sv_navmesh_polies[i].connected_polies[2] = stof(fgets(file));
|
||||
sv_navmesh_polies[i].connected_polies[3] = stof(fgets(file));
|
||||
//Getting link edge left vertex
|
||||
// Get link edge left vertex
|
||||
sv_navmesh_polies[i].connected_polies_left_vert[0] = stof(fgets(file));
|
||||
sv_navmesh_polies[i].connected_polies_left_vert[1] = stof(fgets(file));
|
||||
sv_navmesh_polies[i].connected_polies_left_vert[2] = stof(fgets(file));
|
||||
sv_navmesh_polies[i].connected_polies_left_vert[3] = stof(fgets(file));
|
||||
//Getting link edge right vertex
|
||||
// Get link edge right vertex
|
||||
sv_navmesh_polies[i].connected_polies_right_vert[0] = stof(fgets(file));
|
||||
sv_navmesh_polies[i].connected_polies_right_vert[1] = stof(fgets(file));
|
||||
sv_navmesh_polies[i].connected_polies_right_vert[2] = stof(fgets(file));
|
||||
sv_navmesh_polies[i].connected_polies_right_vert[3] = stof(fgets(file));
|
||||
//Get link edge neighbor entrance edge index
|
||||
// Get link edge neighbor entrance edge index
|
||||
sv_navmesh_polies[i].connected_polies_neighbor_edge_index[0] = stof(fgets(file));
|
||||
sv_navmesh_polies[i].connected_polies_neighbor_edge_index[1] = stof(fgets(file));
|
||||
sv_navmesh_polies[i].connected_polies_neighbor_edge_index[2] = stof(fgets(file));
|
||||
sv_navmesh_polies[i].connected_polies_neighbor_edge_index[3] = stof(fgets(file));
|
||||
|
||||
// If navmesh file version 0.0.0, no traversals
|
||||
// FIXME - Need a better way to do this..
|
||||
if(strcmp(nav_file_version, "0.0.0") != 0) {
|
||||
// Get connected traversals
|
||||
sv_navmesh_polies[i].connected_traversals_count = stof(fgets(file));
|
||||
for(float j = 0; j < sv_navmesh_polies[i].connected_traversals_count; j++) {
|
||||
sv_navmesh_polies[i].connected_traversals[j] = stof(fgets(file));
|
||||
}
|
||||
}
|
||||
//Get polygon doortarget
|
||||
sv_navmesh_polies[i].doortarget = fgets(file);
|
||||
//Get entrance edge
|
||||
sv_navmesh_polies[i].entrance_edge = stoi(fgets(file));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Load Traversals
|
||||
// -----------------------------------------------------------------------
|
||||
// If navmesh file version 0.0.0, no traversals
|
||||
// FIXME - Need a better way to do this..
|
||||
if(strcmp(nav_file_version, "0.0.0") != 0) {
|
||||
//Next line contains the number of traverals
|
||||
sv_navmesh_traversal_count = stof(fgets(file));
|
||||
|
||||
//The next lines are each traversal
|
||||
for(float i = 0; i < sv_navmesh_traversal_count; i++) {
|
||||
line = fgets(file);
|
||||
temp = stov(line);
|
||||
sv_navmesh_traversals[i].start_pos.x = temp.x;
|
||||
sv_navmesh_traversals[i].start_pos.y = temp.y;
|
||||
sv_navmesh_traversals[i].start_pos.z = temp.z;
|
||||
line = fgets(file);
|
||||
temp = stov(line);
|
||||
sv_navmesh_traversals[i].midpoint_pos.x = temp.x;
|
||||
sv_navmesh_traversals[i].midpoint_pos.y = temp.y;
|
||||
sv_navmesh_traversals[i].midpoint_pos.z = temp.z;
|
||||
line = fgets(file);
|
||||
temp = stov(line);
|
||||
sv_navmesh_traversals[i].end_pos.x = temp.x;
|
||||
sv_navmesh_traversals[i].end_pos.y = temp.y;
|
||||
sv_navmesh_traversals[i].end_pos.z = temp.z;
|
||||
sv_navmesh_traversals[i].angle = stof(fgets(file));
|
||||
sv_navmesh_traversals[i].use_midpoint = stof(fgets(file));
|
||||
sv_navmesh_traversals[i].end_poly = stof(fgets(file));
|
||||
}
|
||||
}
|
||||
// -----------------------------------------------------------------------
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
|
|
779
source/server/ai/pathfind_core.qc
Normal file
779
source/server/ai/pathfind_core.qc
Normal file
|
@ -0,0 +1,779 @@
|
|||
/*
|
||||
server/ai/pathfind_core.qc
|
||||
|
||||
Navmesh-based pathfinding
|
||||
|
||||
Copyright (C) 2021 NZ:P Team
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
|
||||
Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
//===========================================================================================================
|
||||
//==================================== Navmesh-based Pathfinding Functions ==================================
|
||||
//===========================================================================================================
|
||||
|
||||
//Calculates the heuristic h score value (Our best guess for how far this node is from the goal node)
|
||||
float sv_pathfind_calc_h_score(float current, float goal)
|
||||
{
|
||||
//FIXME: we could just as easily return vlen()^2 for comparisons... (saves a sqrt operation)
|
||||
return vlen(sv_navmesh_polies[goal].center - sv_navmesh_polies[current].center);
|
||||
}
|
||||
|
||||
//Returns the polygon with the lowest f score from polygons the open set
|
||||
float sv_pathfind_get_lowest_f_score(navmesh_pathfind_result* res)
|
||||
{
|
||||
//TODO: implement a better algorithm for finding the lowest score
|
||||
float best_score = 100000;
|
||||
float best_score_index = -1;
|
||||
|
||||
for(float i = 0; i < sv_navmesh_poly_count; i++)
|
||||
{
|
||||
if(res->poly_set[i] == PATHFIND_POLY_SET_OPEN)
|
||||
{
|
||||
if(res->poly_f_score[i] < best_score)
|
||||
{
|
||||
best_score = res->poly_f_score[i];
|
||||
best_score_index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return best_score_index;
|
||||
}
|
||||
|
||||
void sv_pathfind_clear_result_data(navmesh_pathfind_result* res) {
|
||||
//Technically we only need to iterate over navmesh_poly_count...
|
||||
for(float i = 0; i < NAV_MAX_POLIES; i++) {
|
||||
res->poly_set[i] = PATHFIND_POLY_SET_NONE;
|
||||
res->poly_prev_poly[i] = -1;
|
||||
res->poly_prev_traversal[i] = -1;
|
||||
res->poly_g_score[i] = 0;
|
||||
res->poly_f_score[i] = 0;
|
||||
|
||||
res->path_polygons[i] = -1;
|
||||
res->path_traversals[i] = -1;
|
||||
|
||||
res->portals_left_pos[i].x = 0;
|
||||
res->portals_left_pos[i].y = 0;
|
||||
res->portals_left_pos[i].z = 0;
|
||||
res->portals_right_pos[i].x = 0;
|
||||
res->portals_right_pos[i].y = 0;
|
||||
res->portals_right_pos[i].z = 0;
|
||||
res->portals_traversal[i] = -1;
|
||||
|
||||
res->point_path_points[i].x = 0;
|
||||
res->point_path_points[i].y = 0;
|
||||
res->point_path_points[i].y = 0;
|
||||
res->point_path_traversals[i] = -1;
|
||||
}
|
||||
for(float i = 0;i < NAV_MAX_TRAVERSALS; i++) {
|
||||
res->traversal_set[i] = PATHFIND_POLY_SET_NONE;
|
||||
res->traversal_prev_poly[i] = -1;
|
||||
res->traversal_g_score[i] = 0;
|
||||
res->traversal_f_score[i] = 0;
|
||||
}
|
||||
res->path_length = 0;
|
||||
res->portals_length = 0;
|
||||
res->point_path_length = 0;
|
||||
}
|
||||
|
||||
//Applies a funnel algorithm to the path defined by the array res->result_node_path
|
||||
// and populates pathfind result path
|
||||
void sv_pathfind_smooth_path(vector start_point, vector goal_point, navmesh_pathfind_result* res) {};
|
||||
// void sv_pathfind_smooth_path(vector start_point, vector goal_point, navmesh_pathfind_result* res) {
|
||||
// //Evaluate portals and identify left / right portal vertices
|
||||
// for(float i = 1; i < res->result_node_length; i++)
|
||||
// {
|
||||
// float prev_poly = res->result_node_path[i-1];
|
||||
// float poly = res->result_node_path[i];
|
||||
|
||||
// //TODO: find link index of portal
|
||||
|
||||
// //Finding what index prev_poly is linked to poly
|
||||
|
||||
// float link_index = -1;
|
||||
|
||||
// for(float j = 0; j < sv_navmesh_polies[prev_poly].connected_polies_count; j++)
|
||||
// {
|
||||
// if(sv_navmesh_polies[prev_poly].connected_polies[j] == poly)
|
||||
// {
|
||||
// link_index = j;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// //Use that index to get left and right vertices
|
||||
// res->portals_left_vert[res->portals_length] = sv_navmesh_polies[prev_poly].connected_polies_left_vert[link_index];
|
||||
// res->portals_right_vert[res->portals_length++] = sv_navmesh_polies[prev_poly].connected_polies_right_vert[link_index];
|
||||
// }
|
||||
// //the last left edge / right edge is the goal position
|
||||
|
||||
// /*print("Portals: ");
|
||||
// for(float i = 0; i < res->portals_length; i++)
|
||||
// {
|
||||
// print("[");
|
||||
// print(ftos(i));
|
||||
// print("] = (");
|
||||
// print(ftos(res->portals_left_vert[i]));
|
||||
// print(" , ");
|
||||
// print(ftos(res->portals_right_vert[i]));
|
||||
// print(") , ");
|
||||
// }*/
|
||||
|
||||
// //starting at start_pos (not at start center point)
|
||||
// //get vectors that point to first left edge and first right edge
|
||||
|
||||
// vector funnel_apex = start_point;
|
||||
// float funnel_left_index = 0;
|
||||
// //Index of the funnel's left vertex in the portal list
|
||||
// vector funnel_left = sv_navmesh_verts[res->portals_left_vert[0]].pos;
|
||||
// //Index of the funnel's right vertex in the portal list
|
||||
// float funnel_right_index = 0;
|
||||
// vector funnel_right = sv_navmesh_verts[res->portals_right_vert[0]].pos;
|
||||
|
||||
// vector next_funnel_left = '0 0 0';
|
||||
// vector next_funnel_right = '0 0 0';
|
||||
// vector corner;
|
||||
// int portal_idx;
|
||||
|
||||
|
||||
// float inside_right_edge;
|
||||
// float inside_left_edge;
|
||||
|
||||
// while(1)
|
||||
// {
|
||||
// //print("=============Funnel iteration.==========\n");
|
||||
// //Check if we have reached the end of the portals, consider the goal position as the last portal
|
||||
// //If we are not at the last index of the left portal
|
||||
|
||||
// //Keeping track of whether or not advancing the left edge or right edge narrows the funnel
|
||||
// float advanced_left = FALSE;
|
||||
// float advanced_right = FALSE;
|
||||
|
||||
|
||||
|
||||
// //Consider the end goal point as the last portal
|
||||
// //================ Checking left funnel edge =================
|
||||
// if(funnel_left_index < res->portals_length)
|
||||
// {
|
||||
// //print("Trying to advance left edge.\n");
|
||||
|
||||
// if(funnel_left_index < res->portals_length - 1)
|
||||
// {
|
||||
// next_funnel_left = sv_navmesh_verts[res->portals_left_vert[funnel_left_index+1]].pos;
|
||||
// // print("Next left edge is vert: ",ftos(res->portals_left_vert[funnel_left_index+1],".\n");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // print("Trying next left edge as goal point.\n");
|
||||
// //If funnel_left is pointing to the last portal in the array, consider the goal_point the last portal
|
||||
// next_funnel_left = goal_point;
|
||||
// }
|
||||
|
||||
// inside_right_edge = pathfind_point_is_to_left(funnel_apex,funnel_right,next_funnel_left);
|
||||
// //If the next left edge crosses the current right edge
|
||||
// if(inside_right_edge < 0)
|
||||
// {
|
||||
// //print("next left edge crosses current right edge.\n");
|
||||
// //Add funnel right point to path
|
||||
// // res->result_path[res->result_length++] = funnel_right;
|
||||
// corner = funnel_right;
|
||||
// // ------------------------------------------------------------
|
||||
// // Instead of adding the funnel's corner to the path,
|
||||
// // move away from the corner by some small amount
|
||||
// // ------------------------------------------------------------
|
||||
// // Find the first portal along the path with this corner
|
||||
// portal_idx = funnel_right_index;
|
||||
// for(int i = funnel_right_index; i >= 0; i--) {
|
||||
// if(res->portals_right_vert[i] == res->portals_right_vert[funnel_right_index]) {
|
||||
// portal_idx = i;
|
||||
// continue;
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// // For every portal with this corner, add a point to the path
|
||||
// for(int i = portal_idx; i <= funnel_right_index; i++) {
|
||||
// // Find the vertex opposite this portal edge
|
||||
// vector opposite_corner = sv_navmesh_verts[res->portals_left_vert[i]].pos;
|
||||
// // Move 20 qu or 20% of the edge length, whichever is smaller:
|
||||
// float edge_len = vlen(opposite_corner - corner);
|
||||
// vector edge_dir = normalize(opposite_corner - corner);
|
||||
// vector delta_corner = edge_dir * min(edge_len * 0.2, 20);
|
||||
// res->result_path[res->result_length++] = corner + delta_corner;
|
||||
// }
|
||||
// // ------------------------------------------------------------
|
||||
// // res->result_path[res->result_length++] = corner;
|
||||
|
||||
|
||||
// // TODO - Move 10% of the way the funnel's right portal vert to the left
|
||||
|
||||
// //Check if we are at the end of our portal list
|
||||
// if(funnel_right_index >= res->portals_length - 1)
|
||||
// {
|
||||
// res->result_path[res->result_length++] = goal_point;
|
||||
// return;
|
||||
// }
|
||||
|
||||
|
||||
// //Restart algorithm with the portal after the right point
|
||||
// funnel_apex = funnel_right;
|
||||
|
||||
// funnel_left_index = funnel_right_index + 1;
|
||||
// funnel_left = sv_navmesh_verts[res->portals_left_vert[funnel_left_index]].pos;
|
||||
// funnel_right_index = funnel_right_index + 1;
|
||||
// funnel_right = sv_navmesh_verts[res->portals_right_vert[funnel_right_index]].pos;
|
||||
// continue;
|
||||
// }
|
||||
// //If the next left edge is in the funnel
|
||||
// inside_left_edge = pathfind_point_is_to_left(funnel_left,funnel_apex,next_funnel_left);
|
||||
// if(inside_left_edge >= 0 && inside_right_edge >= 0)
|
||||
// {
|
||||
// //print("next left edge is in the funnel.\n");
|
||||
// //Advance the left edge
|
||||
// funnel_left = next_funnel_left;
|
||||
// funnel_left_index++;
|
||||
|
||||
// advanced_left = TRUE;
|
||||
|
||||
// //Check if the portal we just added was the goal point (will be after the list)
|
||||
// if(funnel_left_index >= res->portals_length)
|
||||
// {
|
||||
// res->result_path[res->result_length++] = goal_point;
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// //================ Checking right funnel edge =================
|
||||
// if(funnel_right_index < res->portals_length)
|
||||
// {
|
||||
// //print("Trying to advance right edge:.\n");
|
||||
|
||||
// if(funnel_right_index < res->portals_length - 1)
|
||||
// {
|
||||
// next_funnel_right = sv_navmesh_verts[res->portals_right_vert[funnel_right_index+1]].pos;
|
||||
// //print("Next right edge is vert: ",ftos(res->portals_right_vert[funnel_right_index+1]),".\n");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //print("Trying next right edge as goal point.\n");
|
||||
// //If funnel_right is pointing to the last portal in the array, consider the goal_point the last portal
|
||||
// next_funnel_right = goal_point;
|
||||
// }
|
||||
|
||||
// //If the next right edge crosses the current left edge
|
||||
// inside_left_edge = pathfind_point_is_to_left(funnel_left,funnel_apex,next_funnel_right);
|
||||
// if(inside_left_edge < 0)
|
||||
// {
|
||||
// //print("next right edge crosses current left edge.\n");
|
||||
// //Add funnel left point to path
|
||||
// // res->result_path[res->result_length++] = funnel_left;
|
||||
// corner = funnel_left;
|
||||
// // ------------------------------------------------------------
|
||||
// // Instead of adding the funnel's corner to the path,
|
||||
// // move away from the corner by some small amount
|
||||
// // ------------------------------------------------------------
|
||||
// // Find the first portal along the path with this corner
|
||||
// portal_idx = funnel_left_index;
|
||||
// for(int i = funnel_left_index; i >= 0; i--) {
|
||||
// if(res->portals_left_vert[i] == res->portals_left_vert[funnel_left_index]) {
|
||||
// portal_idx = i;
|
||||
// continue;
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// // For every portal with this corner, add a point to the path
|
||||
// for(int i = portal_idx; i <= funnel_left_index; i++) {
|
||||
// // Find the vertex opposite this portal edge
|
||||
// vector opposite_corner = sv_navmesh_verts[res->portals_right_vert[i]].pos;
|
||||
// // Move 20 qu or 20% of the edge length, whichever is smaller:
|
||||
// float edge_len = vlen(opposite_corner - corner);
|
||||
// vector edge_dir = normalize(opposite_corner - corner);
|
||||
// vector delta_corner = edge_dir * min(edge_len * 0.2, 20);
|
||||
// res->result_path[res->result_length++] = corner + delta_corner;
|
||||
// }
|
||||
// // ------------------------------------------------------------
|
||||
// // res->result_path[res->result_length++] = corner;
|
||||
|
||||
// //Check if we are at the end of our portal list
|
||||
// if(funnel_left_index >= res->portals_length - 1)
|
||||
// {
|
||||
// res->result_path[res->result_length++] = goal_point;
|
||||
// return;
|
||||
// }
|
||||
|
||||
|
||||
// //Restart algorithm with the portal after the right point
|
||||
// funnel_apex = funnel_left;
|
||||
|
||||
// funnel_right_index = funnel_left_index + 1;
|
||||
// funnel_right = sv_navmesh_verts[res->portals_right_vert[funnel_right_index]].pos;
|
||||
// funnel_left_index = funnel_left_index + 1;
|
||||
// funnel_left = sv_navmesh_verts[res->portals_left_vert[funnel_left_index]].pos;
|
||||
// continue;
|
||||
// }
|
||||
// //If the next right edge is in the funnel
|
||||
// inside_right_edge = pathfind_point_is_to_left(funnel_apex,funnel_right,next_funnel_right);
|
||||
// if(inside_left_edge >= 0 && inside_right_edge >= 0)
|
||||
// {
|
||||
// //print("next right edge is in the funnel.\n");
|
||||
// //Advance the left edge
|
||||
// funnel_right = next_funnel_right;
|
||||
// funnel_right_index++;
|
||||
|
||||
// advanced_right = TRUE;
|
||||
|
||||
// //Check if the portal we just added was the goal point (will be after the list)
|
||||
// if(funnel_right_index >= res->portals_length)
|
||||
// {
|
||||
// res->result_path[res->result_length++] = goal_point;
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if(advanced_left == FALSE && advanced_right == FALSE)
|
||||
// {
|
||||
// //print("Hit funnel freeze condition.\n");
|
||||
|
||||
// float left_vert = -1;
|
||||
// float left_type = -1;
|
||||
// float left_portal_index = -1;//index of the vertex in the list of portals
|
||||
|
||||
// float right_vert = -1;
|
||||
// float right_type = -1;
|
||||
// float right_portal_index = -1;//index of the vertex in the list of portals
|
||||
|
||||
// float crossed = 1;//Indicates the vert crossed the funnel
|
||||
// float contained = 2;//Indicates a vert is in the funnel
|
||||
|
||||
// float last_vert = -1;//used to skip calculating the same vertex more than once
|
||||
|
||||
|
||||
// //Find the closest vertex in the portal left with the lowest index that crosses the funnel or is in the funnel
|
||||
// for(float i = funnel_left_index + 1; i < res->portals_length + 1; i++)
|
||||
// {
|
||||
// if(i < res->portals_length - 1)
|
||||
// {
|
||||
// //Don't calculate the same vertex again
|
||||
// if(last_vert == res->portals_left_vert[i])
|
||||
// continue;
|
||||
// last_vert = res->portals_left_vert[i];
|
||||
// next_funnel_left = sv_navmesh_verts[last_vert].pos;
|
||||
// }
|
||||
// else//consider goal pos as last portal edge
|
||||
// {
|
||||
// next_funnel_left = goal_point;
|
||||
// }
|
||||
|
||||
// inside_right_edge = pathfind_point_is_to_left(funnel_apex,funnel_right,next_funnel_left);
|
||||
|
||||
// //If the left vertex crosses the funnel (left vertex is outside the funnel's right edge)
|
||||
// if(inside_right_edge < 0)
|
||||
// {
|
||||
// left_vert = res->portals_left_vert[i];
|
||||
// left_type = crossed;
|
||||
// break;
|
||||
// }
|
||||
|
||||
// inside_left_edge = pathfind_point_is_to_left(funnel_left,funnel_apex,next_funnel_left);
|
||||
|
||||
// //If the left vertex is within the funnel
|
||||
// if(inside_left_edge >= 0 && inside_right_edge >= 0)
|
||||
// {
|
||||
// left_vert = res->portals_left_vert[i];
|
||||
// left_type = contained;
|
||||
// left_portal_index = i;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// last_vert = -1;
|
||||
|
||||
// //Find the closest vertex in the portal right with the lowest index that crosses the funnel or is in the funnel
|
||||
// for(float i = funnel_right_index + 1; i < res->portals_length; i++)
|
||||
// {
|
||||
// if(i < res->portals_length - 1)
|
||||
// {
|
||||
// //Don't calculate the same vertex again
|
||||
// if(last_vert == res->portals_right_vert[i])
|
||||
// continue;
|
||||
// last_vert = res->portals_right_vert[i];
|
||||
// next_funnel_right = sv_navmesh_verts[last_vert].pos;
|
||||
// }
|
||||
// else//consider goal pos as last portal edge
|
||||
// {
|
||||
// next_funnel_right = goal_point;
|
||||
// }
|
||||
|
||||
// inside_left_edge = pathfind_point_is_to_left(funnel_left,funnel_apex,next_funnel_right);
|
||||
|
||||
// //If the right vertex crosses the funnel (right vertex is outside the funnel's left edge)
|
||||
// if(inside_left_edge < 0)
|
||||
// {
|
||||
// right_vert = res->portals_right_vert[i];
|
||||
// right_type = crossed;
|
||||
// break;
|
||||
// }
|
||||
|
||||
// inside_right_edge = pathfind_point_is_to_left(funnel_apex,funnel_right,next_funnel_right);
|
||||
|
||||
// //If the right vertex is within the funnel
|
||||
// if(inside_left_edge >= 0 && inside_right_edge >= 0)
|
||||
// {
|
||||
// right_vert = res->portals_right_vert[i];
|
||||
// right_type = contained;
|
||||
// right_portal_index = i;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// //If no vertices were found, goal is reachable from here
|
||||
// if(left_vert == -1 && right_vert == -1)
|
||||
// {
|
||||
// res->result_path[res->result_length++] = goal_point;
|
||||
// return;
|
||||
// }
|
||||
|
||||
|
||||
// float use_left;
|
||||
|
||||
// //If both verts were found
|
||||
// //Find which vertex has a lower index
|
||||
// if(left_vert != -1 && right_vert != -1)
|
||||
// {
|
||||
// if(left_portal_index < right_portal_index)
|
||||
// {
|
||||
// use_left = TRUE;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// use_left = FALSE;
|
||||
// }
|
||||
// }
|
||||
|
||||
// //if left vert was found
|
||||
// else if(left_vert != -1)
|
||||
// {
|
||||
// use_left = TRUE;
|
||||
// }
|
||||
// //else right vert was found
|
||||
// else
|
||||
// {
|
||||
// use_left = FALSE;
|
||||
// }
|
||||
|
||||
// if(use_left)
|
||||
// {
|
||||
// if(left_type == crossed)
|
||||
// {
|
||||
// //Place corner at current right funnel edge
|
||||
// res->result_path[res->result_length++] = funnel_right;
|
||||
|
||||
// //Restart algorithm with the portal after the right point
|
||||
// funnel_apex = funnel_right;
|
||||
|
||||
// //Check if portal after this portal is goal:
|
||||
// funnel_right_index++;
|
||||
// if(funnel_right_index >= res->portals_length)
|
||||
// {
|
||||
// res->result_path[res->result_length++] = goal_point;
|
||||
// return;
|
||||
// }
|
||||
|
||||
// funnel_right = sv_navmesh_verts[res->portals_right_vert[funnel_right_index]].pos;
|
||||
// funnel_left_index = funnel_right_index;
|
||||
// funnel_left = sv_navmesh_verts[res->portals_left_vert[funnel_left_index]].pos;
|
||||
// continue;
|
||||
// }
|
||||
// else//in funnel
|
||||
// {
|
||||
// //If the next funnel right is the goal, we are done
|
||||
// //Check if portal after this portal is goal:
|
||||
// if(right_portal_index >= res->portals_length)
|
||||
// {
|
||||
// res->result_path[res->result_length++] = goal_point;
|
||||
// return;
|
||||
// }
|
||||
// //Update current left funnel edge to use this vertex
|
||||
// funnel_left = next_funnel_left;
|
||||
// funnel_left_index = left_portal_index;
|
||||
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// else//use right
|
||||
// {
|
||||
// if(right_type == crossed)
|
||||
// {
|
||||
// //Place corner at current left funnel edge
|
||||
// res->result_path[res->result_length++] = funnel_left;
|
||||
|
||||
// //Restart algorithm with the portal after the left point
|
||||
// funnel_apex = funnel_left;
|
||||
|
||||
// //Check if portal after this portal is goal:
|
||||
// funnel_left_index++;
|
||||
// if(funnel_left_index >= res->portals_length)
|
||||
// {
|
||||
// res->result_path[res->result_length++] = goal_point;
|
||||
// return;
|
||||
// }
|
||||
|
||||
// funnel_left = sv_navmesh_verts[res->portals_left_vert[funnel_left_index]].pos;
|
||||
// funnel_right_index = funnel_left_index;
|
||||
// funnel_right = sv_navmesh_verts[res->portals_right_vert[funnel_right_index]].pos;
|
||||
// continue;
|
||||
// }
|
||||
// else//in funnel
|
||||
// {
|
||||
// //If the next funnel right is the goal, we are done
|
||||
// //Check if portal after this portal is goal:
|
||||
// if(right_portal_index >= res->portals_length)
|
||||
// {
|
||||
// res->result_path[res->result_length++] = goal_point;
|
||||
// return;
|
||||
// }
|
||||
// //Update current right funnel edge to use this vertex
|
||||
// funnel_right = next_funnel_right;
|
||||
// funnel_right_index = right_portal_index;
|
||||
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// //print("==========================\n");
|
||||
// }
|
||||
// }
|
||||
|
||||
//Evaluates the path that is found in the pathfinding algorithm and populates an array with the nodes in the path from start to goal
|
||||
void sv_pathfind_trace_path(float start, float goal, navmesh_pathfind_result* res) {}
|
||||
// void sv_pathfind_trace_path(float start, float goal, navmesh_pathfind_result* res)
|
||||
// {
|
||||
// float current = start;
|
||||
// //Count the length of the path (how many polygons the path traverses)
|
||||
// current = goal;
|
||||
// res->result_node_length = 1;
|
||||
// do
|
||||
// {
|
||||
// //print("Poly ");
|
||||
// //print(ftos(current));
|
||||
// //print(" came from ");
|
||||
// //print(ftos(res->poly_prev[current]));
|
||||
// //print(".\n");
|
||||
// current = res->poly_prev[current];
|
||||
// res->result_node_length++;
|
||||
// } while(current != start);
|
||||
|
||||
|
||||
// //Starting at goal waypoint, add the traversed waypoints to the result path in reverse order
|
||||
// current = goal;
|
||||
// for(float i = 0; i < res->result_node_length; i++)
|
||||
// {
|
||||
// res->result_node_path[res->result_node_length - 1 - i] = current;
|
||||
// current = res->poly_prev[current];
|
||||
// }
|
||||
|
||||
|
||||
// // print("Pathfind success, path length: ");
|
||||
// // print(ftos(res->result_node_length));
|
||||
// // print(", Path: [");
|
||||
// // for(float i = 0; i < res->result_node_length; i++)
|
||||
// // {
|
||||
// // if(i > 0)
|
||||
// // print(" , ");
|
||||
// // print(ftos(res->result_node_path[i]));
|
||||
// // }
|
||||
// // print(" ].\n");
|
||||
// }
|
||||
|
||||
//Accepts start polygon and goal polygon
|
||||
//Returns 1 on success.
|
||||
//Returns 0 on fail.
|
||||
float sv_navmesh_pathfind_start(float start, float goal, vector start_pos, vector end_pos, navmesh_pathfind_result* res) {}
|
||||
// float sv_navmesh_pathfind_start(float start, float goal, vector start_pos, vector end_pos, navmesh_pathfind_result* res) {
|
||||
// if(start == -1) {
|
||||
// //print("Error: pathfind start node invalid.\n");
|
||||
// return 0;
|
||||
// }
|
||||
// if(goal == -1) {
|
||||
// //print("Error: pathfind goal node invalid.\n");
|
||||
// return 0;
|
||||
// }
|
||||
// if(start == goal) {
|
||||
// //Calculating node path
|
||||
// res->result_node_path[0] = start;
|
||||
// res->result_node_length = 1;
|
||||
// // print("Pathind success: trivial case (start = goal).\n");
|
||||
|
||||
// //Calculating vector based path (go directly to goal)
|
||||
// res->result_path[0].x = end_pos.x;
|
||||
// res->result_path[0].y = end_pos.y;
|
||||
// res->result_path[0].z = end_pos.z;
|
||||
// res->result_length = 1;
|
||||
// return 1;
|
||||
// }
|
||||
|
||||
// //Clearing previous data
|
||||
// sv_pathfind_clear_result_data(res);
|
||||
|
||||
// //Adding start polygon to the open set
|
||||
// res->poly_set[start] = PATHFIND_POLY_SET_OPEN;
|
||||
// res->poly_g_score[start] = 0;
|
||||
// res->poly_f_score[start] = 0 + sv_pathfind_calc_h_score(start , goal);
|
||||
|
||||
// //Fields that need to be set:
|
||||
// //res->poly_set[NAV_MAX_POLIES];
|
||||
// //res->prev_poly[NAV_MAX_POLIES];
|
||||
// //res->poly_g_score[NAV_MAX_POLIES];
|
||||
// //res->poly_f_score[NAV_MAX_POLIES];
|
||||
|
||||
// // print("Pathfind init. Start: ");
|
||||
// // print(ftos(start));
|
||||
// // print(" , Goal: ");
|
||||
// // print(ftos(goal));
|
||||
// // print(".\n");
|
||||
|
||||
// float current = start;
|
||||
|
||||
// float pathfind_success = FALSE;
|
||||
|
||||
// while(current != -1) {
|
||||
// if(current == goal) {
|
||||
// //print("Current is now goal. Breaking.\n");
|
||||
// pathfind_success = TRUE;
|
||||
// break;
|
||||
// }
|
||||
// //Add current node to the closed set
|
||||
// res->poly_set[current] = PATHFIND_POLY_SET_CLOSED;
|
||||
// //Add connected nodes to the open set
|
||||
// for(float i = 0; i < sv_navmesh_polies[current].connected_polies_count; i++) {
|
||||
// float neighbor = sv_navmesh_polies[current].connected_polies[i];
|
||||
|
||||
// //print("Checking poly ");
|
||||
// //print(ftos(current));
|
||||
// //print("'s neighbor ");
|
||||
// //print(ftos(neighbor));
|
||||
// //print(".\n");
|
||||
|
||||
// // ----------------------------------------------------------------
|
||||
// // Door check
|
||||
// // ----------------------------------------------------------------
|
||||
// // If this polygon references a door:
|
||||
// if(sv_navmesh_polies[neighbor].doortarget != "") {
|
||||
// entity door;
|
||||
// door = find(world, wayTarget, sv_navmesh_polies[neighbor].doortarget);
|
||||
// if(door != world) {
|
||||
// // If the referenced door is closed, don't consider this polygon.
|
||||
// if(door.state == STATE_BOTTOM) {
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // ----------------------------------------------------------------
|
||||
|
||||
// // ----------------------------------------------------------------
|
||||
// // Entrance edge
|
||||
// // ----------------------------------------------------------------
|
||||
// // Check if we can enter the neighbor polygon from the current polygon
|
||||
// // across the edge connecting the current and neighbor polygons.
|
||||
|
||||
// // If entrance_edge != -1, we can only enter the polygon from the edge at index "entrance_edge"
|
||||
// if(sv_navmesh_polies[neighbor].entrance_edge != -1) {
|
||||
// // Check if the edge we're crossing from current to neighbor is the entrance edge
|
||||
// if(sv_navmesh_polies[neighbor].entrance_edge != sv_navmesh_polies[current].connected_polies_neighbor_edge_index[i]) {
|
||||
// // If it's not the entrance edge, skip this neighbor.
|
||||
// // We can't walk from current to neighbor.
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// // ----------------------------------------------------------------
|
||||
|
||||
|
||||
// if(res->poly_set[neighbor] != PATHFIND_POLY_SET_CLOSED) {
|
||||
// //print("Neighbor is not in closed list.\n");
|
||||
// //Calculate tentative f score
|
||||
// //Calculate tentative g score (distance from start to current + distance from current to neighbor)
|
||||
// float tentative_g_score = res->poly_g_score[current] + vlen(sv_navmesh_polies[neighbor].center - sv_navmesh_polies[current].center);
|
||||
|
||||
// if(res->poly_set[neighbor] != PATHFIND_POLY_SET_OPEN || tentative_g_score < res->poly_g_score[neighbor]) {
|
||||
// //print("Neighbor is not in open list, or a better g score has been found.\n");
|
||||
// //Adding neighbor to open set
|
||||
// res->poly_set[neighbor] = PATHFIND_POLY_SET_OPEN;
|
||||
|
||||
// //Updating scores for neighbor node
|
||||
// float tentative_f_score = tentative_g_score + sv_pathfind_calc_h_score(neighbor , goal);
|
||||
// res->poly_g_score[neighbor] = tentative_g_score;
|
||||
// res->poly_f_score[neighbor] = tentative_f_score;
|
||||
|
||||
// //Linking neighbor node to current node (for tracing path)
|
||||
// res->poly_prev[neighbor] = current;
|
||||
|
||||
// //print("Assigning Poly ");
|
||||
// //print(ftos(neighbor));
|
||||
// //print(" came from ");
|
||||
// //print(ftos(current));
|
||||
// //print(".\n");
|
||||
// }
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
// current = sv_pathfind_get_lowest_f_score(res);
|
||||
// }
|
||||
|
||||
|
||||
// //Tracing the pathfind results
|
||||
// if(pathfind_success == TRUE) {
|
||||
// sv_pathfind_trace_path(start,goal,res);
|
||||
// sv_pathfind_smooth_path(start_pos,end_pos,res);
|
||||
|
||||
// return 1;
|
||||
// }
|
||||
// print("Pathfind fail");
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
float sv_navmesh_pathfind(entity from, entity to) {}
|
||||
// float sv_navmesh_pathfind(entity from, entity to) {
|
||||
// // TODO ... How do I get...?
|
||||
// // Every AI_Chase entity gets an integer
|
||||
// // TODO - In pathfinding, each entity needs a place to store its results...
|
||||
// // TODO - I should really make a MAX_AI number
|
||||
// // TODO - I can have an array of pre-allocated pathfind results, and dynamically use them?
|
||||
// // TODO - Or maybe I have one reserved per AI, and one supplemental one reesrved per AI...
|
||||
|
||||
|
||||
|
||||
// // FIXME - Will need to pre-allocate a certain number of AI_Chase ents and reuse them... or maybe
|
||||
// // FIXME I need to pre-allocate a certain number of zombies, and dogs?
|
||||
// // FIXME I wanted to subclass zombies to crawlers to override their behaviors...
|
||||
|
||||
|
||||
// // We can perform pathfinding logic without losing our current path.
|
||||
|
||||
// // TODO - In pathfinding, I need a reference to the calling entity to know which traversal types this entity can use
|
||||
// AI_Chase chase_ent = (AI_Chase) from;
|
||||
// navmesh_pathfind_result* res = &(sv_zombie_pathfind_result[chase_ent.pathfind_result_idx]);
|
||||
|
||||
// float start = sv_navmesh_get_containing_poly(from.origin);
|
||||
// float goal = sv_navmesh_get_containing_poly(to.origin);
|
||||
|
||||
// return sv_navmesh_pathfind_start(start,goal,from.origin,to.origin,res);
|
||||
// }
|
||||
|
||||
//===========================================================================================================
|
|
@ -1593,7 +1593,7 @@ float() getMaxZombies =
|
|||
if (dogWave == true) {
|
||||
return 2;
|
||||
} else {
|
||||
return MAX_ZOMB;
|
||||
return MAX_ZOMBIES;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -230,9 +230,9 @@ void() SUB_Null2 = {};
|
|||
#define VEC_VIEW_OFS '0 0 32'
|
||||
|
||||
#ifndef HANDHELD
|
||||
#define MAX_ZOMB 24
|
||||
#define MAX_ZOMBIES 24
|
||||
#else
|
||||
#define MAX_ZOMB 12
|
||||
#define MAX_ZOMBIES 12
|
||||
#endif // HANDHELD
|
||||
|
||||
vector trace_plane_normal;
|
||||
|
@ -477,14 +477,99 @@ string world_fog;
|
|||
#ifdef PC
|
||||
void() Navmesh_Editor_Logic;
|
||||
void() sv_load_navmesh_data;
|
||||
//One struct for temp pathfinding calculations per zombie
|
||||
//navmesh_pathfind_result sv_zombie_pathfind_result[MAX_ZOMBIES];
|
||||
navmesh_pathfind_result *sv_zombie_pathfind_result; // Used as primary. The one that's followed
|
||||
navmesh_pathfind_result *sv_zombie_pathfind_result_aux; // Used as secondary, so we can pathfind without losing current path
|
||||
#endif // PC
|
||||
|
||||
// //One struct for temp pathfinding calculations per zombie
|
||||
// //pathfind_result sv_zombie_pathfind_result[MAX_ZOMBIES];
|
||||
// pathfind_result *sv_zombie_pathfind_result;
|
||||
//=================================================================
|
||||
//========================== AI defs ==============================
|
||||
var struct framegroup {
|
||||
float start_frame;
|
||||
float end_frame;
|
||||
float duration;
|
||||
// void(entity) start_callback; // Called at the first frame of the range
|
||||
void(entity) frame_callback; // Called at every frame of the range (after start / finish callbacks)
|
||||
// void(entity) end_callback; // Called at the last frame of the range
|
||||
// FIXME - Increasing the size of this framegroup by one more function pointer causes chaos...
|
||||
// I assume I run out of space to pass framegroups along as arguments at any point...
|
||||
// It starts to write into weird memory locations, like overriding self / world... things just break down.
|
||||
// TODO - How can I refactor this to work?
|
||||
void(entity) think_callback; // Called at each think invocation
|
||||
// -- Maybe I can get rid of start / end callbacks? idk
|
||||
// I should think about how I actually plan on using these...
|
||||
|
||||
// TODO - Get rid of start / end callback, add fields to the AI_Chase class that denotes
|
||||
// TODO - These callbacks will be executed at the same time as frame anyways.
|
||||
};
|
||||
|
||||
class AI_Chase : entity {
|
||||
// framegroup_registry *th_reg;
|
||||
entity path_target; // If specified, path towards entity
|
||||
vector path_pos; // Otherwise, path towards location specified
|
||||
float think_delta_time; // Delta time (seconds) between each `this.think();`
|
||||
|
||||
// ------------------------------
|
||||
// Current Framegroup value vars
|
||||
// ------------------------------
|
||||
float cur_fg_start_time;
|
||||
float cur_fg_start_frame;
|
||||
float cur_fg_end_frame;
|
||||
float cur_fg_duration;
|
||||
// void() endanimfunc = SUB_Null;
|
||||
// virtual void(entity) cur_fg_start_callback = SUB_Null;
|
||||
virtual void(entity) cur_fg_frame_callback = (void(entity)) SUB_Null;
|
||||
// virtual void(entity) cur_fg_end_callback = SUB_Null;
|
||||
virtual void(entity) cur_fg_think_callback = (void(entity)) SUB_Null;
|
||||
// ------------------------------
|
||||
// Framegroup state vars
|
||||
// ------------------------------
|
||||
float cur_fg_idx; // Counter for checking when the framegroup has been changed
|
||||
float cur_fg_frame_callback_next_frame;
|
||||
float cur_fg_frame_callback_is_start_frame; // Is set to true when performing cur_fg_frame_callback for the first framegroup frame
|
||||
float cur_fg_frame_callback_is_end_frame; // Is set to true when performing cur_fg_frame_callback for the final framegroup frame
|
||||
// ------------------------------
|
||||
|
||||
float pathfind_result_idx; // TODO - Need to increment this on instantiation
|
||||
|
||||
|
||||
// Constructor. Called when calling `spawn(AI_Chase);`
|
||||
virtual void() AI_Chase;
|
||||
|
||||
|
||||
virtual void() think;
|
||||
virtual void (float duration) set_framegroup_duration;
|
||||
virtual void (framegroup fgroup) set_framegroup;
|
||||
|
||||
|
||||
virtual void () fg_die = {};
|
||||
virtual void () fg_walk = {};
|
||||
virtual void () fg_attack = {};
|
||||
virtual void () fg_idle = {};
|
||||
|
||||
virtual void (float dist) do_walk_to_goal;
|
||||
};
|
||||
|
||||
|
||||
class AI_Zombie : AI_Chase {
|
||||
entity enemy; // If near, attack
|
||||
|
||||
// Constructor. Called when calling `spawn(AI_Zombie);`
|
||||
// virtual void() AI_Zombie = {};
|
||||
// This should be called explicitly:
|
||||
virtual void(vector org) init;
|
||||
|
||||
virtual void () fg_die;
|
||||
virtual void () fg_walk;
|
||||
virtual void () fg_attack;
|
||||
virtual void () fg_idle;
|
||||
};
|
||||
|
||||
|
||||
//=================================================================
|
||||
|
||||
|
||||
//Misc patch definitions
|
||||
.string teddyremovetarget;
|
||||
|
||||
|
|
|
@ -340,12 +340,17 @@ void() worldspawn =
|
|||
mappath = strzone(mappath);
|
||||
|
||||
LoadWaypointData();
|
||||
// sv_load_navmesh_data();
|
||||
// TODO - Add PSP equivalent for loading navmesh
|
||||
#ifdef PC
|
||||
sv_load_navmesh_data();
|
||||
//Allocating memory for the zombie's pathfinding data
|
||||
sv_zombie_pathfind_result = memalloc(sizeof(navmesh_pathfind_result) * MAX_ZOMBIES);
|
||||
sv_zombie_pathfind_result_aux = memalloc(sizeof(navmesh_pathfind_result) * MAX_ZOMBIES);
|
||||
// I should technically deallocate the memory somewhere, but I've nowhere to put this, and it SHOULD be cleared automatically upon vm reset
|
||||
// memfree(sv_zombie_pathfind_result);
|
||||
// memfree(sv_zombie_pathfind_result_aux);
|
||||
#endif // PC
|
||||
|
||||
// //Allocating memory for the zombie's pathfinding data
|
||||
// sv_zombie_pathfind_result = memalloc(sizeof(pathfind_result) * MAX_ZOMBIES);
|
||||
//I should technically deallocate the memory, but I've nowhere to put this, and it SHOULD be cleared automatically upon vm reset
|
||||
//memfree(sv_zombie_pathfind_result);
|
||||
|
||||
|
||||
//set game elements
|
||||
|
|
|
@ -147,7 +147,7 @@ struct navmesh_pathfind_result {
|
|||
float portals_traversal[NAV_MAX_POLIES];
|
||||
// ---------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Final path is a list of 3D points and traversals
|
||||
// ---------------------------------------------------------
|
||||
|
|
Loading…
Reference in a new issue