/* * 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/editor/EPath.cpp $ * $Revision: 1.1.1.1 $ * $Date: 2003-08-26 03:57:38 $ * $Author: kevinb $ * * Code to create & modify paths * * $Log: not supported by cvs2svn $ * * 13 8/20/99 9:43p Gwar * changes to InsertNodeIntoPath and DrawAllPaths for NEWEDITOR * * 12 8/18/99 7:00a Gwar * changes to add EPath.cpp to NEWEDITOR project * * 11 4/18/99 5:42a Chris * Added the FQ_IGNORE_RENDER_THROUGH_PORTALS flag * * 10 2/26/99 6:14p Matt * Made new nodes go at the viewer position instead of in front of it, and * make the selected path draw in a different color than the others. * * 9 2/26/99 12:40p Matt * Changed occluded messages to (hopefully) be more understandable * * 8 2/26/99 12:26p Matt * Fixed spelling error. * * 7 2/18/99 2:01p Dan * Fixed outside/inside problem * * 6 2/08/99 3:29p Chris * Added a no orient move for objects * * 5 2/03/99 6:56p Chris * Added the "Move node to current object" function * * 4 10/14/98 2:48p Kevin * Changed memory code to comply with mem lib * * 3 5/03/98 8:36p Chris * Additional debug info * * 2 2/10/98 10:48a Matt * Moved editor code from gamepath.cpp to epath.cpp * * 1 2/10/98 10:13a Matt * */ #include #ifndef NEWEDITOR #include "d3edit.h" #else #include "globals.h" #include "terrain.h" #include "renderer.h" #endif #include "epath.h" #include "mono.h" #include "object.h" #include "findintersection.h" uint8_t Show_paths = 1; // Allocs a gamepath that a robot will follow. Returns an index into the GamePaths // array int AllocGamePath() { int i; if (Viewer_object->flags & OF_OUTSIDE_MINE) { OutrageMessageBox("You must be in a room or on the terrain to create a path"); return -1; } for (i = 0; i < MAX_GAME_PATHS; i++) { if (GamePaths[i].used == 0) { GamePaths[i].used = 1; GamePaths[i].name[0] = 0; GamePaths[i].num_nodes = 0; GamePaths[i].flags = 0; GamePaths[i].pathnodes = (node *)mem_malloc(MAX_NODES_PER_PATH * sizeof(node)); mprintf(0, "Path %d got some\n", i); Num_game_paths++; return i; } } OutrageMessageBox("ERROR: Too many paths to add another."); return -1; } int MovePathNode(int pathnum, int nodenum, vector *delta_pos) { vector attempted_pos = GamePaths[pathnum].pathnodes[nodenum].pos + *delta_pos; return MovePathNodeToPos(pathnum, nodenum, &attempted_pos); } int MovePathNodeToPos(int pathnum, int nodenum, vector *attempted_pos) { fvi_query fq; fvi_info hit_info; fq.p0 = &GamePaths[pathnum].pathnodes[nodenum].pos; fq.startroom = GamePaths[pathnum].pathnodes[nodenum].roomnum; fq.p1 = attempted_pos; fq.rad = 0.0f; fq.thisobjnum = -1; fq.ignore_obj_list = NULL; fq.flags = FQ_TRANSPOINT | FQ_IGNORE_RENDER_THROUGH_PORTALS; fvi_FindIntersection(&fq, &hit_info); // Check if new node is valid if (nodenum >= 1) { fvi_query fq1; fvi_info hit_info1; fq1.p0 = &GamePaths[pathnum].pathnodes[nodenum - 1].pos; fq1.startroom = GamePaths[pathnum].pathnodes[nodenum - 1].roomnum; fq1.p1 = &hit_info.hit_pnt; fq1.rad = 0.0f; fq1.thisobjnum = -1; fq1.ignore_obj_list = NULL; fq1.flags = FQ_TRANSPOINT | FQ_IGNORE_RENDER_THROUGH_PORTALS; fvi_FindIntersection(&fq1, &hit_info1); if (vm_VectorDistance(&hit_info.hit_pnt, &hit_info1.hit_pnt) > .005) { OutrageMessageBox("Cannot move point. There is no line of sight from the previous node to the new position."); return -1; } } // Check if new node is valid if (nodenum < GamePaths[pathnum].num_nodes - 1) { fvi_query fq1; fvi_info hit_info1; fq1.p0 = &GamePaths[pathnum].pathnodes[nodenum + 1].pos; fq1.startroom = GamePaths[pathnum].pathnodes[nodenum + 1].roomnum; fq1.p1 = &hit_info.hit_pnt; fq1.rad = 0.0f; fq1.thisobjnum = -1; fq1.ignore_obj_list = NULL; fq1.flags = FQ_TRANSPOINT | FQ_IGNORE_RENDER_THROUGH_PORTALS; fvi_FindIntersection(&fq1, &hit_info1); if (vm_VectorDistance(&hit_info.hit_pnt, &hit_info1.hit_pnt) > .005) { OutrageMessageBox("Cannot move point. There is no line of sight from the next node to the new position."); return -1; } } GamePaths[pathnum].pathnodes[nodenum].pos = hit_info.hit_pnt; GamePaths[pathnum].pathnodes[nodenum].roomnum = hit_info.hit_room; return 0; } // Given a path number, and a node number in that path, adds another node after the // specified node // Returns the index number of the new node // If nodenum is -1, this node was not added // roomnum is only valid if this is the first node in a room #ifndef NEWEDITOR int InsertNodeIntoPath(int pathnum, int nodenum, int flags) #else int InsertNodeIntoPath(int pathnum, int nodenum, int flags, int roomnum, vector pos, matrix orient) #endif { int i; vector attempted_pos; fvi_query fq; fvi_info hit_info; if (Viewer_object->flags & OF_OUTSIDE_MINE) { OutrageMessageBox("Error: Must be in mine or on terrain to insert a node."); return -1; } if (GamePaths[pathnum].num_nodes >= MAX_NODES_PER_PATH) { OutrageMessageBox("Error: Path already has its maximum amount of nodes."); return -1; } // First, move all nodes up one for (i = GamePaths[pathnum].num_nodes - 1; i > nodenum; i--) { memcpy(&GamePaths[pathnum].pathnodes[i + 1], &GamePaths[pathnum].pathnodes[i], sizeof(node)); } #ifndef NEWEDITOR attempted_pos = Viewer_object->pos; // + Viewer_object->orient.fvec * 10.0; #else attempted_pos = pos; // + Viewer_object->orient.fvec * 10.0; #endif #ifndef NEWEDITOR fq.p0 = &Viewer_object->pos; fq.startroom = Viewer_object->roomnum; #else fq.p0 = &pos; fq.startroom = roomnum; #endif fq.p1 = &attempted_pos; fq.rad = 0.0f; fq.thisobjnum = -1; fq.ignore_obj_list = NULL; fq.flags = FQ_TRANSPOINT | FQ_IGNORE_RENDER_THROUGH_PORTALS; fvi_FindIntersection(&fq, &hit_info); // Check if new node is valid if (nodenum >= 0) { fvi_query fq1; fvi_info hit_info1; fq1.p0 = &GamePaths[pathnum].pathnodes[nodenum].pos; fq1.startroom = GamePaths[pathnum].pathnodes[nodenum].roomnum; fq1.p1 = &hit_info.hit_pnt; fq1.rad = 0.0f; fq1.thisobjnum = -1; fq1.ignore_obj_list = NULL; fq1.flags = FQ_TRANSPOINT | FQ_IGNORE_RENDER_THROUGH_PORTALS; fvi_FindIntersection(&fq1, &hit_info1); if (vm_VectorDistance(&hit_info.hit_pnt, &hit_info1.hit_pnt) > .005) { OutrageMessageBox("Cannot add new point. There is no line of sight from the previous node."); return -1; } } // Check if new node is valid if (nodenum >= 0 && nodenum < GamePaths[pathnum].num_nodes - 1) { fvi_query fq1; fvi_info hit_info1; fq1.p0 = &GamePaths[pathnum].pathnodes[nodenum + 1].pos; fq1.startroom = GamePaths[pathnum].pathnodes[nodenum + 1].roomnum; fq1.p1 = &hit_info.hit_pnt; fq1.rad = 0.0f; fq1.thisobjnum = -1; fq1.ignore_obj_list = NULL; fq1.flags = FQ_TRANSPOINT | FQ_IGNORE_RENDER_THROUGH_PORTALS; fvi_FindIntersection(&fq1, &hit_info1); if (vm_VectorDistance(&hit_info.hit_pnt, &hit_info1.hit_pnt) > .005) { OutrageMessageBox("Cannot add new point. There is no line of sight from the next node."); return -1; } } int newnode = nodenum + 1; GamePaths[pathnum].pathnodes[newnode].pos = hit_info.hit_pnt; GamePaths[pathnum].pathnodes[newnode].roomnum = hit_info.hit_room; #ifndef NEWEDITOR GamePaths[pathnum].pathnodes[newnode].fvec = Viewer_object->orient.fvec; GamePaths[pathnum].pathnodes[newnode].uvec = Viewer_object->orient.uvec; #else GamePaths[pathnum].pathnodes[newnode].fvec = orient.fvec; GamePaths[pathnum].pathnodes[newnode].uvec = orient.uvec; #endif GamePaths[pathnum].num_nodes++; GamePaths[pathnum].pathnodes[newnode].flags = flags; return newnode; } // Given a pathnum and a node index, deletes that node and moves all the following nodes down // by one void DeleteNodeFromPath(int pathnum, int nodenum) { int i; // First, move all nodes up one for (i = nodenum; i < GamePaths[pathnum].num_nodes - 1; i++) { memcpy(&GamePaths[pathnum].pathnodes[i], &GamePaths[pathnum].pathnodes[i + 1], sizeof(node)); } GamePaths[pathnum].num_nodes--; } // Gets next path from n that has actually been alloced int GetNextPath(int n) { int i; ASSERT(n >= 0 && n < MAX_GAME_PATHS); if (Num_game_paths == 0) return -1; for (i = n + 1; i < MAX_GAME_PATHS; i++) if (GamePaths[i].used) return i; for (i = 0; i < n; i++) if (GamePaths[i].used) return i; // this is the only one return n; } // Gets previous path from n that has actually been alloced int GetPrevPath(int n) { int i; ASSERT(n >= 0 && n < MAX_GAME_PATHS); if (Num_game_paths == 0) return -1; for (i = n - 1; i >= 0; i--) { if (GamePaths[i].used) return i; } for (i = MAX_GAME_PATHS - 1; i > n; i--) { if (GamePaths[i].used) return i; } // this is the only one return n; } // returns the index of the first path (from 0) alloced // returns -1 if there are no paths int GetFirstPath() { int i; for (i = 0; i < MAX_GAME_PATHS; i++) { if (GamePaths[i].used) return i; } return -1; } #ifndef NEWEDITOR #include "editor/d3edit.h" #endif #include "gr.h" void DrawAllPaths(grViewport *vp, vector *viewer_eye, matrix *viewer_orient, float zoom) { int i, current_path_index = 0, t; g3Point rot_points[300]; int sort_index[300]; if (!Show_paths) return; current_path_index = GetFirstPath(); for (i = 0; i < Num_game_paths; i++, current_path_index = GetNextPath(current_path_index)) { game_path *gp = &GamePaths[current_path_index]; #ifndef NEWEDITOR int curnode = D3EditState.current_node; #else int curnode = Editor_state.GetCurrentNode(); #endif for (t = 0; t < gp->num_nodes; t++) { g3_RotatePoint(&rot_points[t], &gp->pathnodes[t].pos); sort_index[t] = t; } #ifndef NEWEDITOR ddgr_color path_color = (current_path_index == D3EditState.current_path) ? GR_RGB(255, 255, 255) : GR_RGB(36, 99, 238); #else ddgr_color path_color = (current_path_index == Editor_state.GetCurrentPath()) ? GR_RGB(255, 255, 255) : GR_RGB(36, 99, 238); #endif for (t = 0; t < gp->num_nodes - 1; t++) { g3Point p1 = rot_points[t]; g3Point p2 = rot_points[t + 1]; g3_DrawLine(path_color, &p1, &p2); } for (t = 0; t < gp->num_nodes; t++) { for (int k = 0; k < gp->num_nodes; k++) { if (rot_points[k].p3_vec.z < rot_points[t].p3_vec.z) { g3Point temp; int tindex; memcpy(&temp, &rot_points[t], sizeof(g3Point)); memcpy(&rot_points[t], &rot_points[k], sizeof(g3Point)); memcpy(&rot_points[k], &temp, sizeof(g3Point)); tindex = sort_index[t]; sort_index[t] = sort_index[k]; sort_index[k] = tindex; } } } for (t = 0; t < gp->num_nodes; t++) { int color; float size; #ifdef NEWEDITOR ddgr_color oldcolor; if (TSearch_on) { rend_SetPixel(GR_RGB(16, 255, 16), TSearch_x, TSearch_y); oldcolor = rend_GetPixel(TSearch_x, TSearch_y); } #endif #ifndef NEWEDITOR if (current_path_index == D3EditState.current_path && curnode == sort_index[t]) #else if (current_path_index == Editor_state.GetCurrentPath() && curnode == sort_index[t]) #endif { color = GR_RGB(100, 128, 255); size = 1.2f; } else { color = GR_RGB(0, 128, 160); size = 1.2f; } g3_DrawSphere(color, &rot_points[t], size); vector fpos = gp->pathnodes[sort_index[t]].pos + gp->pathnodes[sort_index[t]].fvec * (size - size / 10.0); vector upos = gp->pathnodes[sort_index[t]].pos + gp->pathnodes[sort_index[t]].uvec * (size - size / 10.0); g3Point frot; g3Point urot; g3_RotatePoint(&frot, &fpos); g3_RotatePoint(&urot, &upos); g3_DrawLine(GR_RGB(0, 255, 0), &rot_points[t], &frot); g3_DrawLine(GR_RGB(0, 0, 255), &rot_points[t], &urot); g3_DrawSphere(GR_RGB(0, 255, 0), &frot, size / 10.0); g3_DrawSphere(GR_RGB(0, 0, 255), &urot, size / 10.0); #ifdef NEWEDITOR if (TSearch_on) { if (rend_GetPixel(TSearch_x, TSearch_y) != oldcolor) { TSearch_found_type = TSEARCH_FOUND_NODE; TSearch_seg = current_path_index; TSearch_face = sort_index[t]; } } #endif DrawNumber(sort_index[t] + 1, gp->pathnodes[sort_index[t]].pos, size * .5); } } } static float ArrayX[10][20] = {{-1, 1, 1, -1, -1}, {-.25, 0.0, 0.0, 0.0, -1.0, 1.0}, {-1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0}, {-1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -.5}, {-1.0, -1.0, -1.0, 1.0, 1.0, 1.0}, {-1.0, 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, -1.0, 1.0}, {-1.0, 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, -1.0, 1.0}, {-1.0, 1.0, 1.0, 1.0}, {-1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0}, {1.0, -1.0, -1.0, 1.0, 1.0, 1.0}}; static float ArrayY[10][20] = {{1, 1, -1, -1, 1}, {.75, 1.0, 1.0, -1.0, -1.0, -1.0}, {1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0}, {1.0, 1.0, -1.0, -1.0, -1.0, 0, 0.0}, {1.0, 0.0, 0.0, 0.0, 1.0, -1.0}, {1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0}, {1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0}, {1.0, 1.0, 1.0, -1.0}, {1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 0.0, 0.0}, {1.0, 1.0, 0.0, 0.0, 1.0, -1.0}}; static int NumOfPoints[] = {5, 6, 10, 7, 6, 10, 10, 4, 10, 6}; void DrawNumber(int num, vector pos, float size) { g3Point basepnt, rot_pnt[20]; int num_array[10]; int i, j; int total = num; size /= 2; if (num < 0) { return; } int num_numbers = log10(num) + 1; if (num_numbers > 10) { mprintf(0, "Cannot represent a number with over 10 digits\n"); Int3(); return; } for (j = num_numbers - 1; j >= 0; j--) { num_array[j] = total / (pow(10, j)); total -= num_array[j] * pow(10, j); } for (j = 0; j < num_numbers; j++) { vector cur_pos; if (num_numbers & 0x00000001) cur_pos = pos + (2.1 * size * ((num_numbers >> 1) - j)) * Viewer_object->orient.rvec; else cur_pos = pos + (2.1 * size * ((num_numbers >> 1) - j) - size) * Viewer_object->orient.rvec; g3_RotatePoint(&basepnt, &cur_pos); for (i = 0; i < NumOfPoints[num_array[j]]; i++) { rot_pnt[i] = basepnt; rot_pnt[i].p3_vec.x += (ArrayX[num_array[j]][i] * size); rot_pnt[i].p3_vec.y += (ArrayY[num_array[j]][i] * size); rot_pnt[i].p3_flags = 0; g3_CodePoint(&rot_pnt[i]); g3_ProjectPoint(&rot_pnt[i]); } for (i = 0; i < NumOfPoints[num_array[j]] - 1; i++) { g3Point p1, p2; p1 = rot_pnt[i]; p2 = rot_pnt[i + 1]; g3_DrawLine(GR_RGB(255, 255, 255), &p1, &p2); } } }