/* Copyright (C) 1996-1997 Id Software, Inc. 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 the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See file, 'COPYING', for details. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif static __attribute__ ((used)) const char rcsid[] = "$Id$"; #ifdef HAVE_STRING_H # include #endif #include #include "QF/sys.h" #include "brush.h" #include "bsp5.h" #include "draw.h" #include "options.h" #include "portals.h" #include "winding.h" /** \addtogroup qfbsp_portals */ //@{ int c_activeportals, c_peakportals; node_t outside_node; // portals outside the world face this portal_t * AllocPortal (void) { portal_t *p; c_activeportals++; if (c_activeportals > c_peakportals) c_peakportals = c_activeportals; p = malloc (sizeof (portal_t)); memset (p, 0, sizeof (portal_t)); return p; } void FreePortal (portal_t *p) { c_activeportals--; free (p); } /** Link the portal into the nodes on either side of the portal. \param p The portal to link. \param front The node on the front side of the portal. \param back The node on the back side of the portal. */ static void AddPortalToNodes (portal_t *p, node_t *front, node_t *back) { if (p->nodes[0] || p->nodes[1]) Sys_Error ("AddPortalToNode: allready included"); p->nodes[0] = front; p->next[0] = front->portals; front->portals = p; p->nodes[1] = back; p->next[1] = back->portals; back->portals = p; } /** Remove the portal from a node. The portal most be linked into the node and bounding the node. \param portal The portal to remove. \param l The leaf node from which to remove the portal. */ static void RemovePortalFromNode (portal_t *portal, node_t *l) { portal_t **pp, *t; // remove reference to the current portal pp = &l->portals; while (1) { t = *pp; if (!t) Sys_Error ("RemovePortalFromNode: portal not in leaf"); if (t == portal) break; if (t->nodes[0] == l) pp = &t->next[0]; else if (t->nodes[1] == l) pp = &t->next[1]; else Sys_Error ("RemovePortalFromNode: portal not bounding leaf"); } if (portal->nodes[0] == l) { *pp = portal->next[0]; portal->nodes[0] = NULL; } else if (portal->nodes[1] == l) { *pp = portal->next[1]; portal->nodes[1] = NULL; } } /** Calculate the bounding box of the node based on its portals. \param node The node of which to calculate the bounding box. */ static void CalcNodeBounds (node_t *node) { int i, j; portal_t *p; winding_t *w; int side; for (i=0 ; i<3 ; i++) { node->mins[i] = BOGUS_RANGE; node->maxs[i] = -BOGUS_RANGE; } for (p = node->portals ; p ; p = p->next[side]) { if (p->nodes[0] == node) side = 0; else if (p->nodes[1] == node) side = 1; else Sys_Error ("CalcNodeBounds: mislinked portal"); w = p->winding; for (i = 0; i < w->numpoints; i++) { for (j=0 ; j<3 ; j++) { if (w->points[i][j] < node->mins[j]) node->mins[j] = w->points[i][j]; if (w->points[i][j] > node->maxs[j]) node->maxs[j] = w->points[i][j]; } } } } /** Make portals for the head node, initializing outside_node. The created portals will face the global outside_node. \param node The head node. */ static void MakeHeadnodePortals (node_t *node) { int side, i, j, n; plane_t bplanes[6], *pl; portal_t *p, *portals[6]; vec3_t bounds[2]; Draw_ClearWindow (); // pad with some space so there will never be null volume leafs for (i = 0; i < 3; i++) { bounds[0][i] = brushset->mins[i] - SIDESPACE; bounds[1][i] = brushset->maxs[i] + SIDESPACE; } outside_node.contents = CONTENTS_SOLID; outside_node.portals = NULL; // create a brush based on the enlarged bounding box. // The brush has all sides pointing in. for (i = 0; i < 3; i++) { for (j = 0; j < 2; j++) { n = j * 3 + i; p = AllocPortal (); portals[n] = p; pl = &bplanes[n]; memset (pl, 0, sizeof (*pl)); if (j) { pl->normal[i] = -1; pl->dist = -bounds[j][i]; } else { pl->normal[i] = 1; pl->dist = bounds[j][i]; } p->planenum = FindPlane (pl, &side); p->winding = BaseWindingForPlane (pl); if (side) AddPortalToNodes (p, &outside_node, node); else AddPortalToNodes (p, node, &outside_node); } } // clip the basewindings by all the other planes for (i = 0; i < 6; i++) { for (j = 0; j < 6; j++) { if (j == i) continue; portals[i]->winding = ClipWinding (portals[i]->winding, &bplanes[j], true); } } } /** Calculate the plane holding the winding. Uses the first three points of the winding. \param w The plane for which to calculate the plane. \param plane The plane to set. */ static void PlaneFromWinding (winding_t *w, plane_t *plane) { vec3_t v1, v2; // calc plane VectorSubtract (w->points[2], w->points[1], v1); VectorSubtract (w->points[0], w->points[1], v2); CrossProduct (v2, v1, plane->normal); _VectorNormalize (plane->normal); plane->dist = DotProduct (w->points[0], plane->normal); } static int cutnode_detail; static void CutNodePortals_r (node_t *node) { int side; node_t *f, *b, *other_node; plane_t *plane, clipplane; portal_t *p, *new_portal, *next_portal; winding_t *w, *frontwinding, *backwinding; // CheckLeafPortalConsistancy (node); CalcNodeBounds (node); // seperate the portals on node into it's children if (node->contents) return; // at a leaf, no more dividing if (node->detail && cutnode_detail) return; plane = &planes[node->planenum]; f = node->children[0]; b = node->children[1]; // create the new portal by taking the full plane winding for the cutting // plane and clipping it by all of the planes from the other portals w = BaseWindingForPlane (plane); side = 0; for (p = node->portals; p; p = p->next[side]) { clipplane = planes[p->planenum]; if (p->nodes[0] == node) side = 0; else if (p->nodes[1] == node) { clipplane.dist = -clipplane.dist; VectorNegate (clipplane.normal, clipplane.normal); side = 1; } else Sys_Error ("CutNodePortals_r: mislinked portal"); w = ClipWinding (w, &clipplane, true); if (!w) { printf ("WARNING: CutNodePortals_r:new portal was clipped away\n"); break; } } if (w) { // if the plane was not clipped on all sides, there was an error new_portal = AllocPortal (); new_portal->planenum = node->planenum; new_portal->winding = w; AddPortalToNodes (new_portal, f, b); } // partition the portals for (p = node->portals; p; p = next_portal) { if (p->nodes[0] == node) side = 0; else if (p->nodes[1] == node) side = 1; else Sys_Error ("CutNodePortals_r: mislinked portal"); next_portal = p->next[side]; other_node = p->nodes[!side]; RemovePortalFromNode (p, node); RemovePortalFromNode (p, other_node); // cut the portal into two portals, one on each side of the cut plane DivideWinding (p->winding, plane, &frontwinding, &backwinding); if (!frontwinding) { if (side == 0) AddPortalToNodes (p, b, other_node); else AddPortalToNodes (p, other_node, b); continue; } if (!backwinding) { if (side == 0) AddPortalToNodes (p, f, other_node); else AddPortalToNodes (p, other_node, f); continue; } // the winding is split new_portal = AllocPortal (); *new_portal = *p; new_portal->winding = backwinding; FreeWinding (p->winding); p->winding = frontwinding; if (side == 0) { AddPortalToNodes (p, f, other_node); AddPortalToNodes (new_portal, b, other_node); } else { AddPortalToNodes (p, other_node, f); AddPortalToNodes (new_portal, other_node, b); } } DrawLeaf (f, 1); DrawLeaf (b, 2); CutNodePortals_r (f); CutNodePortals_r (b); } void PortalizeWorld (node_t *headnode) { qprintf ("----- portalize ----\n"); MakeHeadnodePortals (headnode); cutnode_detail = 0; CutNodePortals_r (headnode); } void PortalizeWorldDetail (node_t *headnode) { qprintf ("----- portalize ----\n"); MakeHeadnodePortals (headnode); cutnode_detail = 1; CutNodePortals_r (headnode); } void FreeAllPortals (node_t *node) { portal_t *p, *nextp; if (!node->contents) { FreeAllPortals (node->children[0]); FreeAllPortals (node->children[1]); } for (p = node->portals; p; p = nextp) { if (p->nodes[0] == node) nextp = p->next[0]; else nextp = p->next[1]; RemovePortalFromNode (p, p->nodes[0]); RemovePortalFromNode (p, p->nodes[1]); FreeWinding (p->winding); FreePortal (p); } } // PORTAL FILE GENERATION #define PORTALFILE "PRT1-AM" FILE *pf; int num_visleafs; // leafs the player can be in int num_visportals; int num_realleafs; /** Check if a node has the specified contents. \param n The node to check. \param cont The contents for which to check. \return 1 if the node has the specified contents, otherwise 0. */ static int HasContents (node_t *n, int cont) { if (n->contents == cont) return 1; if (n->contents) return 0; if (HasContents (n->children[0], cont)) return 1; return HasContents (n->children[1], cont); } /** Check if two nodes have the same non-solid contents somewhere within them. \param n1 The first node to check. \param n2 The second node to check. */ static int ShareContents (node_t *n1, node_t *n2) { if (n1->contents) { if (n1->contents == CONTENTS_SOLID) return 0; else return HasContents (n2, n1->contents); } if (ShareContents (n1->children[0], n2)) return 1; return ShareContents (n1->children[1], n2); } /** Check if two nodes have the same non-solid, non-sky contents. \note Affected by watervis. \param n1 The first node to check. \param n2 The second node to check. */ static int SameContents (node_t *n1, node_t *n2) { if (n1->contents == CONTENTS_SOLID || n2->contents == CONTENTS_SOLID) return 0; if (n1->contents == CONTENTS_SKY || n2->contents == CONTENTS_SKY) return 0; if (options.watervis) //FIXME be more picky? return 1; if (n1->detail && n2->detail) ShareContents (n1, n2); if (n1->detail) return HasContents (n1, n2->contents); if (n2->detail) return HasContents (n2, n1->contents); return n1->contents == n2->contents; } /** Recurse through the world bsp, writing the portals for each leaf node to the portal file. \param node The current node of the bsp. Call with the root node. */ static void WritePortalFile_r (node_t *node) { int i; plane_t *pl, plane2; portal_t *p; winding_t *w; if (!node->contents && !node->detail) { WritePortalFile_r (node->children[0]); WritePortalFile_r (node->children[1]); return; } if (node->contents == CONTENTS_SOLID) return; for (p = node->portals; p;) { w = p->winding; if (w && p->nodes[0] == node && SameContents (p->nodes[0], p->nodes[1])) { // write out to the file // sometimes planes get turned around when they are very near the // changeover point between different axis. interpret the plane // the same way vis will, and flip the side orders if needed pl = &planes[p->planenum]; PlaneFromWinding (w, &plane2); if (DotProduct (pl->normal, plane2.normal) < 0.99) { // backwards.. fprintf (pf, "%i %i %i ", w->numpoints, p->nodes[1]->visleafnum, p->nodes[0]->visleafnum); } else fprintf (pf, "%i %i %i ", w->numpoints, p->nodes[0]->visleafnum, p->nodes[1]->visleafnum); for (i = 0; i < w->numpoints - 1; i++) { fprintf (pf, "(%g %g %g) ", w->points[i][0], w->points[i][1], w->points[i][2]); } fprintf (pf, "(%g %g %g)\n", w->points[i][0], w->points[i][1], w->points[i][2]); } if (p->nodes[0] == node) p = p->next[0]; else p = p->next[1]; } } /** Write the vis leaf number to the portal file. \param n The current node of the bsp. Call with the root node. */ static void WritePortalLeafs_r (node_t *n) { if (!n->contents) { WritePortalLeafs_r (n->children[0]); WritePortalLeafs_r (n->children[1]); } else { if (n->visleafnum != -1) fprintf (pf, "%i\n", n->visleafnum); } } /** Set the vis leaf number of the leafs in a detail cluster. \param n The current node. Call with the detail node. \param num The vis leaf number. */ static void SetCluster_r (node_t *n, int num) { if (n->contents == CONTENTS_SOLID) { // solid block, viewpoint never inside n->visleafnum = -1; return; } n->visleafnum = num; if (!n->contents) { SetCluster_r (n->children[0], num); SetCluster_r (n->children[1], num); } else num_realleafs++; } /** Set the vis leaf number of the leafs in a bsp tree. \param node The current node. Call with the root node. */ static void NumberLeafs_r (node_t *node) { portal_t *p; if (!node->contents && !node->detail) { // decision node node->visleafnum = -99; NumberLeafs_r (node->children[0]); NumberLeafs_r (node->children[1]); return; } Draw_ClearWindow (); DrawLeaf (node, 1); if (node->contents == CONTENTS_SOLID) { // solid block, viewpoint never inside node->visleafnum = -1; return; } node->visleafnum = num_visleafs++; for (p = node->portals; p;) { if (p->nodes[0] == node) { // write out from only the first leaf if (SameContents(p->nodes[0], p->nodes[1])) num_visportals++; p = p->next[0]; } else p = p->next[1]; } if (node->detail) { SetCluster_r (node->children[0], node->visleafnum); SetCluster_r (node->children[1], node->visleafnum); } else { num_realleafs++; } } void WritePortalfile (node_t *headnode) { // set the visleafnum field in every leaf and count the total number of // portals num_visleafs = 0; num_visportals = 0; num_realleafs = 0; NumberLeafs_r (headnode); // write the file printf ("writing %s\n", options.portfile); pf = fopen (options.portfile, "w"); if (!pf) Sys_Error ("Error opening %s", options.portfile); fprintf (pf, "%s\n", PORTALFILE); fprintf (pf, "%i\n", num_visleafs); fprintf (pf, "%i\n", num_visportals); fprintf (pf, "%i\n", num_realleafs); WritePortalFile_r (headnode); WritePortalLeafs_r (headnode); fclose (pf); } //@}