/*** * * 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. * ****/ // csg4.c #include "csg.h" /* NOTES ----- check map size for +/- 4k limit at load time */ int brushfaces; int c_csgfaces; FILE *out[NUM_HULLS]; int c_tiny, c_tiny_clip; int c_outfaces; qboolean hullfile = false; static char qhullfile[ 256 ]; qboolean glview; qboolean noclip; qboolean onlyents; qboolean wadtextures = true; vec3_t world_mins, world_maxs; /* ================== NewFaceFromFace Duplicates the non point information of a face, used by SplitFace ================== */ bface_t *NewFaceFromFace (bface_t *in) { bface_t *newf; newf = malloc (sizeof(bface_t)); memset (newf, 0, sizeof(newf)); newf->contents = in->contents; newf->texinfo = in->texinfo; newf->planenum = in->planenum; newf->plane = in->plane; return newf; } void FreeFace (bface_t *f) { free (f->w); free (f); } /* ================= ClipFace Clips a faces by a plane, returning the fragment on the backside and adding any fragment to the outside. Faces exactly on the plane will stay inside unless overdrawn by later brush frontside is the side of the plane that holds the outside list Precedence is necesary to handle overlapping coplanar faces. ================= */ #define SPLIT_EPSILON 0.3 bface_t *ClipFace (brush_t *b, bface_t *f, bface_t **outside, int splitplane, qboolean precedence) { bface_t *front; winding_t *fw, *bw; vec_t d; plane_t *split; int i, count[3]; // handle exact plane matches special if (f->planenum == (splitplane^1) ) { // opposite side, so put on inside list return f; } if ( f->planenum == splitplane ) { // coplanar if (precedence) { // this fragment will go to the inside, because // the earlier one was clipped to the outside return f; } f->next = *outside; *outside = f; return NULL; } split = &mapplanes[splitplane]; #if 0 count[0] = count[1] = count[2] = 0; for (i=0 ; iw->numpoints ; i++) { d = DotProduct (f->w->p[i], split->normal) - split->dist; if (d < -SPLIT_EPSILON) count[1]++; else if (d > SPLIT_EPSILON) count[0]++; else count[2]++; } if (!count[0]) { fw = NULL; bw = f->w; } else if (!count[1]) { fw = f->w; bw = NULL; } else #endif ClipWindingNoCopy (f->w, split->normal, split->dist, &fw, &bw); if (!fw) return f; if (!bw) { f->next = *outside; *outside = f; return NULL; } FreeWinding (f->w); front = NewFaceFromFace (f); front->w = fw; WindingBounds (fw, front->mins, front->maxs); front->next = *outside; *outside = front; f->w = bw; WindingBounds (bw, f->mins, f->maxs); return f; } /* =========== WriteFace =========== */ void WriteFace (int hull, bface_t *f) { int i, j; winding_t *w; static int level = 128; vec_t light; ThreadLock (); if (!hull) c_csgfaces++; if (glview) { // .gl format w = f->w; fprintf (out[hull], "%i\n", w->numpoints); level+=28; light = (level&255)/255.0; for (i=0 ; inumpoints ; i++) { fprintf (out[hull], "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", w->p[i][0], w->p[i][1], w->p[i][2], light, light, light); } fprintf (out[hull], "\n"); } else { // .p0 format w = f->w; fprintf (out[hull], "%i %i %i %i\n", f->planenum, f->texinfo, f->contents, w->numpoints); for (i=0 ; inumpoints ; i++) { fprintf (out[hull], "%5.2f %5.2f %5.2f\n", w->p[i][0], w->p[i][1], w->p[i][2]); } fprintf (out[hull], "\n"); } ThreadUnlock (); } /* ================== SaveOutside The faces remaining on the outside list are final polygons. Write them to the output file. Passable contents (water, lava, etc) will generate a mirrored copy of the face to be seen from the inside. ================== */ void SaveOutside (brush_t *b, int hull, bface_t *outside, int mirrorcontents) { bface_t *f , *next, *f2; int i; int planenum; vec3_t temp; for (f=outside ; f ; f=next) { next = f->next; if (WindingArea (f->w) < 1.0) { c_tiny++; qprintf ("Entity %i, Brush %i: tiny fragment\n" , b->entitynum, b->brushnum); continue; } // count unique faces if (!hull) { for (f2=b->hulls[hull].faces ; f2 ; f2=f2->next) { if (f2->planenum == f->planenum) { if (!f2->used) { f2->used = true; c_outfaces++; } break; } } } WriteFace (hull, f); // if (mirrorcontents != CONTENTS_SOLID) { f->planenum ^= 1; f->plane = &mapplanes[f->planenum]; f->contents = mirrorcontents; // swap point orders for (i=0 ; iw->numpoints/2 ; i++) // add points backwards { VectorCopy (f->w->p[i], temp); VectorCopy (f->w->p[f->w->numpoints-1-i] , f->w->p[i]); VectorCopy (temp, f->w->p[f->w->numpoints-1-i]); } WriteFace (hull, f); } FreeFace (f); } } /* ================== EncloseInside Changes the contents on all faces that got clipped out and moves them back to the outside list ================== */ void EncloseInside (bface_t *inside, bface_t **outside , int contents) { bface_t *f, *next; for (f=inside ; f ; f=next) { next = f->next; f->contents = contents; f->next = *outside; *outside = f; } } //========================================================================== bface_t *CopyFace (bface_t *f) { bface_t *n; n = NewFaceFromFace (f); n->w = CopyWinding (f->w); VectorCopy (f->mins, n->mins); VectorCopy (f->maxs, n->maxs); return n; } /* ================== CopyFacesToOutside Make a copy of all the faces of the brush, so they can be chewed up by other brushes. All of the faces start on the outside list. As other brushes take bites out of the faces, the fragments are moved to the inside list, so they can be freed when they are determined to be completely enclosed in solid. ================== */ bface_t *CopyFacesToOutside (brushhull_t *bh) { bface_t *f, *newf; bface_t *outside; outside = NULL; for (f=bh->faces ; f ; f=f->next) { brushfaces++; newf = CopyFace (f); WindingBounds (newf->w, newf->mins, newf->maxs); newf->next = outside; outside = newf; } return outside; } //============================================================ /* =========== CSGBrush =========== */ void CSGBrush (int brushnum) { int hull; brush_t *b1, *b2; brushhull_t *bh1, *bh2; int bn; qboolean overwrite; int i; bface_t *f, *f2, *next, *fcopy; bface_t *outside, *oldoutside; entity_t *e; vec_t area; SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_ABOVE_NORMAL); b1 = &mapbrushes[brushnum]; e = &entities[b1->entitynum]; for (hull = 0 ; hullhulls[hull]; // set outside to a copy of the brush's faces outside = CopyFacesToOutside (bh1); overwrite = false; for (bn=0 ; bnnumbrushes ; bn++) { // see if b2 needs to clip a chunk out of b1 if (bn==brushnum) { overwrite = true; // later brushes now overwrite continue; } b2 = &mapbrushes[e->firstbrush + bn]; bh2 = &b2->hulls[hull]; if (!bh2->faces) continue; // brush isn't in this hull // check brush bounding box first for (i=0 ; i<3 ; i++) if (bh1->mins[i] > bh2->maxs[i] || bh1->maxs[i] < bh2->mins[i]) break; if (i<3) continue; // divide faces by the planes of the b2 to find which // fragments are inside f = outside; outside = NULL; for ( ; f ; f=next) { next = f->next; // check face bounding box first for (i=0 ; i<3 ; i++) if (bh2->mins[i] > f->maxs[i] || bh2->maxs[i] < f->mins[i]) break; if (i<3) { // this face doesn't intersect brush2's bbox f->next = outside; outside = f; continue; } oldoutside = outside; fcopy = CopyFace (f); // save to avoid fake splits // throw pieces on the front sides of the planes // into the outside list, return the remains on the inside for (f2=bh2->faces ; f2 && f ; f2=f2->next) f = ClipFace (b1, f, &outside, f2->planenum, overwrite); area = f ? WindingArea (f->w) : 0; if (f && area < 1.0) { qprintf ("Entity %i, Brush %i: tiny penetration\n" , b1->entitynum, b1->brushnum); c_tiny_clip++; FreeFace (f); f = NULL; } if (f) { // there is one convex fragment of the original // face left inside brush2 FreeFace (fcopy); if (b1->contents > b2->contents) { // inside a water brush f->contents = b2->contents; f->next = outside; outside = f; } else // inside a solid brush FreeFace (f); // throw it away } else { // the entire thing was on the outside, even // though the bounding boxes intersected, // which will never happen with axial planes // free the fragments chopped to the outside while (outside != oldoutside) { f2 = outside->next; FreeFace (outside); outside = f2; } // revert to the original face to avoid // unneeded false cuts fcopy->next = outside; outside = fcopy; } } } // all of the faces left in outside are real surface faces SaveOutside (b1, hull, outside, b1->contents); } } //====================================================================== /* ============ EmitPlanes ============ */ void EmitPlanes (void) { int i; dplane_t *dp; plane_t *mp; numplanes = nummapplanes; mp = mapplanes; dp = dplanes; for (i=0 ; inormal, dp->normal); dp->dist = mp->dist; dp->type = mp->type; } } /* ============ SetModelNumbers ============ */ void SetModelNumbers (void) { int i; int models; char value[10]; models = 1; for (i=1 ; ifaces) continue; AddPointToBounds (h->mins, world_mins, world_maxs); AddPointToBounds (h->maxs, world_mins, world_maxs); } qprintf ("World bounds: (%i %i %i) to (%i %i %i)\n", (int)world_mins[0],(int)world_mins[1],(int)world_mins[2], (int)world_maxs[0],(int)world_maxs[1],(int)world_maxs[2]); VectorCopy (world_mins, draw_mins); VectorCopy (world_maxs, draw_maxs); Draw_ClearWindow (); } /* ============ main ============ */ extern char qproject[]; int main (int argc, char **argv) { int i, j; int hull; entity_t *ent; char source[1024]; char name[1024]; double start, end; printf( "qcsg.exe v2.7 (%s)\n", __DATE__ ); printf ("---- qcsg ----\n" ); for (i=1 ; i] [-draw] [-glview] [-noclip] [-onlyents] [-proj ] [-threads #] [-v] [-hullfile ] mapfile"); SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_ABOVE_NORMAL); start = I_FloatTime (); CheckHullFile( hullfile, qhullfile ); ThreadSetDefault (); SetQdirFromPath (argv[i]); strcpy (source, ExpandArg (argv[i])); StripExtension (source); strcpy (name, ExpandArg (argv[i])); DefaultExtension (name, ".map"); // might be .reg // // if onlyents, just grab the entites and resave // if (onlyents && !glview) { char out[1024]; int old_entities; sprintf (out, "%s.bsp", source); LoadBSPFile (out); // Get the new entity data from the map file LoadMapFile (name); // Write it all back out again. WriteBSP (source); end = I_FloatTime (); printf ("%5.0f seconds elapsed\n", end-start); return 0; } // // start from scratch // LoadMapFile (name); RunThreadsOnIndividual (nummapbrushes, true, CreateBrush); BoundWorld (); qprintf ("%5i map planes\n", nummapplanes); for (i=0 ; i