/* * Descent 3 * Copyright (C) 2024 Parallax Software * * 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 3 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, see . --- HISTORICAL COMMENTS FOLLOW --- * $Logfile: /DescentIII/main/aiterrain.cpp $ * $Revision: 7 $ * $Date: 2/04/98 6:09p $ * $Author: Matt $ * * Terrain specific AI stuff * * $Log: /DescentIII/main/aiterrain.cpp $ * * 7 2/04/98 6:09p Matt * Changed object room number to indicate a terrain cell via a flag. Got * rid of the object flag which used to indicate terrain. * * 6 8/20/97 3:16p Chris * Working on the edge of terrain problems * * 5 7/31/97 3:12p Chris * Fixed a problem with the new iterator based radius checker * * 4 7/30/97 1:31p Chris * Made helicopters slightly more interesting. * * 3 7/29/97 12:20p Chris * Incremental improvements. Fixed a memory bug. * * 2 7/28/97 1:19p Chris * Expanding the AI system * * $NoKeywords: $ */ #include "vecmat.h" #include "terrain.h" #include "pstypes.h" #include "findintersection.h" #include "aiterrain.h" #include #define AI_MAX_SEGS_CHECKED 200 static int ai_num_segs_checked = 0; static uint8_t ai_terrain_check_list[((TERRAIN_WIDTH * TERRAIN_DEPTH) >> 3) + 1]; static int ai_segs_checked[AI_MAX_SEGS_CHECKED]; #ifdef _DEBUG static int ai_num_checks_since_init = 0; #endif static float ai_rad; static ground_information *ai_ground_info_ptr; void ait_Init() { ai_num_segs_checked = 0; memset(ai_terrain_check_list, 0, ((TERRAIN_WIDTH * TERRAIN_DEPTH) >> 3) + 1); #ifdef _DEBUG ai_num_checks_since_init = 0; #endif } static void ait_terrain_clean() { int i; assert(ai_num_segs_checked >= 0 && ai_num_segs_checked <= AI_MAX_SEGS_CHECKED); for (i = 0; i < ai_num_segs_checked; i++) { ASSERT((CELLNUM(ai_segs_checked[i]) >= 0) && (CELLNUM(ai_segs_checked[i]) < TERRAIN_WIDTH * TERRAIN_DEPTH)); ai_terrain_check_list[CELLNUM(ai_segs_checked[i]) >> 3] = 0; } // #ifdef _DEBUG // for(i = 0; i < ((TERRAIN_WIDTH*TERRAIN_DEPTH)>>3)+1; i++) // { // ASSERT(ai_terrain_check_list[i] == 0); // } // #endif ai_num_segs_checked = 0; } static void ai_check_terrain_node(int cur_node, int f_check_local_nodes) { int check_x, check_y; int new_node; int xcounter, ycounter; int xstart, xend; int ystart, yend; int f_ignore_terrain_in_this_node = 0; // Chrishack -- Note ASSERT(cur_node >= 0 && cur_node < TERRAIN_WIDTH * TERRAIN_DEPTH); ASSERT((ai_terrain_check_list[cur_node >> 3] & (0x00000001 << (cur_node % 8))) == 0); // Mark the current node as visited ai_terrain_check_list[cur_node >> 3] |= 0x00000001 << (cur_node % 8); ai_segs_checked[ai_num_segs_checked] = MAKE_ROOMNUM(cur_node); ai_num_segs_checked++; ASSERT(ai_num_segs_checked < AI_MAX_SEGS_CHECKED); if (Terrain_seg[cur_node].y > ai_ground_info_ptr->highest_y) ai_ground_info_ptr->highest_y = Terrain_seg[cur_node].y; if (Terrain_seg[cur_node].y < ai_ground_info_ptr->lowest_y) ai_ground_info_ptr->lowest_y = Terrain_seg[cur_node].y; // check local nodes for any collision type, but no recursion for them :) if (f_check_local_nodes) { int next_y_delta; // Check worst-case collisions. This includes all nodes within a radius edge of the current node check_x = ai_rad / TERRAIN_SIZE + 1; check_y = ai_rad / TERRAIN_SIZE + 1; xstart = cur_node % TERRAIN_WIDTH - check_x; xend = cur_node % TERRAIN_WIDTH + check_x; ystart = cur_node / TERRAIN_WIDTH - check_y; yend = cur_node / TERRAIN_WIDTH + check_y; if (xstart < 0) xstart = 0; if (xend >= TERRAIN_WIDTH) xend = TERRAIN_WIDTH - 1; if (ystart < 0) ystart = 0; if (yend >= TERRAIN_DEPTH) yend = TERRAIN_DEPTH - 1; // This should be a faster interative why to do a square with center at original position new_node = TERRAIN_WIDTH * ystart + xstart; next_y_delta = TERRAIN_WIDTH - (xend - xstart) - 1; for (ycounter = ystart; ycounter <= yend; ycounter++) { for (xcounter = xstart; xcounter <= xend; xcounter++) { if ((ai_terrain_check_list[new_node >> 3] & (0x00000001 << (new_node % 8))) == 0) ai_check_terrain_node(new_node, 0); new_node += 1; } new_node += next_y_delta; } } return; } // Returns true if the new point is on the terrain and false if the path results in leaving the terrain bool ait_GetGroundInfo(ground_information *ground_info, vector *p0, vector *p1, float rad, angle fov) { int start_node, end_node; int x1, x2, y1, y2, x, y, delta_y, delta_x, change_x, change_y, length, cur_node, error_term, i; // Clean up the last call. This will make the info available till the next call. Maybe // useful at a later date. Plus, we have multiple exits so, this nice spot so that // we need only one call. :) ait_terrain_clean(); #ifdef _DEBUG ai_num_checks_since_init++; #endif ai_ground_info_ptr = ground_info; ai_rad = rad; start_node = GetTerrainCellFromPos(p0); end_node = GetTerrainCellFromPos(p1); if (start_node == -1) return false; if (end_node == -1) { float delta = 1.0; vector movement = *p1 - *p0; if (p1->x < (0.5f * TERRAIN_SIZE)) { delta = (p0->x - (0.5f * TERRAIN_SIZE)) / (-movement.x); } else if (p1->x > (float)(TERRAIN_WIDTH * TERRAIN_SIZE) - (0.5f * TERRAIN_SIZE)) { delta = ((float)(TERRAIN_WIDTH * TERRAIN_SIZE) - (0.5f * TERRAIN_SIZE) - p0->x) / (movement.x); } if (p1->z < (0.5f * TERRAIN_SIZE)) { if ((p0->z - (0.5f * TERRAIN_SIZE)) / (-movement.z) < delta) delta = (p0->z - (0.5f * TERRAIN_SIZE)) / (-movement.z); } else if (p1->z > (float)(TERRAIN_DEPTH * TERRAIN_SIZE) - (0.5f * TERRAIN_SIZE)) { if (((float)(TERRAIN_WIDTH * TERRAIN_SIZE) - (0.5f * TERRAIN_SIZE) - p0->z) / (movement.z) < delta) delta = ((float)(TERRAIN_WIDTH * TERRAIN_SIZE) - (0.5f * TERRAIN_SIZE) - p0->z) / (movement.z); } *p1 = *p0 + delta * movement; end_node = GetTerrainCellFromPos(p1); ASSERT(end_node != -1); } ai_ground_info_ptr->highest_y = Terrain_seg[start_node].y; ai_ground_info_ptr->lowest_y = Terrain_seg[start_node].y; // Determine the start end end nodes x1 = start_node % TERRAIN_WIDTH; y1 = start_node / TERRAIN_WIDTH; x2 = end_node % TERRAIN_WIDTH; y2 = end_node / TERRAIN_WIDTH; x = x1; y = y1; // How many nodes did I move? delta_x = x2 - x1; delta_y = y2 - y1; // check the current node for collsions (if we are done, return) ASSERT((ai_terrain_check_list[start_node >> 3] & (0x00000001 << (start_node % 8))) == 0); ai_check_terrain_node(start_node, 1); if (delta_x == 0 && delta_y == 0) return true; // Do a Breshenham line algorithm if (delta_x < 0) { change_x = -1; delta_x = -delta_x; } else { change_x = 1; } if (delta_y < 0) { change_y = -1; delta_y = -delta_y; } else { change_y = 1; } error_term = 0; i = 1; if (delta_x < delta_y) { length = delta_y + 1; while (i < length) { y += change_y; error_term += delta_x; if (error_term >= delta_y) { x += change_x; error_term -= delta_y; } if (y >= TERRAIN_DEPTH || y < 0 || x < 0 || x >= TERRAIN_WIDTH) { return false; } // Check the current node for collisions cur_node = y * TERRAIN_WIDTH + x; if ((ai_terrain_check_list[cur_node >> 3] & (0x00000001 << (cur_node % 8))) == 0) ai_check_terrain_node(cur_node, 1); i++; } } else { length = delta_x + 1; while (i < length) { x += change_x; error_term += delta_y; if (error_term >= delta_x) { y += change_y; error_term -= delta_x; } if (y >= TERRAIN_DEPTH || y < 0 || x < 0 || x >= TERRAIN_WIDTH) { return false; } // Check the current node for collisions cur_node = y * TERRAIN_WIDTH + x; if ((ai_terrain_check_list[cur_node >> 3] & (0x00000001 << (cur_node % 8))) == 0) ai_check_terrain_node(cur_node, 1); i++; } } ASSERT(x == x2 && y == y2); return true; }