mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-11 07:42:18 +00:00
6d5ffa9f8e
There's still some cleanup to do, but everything seems to be working nicely: `make -j` works, `make distcheck` passes. There is probably plenty of bitrot in the package directories (RPM, debian), though. The vc project files have been removed since those versions are way out of date and quakeforge is pretty much dependent on gcc now anyway. Most of the old Makefile.am files are now Makemodule.am. This should allow for new Makefile.am files that allow local building (to be added on an as-needed bases). The current remaining Makefile.am files are for standalone sub-projects.a The installable bins are currently built in the top-level build directory. This may change if the clutter gets to be too much. While this does make a noticeable difference in build times, the main reason for the switch was to take care of the growing dependency issues: now it's possible to build tools for code generation (eg, using qfcc and ruamoko programs for code-gen).
712 lines
16 KiB
C
712 lines
16 KiB
C
/*
|
|
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
|
|
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
|
|
#include "QF/sys.h"
|
|
|
|
#include "tools/qfbsp/include/brush.h"
|
|
#include "tools/qfbsp/include/csg4.h"
|
|
#include "tools/qfbsp/include/bsp5.h"
|
|
#include "tools/qfbsp/include/draw.h"
|
|
#include "tools/qfbsp/include/solidbsp.h"
|
|
#include "tools/qfbsp/include/surfaces.h"
|
|
|
|
/** \addtogroup qfbsp_solidbsp
|
|
*/
|
|
//@{
|
|
|
|
int leaffaces;
|
|
int nodefaces;
|
|
int splitnodes;
|
|
|
|
int c_solid, c_empty, c_water;
|
|
|
|
qboolean usemidsplit;
|
|
|
|
|
|
/** Determine on which side of the plane a face is.
|
|
|
|
\param in The face.
|
|
\param split The plane.
|
|
\return <dl>
|
|
<dt>SIDE_FRONT</dt>
|
|
<dd>The face is in front of or on the plane.</dd>
|
|
<dt>SIDE_BACK</dt>
|
|
<dd>The face is behind or on the plane.</dd>
|
|
<dt>SIDE_ON</dt>
|
|
<dd>The face is on or cut by the plane.</dd>
|
|
</dl>
|
|
*/
|
|
static int
|
|
FaceSide (const face_t *in, const plane_t *split)
|
|
{
|
|
int frontcount, backcount, i;
|
|
vec_t dot;
|
|
const vec_t *p;
|
|
const winding_t *inp = in->points;
|
|
|
|
frontcount = backcount = 0;
|
|
|
|
// axial planes are fast
|
|
if (split->type < 3) {
|
|
for (i = 0, p = inp->points[0] + split->type; i < inp->numpoints;
|
|
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 = inp->points[0]; i < inp->numpoints; 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;
|
|
}
|
|
|
|
/** Chose the best plane for dividing the bsp.
|
|
|
|
The clipping hull BSP doesn't worry about avoiding splits, so this
|
|
function tries to find the plane that gives the most even split of the
|
|
bounding volume.
|
|
|
|
\param surfaces The surface chain of the bsp.
|
|
\param mins The minimum coordinate of the boundiing box.
|
|
\param maxs The maximum coordinate of the boundiing box.
|
|
\return The chosen surface.
|
|
*/
|
|
static __attribute__((pure)) surface_t *
|
|
ChooseMidPlaneFromList (surface_t *surfaces,
|
|
const vec3_t mins, const vec3_t maxs)
|
|
{
|
|
int j, l;
|
|
plane_t *plane;
|
|
surface_t *p, *bestsurface;
|
|
vec_t bestvalue, value, dist;
|
|
|
|
// 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
|
|
Sys_Error ("ChooseMidPlaneFromList: no valid planes");
|
|
}
|
|
|
|
return bestsurface;
|
|
}
|
|
|
|
/** Choose the best plane that produces the fewest splits.
|
|
|
|
\param surfaces The surface chain of the bsp.
|
|
\param mins The minimum coordinate of the boundiing box.
|
|
\param maxs The maximum coordinate of the boundiing box.
|
|
\param usefloors If false, floors will not be chosen.
|
|
\param usedetail If true, the plain must have structure faces, else
|
|
the plain must not have structure faces.
|
|
\return The chosen surface, or NULL if a suitable surface could
|
|
not be found.
|
|
*/
|
|
static __attribute__((pure)) surface_t *
|
|
ChoosePlaneFromList (surface_t *surfaces, const vec3_t mins, const vec3_t maxs,
|
|
qboolean usefloors, qboolean usedetail)
|
|
{
|
|
face_t *f;
|
|
int j, k, l, ishint;
|
|
plane_t *plane;
|
|
surface_t *p, *p2, *bestsurface;
|
|
vec_t bestvalue, bestdistribution, value, dist;
|
|
|
|
// pick the plane that splits the least
|
|
bestvalue = 999999;
|
|
bestsurface = NULL;
|
|
bestdistribution = 9e30;
|
|
|
|
for (p = surfaces; p; p = p->next) {
|
|
if (p->onnode)
|
|
continue;
|
|
|
|
for (f = p->faces; f; f = f->next)
|
|
if (f->texturenum == TEX_HINT)
|
|
break;
|
|
ishint = f != 0;
|
|
|
|
if (p->has_struct && usedetail)
|
|
continue;
|
|
if (!p->has_struct && !usedetail)
|
|
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) {
|
|
if (!ishint && f->texturenum == TEX_HINT)
|
|
k += 9999;
|
|
else
|
|
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;
|
|
}
|
|
|
|
/** Select a surface on which to split the group of surfaces.
|
|
|
|
\param surfaces The group of surfaces.
|
|
\param detail Set to 1 if the selected surface has detail.
|
|
\return The selected surface or NULL if the list can no longer
|
|
be defined (ie, a leaf).
|
|
*/
|
|
static surface_t *
|
|
SelectPartition (surface_t *surfaces, int *detail)
|
|
{
|
|
int i, j;
|
|
surface_t *p, *bestsurface;
|
|
vec3_t mins, maxs;
|
|
|
|
*detail = 0;
|
|
|
|
// 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) {
|
|
if (!bestsurface->has_struct && !usemidsplit)
|
|
*detail = 1;
|
|
return bestsurface; // this is a final split
|
|
}
|
|
|
|
// calculate a bounding box of the entire surfaceset
|
|
for (i = 0; i < 3; i++) {
|
|
mins[i] = BOGUS_RANGE;
|
|
maxs[i] = -BOGUS_RANGE;
|
|
}
|
|
|
|
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
|
|
bestsurface = ChoosePlaneFromList (surfaces, mins, maxs, true, false);
|
|
if (bestsurface)
|
|
return bestsurface;
|
|
*detail = 1;
|
|
return ChoosePlaneFromList (surfaces, mins, maxs, true, true);
|
|
}
|
|
|
|
void
|
|
CalcSurfaceInfo (surface_t *surf)
|
|
{
|
|
face_t *f;
|
|
int i, j;
|
|
|
|
if (!surf->faces)
|
|
Sys_Error ("CalcSurfaceInfo: surface without a face");
|
|
|
|
// calculate a bounding box
|
|
for (i = 0; i < 3; i++) {
|
|
surf->mins[i] = BOGUS_RANGE;
|
|
surf->maxs[i] = -BOGUS_RANGE;
|
|
}
|
|
|
|
for (f = surf->faces; f; f = f->next) {
|
|
winding_t *fp = f->points;
|
|
if (f->contents[0] >= 0 || f->contents[1] >= 0)
|
|
Sys_Error ("Bad contents");
|
|
for (i = 0; i < fp->numpoints; i++) {
|
|
for (j = 0; j < 3; j++) {
|
|
if (fp->points[i][j] < surf->mins[j])
|
|
surf->mins[j] = fp->points[i][j];
|
|
if (fp->points[i][j] > surf->maxs[j])
|
|
surf->maxs[j] = fp->points[i][j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Divide a surface by the plane.
|
|
|
|
\param in The surface to divide.
|
|
\param split The plane by which to divide the surface.
|
|
\param front Tne part of the surface in front of the plane.
|
|
\param back The part of the surface behind the plane.
|
|
*/
|
|
static 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;
|
|
plane_t *inplane;
|
|
surface_t *news;
|
|
|
|
int have[2][2]; // [front|back][detail|struct]
|
|
|
|
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;
|
|
in->has_detail = in->has_struct = false;
|
|
|
|
for (; facet; facet = next) {
|
|
next = facet->next;
|
|
if (facet->planeside == 1) {
|
|
facet->next = news->faces;
|
|
news->faces = facet;
|
|
|
|
if (facet->detail)
|
|
news->has_detail = true;
|
|
else
|
|
news->has_struct = true;
|
|
} else {
|
|
facet->next = in->faces;
|
|
in->faces = facet;
|
|
|
|
if (facet->detail)
|
|
in->has_detail = true;
|
|
else
|
|
in->has_struct = true;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
memset (have, 0, sizeof (have));
|
|
for (facet = in->faces; facet; facet = next) {
|
|
next = facet->next;
|
|
SplitFace (facet, split, &frontfrag, &backfrag);
|
|
if (frontfrag) {
|
|
frontfrag->next = frontlist;
|
|
frontlist = frontfrag;
|
|
have[0][frontfrag->detail] = 1;
|
|
}
|
|
if (backfrag) {
|
|
backfrag->next = backlist;
|
|
backlist = backfrag;
|
|
have[1][backfrag->detail] = 1;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
|
|
in->has_struct = have[0][0];
|
|
in->has_detail = have[0][1];
|
|
|
|
news->has_struct = have[1][0];
|
|
news->has_detail = have[1][1];
|
|
|
|
// recalc bboxes and flags
|
|
CalcSurfaceInfo (news);
|
|
CalcSurfaceInfo (in);
|
|
}
|
|
|
|
/** Determine the contents of the leaf and create the final list of
|
|
original faces that have some fragment inside this leaf
|
|
|
|
\param planelist surfaces bounding the leaf.
|
|
\param leafnode The leaf.
|
|
*/
|
|
static void
|
|
LinkConvexFaces (surface_t *planelist, node_t *leafnode)
|
|
{
|
|
face_t *f, *next;
|
|
int i, count;
|
|
surface_t *surf, *pnext;
|
|
|
|
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) {
|
|
if (f->texturenum < 0)
|
|
continue;
|
|
count++;
|
|
if (!leafnode->contents)
|
|
leafnode->contents = f->contents[0];
|
|
else if (leafnode->contents != f->contents[0])
|
|
Sys_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:
|
|
Sys_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;
|
|
if (f->texturenum >= 0) {
|
|
leafnode->markfaces[i] = f->original;
|
|
i++;
|
|
}
|
|
FreeFace (f);
|
|
}
|
|
FreeSurface (surf);
|
|
}
|
|
leafnode->markfaces[i] = NULL; // sentinal
|
|
}
|
|
|
|
/** Return a duplicated list of all faces on surface
|
|
|
|
\param surface The surface of which to duplicate the faces.
|
|
\return The duplicated list.
|
|
*/
|
|
static face_t *
|
|
LinkNodeFaces (surface_t *surface)
|
|
{
|
|
face_t *list, *new, **prevptr, *f;
|
|
|
|
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;
|
|
new->points = CopyWinding (f->points);
|
|
f->original = new;
|
|
new->next = list;
|
|
list = new;
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
/** Partition the surfaces, creating a nice bsp.
|
|
|
|
\param surfaces The surfaces to partition.
|
|
\param node The current node.
|
|
*/
|
|
static 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, &node->detail);
|
|
if (!split) { // this is a leaf node
|
|
node->detail = 0;
|
|
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];
|
|
|
|
// 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)
|
|
Sys_Error ("surface with no faces");
|
|
frontfrag->next = frontlist;
|
|
frontlist = frontfrag;
|
|
}
|
|
if (backfrag) {
|
|
if (!backfrag->faces)
|
|
Sys_Error ("surface with no faces");
|
|
backfrag->next = backlist;
|
|
backlist = backfrag;
|
|
}
|
|
}
|
|
|
|
PartitionSurfaces (frontlist, node->children[0]);
|
|
PartitionSurfaces (backlist, node->children[1]);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
//@}
|