/*
* 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 <http://www.gnu.org/licenses/>.

--- HISTORICAL COMMENTS FOLLOW ---

* $Logfile: /DescentIII/Main/editor/ebnode.cpp $
* $Revision: 1.1.1.1 $
* $Date: 2003-08-26 03:57:37 $
* $Author: kevinb $
*
* BOA Helper Node functions
*
* $Log: not supported by cvs2svn $
 *
 * 35    10/24/99 7:27p Chris
 *
 * 34    10/24/99 7:22p Chris
 *
 * 33    10/24/99 5:19p Chris
 *
 * 32    9/03/99 10:02a Gwar
 * use special version of the verify graph function for NEWEDITOR (outputs
 * error info to a list dialog)
 *
 * 31    8/25/99 4:57p Gwar
 * added a way to select bnodes by clicking on them in the 3D views (for
 * NEWEDITOR)
 *
 * 30    8/20/99 4:26a Gwar
 * made EBNode_Draw use correct room number in NEWEDITOR
 *
 * 29    5/21/99 4:14p Chris
 * Improved verify
 *
 * 28    5/18/99 10:55p Chris
 * Improved the Bnode verification process
 *
 * 27    5/08/99 10:46p Chris
 * Default to not draw bnodes in the editor
 *
 * 26    5/06/99 5:18p Chris
 * Updated the BNode system - vastly improved error checking and verify
 *
 * 25    5/06/99 3:10p Chris
 * More mprintf in verify
 *
 * 23    5/02/99 11:30p Chris
 *
 * 22    5/02/99 8:20a Chris
 * Increased the min edge size
 *
 * 21    5/02/99 6:48a Chris
 * Got rid of zero length edges
 *
 * 20    5/02/99 6:16a Chris
 * Massive bnode error checking improvements
 *
 * 19    5/02/99 6:05a Chris
 *
 * 18    5/02/99 6:02a Chris
 *
 * 17    5/02/99 5:57a Chris
 *
 * 16    5/02/99 5:56a Chris
 *
 * 15    5/02/99 5:19a Chris
 * Vastly improved verify...
 *
 * 14    5/01/99 3:40p Chris
 * TOO_SMALL_FOR_ROBOT_PORTALS ARE THROWN OUT
 *
 * 13    4/29/99 10:16a Chris
 * Blocked portals where only being removed from the bnode list if they
 * where rendered!  (Oops)
 *
 * 12    4/29/99 1:59a Chris
 * Added the portal blockage support
 *
 * 11    4/28/99 9:17a Chris
 * Moving nodes now updates edge max_sizes
 *
 * 10    4/28/99 9:06a Chris
 * Improved the first pass of the BNodes
 *
 * 9     4/27/99 11:37p Chris
 * Improving the BNode system
 *
 * 8     4/27/99 3:01a Chris
 * Improving Bnode stuff
 *
 * 7     4/26/99 10:31p Chris
 * Improving the BNode system
 *
 * 6     4/26/99 11:11a Chris
 * Updated Bnode system
 *
 * 5     4/25/99 9:02p Chris
 * Improving the Bnode system
 *
 * 4     4/18/99 5:39a Chris
 * Vastly improved the path node system
 *
 * 3     4/15/99 5:49p Chris
 * Fixed a bug with rendering the BOAPath nodes
 *
 * 2     4/14/99 3:13p Chris
 * Beginning to add BoaNode stuff
*
* $NoKeywords: $
*/

#ifdef NEWEDITOR
#include "neweditor/stdafx.h"
#include "neweditor/NewEditor.h"
#endif

#include "bnode.h"
#include "room.h"
#include "mem.h"
#include "memory.h"
#include "vecmat.h"
#include "object.h"
#include "3d.h"
#include "ebnode.h"
#include "findintersection.h"
#include "pserror.h"
#include "terrain.h"
#include "boa.h"
#include "aimain.h"

char EBN_draw_type = EBDRAW_NONE;
#define EBN_MAX_NEXT_ROOMS 200

#define BNODE_VERY_CLOSE_DIST 5.0f

bool EBNode_VerifyGraph() {
  bool f_verified = true;
  int i;
  int j;
  int k;

  if (!BNode_allocated) {
    mprintf(0, "EBNode Verify: No BNodes for this level\n");
    return false;
  }

  MakeBOA();

  for (i = Highest_room_index + 1; i <= Highest_room_index + BOA_num_terrain_regions; i++) {
    bn_list *nlist;
    nlist = BNode_GetBNListPtr(i);
    int cur_region = i - Highest_room_index - 1;

    for (j = nlist->num_nodes - 1; j >= 0; j--) {
      int cell = GetTerrainRoomFromPos(&nlist->nodes[j].pos);
      if (cur_region != TERRAIN_REGION(cell)) {
        for (k = 0; k < nlist->nodes[j].num_edges; k++) {
          if (BOA_INDEX(nlist->nodes[j].edges[k].end_room) >= 0 &&
              BOA_INDEX(nlist->nodes[j].edges[k].end_room) <= Highest_room_index) {
            int r = nlist->nodes[j].edges[k].end_room;
            int p = nlist->nodes[j].edges[k].end_index;

            int x;
            for (x = 0; x < Rooms[r].num_portals; x++) {
              if (Rooms[r].portals[x].bnode_index == p) {
                int cr = Rooms[r].portals[x].croom;
                int cp = Rooms[r].portals[x].cportal;

                Rooms[cr].portals[cp].bnode_index = -1;
              }
            }
          }
        }

        EBNode_RemoveNode(i, j);
      }
    }
  }

  for (i = 0; i <= Highest_room_index + BOA_num_terrain_regions; i++) {
    bn_list *nlist;
    int j;
    int k;

    if (i >= 0 && i <= Highest_room_index && !Rooms[i].used)
      continue;

    if (i <= Highest_room_index && (Rooms[i].flags & RF_EXTERNAL))
      continue;

    nlist = BNode_GetBNListPtr(i);

    for (j = 0; j < nlist->num_nodes; j++) {
      for (k = 0; k < nlist->nodes[j].num_edges; k++) {
        if (nlist->nodes[j].edges[k].max_rad < 5.0f) {
          mprintf(0, "EBNode Verify: Removed a skinny edge.\n");
          EBNode_RemoveEdge(j, i, nlist->nodes[j].edges[k].end_index, nlist->nodes[j].edges[k].end_room);
          k--;
        }
      }
    }
  }

  // Bash invalid nodes
  for (i = 0; i <= Highest_room_index; i++) {
    if (Rooms[i].used) {
      room *rp = &Rooms[i];

      if (i <= Highest_room_index && (Rooms[i].flags & RF_EXTERNAL))
        continue;

      for (j = 0; j < Rooms[i].num_portals; j++) {
        if (Rooms[i].portals[j].bnode_index >= 0 && Rooms[i].portals[j].bnode_index >= Rooms[i].bn_info.num_nodes) {
          mprintf(0, "EBNode: Bashed an invalid node\n");
          Rooms[i].portals[j].bnode_index = -1;
        } else if (Rooms[i].portals[j].bnode_index < 0) {
          room *rp = &Rooms[i];
          bool f_add = true;

          if (!((rp->portals[j].flags & PF_BLOCK) && !(rp->portals[j].flags & PF_BLOCK_REMOVABLE))) {
            f_add = false;
          }

          if ((rp->portals[j].flags & PF_RENDER_FACES) && !(rp->portals[j].flags & PF_RENDERED_FLYTHROUGH)) {
            if (!(GameTextures[rp->faces[rp->portals[j].portal_face].tmap].flags & (TF_BREAKABLE | TF_FORCEFIELD))) {
              f_add = false;
            }
          }

          if (rp->portals[j].flags & PF_TOO_SMALL_FOR_ROBOT) {
            f_add = false;
          }

          if (f_add) {
            vector pos;
            pos = rp->portals[j].path_pnt + rp->faces[rp->portals[j].portal_face].normal * 0.75f;
            rp->portals[j].bnode_index = EBNode_AddNode(i, &pos, false, false);
            mprintf(0, "EBNode Verify: Added a portal node\n");
          }
        }
      }
    }
  }

  int region;
  for (region = 0; region < BOA_num_terrain_regions; region++) {
    for (i = 0; i < BOA_num_connect[region]; i++) {
      int end_room = BOA_connect[region][i].roomnum;
      room *rp = &Rooms[end_room];
      int p = BOA_connect[region][i].portal;

      vector pos;
      pos = rp->portals[p].path_pnt - rp->faces[rp->portals[p].portal_face].normal * 0.75f;

      int external_room = rp->portals[p].croom;
      int external_portal = rp->portals[p].cportal;
      ASSERT(Rooms[external_room].flags & RF_EXTERNAL);

      if (Rooms[external_room].portals[external_portal].bnode_index < 0) {
        Rooms[external_room].portals[external_portal].bnode_index =
            EBNode_AddNode(Highest_room_index + region + 1, &pos, false, false);
        if (Rooms[end_room].portals[p].bnode_index >= 0)
          EBNode_AddEdge(Rooms[external_room].portals[external_portal].bnode_index, Highest_room_index + region + 1,
                         Rooms[end_room].portals[p].bnode_index, end_room);
      }
    }
  }

  for (i = 0; i <= Highest_room_index; i++) {
    if (Rooms[i].used) {
      room *rp = &Rooms[i];
      if (i <= Highest_room_index && (Rooms[i].flags & RF_EXTERNAL))
        continue;

      for (j = 0; j < Rooms[i].num_portals; j++) {
        if ((Rooms[i].portals[j].flags & PF_BLOCK) && !(Rooms[i].portals[j].flags & PF_BLOCK_REMOVABLE)) {
          if (Rooms[i].portals[j].bnode_index >= 0) {
            mprintf(0, "EBNode Verify: Removed a node.\n");
            EBNode_RemoveNode(i, Rooms[i].portals[j].bnode_index);
          }
          continue;
        }

        if ((Rooms[i].portals[j].flags & PF_RENDER_FACES) && !(Rooms[i].portals[j].flags & PF_RENDERED_FLYTHROUGH)) {
          if (!(GameTextures[Rooms[i].faces[Rooms[i].portals[j].portal_face].tmap].flags &
                (TF_BREAKABLE | TF_FORCEFIELD))) {
            if (Rooms[i].portals[j].bnode_index >= 0) {
              mprintf(0, "EBNode Verify: Removed a node.\n");
              EBNode_RemoveNode(i, Rooms[i].portals[j].bnode_index);
            }
            continue;
          }
        }

        if (Rooms[i].portals[j].flags & PF_TOO_SMALL_FOR_ROBOT) {
          if (Rooms[i].portals[j].bnode_index >= 0) {
            mprintf(0, "EBNode Verify: Removed a node.\n");
            EBNode_RemoveNode(i, Rooms[i].portals[j].bnode_index);
          }
          continue;
        }

        if (Rooms[i].portals[j].bnode_index < 0) {
          if (Rooms[i].flags & RF_EXTERNAL) {
            int cr = rp->portals[j].croom;
            int ci = Rooms[cr].portals[rp->portals[j].cportal].bnode_index;

            if (Rooms[cr].flags & RF_EXTERNAL) {
              continue;
            }

            vector pos;
            pos = rp->portals[j].path_pnt + rp->faces[rp->portals[j].portal_face].normal * 0.75f;
            int roomnum = BOA_INDEX(GetTerrainRoomFromPos(&pos));

            int xxx;
            for (xxx = 0; xxx < BOA_num_connect[TERRAIN_REGION(roomnum)]; xxx++) {
              if (BOA_connect[TERRAIN_REGION(roomnum)][xxx].roomnum == cr &&
                  BOA_connect[TERRAIN_REGION(roomnum)][xxx].portal == rp->portals[j].cportal) {
                break;
              }
            }

            if (xxx >= BOA_num_connect[TERRAIN_REGION(roomnum)]) {
              mprintf(0, "EBNode Verify:  External room isn't in terrain region list\n");
              f_verified = false;
              continue;
            }

            rp->portals[j].bnode_index = EBNode_AddNode(roomnum, &pos, false, false);
            ASSERT(rp->portals[j].bnode_index >= 0);
            EBNode_AutoEdgeNode(rp->portals[j].bnode_index, roomnum);
            mprintf(0, "EBNode Verify: Added a node and autoedged it.\n");

            if (ci >= 0) {
              EBNode_AddEdge(rp->portals[j].bnode_index, roomnum, ci, cr);
            }
          } else {
            vector pos;
            pos = rp->portals[j].path_pnt + rp->faces[rp->portals[j].portal_face].normal * 0.75f;
            rp->portals[j].bnode_index = EBNode_AddNode(i, &pos, false, false);
            ASSERT(rp->portals[j].bnode_index >= 0);
            EBNode_AutoEdgeNode(rp->portals[j].bnode_index, i);
            mprintf(0, "EBNode Verify: Added a node and autoedged it.\n");

            int cr = rp->portals[j].croom;
            int ci = Rooms[cr].portals[rp->portals[j].cportal].bnode_index;

            if (ci < 0) {
              continue;
            }

            if (Rooms[cr].flags & RF_EXTERNAL) {
              vector pos;
              pos = rp->portals[j].path_pnt - rp->faces[rp->portals[j].portal_face].normal * 0.75f;
              int roomnum = BOA_INDEX(GetTerrainRoomFromPos(&pos));

              EBNode_AddEdge(rp->portals[j].bnode_index, i, ci, roomnum);
            } else {
              if (ci >= 0) {
                EBNode_AddEdge(rp->portals[j].bnode_index, i, ci, cr);
              }
            }
          }
        }
      }
    }
  }

  for (i = 0; i <= Highest_room_index + BOA_num_terrain_regions; i++) {
    bn_list *nlist;
    int j;
    int k;

    if (i >= 0 && i <= Highest_room_index && !Rooms[i].used)
      continue;

    if (i >= 0 && i <= Highest_room_index && (Rooms[i].flags & RF_EXTERNAL))
      continue;

    nlist = BNode_GetBNListPtr(i);

    for (j = 0; j < nlist->num_nodes; j++) {
      for (k = 0; k < nlist->nodes[j].num_edges; k++) {
        if (nlist->nodes[j].edges[k].max_rad < 5.0f) {
          mprintf(0, "EBNode Verify: Removed a skinny edge.\n");
          EBNode_RemoveEdge(j, i, nlist->nodes[j].edges[k].end_index, nlist->nodes[j].edges[k].end_room);
          k--;
        }
      }

      for (k = 0; k < nlist->nodes[j].num_edges; k++) {
        if (nlist->nodes[j].edges[k].end_room <= Highest_room_index && !Rooms[nlist->nodes[j].edges[k].end_room].used) {
          mprintf(0, "EBNode Verify: Removed a edge to a non-existant room. Room %d, node %d, edge %d\n", i, j, k);
          EBNode_RemoveEdge(j, i, nlist->nodes[j].edges[k].end_index, nlist->nodes[j].edges[k].end_room);
          k--;
        }
      }
    }
  }

  for (i = 0; i <= Highest_room_index + BOA_num_terrain_regions; i++) {
    bn_list *nlist;
    int j;
    int k;

    if (i >= 0 && i <= Highest_room_index && !Rooms[i].used)
      continue;

    if (i >= 0 && i <= Highest_room_index && (Rooms[i].flags & RF_EXTERNAL))
      continue;

    nlist = BNode_GetBNListPtr(i);

    for (j = 0; j < nlist->num_nodes; j++) {
      for (k = 0; k < nlist->nodes[j].num_edges; k++) {
        if (nlist->nodes[j].edges[k].max_rad < 5.0f) {
          mprintf(0, "Skinny Edge - from r%d n%d to r%d n%d\n", i, j, nlist->nodes[j].edges[k].end_room,
                  nlist->nodes[j].edges[k].end_index);
          f_verified = false;
        }
      }
    }
  }

  for (i = 0; i <= Highest_room_index + BOA_num_terrain_regions; i++) {
    if (i >= 0 && i <= Highest_room_index && !Rooms[i].used)
      continue;

    if (i >= 0 && i <= Highest_room_index && (Rooms[i].flags & RF_EXTERNAL))
      continue;

    bn_list *nlist = BNode_GetBNListPtr(i);
    ASSERT(nlist);

    for (j = 0; j < nlist->num_nodes; j++) {
      for (k = j + 1; k < nlist->num_nodes; k++) {
        if (!BNode_FindPath(i, j, k, 0.0f)) {
          mprintf(0, "BNODE ERROR: No path from %d to %d in room %d\n", j + 1, k + 1, i);
          f_verified = false;
        }
      }
    }
  }

  BNode_verified = f_verified;

  if (f_verified)
    mprintf(0, "EBNode:  VERIFY OK!\n");
  else
    mprintf(0, "EBNode:  VERIFY FAILED!\n");

  return f_verified;
}

void EBNode_ClearLevel() {
  int i, j;

  for (i = 0; i <= Highest_room_index + BOA_num_terrain_regions; i++) {
    if (i >= 0 && i <= Highest_room_index) {
      if (!Rooms[i].used) {
        continue;
      } else {
        int j;

        for (j = 0; j < Rooms[i].num_portals; j++) {
          Rooms[i].portals[j].bnode_index = -1;
        }
      }
    }

    bn_list *nlist;
    nlist = BNode_GetBNListPtr(i);

    ASSERT(nlist);

    for (j = nlist->num_nodes - 1; j >= 0; j--) {
      if (nlist->nodes[j].edges) {
        mem_free(nlist->nodes[j].edges);
      }
      nlist->nodes[j].edges = NULL;
      nlist->nodes[j].num_edges = 0;
    }

    if (nlist->nodes) {
      mem_free(nlist->nodes);
    }
    nlist->nodes = NULL;
    nlist->num_nodes = 0;
  }

  BNode_allocated = false;
  BNode_verified = false;
}

void RemapEdgeNodesEqualAndAbove(int croom, int sroom, int spnt) {
  int i;
  int j;

  bn_list *cnlist;
  cnlist = BNode_GetBNListPtr(croom);
  if (!cnlist)
    return;

  for (i = 0; i < cnlist->num_nodes; i++) {
    for (j = 0; j < cnlist->nodes[i].num_edges; j++) {
      if (cnlist->nodes[i].edges[j].end_room == sroom && cnlist->nodes[i].edges[j].end_index >= spnt) {
        // The assert is because all these edges should have been deleted!
        ASSERT(cnlist->nodes[i].edges[j].end_index != spnt);
        cnlist->nodes[i].edges[j].end_index--;
      }
    }
  }
}

void RemapPortalNodeIndices(int roomnum, int pnt) {
  int i;

  if (roomnum >= 0 && roomnum <= Highest_room_index) {
    ASSERT(Rooms[roomnum].used);

    for (i = 0; i < Rooms[roomnum].num_portals; i++) {
      if (Rooms[roomnum].portals[i].bnode_index == pnt) {
        Rooms[roomnum].portals[i].bnode_index = -1;
      } else if (Rooms[roomnum].portals[i].bnode_index > pnt) {
        Rooms[roomnum].portals[i].bnode_index--;
      }
    }
  } else {
    int region = BOA_INDEX(roomnum) - Highest_room_index - 1;

    for (i = 0; i < BOA_num_connect[region]; i++) {
      int r = BOA_connect[region][i].roomnum;
      int p = BOA_connect[region][i].portal;

      int cr = Rooms[r].portals[p].croom;
      int cp = Rooms[r].portals[p].cportal;

      vector pos = Rooms[cr].portals[cp].path_pnt + Rooms[cr].faces[Rooms[cr].portals[cp].portal_face].normal * 0.75f;
      int cell = GetTerrainRoomFromPos(&pos);

      if (region == TERRAIN_REGION(cell) && Rooms[cr].portals[cp].bnode_index == pnt) {
        Rooms[cr].portals[cp].bnode_index = -1;
      }
    }
  }
}

void EBNode_RemoveNode(int roomnum, int pnt) {
  int i;

  BNode_verified = false;

  bn_list *nlist;
  nlist = BNode_GetBNListPtr(roomnum);
  if (!nlist)
    return;

  ASSERT(pnt >= 0 && pnt < nlist->num_nodes);

  // Remove all connects to the world...
  for (i = nlist->nodes[pnt].num_edges - 1; i >= 0; i--) {
    EBNode_RemoveEdge(pnt, roomnum, nlist->nodes[pnt].edges[i].end_index, nlist->nodes[pnt].edges[i].end_room);
  }

  // Copy the nodes down the list
  for (i = pnt; i < nlist->num_nodes - 1; i++) {
    nlist->nodes[i] = nlist->nodes[i + 1];
  }

  nlist->num_nodes--;

  if (nlist->num_nodes == 0) {
    mem_free(nlist->nodes);
    nlist->nodes = NULL;
  } else {
    nlist->nodes = (bn_node *)mem_realloc(nlist->nodes, sizeof(bn_node) * nlist->num_nodes);
  }

  // Not super efficient, but works.  :)
  int next_rooms[1000];
  int num_next_rooms = AIMakeNextRoomList(roomnum, next_rooms, 1000);

  for (i = 0; i < num_next_rooms; i++) {
    RemapEdgeNodesEqualAndAbove(next_rooms[i], roomnum, pnt);
  }
  RemapEdgeNodesEqualAndAbove(roomnum, roomnum, pnt);
  RemapPortalNodeIndices(roomnum, pnt);
}

void EBNode_RemoveEdge(int spnt, int sroom, int epnt, int eroom, bool f_remove_reverse) {
  int i;

  BNode_verified = false;

  // Make sure there are no zero length paths
  if (sroom == eroom && spnt == epnt) {
    return;
  }

  bn_list *snlist;
  snlist = BNode_GetBNListPtr(sroom);
  bn_list *enlist;
  enlist = BNode_GetBNListPtr(eroom);

  if (!snlist)
    return;

  bool f_exists = false;
  int e_index;

  // Check to see if this edge already exists
  for (i = 0; i < snlist->nodes[spnt].num_edges; i++) {
    if (snlist->nodes[spnt].edges[i].end_index == epnt && snlist->nodes[spnt].edges[i].end_room == eroom) {
      e_index = i;
      f_exists = true;
      break;
    }
  }

  ASSERT(f_exists);

  // Copy the edges down the list
  for (i = e_index; i < snlist->nodes[spnt].num_edges - 1; i++) {
    snlist->nodes[spnt].edges[i] = snlist->nodes[spnt].edges[i + 1];
  }

  snlist->nodes[spnt].num_edges--;

  if (snlist->nodes[spnt].num_edges == 0) {
    mem_free(snlist->nodes[spnt].edges);
    snlist->nodes[spnt].edges = NULL;
  } else {
    snlist->nodes[spnt].edges =
        (bn_edge *)mem_realloc(snlist->nodes[spnt].edges, sizeof(bn_edge) * snlist->nodes[spnt].num_edges);
  }

  if (f_remove_reverse && enlist) {
    EBNode_RemoveEdge(epnt, eroom, spnt, sroom, false);
  }
}

int EBNode_AddNode(int roomnum, vector *pnt, bool f_from_editor, bool f_check_for_close_nodes) {
  bn_list *nlist;
  nlist = BNode_GetBNListPtr(roomnum);
  if (!nlist)
    return -1;

  BNode_verified = false;

  if (nlist->num_nodes >= MAX_BNODES_PER_ROOM) {
#ifndef NEWEDITOR
    OutrageMessageBox("Too many BOA Nodes for this room/region.\nGet Chris if you need more nodes.");
#else
    OutrageMessageBox("Too many BOA Nodes for this room/region.");
#endif
    return -1;
  }

  int i;
  bool f_really_close_neighbor = false;
  int new_node;

  if (f_check_for_close_nodes) {
    for (i = 0; i < nlist->num_nodes; i++) {
      float min_dist = 0.0f;

      if (f_check_for_close_nodes) {
        min_dist = BNODE_VERY_CLOSE_DIST;
      }

      if (vm_VectorDistance(&nlist->nodes[i].pos, pnt) <= min_dist) {
        f_really_close_neighbor = true;
        break;
      }
    }

    if (f_really_close_neighbor) {
      if (f_from_editor) {
#ifndef NEWEDITOR
        OutrageMessageBox(
            "This node is really close to another one and isn't needed.\nSee Chris if this is a problem.");
#else
        OutrageMessageBox("This node is really close to another one and isn't needed.");
#endif
      }

      return -1;
    }
  }
  // Makes sure that if there are no nodes, then the node pointer is NULL and that if
  // there are nodes that the node pointer isn't NULL
  ASSERT(!((nlist->num_nodes == 0) ^ (nlist->nodes == NULL)));

  new_node = nlist->num_nodes;
  nlist->num_nodes++;

  if (new_node != 0)
    nlist->nodes = (bn_node *)mem_realloc(nlist->nodes, sizeof(bn_node) * (nlist->num_nodes));
  else
    nlist->nodes = (bn_node *)mem_malloc(sizeof(bn_node));

  nlist->nodes[new_node].edges = NULL;
  nlist->nodes[new_node].num_edges = 0;
  nlist->nodes[new_node].pos = *pnt;

  return new_node;
}

int EBNode_InsertNodeOnEdge(int spnt, int sroom, int epnt, int eroom) {
  int i;

  BNode_verified = false;

  bn_list *snlist;
  snlist = BNode_GetBNListPtr(sroom);
  bn_list *enlist;
  enlist = BNode_GetBNListPtr(eroom);

  bool f_exists = false;
  int e_index;

  // Check to see if this edge already exists
  for (i = 0; i < snlist->nodes[spnt].num_edges; i++) {
    if (snlist->nodes[spnt].edges[i].end_index == epnt && snlist->nodes[spnt].edges[i].end_room == eroom) {
      e_index = i;
      f_exists = true;
      break;
    }
  }

  ASSERT(f_exists);

  vector new_pos = (snlist->nodes[spnt].pos + enlist->nodes[epnt].pos) / 2.0f;
  fvi_info hit_info;
  hit_info.hit_room = sroom;

  if (sroom != eroom) {
    fvi_query fq;
    int fate;

    // shoot a ray from the light position to the current vertex
    fq.p0 = &snlist->nodes[spnt].pos;
    fq.p1 = &enlist->nodes[epnt].pos;
    fq.startroom = (sroom > Highest_room_index && sroom <= Highest_room_index + 8)
                       ? GetTerrainRoomFromPos(&snlist->nodes[spnt].pos)
                       : sroom;

    fq.rad = 0.0f;
    fq.flags = FQ_IGNORE_RENDER_THROUGH_PORTALS;
    fq.thisobjnum = -1;
    fq.ignore_obj_list = NULL;

    fate = fvi_FindIntersection(&fq, &hit_info);

    if (fate != HIT_NONE) {
      OutrageMessageBox("You can only do this function if the 2 nodes can\nsee each other or are in the same room.\n");
      return -1;
    }
  }

  int n_index = EBNode_AddNode(hit_info.hit_room, &new_pos, false, false);
  EBNode_AddEdge(n_index, hit_info.hit_room, spnt, sroom);
  EBNode_AddEdge(n_index, hit_info.hit_room, epnt, eroom);

  EBNode_RemoveEdge(epnt, eroom, spnt, sroom);

  return n_index;
}

float EBNode_DetermineMaxSizeForEdge(int spnt, int sroom, int epnt, int eroom) {
  bn_list *snlist;
  snlist = BNode_GetBNListPtr(sroom);
  bn_list *enlist;
  enlist = BNode_GetBNListPtr(eroom);

  float size = 0.0f;
  fvi_info hit_info;
  fvi_query fq;
  int fate;

  // shoot a ray from the light position to the current vertex
  fq.flags = FQ_IGNORE_RENDER_THROUGH_PORTALS;
  fq.thisobjnum = -1;
  fq.ignore_obj_list = NULL;

  do {
    fq.p0 = &snlist->nodes[spnt].pos;
    fq.p1 = &enlist->nodes[epnt].pos;
    fq.startroom = (sroom > Highest_room_index && sroom <= Highest_room_index + 8)
                       ? GetTerrainRoomFromPos(&snlist->nodes[spnt].pos)
                       : sroom;
    fq.rad = size;

    fate = fvi_FindIntersection(&fq, &hit_info);

    if (fate == HIT_NONE) {
      fq.p0 = &enlist->nodes[epnt].pos;
      fq.p1 = &snlist->nodes[spnt].pos;
      fq.startroom = (eroom > Highest_room_index && eroom <= Highest_room_index + 8)
                         ? GetTerrainRoomFromPos(&enlist->nodes[epnt].pos)
                         : eroom;
      fq.rad = size;

      fate = fvi_FindIntersection(&fq, &hit_info);
    }

    if (fate == HIT_NONE) {
      size += 1.0f;
    }
  } while (fate == HIT_NONE && size < MAX_BNODE_SIZE + 1.0f);

  return (size - 1.0f);
}

void EBNode_AutoEdgeNode(int spnt, int sroom) {
  BNode_verified = false;

  bn_list *snlist;
  snlist = BNode_GetBNListPtr(sroom);

  int i;

  for (i = 0; i < snlist->num_nodes; i++) {
    if (i != spnt) {
      fvi_info hit_info;
      fvi_query fq;

      // check to make sure it is still in the room and valid
      fq.p0 = &snlist->nodes[spnt].pos;
      fq.p1 = &snlist->nodes[i].pos;
      fq.startroom = (sroom > Highest_room_index && sroom <= Highest_room_index + 8)
                         ? GetTerrainRoomFromPos(&snlist->nodes[spnt].pos)
                         : sroom;

      fq.rad = 3.0f;
      fq.flags = FQ_NO_RELINK;
      fq.thisobjnum = -1;
      fq.ignore_obj_list = NULL;

      if (fvi_FindIntersection(&fq, &hit_info) == HIT_NONE) {
        EBNode_AddEdge(spnt, sroom, i, sroom);
      }
    }
  }
}

void EBNode_AddEdge(int spnt, int sroom, int epnt, int eroom, bool f_add_reverse, float computed_max_rad) {
  int i;

  BNode_verified = false;

  // Make sure there are no zero length paths
  if (sroom == eroom && spnt == epnt) {
    return;
  }

  bn_list *snlist;
  snlist = BNode_GetBNListPtr(sroom);
  bn_list *enlist;
  enlist = BNode_GetBNListPtr(eroom);

  ASSERT(snlist && enlist);

  bool f_exists = false;

  // Check to see if this edge already exists
  for (i = 0; i < snlist->nodes[spnt].num_edges; i++) {
    if (snlist->nodes[spnt].edges[i].end_index == epnt && snlist->nodes[spnt].edges[i].end_room == eroom) {
      f_exists = true;
      break;
    }
  }

  if (!f_exists) {
    int new_edge;

    // Makes sure that if there are no edges, then the edge pointer is NULL and that if
    // there are edges that the edge pointer isn't NULL
    ASSERT(!((snlist->nodes[spnt].num_edges == 0) ^ (snlist->nodes[spnt].edges == NULL)));

    new_edge = snlist->nodes[spnt].num_edges;
    snlist->nodes[spnt].num_edges++;

    if (new_edge == 0) {
      snlist->nodes[spnt].edges = (bn_edge *)mem_malloc(sizeof(bn_edge));
    } else {
      snlist->nodes[spnt].edges =
          (bn_edge *)mem_realloc(snlist->nodes[spnt].edges, sizeof(bn_edge) * snlist->nodes[spnt].num_edges);
    }

    float cost = vm_VectorDistance(&snlist->nodes[spnt].pos, &enlist->nodes[epnt].pos);
    if (cost < 1.0f)
      cost = 1.0f;

    snlist->nodes[spnt].edges[new_edge].cost = (cost < 32767.0f) ? (int16_t)cost : (int16_t)32767;
    snlist->nodes[spnt].edges[new_edge].end_index = epnt;
    snlist->nodes[spnt].edges[new_edge].end_room = BOA_INDEX(eroom);
    snlist->nodes[spnt].edges[new_edge].flags = 0;

    if (f_add_reverse) {
      snlist->nodes[spnt].edges[new_edge].max_rad = EBNode_DetermineMaxSizeForEdge(spnt, sroom, epnt, eroom);
      EBNode_AddEdge(epnt, eroom, spnt, sroom, false, snlist->nodes[spnt].edges[new_edge].max_rad);
    } else {
      snlist->nodes[spnt].edges[new_edge].max_rad = computed_max_rad;
    }
  }
}

void EBNode_MakeDefaultIntraRoomNodes(int roomnum) {
  int i, j;

  room *rp = &Rooms[roomnum];

  // Adds the nodes from portals
  for (i = 0; i < rp->num_portals; i++) {
    vector pos;
    pos = rp->portals[i].path_pnt + rp->faces[rp->portals[i].portal_face].normal * 0.75f;
    rp->portals[i].bnode_index = i;

    EBNode_AddNode(roomnum, &pos, false, false);
  }

  // Adds a node for centerpoint of the room
  EBNode_AddNode(roomnum, &rp->path_pnt, false, false);

  // Add the edges
  for (i = 0; i < rp->bn_info.num_nodes; i++) {
    // Edges go both ways - always
    for (j = i + 1; j < rp->bn_info.num_nodes; j++) {
      // Check to see if the center is really close to this edge, if so - Dont make this edge
      if (i < rp->bn_info.num_nodes - 1 && j < rp->bn_info.num_nodes - 1) {
        vector vec = rp->portals[j].path_pnt - rp->portals[i].path_pnt;
        vector cvec = rp->path_pnt - rp->portals[i].path_pnt;

        float len = vm_NormalizeVector(&vec);
        float cproj = cvec * vec;

        if (len >= cproj && cproj >= 0.0f) {
          vector cxline = cproj * vec;
          vector dvec = rp->path_pnt - (rp->portals[i].path_pnt + cxline);

          if (vm_GetMagnitude(&dvec) < 3.0f) {
            continue;
          }
        }
      }

      fvi_info hit_info;
      fvi_query fq;

      // check to make sure it is still in the room and valid
      fq.p0 = &Rooms[roomnum].bn_info.nodes[i].pos;
      fq.p1 = &Rooms[roomnum].bn_info.nodes[j].pos;
      fq.startroom = (roomnum > Highest_room_index && roomnum <= Highest_room_index + 8)
                         ? GetTerrainRoomFromPos(&Rooms[roomnum].bn_info.nodes[i].pos)
                         : roomnum;

      fq.rad = 0.1f;
      fq.flags = FQ_SOLID_PORTALS | FQ_NO_RELINK;
      fq.thisobjnum = -1;
      fq.ignore_obj_list = NULL;

      if (fvi_FindIntersection(&fq, &hit_info) != HIT_NONE) {
        continue;
      }

      EBNode_AddEdge(i, roomnum, j, roomnum);
    }
  }
}

void EBNode_MakeDefaultInterRoomEdges(int roomnum) {
  int i;

  for (i = 0; i < Rooms[roomnum].num_portals; i++) {
    if (Rooms[roomnum].portals[i].cportal >= 0 && Rooms[roomnum].portals[i].croom > roomnum &&
        !(Rooms[Rooms[roomnum].portals[i].croom].flags & RF_EXTERNAL)) {
      if ((Rooms[roomnum].portals[i].flags & PF_RENDER_FACES) &&
          !(Rooms[roomnum].portals[i].flags & PF_RENDERED_FLYTHROUGH)) {
        if (!(GameTextures[Rooms[roomnum].faces[Rooms[roomnum].portals[i].portal_face].tmap].flags &
              (TF_BREAKABLE | TF_FORCEFIELD))) {
          continue;
        }
      }

      EBNode_AddEdge(i, roomnum, Rooms[roomnum].portals[i].cportal, Rooms[roomnum].portals[i].croom);
    }
  }
}

void EBNode_RemoveNodesAtUnopenablePortals(int roomnum) {
  int i;

  ASSERT(Rooms[roomnum].num_portals + 1 == Rooms[roomnum].bn_info.num_nodes);

  for (i = Rooms[roomnum].num_portals - 1; i >= 0; i--) {
    if ((Rooms[roomnum].portals[i].flags & PF_BLOCK) && !(Rooms[roomnum].portals[i].flags & PF_BLOCK_REMOVABLE)) {
      EBNode_RemoveNode(roomnum, i);
      continue;
    }

    if ((Rooms[roomnum].portals[i].flags & PF_RENDER_FACES) &&
        !(Rooms[roomnum].portals[i].flags & PF_RENDERED_FLYTHROUGH)) {
      if (!(GameTextures[Rooms[roomnum].faces[Rooms[roomnum].portals[i].portal_face].tmap].flags &
            (TF_BREAKABLE | TF_FORCEFIELD))) {
        EBNode_RemoveNode(roomnum, i);
        continue;
      }
    }

    if (Rooms[roomnum].portals[i].flags & PF_TOO_SMALL_FOR_ROBOT) {
      EBNode_RemoveNode(roomnum, i);
      continue;
    }
  }
}

// void EBNode_VerifyGraph()
//{
/*	int i;
        int j;
        int k;
        int l;

        for(i = 0; i <= Highest_room_index; i++)
        {
                if(Rooms[i].used && !(Rooms[i].flags & RF_EXTERNAL))
                {
                        room *rp = &Rooms[i];

                        for(j = 0; j < rp->bn_info.num_nodes; j++)
                        {
                                for(k = 0; k < rp->bn_info.nodes[j].num_edges; k++)
                                {
                                        ASSERT(!(rp->bn_info.nodes[j].edges[k].end_room == i &&
   rp->bn_info.nodes[j].edges[k].end_index == j));

                                        bool f_found = false;

                                        for(l = 0; l <
   Rooms[rp->bn_info.nodes[j].edges[k].end_room].bn_info.nodes[rp->bn_info.nodes[j].edges[k].end_index].num_edges; l++)
                                        {
                                                if(Rooms[rp->bn_info.nodes[j].edges[k].end_room].bn_info.nodes[rp->bn_info.nodes[j].edges[k].end_index].edges[l].end_room
   == i &&
                                                        Rooms[rp->bn_info.nodes[j].edges[k].end_room].bn_info.nodes[rp->bn_info.nodes[j].edges[k].end_index].edges[l].end_index
   == j)
                                                {
                                                        f_found = true;
                                                        break;
                                                }
                                        }

                                        ASSERT(f_found);

                                        for(l = 0; l < rp->bn_info.nodes[j].num_edges; l++)
                                        {
                                                if(l != k)
                                                {
                                                        ASSERT(!(rp->bn_info.nodes[j].edges[k].end_room ==
   rp->bn_info.nodes[j].edges[l].end_room && rp->bn_info.nodes[j].edges[k].end_index ==
   rp->bn_info.nodes[j].edges[l].end_index));
                                                }
                                        }
                                }
                        }
                }
        }
*/
//}

void EBNode_MakeDefaultTerrainNodes(int region) {
  int i, j;

  ASSERT(region >= 0 || region < BOA_num_terrain_regions);

  mprintf(0, "TR %d has %d nodes\n", region, BOA_num_connect[region]);

  // Adds the nodes from portals
  for (i = 0; i < BOA_num_connect[region]; i++) {
    int end_room = BOA_connect[region][i].roomnum;
    room *rp = &Rooms[end_room];
    int p = BOA_connect[region][i].portal;

    vector pos;
    pos = rp->portals[p].path_pnt - rp->faces[rp->portals[p].portal_face].normal * 0.75f;

    int external_room = rp->portals[p].croom;
    int external_portal = rp->portals[p].cportal;
    ASSERT(Rooms[external_room].flags & RF_EXTERNAL);
    Rooms[external_room].portals[external_portal].bnode_index = i;

    EBNode_AddNode(Highest_room_index + region + 1, &pos, false, false);
    EBNode_AddEdge(i, Highest_room_index + region + 1, p, end_room);
  }

  // Add the edges
  for (i = 0; i < BOA_num_connect[region]; i++) {
    // Edges go both ways - always
    for (j = i + 1; j < BOA_num_connect[region]; j++) {
      EBNode_AddEdge(i, Highest_room_index + region + 1, j, Highest_room_index + region + 1);
    }
  }
}

void EBNode_MakeFirstPass(void) {
  int i;

  if (BNode_allocated) {
    OutrageMessageBox("The BNode system is already made.\nUse the other functions to modify the graph.");
    return;
  }

  for (i = 0; i <= Highest_room_index; i++) {
    ASSERT(Rooms[i].bn_info.num_nodes == 0);

    if (Rooms[i].used && !(Rooms[i].flags & RF_EXTERNAL)) {
      EBNode_MakeDefaultIntraRoomNodes(i);
    }
  }

  for (i = 0; i < BOA_num_terrain_regions; i++) {
    ASSERT(BNode_terrain_list[i].num_nodes == 0);
    EBNode_MakeDefaultTerrainNodes(i);
  }

  // This function assumes that Node(X) goes to Portal(X)
  for (i = 0; i <= Highest_room_index; i++) {
    if (Rooms[i].used && !(Rooms[i].flags & RF_EXTERNAL)) {
      EBNode_MakeDefaultInterRoomEdges(i);
    }
  }

  // This function assumes that Node(X) goes to Portal(X)
  for (i = 0; i <= Highest_room_index; i++) {
    if (Rooms[i].used && !(Rooms[i].flags & RF_EXTERNAL)) {
      EBNode_RemoveNodesAtUnopenablePortals(i);
    }
  }

  for (i = 0; i <= Highest_room_index + BOA_num_terrain_regions; i++) {
    bn_list *nlist;
    int j;
    int k;

    if (i >= 0 && i <= Highest_room_index && !Rooms[i].used)
      continue;

    nlist = BNode_GetBNListPtr(i);

    for (j = 0; j < nlist->num_nodes; j++) {
      for (k = 0; k < nlist->nodes[j].num_edges; k++) {
        if (nlist->nodes[j].edges[k].max_rad < 5.0f) {
          EBNode_RemoveEdge(j, i, nlist->nodes[j].edges[k].end_index, nlist->nodes[j].edges[k].end_room);
          k--;
        }
      }
    }
  }

  BNode_allocated = true;
#ifndef NEWEDITOR
  EBNode_VerifyGraph();
#else
  bool ned_EBNode_VerifyGraph();
  ned_EBNode_VerifyGraph();
#endif
}

#ifndef NEWEDITOR
#include "editor/d3edit.h"
#else
#include "neweditor/globals.h"
#include "terrain.h"
#include "renderer.h"
#endif

#include "gr.h"
#include "epath.h"

void EBNode_DrawRoom(int room, grViewport *vp, vector *viewer_eye, matrix *viewer_orient, float zoom,
                     bool f_current_room = false) {
  int i, current_path_index = 0, t;
  g3Point rot_points[300];
  int sort_index[300];

  bn_list *bn_info = BNode_GetBNListPtr(room);

  for (i = 0; i < bn_info->num_nodes; i++) {
    //		int curnode=D3EditState.current_node;
    //
    g3_RotatePoint(&rot_points[0], &bn_info->nodes[i].pos);
    sort_index[0] = 0;

    for (t = 0; t < bn_info->nodes[i].num_edges; t++) {

      bn_list *ebn_info = BNode_GetBNListPtr(bn_info->nodes[i].edges[t].end_room);

      g3_RotatePoint(&rot_points[t + 1], &ebn_info->nodes[bn_info->nodes[i].edges[t].end_index].pos);
      sort_index[t + 1] = t + 1;
    }

    //		ddgr_color path_color = (current_path_index==D3EditState.current_path) ? GR_RGB(255,255,255) :
    //GR_RGB(36,99,238);
    ddgr_color path_color;

    if (f_current_room)
      path_color = GR_RGB(255, 0, 0);
    else
      path_color = GR_RGB(100, 0, 0);

    int x;
    g3Point p1 = rot_points[0];

    for (x = 0; x < bn_info->nodes[i].num_edges; x++) {
      g3Point p2 = rot_points[x + 1];
      g3_DrawLine(path_color, &p1, &p2);
    }

    for (t = 0; t < bn_info->nodes[i].num_edges + 1; t++) {
      for (int k = 0; k < bn_info->nodes[i].num_edges + 1; 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;
        }
      }
    }
  }

  int color;
  float size;

  if (f_current_room) {
    color = GR_RGB(0, 128, 160);
    size = 0.7f;
  } else {
    color = GR_RGB(0, 64, 80);
    size = 0.5f;
  }

  for (i = 0; i < bn_info->num_nodes; i++) {
    g3_RotatePoint(&rot_points[0], &bn_info->nodes[i].pos);

#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

    g3_DrawSphere(color, &rot_points[0], size);

#ifdef NEWEDITOR
    if (TSearch_on) {
      if (rend_GetPixel(TSearch_x, TSearch_y) != oldcolor) {
        TSearch_found_type = TSEARCH_FOUND_BNODE;
        TSearch_seg = room;
        TSearch_face = i;
      }
    }
#endif

    DrawNumber(i + 1, bn_info->nodes[i].pos, size * .5);
  }
}

void EBNode_ComputeEdgeCosts(int sroom, int spnt, int eroom, int epnt) {
  int i, j;
  bool f_found = false;

  bn_list *snlist;
  snlist = BNode_GetBNListPtr(sroom);
  bn_list *enlist;
  enlist = BNode_GetBNListPtr(eroom);

  for (i = 0; i < snlist->nodes[spnt].num_edges; i++) {
    for (j = 0; j < enlist->nodes[epnt].num_edges; j++) {
      if ((snlist->nodes[spnt].edges[i].end_index == epnt && snlist->nodes[spnt].edges[i].end_room == eroom) &&
          (enlist->nodes[epnt].edges[j].end_index == spnt && enlist->nodes[epnt].edges[j].end_room == sroom)) {
        f_found = true;

        float cost = vm_VectorDistance(&snlist->nodes[spnt].pos, &enlist->nodes[epnt].pos);
        if (cost < 1.0f)
          cost = 1.0f;

        int16_t scost = (cost < 32767.0f) ? (int16_t)cost : (int16_t)32767;

        snlist->nodes[spnt].edges[i].cost = scost;
        enlist->nodes[epnt].edges[j].cost = scost;

        snlist->nodes[spnt].edges[i].max_rad = enlist->nodes[epnt].edges[j].max_rad =
            EBNode_DetermineMaxSizeForEdge(spnt, sroom, epnt, eroom);
      }
    }
  }

  ASSERT(f_found == true); // If you get this -- Get Chris!!!!!!!
}

void EBNode_Move(bool f_offset, int roomnum, int pnt, vector *pos) {
  bn_list *nlist;
  nlist = BNode_GetBNListPtr(roomnum);

  int i;
  vector npos = *pos;

  if (f_offset) {
    npos += nlist->nodes[pnt].pos;

    fvi_info hit_info;
    fvi_query fq;

    // check to make sure it is still in the room and valid
    fq.p0 = &nlist->nodes[pnt].pos;
    fq.p1 = &npos;
    fq.startroom = (roomnum > Highest_room_index && roomnum <= Highest_room_index + 8)
                       ? GetTerrainRoomFromPos(&nlist->nodes[pnt].pos)
                       : roomnum;

    fq.rad = 0.25f;
    fq.flags =
        FQ_SOLID_PORTALS | FQ_NO_RELINK; // chrishack -- Might want to make FQ_IGNORE_MOVING_OBJECTS into a passed arg
    fq.thisobjnum = -1;
    fq.ignore_obj_list = NULL;

    fvi_FindIntersection(&fq, &hit_info);

    npos = hit_info.hit_pnt;
  }

  nlist->nodes[pnt].pos = npos;

  for (i = 0; i < nlist->nodes[pnt].num_edges; i++) {
    EBNode_ComputeEdgeCosts(roomnum, pnt, nlist->nodes[pnt].edges[i].end_room, nlist->nodes[pnt].edges[i].end_index);
    EBNode_ComputeEdgeCosts(nlist->nodes[pnt].edges[i].end_room, nlist->nodes[pnt].edges[i].end_index, roomnum, pnt);
  }
}

void EBNode_Draw(char draw_type, grViewport *vp, vector *viewer_eye, matrix *viewer_orient, float zoom) {
  int i;

#ifndef NEWEDITOR
  int roomnum = Viewer_object->roomnum;
#else
  int roomnum = ROOMNUM(theApp.m_pLevelWnd->m_Prim.roomp);
#endif

  switch (draw_type) {
  case EBDRAW_NONE:
    break;
  case EBDRAW_ROOM_AND_NEXT_ROOMS: {
    int i;
    int next_rooms[1000];
    int num_next_rooms = AIMakeNextRoomList(roomnum, next_rooms, 1000);

    for (i = 0; i < num_next_rooms; i++) {
      EBNode_DrawRoom(next_rooms[i], vp, viewer_eye, viewer_orient, zoom, false);
    }
  }
  case EBDRAW_ROOM:
    EBNode_DrawRoom(roomnum, vp, viewer_eye, viewer_orient, zoom, true);
    break;
  case EBDRAW_LEVEL:
    for (i = 0; i <= Highest_room_index; i++) {
      if (Rooms[i].used && !(Rooms[i].flags & RF_EXTERNAL)) {
        EBNode_DrawRoom(i, vp, viewer_eye, viewer_orient, zoom, i == roomnum);
      }
    }
    for (i = 0; i < BOA_num_terrain_regions; i++) {
      EBNode_DrawRoom(Highest_room_index + i + 1, vp, viewer_eye, viewer_orient, zoom,
                      ROOMNUM_OUTSIDE(roomnum) && (i == TERRAIN_REGION(roomnum)));
    }
    break;
  }
}
