// solidbsp.c #include "bsp5.h" int leaffaces; int nodefaces; int splitnodes; int c_solid, c_empty, c_water; qboolean usemidsplit; //============================================================================ /* ================== faceside for bsp hueristic ================== */ int faceside (face_t *in, plane_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 the clipping hull bsp doesn't worry about avoiding splits ================== */ surface_t *choosemidplanefromlist (surface_t *surfaces, vec3_t mins, vec3_t maxs) { int j,l; surface_t *p, *bestsurface; vec_t bestvalue, value, dist; plane_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 = &planes[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 the real bsp hueristic ================== */ surface_t *chooseplanefromlist (surface_t *surfaces, vec3_t mins, vec3_t maxs, qboolean usefloors) { int j,k,l; surface_t *p, *p2, *bestsurface; vec_t bestvalue, bestdistribution, value, dist; plane_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 = &planes[p->planenum]; k = 0; if (!usefloors && plane->normal[2] == 1) continue; 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) { int i,j; vec3_t mins, maxs; surface_t *p, *bestsurface; // // count onnode surfaces // i = 0; bestsurface = null; for (p=surfaces ; p ; p=p->next) if (!p->onnode) { i++; bestsurface = p; } if (i==0) return null; if (i==1) return bestsurface; // this is a final split // // calculate a bounding box of the entire surfaceset // for (i=0 ; i<3 ; i++) { mins[i] = 99999; maxs[i] = -99999; } for (p=surfaces ; p ; p=p->next) for (j=0 ; j<3 ; j++) { if (p->mins[j] < mins[j]) mins[j] = p->mins[j]; if (p->maxs[j] > maxs[j]) maxs[j] = p->maxs[j]; } if (usemidsplit) // do fast way for clipping hull return choosemidplanefromlist (surfaces, mins, maxs); // do slow way to save poly splits for drawing hull #if 0 bestsurface = chooseplanefromlist (surfaces, mins, maxs, false); if (bestsurface) return bestsurface; #endif return chooseplanefromlist (surfaces, mins, maxs, true); } //============================================================================ /* ================= 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] >= 0 || f->contents[1] >= 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]; } } } /* ================== divideplane ================== */ void divideplane (surface_t *in, plane_t *split, surface_t **front, surface_t **back) { face_t *facet, *next; face_t *frontlist, *backlist; face_t *frontfrag, *backfrag; surface_t *news; plane_t *inplane; inplane = &planes[in->planenum]; // parallel case is easy if (vectorcompare (inplane->normal, split->normal)) { // check for exactly on node if (inplane->dist == split->dist) { // divide the facets to the front and back sides news = allocsurface (); *news = *in; facet=in->faces; in->faces = null; news->faces = null; in->onnode = news->onnode = true; for ( ; facet ; facet=next) { next = facet->next; if (facet->planeside == 1) { facet->next = news->faces; news->faces = facet; } else { facet->next = in->faces; in->faces = facet; } } if (in->faces) *front = in; else *front = null; if (news->faces) *back = news; else *back = null; return; } if (inplane->dist > split->dist) { *front = in; *back = null; } else { *front = null; *back = in; } 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 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 plane 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); } /* ================== dividenodebounds ================== */ void dividenodebounds (node_t *node, plane_t *split) { vectorcopy (node->mins, node->children[0]->mins); vectorcopy (node->mins, node->children[1]->mins); vectorcopy (node->maxs, node->children[0]->maxs); vectorcopy (node->maxs, node->children[1]->maxs); // optimize: sloping cuts can give a better bbox than this... if (split->type > 2) return; node->children[0]->mins[split->type] = node->children[1]->maxs[split->type] = split->dist; } /* ================== linkconvexfaces determines the contents of the leaf and creates the final list of original faces that have some fragment inside this leaf ================== */ void linkconvexfaces (surface_t *planelist, node_t *leafnode) { face_t *f, *next; surface_t *surf, *pnext; int i, count; leafnode->faces = null; leafnode->contents = 0; leafnode->planenum = -1; count = 0; for ( surf = planelist ; surf ; surf = surf->next) { for (f = surf->faces ; f ; f=f->next) { count++; if (!leafnode->contents) leafnode->contents = f->contents[0]; else if (leafnode->contents != f->contents[0]) error ("mixed face contents in leafnode"); } } if (!leafnode->contents) leafnode->contents = contents_solid; switch (leafnode->contents) { case contents_empty: c_empty++; break; case contents_solid: c_solid++; break; case contents_water: case contents_slime: case contents_lava: case contents_sky: c_water++; break; default: error ("linkconvexfaces: bad contents number"); } // // write the list of faces, and free the originals // leaffaces += count; leafnode->markfaces = malloc(sizeof(face_t *)*(count+1)); i = 0; for ( surf = planelist ; surf ; surf = pnext) { pnext = surf->next; for (f = surf->faces ; f ; f=next) { next = f->next; leafnode->markfaces[i] = f->original; i++; freeface (f); } freesurface (surf); } leafnode->markfaces[i] = null; // sentinal } /* ================== linknodefaces returns a duplicated list of all faces on surface ================== */ face_t *linknodefaces (surface_t *surface) { face_t *f, *new, **prevptr; face_t *list; list = null; // subdivide prevptr = &surface->faces; while (1) { f = *prevptr; if (!f) break; subdivideface (f, prevptr); f = *prevptr; prevptr = &f->next; } // copy for (f=surface->faces ; f ; f=f->next) { nodefaces++; new = allocface (); *new = *f; f->original = new; new->next = list; list = new; } return list; } /* ================== partitionsurfaces ================== */ void partitionsurfaces (surface_t *surfaces, node_t *node) { surface_t *split, *p, *next; surface_t *frontlist, *backlist; surface_t *frontfrag, *backfrag; plane_t *splitplane; split = selectpartition (surfaces); if (!split) { // this is a leaf node node->planenum = planenum_leaf; linkconvexfaces (surfaces, node); return; } splitnodes++; node->faces = linknodefaces (split); node->children[0] = allocnode (); node->children[1] = allocnode (); node->planenum = split->planenum; splitplane = &planes[split->planenum]; dividenodebounds (node, splitplane); // // multiple surfaces, so split all the polysurfaces into front and back lists // frontlist = null; backlist = null; for (p=surfaces ; p ; p=next) { next = p->next; divideplane (p, splitplane, &frontfrag, &backfrag); if (frontfrag && backfrag) { // the plane was split, which may expose oportunities to merge // adjacent faces into a single face // mergeplanefaces (frontfrag); // mergeplanefaces (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; } } partitionsurfaces (frontlist, node->children[0]); partitionsurfaces (backlist, node->children[1]); } /* ================== drawsurface ================== */ void drawsurface (surface_t *surf) { face_t *f; for (f=surf->faces ; f ; f=f->next) draw_drawface (f); } /* ================== drawsurfacelist ================== */ void drawsurfacelist (surface_t *surf) { draw_clearwindow (); while (surf) { drawsurface (surf); surf = surf->next; } } /* ================== solidbsp ================== */ node_t *solidbsp (surface_t *surfhead, qboolean midsplit) { int i; node_t *headnode; qprintf ("----- solidbsp -----\n"); headnode = allocnode (); usemidsplit = midsplit; // // calculate a bounding box for the entire model // for (i=0 ; i<3 ; i++) { headnode->mins[i] = brushset->mins[i] - sidespace; headnode->maxs[i] = brushset->maxs[i] + sidespace; } // // recursively partition everything // draw_clearwindow (); splitnodes = 0; leaffaces = 0; nodefaces = 0; c_solid = c_empty = c_water = 0; partitionsurfaces (surfhead, headnode); qprintf ("%5i split nodes\n", splitnodes); qprintf ("%5i solid leafs\n", c_solid); qprintf ("%5i empty leafs\n", c_empty); qprintf ("%5i water leafs\n", c_water); qprintf ("%5i leaffaces\n",leaffaces); qprintf ("%5i nodefaces\n", nodefaces); return headnode; }