/*** * * Copyright (c) 1998, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * ****/ // solidbsp.c #include "bsp5.h" /* Each node or leaf will have a set of portals that completely enclose the volume of the node and pass into an adjacent node. */ int c_leaffaces; int c_nodefaces; int c_splitnodes; //============================================================================ /* ================== FaceSide For BSP hueristic ================== */ int FaceSide (face_t *in, dplane_t *split) { int frontcount, backcount; vec_t dot; int i; vec_t *p; frontcount = backcount = 0; // axial planes are fast if (split->type < 3) for (i=0, p = in->pts[0]+split->type ; inumpoints ; i++, p+=3) { if (*p > split->dist + ON_EPSILON) { if (backcount) return SIDE_ON; frontcount = 1; } else if (*p < split->dist - ON_EPSILON) { if (frontcount) return SIDE_ON; backcount = 1; } } else // sloping planes take longer for (i=0, p = in->pts[0] ; inumpoints ; i++, p+=3) { dot = DotProduct (p, split->normal); dot -= split->dist; if (dot > ON_EPSILON) { if (backcount) return SIDE_ON; frontcount = 1; } else if (dot < -ON_EPSILON) { if (frontcount) return SIDE_ON; backcount = 1; } } if (!frontcount) return SIDE_BACK; if (!backcount) return SIDE_FRONT; return SIDE_ON; } /* ================== ChooseMidPlaneFromList When there are a huge number of planes, just choose one closest to the middle. ================== */ surface_t *ChooseMidPlaneFromList (surface_t *surfaces, vec3_t mins, vec3_t maxs) { int j,l; surface_t *p, *bestsurface; vec_t bestvalue, value, dist; dplane_t *plane; // // pick the plane that splits the least // bestvalue = 6*8192*8192; bestsurface = NULL; for (p=surfaces ; p ; p=p->next) { if (p->onnode) continue; plane = &dplanes[p->planenum]; // check for axis aligned surfaces l = plane->type; if (l > PLANE_Z) continue; // // calculate the split metric along axis l, smaller values are better // value = 0; dist = plane->dist * plane->normal[l]; for (j=0 ; j<3 ; j++) { if (j == l) { value += (maxs[l]-dist)*(maxs[l]-dist); value += (dist-mins[l])*(dist-mins[l]); } else value += 2*(maxs[j]-mins[j])*(maxs[j]-mins[j]); } if (value > bestvalue) continue; // // currently the best! // bestvalue = value; bestsurface = p; } if (!bestsurface) { for (p=surfaces ; p ; p=p->next) if (!p->onnode) return p; // first valid surface Error ("ChooseMidPlaneFromList: no valid planes"); } return bestsurface; } /* ================== ChoosePlaneFromList Choose the plane that splits the least faces ================== */ surface_t *ChoosePlaneFromList (surface_t *surfaces, vec3_t mins, vec3_t maxs) { int j,k,l; surface_t *p, *p2, *bestsurface; vec_t bestvalue, bestdistribution, value, dist; dplane_t *plane; face_t *f; // // pick the plane that splits the least // bestvalue = 99999; bestsurface = NULL; bestdistribution = 9e30; for (p=surfaces ; p ; p=p->next) { if (p->onnode) continue; plane = &dplanes[p->planenum]; k = 0; for (p2=surfaces ; p2 ; p2=p2->next) { if (p2 == p) continue; if (p2->onnode) continue; for (f=p2->faces ; f ; f=f->next) { if (FaceSide (f, plane) == SIDE_ON) { k++; if (k >= bestvalue) break; } } if (k > bestvalue) break; } if (k > bestvalue) continue; // if equal numbers, axial planes win, then decide on spatial subdivision if (k < bestvalue || (k == bestvalue && plane->type < PLANE_ANYX) ) { // check for axis aligned surfaces l = plane->type; if (l <= PLANE_Z) { // axial aligned // // calculate the split metric along axis l // value = 0; for (j=0 ; j<3 ; j++) { if (j == l) { dist = plane->dist * plane->normal[l]; value += (maxs[l]-dist)*(maxs[l]-dist); value += (dist-mins[l])*(dist-mins[l]); } else value += 2*(maxs[j]-mins[j])*(maxs[j]-mins[j]); } if (value > bestdistribution && k == bestvalue) continue; bestdistribution = value; } // // currently the best! // bestvalue = k; bestsurface = p; } } return bestsurface; } /* ================== SelectPartition Selects a surface from a linked list of surfaces to split the group on returns NULL if the surface list can not be divided any more (a leaf) ================== */ surface_t *SelectPartition (surface_t *surfaces, node_t *node, qboolean usemidsplit) { int i,j; surface_t *p, *bestsurface; // // count surface choices // i = 0; bestsurface = NULL; for (p=surfaces ; p ; p=p->next) if (!p->onnode) { i++; bestsurface = p; } if (i==0) return NULL; // this is a leafnode if (i==1) return bestsurface; // this is a final split if (usemidsplit) // do fast way for clipping hull return ChooseMidPlaneFromList (surfaces, node->mins, node->maxs); // do slow way to save poly splits for drawing hull return ChoosePlaneFromList (surfaces, node->mins, node->maxs); } //============================================================================ /* ================= CalcSurfaceInfo Calculates the bounding box ================= */ void CalcSurfaceInfo (surface_t *surf) { int i,j; face_t *f; if (!surf->faces) Error ("CalcSurfaceInfo: surface without a face"); // // calculate a bounding box // for (i=0 ; i<3 ; i++) { surf->mins[i] = 99999; surf->maxs[i] = -99999; } for (f=surf->faces ; f ; f=f->next) { if (f->contents >= 0) Error ("Bad contents"); for (i=0 ; inumpoints ; i++) for (j=0 ; j<3 ; j++) { if (f->pts[i][j] < surf->mins[j]) surf->mins[j] = f->pts[i][j]; if (f->pts[i][j] > surf->maxs[j]) surf->maxs[j] = f->pts[i][j]; } } } /* ================== DivideSurface ================== */ void DivideSurface (surface_t *in, dplane_t *split, surface_t **front, surface_t **back) { face_t *facet, *next; face_t *frontlist, *backlist; face_t *frontfrag, *backfrag; surface_t *news; dplane_t *inplane; inplane = &dplanes[in->planenum]; // parallel case is easy if (inplane->normal[0] == split->normal[0] && inplane->normal[1] == split->normal[1] && inplane->normal[2] == split->normal[2]) { if (inplane->dist > split->dist) { *front = in; *back = NULL; } else if (inplane->dist < split->dist) { *front = NULL; *back = in; } else { // split the surface into front and back frontlist = NULL; backlist = NULL; for (facet = in->faces ; facet ; facet = next) { next = facet->next; if (facet->planenum & 1) { facet->next = backlist; backlist = facet; } else { facet->next = frontlist; frontlist = facet; } } goto makesurfs; } return; } // do a real split. may still end up entirely on one side // OPTIMIZE: use bounding box for fast test frontlist = NULL; backlist = NULL; for (facet = in->faces ; facet ; facet = next) { next = facet->next; SplitFace (facet, split, &frontfrag, &backfrag); if (frontfrag) { frontfrag->next = frontlist; frontlist = frontfrag; } if (backfrag) { backfrag->next = backlist; backlist = backfrag; } } // if nothing actually got split, just move the in plane makesurfs: if (frontlist == NULL) { *front = NULL; *back = in; in->faces = backlist; return; } if (backlist == NULL) { *front = in; *back = NULL; in->faces = frontlist; return; } // stuff got split, so allocate one new surface and reuse in news = AllocSurface (); *news = *in; news->faces = backlist; *back = news; in->faces = frontlist; *front = in; // recalc bboxes and flags CalcSurfaceInfo (news); CalcSurfaceInfo (in); } /* ============= SplitNodeSurfaces ============= */ void SplitNodeSurfaces (surface_t *surfaces, node_t *node) { surface_t *p, *next; surface_t *frontlist, *backlist; surface_t *frontfrag, *backfrag; dplane_t *splitplane; splitplane = &dplanes[node->planenum]; frontlist = NULL; backlist = NULL; for (p=surfaces ; p ; p=next) { next = p->next; DivideSurface (p, splitplane, &frontfrag, &backfrag); if (frontfrag) { if (!frontfrag->faces) Error ("surface with no faces"); frontfrag->next = frontlist; frontlist = frontfrag; } if (backfrag) { if (!backfrag->faces) Error ("surface with no faces"); backfrag->next = backlist; backlist = backfrag; } } node->children[0]->surfaces = frontlist; node->children[1]->surfaces = backlist; } int RankForContents (int contents) { switch (contents) { case CONTENTS_EMPTY: return 0; case CONTENTS_WATER: return 1; case CONTENTS_TRANSLUCENT: return 2; case CONTENTS_CURRENT_0: return 3; case CONTENTS_CURRENT_90: return 4; case CONTENTS_CURRENT_180: return 5; case CONTENTS_CURRENT_270: return 6; case CONTENTS_CURRENT_UP: return 7; case CONTENTS_CURRENT_DOWN: return 8; case CONTENTS_SLIME: return 9; case CONTENTS_LAVA : return 10; case CONTENTS_SKY : return 11; case CONTENTS_SOLID: return 12; default: Error ("RankForContents: bad contents %i", contents); } return -1; } int ContentsForRank (int rank) { switch (rank) { case -1: return CONTENTS_SOLID; // no faces at all case 0: return CONTENTS_EMPTY; case 1: return CONTENTS_WATER; case 2: return CONTENTS_TRANSLUCENT; case 3: return CONTENTS_CURRENT_0; case 4: return CONTENTS_CURRENT_90; case 5: return CONTENTS_CURRENT_180; case 6: return CONTENTS_CURRENT_270; case 7: return CONTENTS_CURRENT_UP; case 8: return CONTENTS_CURRENT_DOWN; case 9: return CONTENTS_SLIME; case 10: return CONTENTS_LAVA; case 11: return CONTENTS_SKY; case 12: return CONTENTS_SOLID; default: Error ("ContentsForRank: bad rank %i", rank); } return -1; } /* ============= FreeLeafSurfs ============= */ void FreeLeafSurfs (node_t *leaf) { surface_t *surf, *snext; face_t *f, *fnext; for (surf = leaf->surfaces ; surf ; surf=snext) { snext = surf->next; for (f=surf->faces ; f ; f=fnext) { fnext = f->next; FreeFace (f); } FreeSurface (surf); } leaf->surfaces = NULL; } /* ================== LinkLeafFaces Determines the contents of the leaf and creates the final list of original faces that have some fragment inside this leaf ================== */ #define MAX_LEAF_FACES 1024 void LinkLeafFaces (surface_t *planelist, node_t *leafnode) { face_t *f; surface_t *surf; int rank, r; int nummarkfaces; face_t *markfaces[MAX_LEAF_FACES]; leafnode->faces = NULL; leafnode->planenum = -1; rank = -1; for ( surf = planelist ; surf ; surf = surf->next) { for (f = surf->faces ; f ; f=f->next) { r = RankForContents (f->contents); if (r > rank) rank = r; } } leafnode->contents = ContentsForRank (rank); if (leafnode->contents != CONTENTS_SOLID) { nummarkfaces = 0; for (surf = leafnode->surfaces ; surf ; surf=surf->next) { for (f=surf->faces ; f ; f=f->next) { if (nummarkfaces == MAX_LEAF_FACES) Error ("nummarkfaces == MAX_LEAF_FACES"); markfaces[nummarkfaces++] = f->original; } } c_leaffaces += nummarkfaces; markfaces[nummarkfaces] = NULL; // end marker nummarkfaces++; leafnode->markfaces = malloc(nummarkfaces * sizeof(*leafnode->markfaces)); memcpy (leafnode->markfaces, markfaces, nummarkfaces * sizeof(*leafnode->markfaces)); } FreeLeafSurfs (leafnode); leafnode->surfaces = NULL; } /* ================== MakeNodePortal 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. Each portal tracks the node that created it, so unused nodes can be removed later. ================== */ void MakeNodePortal (node_t *node) { portal_t *new_portal, *p; dplane_t *plane; dplane_t clipplane; winding_t *w; int side; plane = &dplanes[node->planenum]; w = BaseWindingForPlane (plane); new_portal = AllocPortal (); new_portal->plane = *plane; new_portal->onnode = node; side = 0; // shut up compiler warning for (p = node->portals ; p ; p = p->next[side]) { clipplane = p->plane; if (p->nodes[0] == node) side = 0; else if (p->nodes[1] == node) { clipplane.dist = -clipplane.dist; VectorSubtract (vec3_origin, clipplane.normal, clipplane.normal); side = 1; } else Error ("MakeNodePortal: mislinked portal"); w = ClipWinding (w, &clipplane, true); if (!w) { printf ("WARNING: MakeNodePortal:new portal was clipped away from node@(%.0f,%.0f,%.0f)-(%.0f,%.0f,%.0f)\n", node->mins[0], node->mins[1], node->mins[2], node->maxs[0], node->maxs[1], node->maxs[2]); FreePortal (new_portal); return; } } new_portal->winding = w; AddPortalToNodes (new_portal, node->children[0], node->children[1]); } /* ============== SplitNodePortals Move or split the portals that bound node so that the node's children have portals instead of node. ============== */ void SplitNodePortals (node_t *node) { portal_t *p, *next_portal, *new_portal; node_t *f, *b, *other_node; int side; dplane_t *plane; winding_t *frontwinding, *backwinding; plane = &dplanes[node->planenum]; f = node->children[0]; b = node->children[1]; for (p = node->portals ; p ; p = next_portal) { if (p->nodes[0] == node) side = 0; else if (p->nodes[1] == node) side = 1; else Error ("CutNodePortals_r: mislinked portal"); next_portal = p->next[side]; other_node = p->nodes[!side]; RemovePortalFromNode (p, p->nodes[0]); RemovePortalFromNode (p, p->nodes[1]); // // 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); } } node->portals = NULL; } /* ================== CalcNodeBounds Determines the boundaries of a node by minmaxing all the portal points, whcih completely enclose the node. Returns true if the node should be midsplit.(very large) ================== */ qboolean CalcNodeBounds (node_t *node) { int i, j; vec_t v; portal_t *p, *next_portal; int side; node->mins[0] = node->mins[1] = node->mins[2] = 9999; node->maxs[0] = node->maxs[1] = node->maxs[2] = -9999; for (p = node->portals ; p ; p = next_portal) { if (p->nodes[0] == node) side = 0; else if (p->nodes[1] == node) side = 1; else Error ("CutNodePortals_r: mislinked portal"); next_portal = p->next[side]; for (i=0 ; iwinding->numpoints ; i++) { for (j=0 ; j<3 ; j++) { v = p->winding->points[i][j]; if (v < node->mins[j]) node->mins[j] = v; if (v > node->maxs[j]) node->maxs[j] = v; } } } for (i=0 ; i<3 ; i++) if (node->maxs[i] - node->mins[i] > 1024) return true; return false; } /* ================== CopyFacesToNode Do a final merge attempt, then subdivide the faces to surface cache size if needed. These are final faces that will be drawable in the game. Copies of these faces are further chopped up into the leafs, but they will reference these originals. ================== */ void CopyFacesToNode (node_t *node, surface_t *surf) { face_t **prevptr, *f, *newf; // merge as much as possible MergePlaneFaces (surf); // subdivide large faces prevptr = &surf->faces; while (1) { f = *prevptr; if (!f) break; SubdivideFace (f, prevptr); f = *prevptr; prevptr = &f->next; } // copy the faces to the node, and consider them the originals node->surfaces = NULL; node->faces = NULL; for (f=surf->faces ; f ; f=f->next) { if (f->contents != CONTENTS_SOLID) { newf = AllocFace (); *newf = *f; f->original = newf; newf->next = node->faces; node->faces = newf; c_nodefaces++; } } } /* ================== DrawSurfaces ================== */ void DrawSurfaces (surface_t *surf) { face_t *f; Draw_ClearWindow (); for ( ; surf ; surf=surf->next) { for (f = surf->faces ; f ; f=f->next) { Draw_DrawFace (f); } } } /* ================== BuildBspTree_r ================== */ void BuildBspTree_r (node_t *node) { surface_t *split; qboolean midsplit; surface_t *allsurfs; midsplit = CalcNodeBounds (node); DrawSurfaces (node->surfaces); split = SelectPartition (node->surfaces, node, midsplit); if (!split) { // this is a leaf node node->planenum = PLANENUM_LEAF; LinkLeafFaces (node->surfaces, node); return; } // // these are final polygons // split->onnode = node; // can't use again allsurfs = node->surfaces; node->planenum = split->planenum; node->faces = NULL; CopyFacesToNode (node, split); c_splitnodes++; node->children[0] = AllocNode (); node->children[1] = AllocNode (); // // split all the polysurfaces into front and back lists // SplitNodeSurfaces (allsurfs, node); // // create the portal that seperates the two children // MakeNodePortal (node); // // carve the portals on the boundaries of the node // SplitNodePortals (node); // // recursively do the children // BuildBspTree_r (node->children[0]); BuildBspTree_r (node->children[1]); } /* ================== SolidBSP Takes a chain of surfaces plus a split type, and returns a bsp tree with faces off the nodes. The original surface chain will be completely freed. ================== */ node_t *SolidBSP (surfchain_t *surfhead) { int i; node_t *headnode; qprintf ("----- SolidBSP -----\n"); headnode = AllocNode (); headnode->surfaces = surfhead->surfaces; Draw_ClearWindow (); c_splitnodes = 0; c_nodefaces = 0; c_leaffaces = 0; if (!surfhead->surfaces) { // nothing at all to build headnode->planenum = -1; headnode->contents = CONTENTS_EMPTY; return headnode; } // // generate six portals that enclose the entire world // MakeHeadnodePortals (headnode, surfhead->mins, surfhead->maxs); // // recursively partition everything // BuildBspTree_r (headnode); qprintf ("%5i split nodes\n", c_splitnodes); qprintf ("%5i node faces\n", c_nodefaces); qprintf ("%5i leaf faces\n", c_leaffaces); return headnode; }