/* CONSTRUCTIVE SOLID GEOMETRY -aka- C S G Code based on original code from Valve Software, Modified by Sean "Zoner" Cavanaugh (seanc@gearboxsoftware.com) with permission. Modified by Tony "Merl" Moore (merlinis@bigpond.net.au) [AJM] */ #ifdef SYSTEM_WIN32 #pragma warning(disable:4996) #endif #pragma warning(disable:4018) // '<' : signed/unsigned mismatch #include "csg.h" /* NOTES - check map size for +/- 4k limit at load time - allow for multiple wad.cfg configurations per compile */ static FILE* out[NUM_HULLS]; // pointer to each of the hull out files (.p0, .p1, ect.) static int c_tiny; static int c_tiny_clip; static int c_outfaces; static int c_csgfaces; BoundingBox world_bounds; #ifdef HLCSG_WADCFG char wadconfigname[MAX_WAD_CFG_NAME]; #endif vec_t g_tiny_threshold = DEFAULT_TINY_THRESHOLD; bool g_noclip = DEFAULT_NOCLIP; // no clipping hull "-noclip" bool g_onlyents = DEFAULT_ONLYENTS; // onlyents mode "-onlyents" bool g_wadtextures = DEFAULT_WADTEXTURES; // "-nowadtextures" bool g_chart = DEFAULT_CHART; // show chart "-chart" bool g_skyclip = DEFAULT_SKYCLIP; // no sky clipping "-noskyclip" bool g_estimate = DEFAULT_ESTIMATE; // progress estimates "-estimate" bool g_info = DEFAULT_INFO; // "-info" ? const char* g_hullfile = NULL; // external hullfile "-hullfie sdfsd" #ifdef ZHLT_NULLTEX // AJM bool g_bUseNullTex = DEFAULT_NULLTEX; // "-nonulltex" #endif #ifdef HLCSG_PRECISIONCLIP // KGP cliptype g_cliptype = DEFAULT_CLIPTYPE; // "-cliptype " #endif #ifdef HLCSG_NULLIFY_INVISIBLE const char* g_nullfile = NULL; #endif #ifdef HLCSG_CLIPECONOMY // AJM bool g_bClipNazi = DEFAULT_CLIPNAZI; // "-noclipeconomy" #endif #ifdef HLCSG_AUTOWAD // AJM bool g_bWadAutoDetect = DEFAULT_WADAUTODETECT; // "-wadautodetect" #endif #ifdef ZHLT_DETAIL // AJM bool g_bDetailBrushes = DEFAULT_DETAIL; // "-detail" #endif #ifdef ZHLT_PROGRESSFILE // AJM char* g_progressfile = DEFAULT_PROGRESSFILE; // "-progressfile path" #endif #ifdef ZHLT_INFO_COMPILE_PARAMETERS // ===================================================================================== // GetParamsFromEnt // parses entity keyvalues for setting information // ===================================================================================== void GetParamsFromEnt(entity_t* mapent) { int iTmp; char szTmp[256]; Log("\nCompile Settings detected from info_compile_parameters entity\n"); // verbose(choices) : "Verbose compile messages" : 0 = [ 0 : "Off" 1 : "On" ] iTmp = IntForKey(mapent, "verbose"); if (iTmp == 1) { g_verbose = true; } else if (iTmp == 0) { g_verbose = false; } Log("%30s [ %-9s ]\n", "Compile Option", "setting"); Log("%30s [ %-9s ]\n", "Verbose Compile Messages", g_verbose ? "on" : "off"); // estimate(choices) :"Estimate Compile Times?" : 0 = [ 0: "Yes" 1: "No" ] if (IntForKey(mapent, "estimate")) { g_estimate = true; } else { g_estimate = false; } Log("%30s [ %-9s ]\n", "Estimate Compile Times", g_estimate ? "on" : "off"); // priority(choices) : "Priority Level" : 0 = [ 0 : "Normal" 1 : "High" -1 : "Low" ] if (!strcmp(ValueForKey(mapent, "priority"), "1")) { g_threadpriority = eThreadPriorityHigh; Log("%30s [ %-9s ]\n", "Thread Priority", "high"); } else if (!strcmp(ValueForKey(mapent, "priority"), "-1")) { g_threadpriority = eThreadPriorityLow; Log("%30s [ %-9s ]\n", "Thread Priority", "low"); } // texdata(string) : "Texture Data Memory" : "4096" iTmp = IntForKey(mapent, "texdata") * 1024; if (iTmp > g_max_map_miptex) { g_max_map_miptex = iTmp; } sprintf_s(szTmp, "%i", g_max_map_miptex); Log("%30s [ %-9s ]\n", "Texture Data Memory", szTmp); // hullfile(string) : "Custom Hullfile" if (ValueForKey(mapent, "hullfile")) { g_hullfile = ValueForKey(mapent, "hullfile"); Log("%30s [ %-9s ]\n", "Custom Hullfile", g_hullfile); } #ifdef HLCSG_AUTOWAD // wadautodetect(choices) : "Wad Auto Detect" : 0 = [ 0 : "Off" 1 : "On" ] if (!strcmp(ValueForKey(mapent, "wadautodetect"), "1")) { g_bWadAutoDetect = true; } else { g_bWadAutoDetect = false; } Log("%30s [ %-9s ]\n", "Wad Auto Detect", g_bWadAutoDetect ? "on" : "off"); #endif #ifdef HLCSG_WADCFG // wadconfig(string) : "Custom Wad Configuration" : "" if (strlen(ValueForKey(mapent, "wadconfig")) > 0) { safe_strncpy(wadconfigname, ValueForKey(mapent, "wadconfig"), MAX_WAD_CFG_NAME); Log("%30s [ %-9s ]\n", "Custom Wad Configuration", wadconfigname); } #endif #ifdef HLCSG_CLIPECONOMY // noclipeconomy(choices) : "Strip Uneeded Clipnodes?" : 1 = [ 1 : "Yes" 0 : "No" ] iTmp = IntForKey(mapent, "noclipeconomy"); if (iTmp == 1) { g_bClipNazi = true; } else if (iTmp == 0) { g_bClipNazi = false; } Log("%30s [ %-9s ]\n", "Clipnode Economy Mode", g_bClipNazi ? "on" : "off"); #endif /* hlcsg(choices) : "HLCSG" : 1 = [ 1 : "Normal" 2 : "Onlyents" 0 : "Off" ] */ iTmp = IntForKey(mapent, "hlcsg"); g_onlyents = false; if (iTmp == 2) { g_onlyents = true; } else if (iTmp == 0) { Fatal(assume_TOOL_CANCEL, "%s was set to \"Off\" (0) in info_compile_parameters entity, execution cancelled", g_Program); CheckFatal(); } Log("%30s [ %-9s ]\n", "Onlyents", g_onlyents ? "on" : "off"); /* nocliphull(choices) : "Generate clipping hulls" : 0 = [ 0 : "Yes" 1 : "No" ] */ iTmp = IntForKey(mapent, "nocliphull"); if (iTmp == 1) { g_noclip = true; } else { g_noclip = false; } Log("%30s [ %-9s ]\n", "Clipping Hull Generation", g_noclip ? "off" : "on"); #ifdef HLCSG_PRECISIONCLIP // cliptype(choices) : "Clip Hull Type" : 4 = [ 0 : "Smallest" 1 : "Normalized" 2: "Simple" 3 : "Precise" 4 : "Legacy" ] iTmp = IntForKey(mapent, "cliptype"); switch(iTmp) { case 0: g_cliptype = clip_smallest; break; case 1: g_cliptype = clip_normalized; break; case 2: g_cliptype = clip_simple; break; case 3: g_cliptype = clip_precise; break; default: g_cliptype = clip_legacy; break; } Log("%30s [ %-9s ]\n", "Clip Hull Type", GetClipTypeString(g_cliptype)); #endif /* noskyclip(choices) : "No Sky Clip" : 0 = [ 1 : "On" 0 : "Off" ] */ iTmp = IntForKey(mapent, "noskyclip"); if (iTmp == 1) { g_skyclip = false; } else { g_skyclip = true; } Log("%30s [ %-9s ]\n", "Sky brush clip generation", g_skyclip ? "on" : "off"); /////////////// Log("\n"); } #endif // ===================================================================================== // FixBevelTextures // ===================================================================================== void FixBevelTextures() { for(int counter = 0; counter < g_numtexinfo; counter++) { if(g_texinfo[counter].flags & TEX_BEVEL) { g_texinfo[counter].flags &= ~TEX_BEVEL; } } } // ===================================================================================== // NewFaceFromFace // Duplicates the non point information of a face, used by SplitFace // ===================================================================================== bface_t* NewFaceFromFace(const bface_t* const in) { bface_t* newf; newf = (bface_t*)Alloc(sizeof(bface_t)); newf->contents = in->contents; newf->texinfo = in->texinfo; newf->planenum = in->planenum; newf->plane = in->plane; return newf; } // ===================================================================================== // FreeFace // ===================================================================================== void FreeFace(bface_t* f) { delete 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 // ===================================================================================== static bface_t* ClipFace(bface_t* f, bface_t** outside, const int splitplane, const bool precedence) { bface_t* front; // clip face Winding* fw; // forward wind Winding* bw; // back wind plane_t* split; // plane to clip on // handle exact plane matches special if (f->planenum == (splitplane ^ 1)) return f; // opposite side, so put on inside list if (f->planenum == splitplane) // coplanar { // this fragment will go to the inside, because // the earlier one was clipped to the outside if (precedence) return f; f->next = *outside; *outside = f; return NULL; } split = &g_mapplanes[splitplane]; f->w->Clip(split->normal, split->dist, &fw, &bw); if (!fw) { delete bw; return f; } else if (!bw) { delete fw; f->next = *outside; *outside = f; return NULL; } else { delete f->w; front = NewFaceFromFace(f); front->w = fw; fw->getBounds(front->bounds); front->next = *outside; *outside = front; f->w = bw; bw->getBounds(f->bounds); return f; } } // ===================================================================================== // WriteFace // ===================================================================================== void WriteFace(const int hull, const bface_t* const f) { unsigned int i; Winding* w; ThreadLock(); if (!hull) c_csgfaces++; // .p0 format w = f->w; // plane summary fprintf(out[hull], "%i %i %i %u\n", f->planenum, f->texinfo, f->contents, w->m_NumPoints); // for each of the points on the face for (i = 0; i < w->m_NumPoints; i++) { // write the co-ords fprintf(out[hull], "%5.2f %5.2f %5.2f\n", w->m_Points[i][0], w->m_Points[i][1], w->m_Points[i][2]); } // put in an extra line break 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. // ===================================================================================== static void SaveOutside(const brush_t* const b, const int hull, bface_t* outside, const int mirrorcontents) { bface_t* f; bface_t* f2; bface_t* next; int i; vec3_t temp; for (f = outside; f; f = next) { next = f->next; if (f->w->getArea() < g_tiny_threshold) { c_tiny++; Verbose("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 = &g_mapplanes[f->planenum]; f->contents = mirrorcontents; // swap point orders for (i = 0; i < f->w->m_NumPoints / 2; i++) // add points backwards { VectorCopy(f->w->m_Points[i], temp); VectorCopy(f->w->m_Points[f->w->m_NumPoints - 1 - i], f->w->m_Points[i]); VectorCopy(temp, f->w->m_Points[f->w->m_NumPoints - 1 - i]); } WriteFace(hull, f); } FreeFace(f); } } // ===================================================================================== // CopyFace // ===================================================================================== bface_t* CopyFace(const bface_t* const f) { bface_t* n; n = NewFaceFromFace(f); n->w = f->w->Copy(); n->bounds = f->bounds; return n; } // ===================================================================================== // CopyFaceList // ===================================================================================== bface_t* CopyFaceList(bface_t* f) { bface_t* head; bface_t* n; if (f) { head = CopyFace(f); n = head; f = f->next; while (f) { n->next = CopyFace(f); n = n->next; f = f->next; } return head; } else { return NULL; } } // ===================================================================================== // FreeFaceList // ===================================================================================== void FreeFaceList(bface_t* f) { if (f) { if (f->next) { FreeFaceList(f->next); } FreeFace(f); } } // ===================================================================================== // 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. // ===================================================================================== static bface_t* CopyFacesToOutside(brushhull_t* bh) { bface_t* f; bface_t* newf; bface_t* outside; outside = NULL; for (f = bh->faces; f; f = f->next) { newf = CopyFace(f); newf->w->getBounds(newf->bounds); newf->next = outside; outside = newf; } return outside; } // ===================================================================================== // CSGBrush // ===================================================================================== static void CSGBrush(int brushnum) { int hull; brush_t* b1; brush_t* b2; brushhull_t* bh1; brushhull_t* bh2; int bn; bool overwrite; bface_t* f; bface_t* f2; bface_t* next; bface_t* fcopy; bface_t* outside; bface_t* oldoutside; entity_t* e; vec_t area; // get entity and brush info from the given brushnum that we can work with b1 = &g_mapbrushes[brushnum]; e = &g_entities[b1->entitynum]; // for each of the hulls for (hull = 0; hull < NUM_HULLS; hull++) { bh1 = &b1->hulls[hull]; // set outside to a copy of the brush's faces outside = CopyFacesToOutside(bh1); overwrite = false; // for each brush in entity e for (bn = 0; bn < e->numbrushes; bn++) { // see if b2 needs to clip a chunk out of b1 if (bn == brushnum) { overwrite = true; // later brushes now overwrite continue; } b2 = &g_mapbrushes[e->firstbrush + bn]; bh2 = &b2->hulls[hull]; if (!bh2->faces) continue; // brush isn't in this hull // check brush bounding box first // TODO: use boundingbox method instead if (bh1->bounds.testDisjoint(bh2->bounds)) { 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 if (bh2->bounds.testDisjoint(f->bounds)) { // 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(f, &outside, f2->planenum, overwrite); } area = f ? f->w->getArea() : 0; if (f && area < g_tiny_threshold) { Verbose("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 // ===================================================================================== static void EmitPlanes() { int i; dplane_t* dp; plane_t* mp; g_numplanes = g_nummapplanes; mp = g_mapplanes; dp = g_dplanes; for (i = 0; i < g_nummapplanes; i++, mp++, dp++) { //if (!(mp->redundant)) //{ // Log("EmitPlanes: plane %i non redundant\n", i); VectorCopy(mp->normal, dp->normal); dp->dist = mp->dist; dp->type = mp->type; // } //else // { // Log("EmitPlanes: plane %i redundant\n", i); // } } } // ===================================================================================== // SetModelNumbers // blah // ===================================================================================== static void SetModelNumbers() { int i; int models; char value[10]; models = 1; for (i = 1; i < g_numentities; i++) { if (g_entities[i].numbrushes) { safe_snprintf(value, sizeof(value), "*%i", models); models++; SetKeyValue(&g_entities[i], "model", value); } } } // ===================================================================================== // SetLightStyles // ===================================================================================== #define MAX_SWITCHED_LIGHTS 32 #define MAX_LIGHTTARGETS_NAME 64 static void SetLightStyles() { int stylenum; const char* t; entity_t* e; int i, j; char value[10]; char lighttargets[MAX_SWITCHED_LIGHTS][MAX_LIGHTTARGETS_NAME]; #ifdef ZHLT_TEXLIGHT bool newtexlight = false; #endif // any light that is controlled (has a targetname) // must have a unique style number generated for it stylenum = 0; for (i = 1; i < g_numentities; i++) { e = &g_entities[i]; t = ValueForKey(e, "classname"); if (strncasecmp(t, "light", 5)) { #ifdef ZHLT_TEXLIGHT //LRC: // if it's not a normal light entity, allocate it a new style if necessary. t = ValueForKey(e, "style"); switch (atoi(t)) { case 0: // not a light, no style, generally pretty boring continue; case -1: // normal switchable texlight safe_snprintf(value, sizeof(value), "%i", 32 + stylenum); SetKeyValue(e, "style", value); stylenum++; continue; case -2: // backwards switchable texlight safe_snprintf(value, sizeof(value), "%i", -(32 + stylenum)); SetKeyValue(e, "style", value); stylenum++; continue; case -3: // (HACK) a piggyback texlight: switched on and off by triggering a real light that has the same name SetKeyValue(e, "style", "0"); // just in case the level designer didn't give it a name newtexlight = true; // don't 'continue', fall out } //LRC (ends) #else continue; #endif } t = ValueForKey(e, "targetname"); if (!t[0]) { continue; } // find this targetname for (j = 0; j < stylenum; j++) { if (!strcmp(lighttargets[j], t)) { break; } } if (j == stylenum) { hlassume(stylenum < MAX_SWITCHED_LIGHTS, assume_MAX_SWITCHED_LIGHTS); safe_strncpy(lighttargets[j], t, MAX_LIGHTTARGETS_NAME); stylenum++; } safe_snprintf(value, sizeof(value), "%i", 32 + j); SetKeyValue(e, "style", value); } } // ===================================================================================== // ConvertHintToEmtpy // ===================================================================================== static void ConvertHintToEmpty() { int i; // Convert HINT brushes to EMPTY after they have been carved by csg for (i = 0; i < MAX_MAP_BRUSHES; i++) { if (g_mapbrushes[i].contents == CONTENTS_HINT) { g_mapbrushes[i].contents = CONTENTS_EMPTY; } } } // ===================================================================================== // WriteBSP // ===================================================================================== void WriteBSP(const char* const name) { char path[_MAX_PATH]; safe_strncpy(path, name, _MAX_PATH); DefaultExtension(path, ".bsp"); SetModelNumbers(); SetLightStyles(); if (!g_onlyents) WriteMiptex(); UnparseEntities(); ConvertHintToEmpty(); WriteBSPFile(path); } // // ===================================================================================== // // AJM: added in function // ===================================================================================== // CopyGenerictoCLIP // clips a generic brush // ===================================================================================== static void CopyGenerictoCLIP(const brush_t* const b) { // code blatently ripped from CopySKYtoCLIP() int i; entity_t* mapent; brush_t* newbrush; mapent = &g_entities[b->entitynum]; mapent->numbrushes++; newbrush = &g_mapbrushes[g_nummapbrushes]; newbrush->entitynum = b->entitynum; newbrush->brushnum = g_nummapbrushes - mapent->firstbrush; newbrush->firstside = g_numbrushsides; newbrush->numsides = b->numsides; newbrush->contents = CONTENTS_CLIP; newbrush->noclip = 0; for (i = 0; i < b->numsides; i++) { int j; side_t* side = &g_brushsides[g_numbrushsides]; *side = g_brushsides[b->firstside + i]; safe_strncpy(side->td.name, "CLIP", sizeof(side->td.name)); for (j = 0; j < NUM_HULLS; j++) { newbrush->hulls[j].faces = NULL; newbrush->hulls[j].bounds = b->hulls[j].bounds; } g_numbrushsides++; hlassume(g_numbrushsides < MAX_MAP_SIDES, assume_MAX_MAP_SIDES); } g_nummapbrushes++; hlassume(g_nummapbrushes < MAX_MAP_BRUSHES, assume_MAX_MAP_BRUSHES); } #ifdef HLCSG_CLIPECONOMY // AJM: added in unsigned int BrushClipHullsDiscarded = 0; unsigned int ClipNodesDiscarded = 0; //AJM: added in function static void MarkEntForNoclip(entity_t* ent) { int i; brush_t* b; for (i = ent->firstbrush; i < ent->firstbrush + ent->numbrushes; i++) { b = &g_mapbrushes[i]; b->noclip = 1; BrushClipHullsDiscarded++; ClipNodesDiscarded += b->numsides; } } // AJM // ===================================================================================== // CheckForNoClip // marks the noclip flag on any brushes that dont need clipnode generation, eg. func_illusionaries // ===================================================================================== static void CheckForNoClip() { int i; entity_t* ent; char entclassname[MAX_KEY]; int spawnflags; if (!g_bClipNazi) return; // NO CLIP FOR YOU!!! for (i = 0; i < g_numentities; i++) { if (!g_entities[i].numbrushes) continue; // not a model if (!i) continue; // dont waste our time with worldspawn ent = &g_entities[i]; strcpy_s(entclassname, ValueForKey(ent, "classname")); spawnflags = atoi(ValueForKey(ent, "spawnflags")); // condition 0, it's marked noclip (KGP) if(strlen(ValueForKey(ent,"zhlt_noclip")) && strcmp(ValueForKey(ent,"zhlt_noclip"),"0")) { MarkEntForNoclip(ent); } // condition 1, its a func_illusionary else if (!strncasecmp(entclassname, "func_illusionary", 16)) { MarkEntForNoclip(ent); } // condition 2, flag 4 (8) is set and it is either a func_door, func_train, momentary_door, // func_door_rotating or func_tracktrain (passable, not-solid flag ) else if ( (spawnflags & 8) && ( /* NOTE: func_doors as far as i can tell may need clipnodes for their player collision detection, so for now, they stay out of it. */ (!strncasecmp(entclassname, "func_train", 10)) || (!strncasecmp(entclassname, "func_door", 9)) // || (!strncasecmp(entclassname, "momentary_door", 14)) // || (!strncasecmp(entclassname, "func_door_rotating", 18)) || (!strncasecmp(entclassname, "func_tracktrain", 15)) ) ) { MarkEntForNoclip(ent); } // condition 3: flag 2 (2) is set, and its a func_conveyor (not solid flag) else if ( (spawnflags & 2) && (!strncasecmp(entclassname, "func_conveyor", 13)) ) { MarkEntForNoclip(ent); } // condition 4: flag 1 (1) is set, and its a func_rot_button (not solid flag) else if ( (spawnflags & 1) && (!strncasecmp(entclassname, "func_rot_button", 15)) ) { MarkEntForNoclip(ent); } // condition 5: flag 7 (64) is set, and its a func_rotating else if ( (spawnflags & 64) && (!strncasecmp(entclassname, "func_rotating", 13)) ) { MarkEntForNoclip(ent); } /* // condition 6: its a func_wall, while we noclip it, we remake the clipnodes manually else if (!strncasecmp(entclassname, "func_wall", 9)) { for (int j = ent->firstbrush; j < ent->firstbrush + ent->numbrushes; j++) CopyGenerictoCLIP(&g_mapbrushes[i]); MarkEntForNoclip(ent); } */ } Log("Brushes discarded from clipping hulls: %i (totalling %i sides)\n", BrushClipHullsDiscarded, ClipNodesDiscarded); } #endif // ===================================================================================== // ProcessModels // ===================================================================================== #define NUM_TYPECONTENTS 5 // AJM: should reflect the number of values below int typecontents[NUM_TYPECONTENTS] = { CONTENTS_WATER, CONTENTS_SLIME, CONTENTS_LAVA, CONTENTS_SKY, CONTENTS_HINT }; static void ProcessModels() { int i, j, type; int placed; int first, contents; brush_t temp; for (i = 0; i < g_numentities; i++) { if (!g_entities[i].numbrushes) // only models continue; // sort the contents down so stone bites water, etc first = g_entities[i].firstbrush; placed = 0; for (type = 0; type < NUM_TYPECONTENTS; type++) // for each of the contents types { contents = typecontents[type]; for (j = placed + 1; j < g_entities[i].numbrushes; j++) // for each of the model's brushes { // if this brush is of the contents type in this for iteration if (g_mapbrushes[first + j].contents == contents) { temp = g_mapbrushes[first + placed]; g_mapbrushes[first + placed] = g_mapbrushes[j]; g_mapbrushes[j] = temp; placed++; } } } // csg them in order if (i == 0) // if its worldspawn.... { NamedRunThreadsOnIndividual(g_entities[i].numbrushes, g_estimate, CSGBrush); CheckFatal(); } else { for (j = 0; j < g_entities[i].numbrushes; j++) { CSGBrush(first + j); } } // write end of model marker for (j = 0; j < NUM_HULLS; j++) { fprintf(out[j], "-1 -1 -1 -1\n"); } } } // ===================================================================================== // SetModelCenters // ===================================================================================== static void SetModelCenters(int entitynum) { int i; int last; char string[MAXTOKEN]; entity_t* e = &g_entities[entitynum]; BoundingBox bounds; vec3_t center; if ((entitynum == 0) || (e->numbrushes == 0)) // skip worldspawn and point entities return; if (!*ValueForKey(e, "light_origin")) // skip if its not a zhlt_flags light_origin return; for (i = e->firstbrush, last = e->firstbrush + e->numbrushes; i < last; i++) { if (g_mapbrushes[i].contents != CONTENTS_ORIGIN) { bounds.add(g_mapbrushes[i].hulls->bounds); } } VectorAdd(bounds.m_Mins, bounds.m_Maxs, center); VectorScale(center, 0.5, center); safe_snprintf(string, MAXTOKEN, "%i %i %i", (int)center[0], (int)center[1], (int)center[2]); SetKeyValue(e, "model_center", string); } // // ===================================================================================== // // ===================================================================================== // BoundWorld // ===================================================================================== static void BoundWorld() { int i; brushhull_t* h; world_bounds.reset(); for (i = 0; i < g_nummapbrushes; i++) { h = &g_mapbrushes[i].hulls[0]; if (!h->faces) { continue; } world_bounds.add(h->bounds); } Verbose("World bounds: (%i %i %i) to (%i %i %i)\n", (int)world_bounds.m_Mins[0], (int)world_bounds.m_Mins[1], (int)world_bounds.m_Mins[2], (int)world_bounds.m_Maxs[0], (int)world_bounds.m_Maxs[1], (int)world_bounds.m_Maxs[2]); } // ===================================================================================== // Usage // prints out usage sheet // ===================================================================================== static void Usage() { Banner(); // TODO: Call banner from main CSG process? Log("\n-= %s Options =-\n\n", g_Program); Log(" -nowadtextures : include all used textures into bsp\n"); Log(" -wadinclude file : place textures used from wad specified into bsp\n"); Log(" -noclip : don't create clipping hull\n"); #ifdef HLCSG_CLIPECONOMY // AJM Log(" -noclipeconomy : turn clipnode economy mode off\n"); #endif #ifdef HLCSG_PRECISIONCLIP // KGP Log(" -cliptype value : set to smallest, normalized, simple, precise, or legacy (default)\n"); #endif #ifdef HLCSG_NULLIFY_INVISIBLE // KGP Log(" -nullfile file : specify list of entities to retexture with NULL\n"); #endif Log(" -onlyents : do an entity update from .map to .bsp\n"); Log(" -noskyclip : disable automatic clipping of SKY brushes\n"); Log(" -tiny # : minmum brush face surface area before it is discarded\n"); Log(" -brushunion # : threshold to warn about overlapping brushes\n\n"); Log(" -hullfile file : Reads in custom collision hull dimensions\n"); Log(" -texdata # : Alter maximum texture memory limit (in kb)\n"); Log(" -lightdata # : Alter maximum lighting memory limit (in kb)\n"); Log(" -chart : display bsp statitics\n"); Log(" -low | -high : run program an altered priority level\n"); Log(" -nolog : don't generate the compile logfiles\n"); Log(" -threads # : manually specify the number of threads to run\n"); Log(" -noconcolors : Disable Windows console text coloring\n"); #ifdef SYSTEM_WIN32 Log(" -estimate : display estimated time during compile\n"); #endif #ifdef ZHLT_PROGRESSFILE // AJM Log(" -progressfile path : specify the path to a file for progress estimate output\n"); #endif #ifdef SYSTEM_POSIX Log(" -noestimate : do not display continuous compile time estimates\n"); #endif Log(" -verbose : compile with verbose messages\n"); Log(" -noinfo : Do not show tool configuration information\n"); #ifdef ZHLT_NULLTEX // AJM Log(" -nonulltex : Turns off null texture stripping\n"); #endif #ifdef ZHLT_DETAIL // AJM Log(" -nodetail : dont handle detail brushes\n"); #endif Log(" -dev # : compile with developer message\n\n"); #ifdef HLCSG_WADCFG // AJM Log(" -wadconfig name : Specify a configuration to use from wad.cfg\n"); Log(" -wadcfgfile path : Manually specify a path to the wad.cfg file\n"); //JK: #endif #ifdef HLCSG_AUTOWAD // AJM: Log(" -wadautodetect : Force auto-detection of wadfiles\n"); #endif Log(" mapfile : The mapfile to compile\n\n"); exit(1); } // ===================================================================================== // DumpWadinclude // prints out the wadinclude list // ===================================================================================== static void DumpWadinclude() { Log("Wadinclude list :\n"); WadInclude_i it; for (it = g_WadInclude.begin(); it != g_WadInclude.end(); it++) { Log("[%s]\n", it->c_str()); } } // ===================================================================================== // Settings // prints out settings sheet // ===================================================================================== static void Settings() { char* tmp; if (!g_info) return; Log("\nCurrent %s Settings\n", g_Program); Log("Name | Setting | Default\n" "---------------------|-------------|-------------------------\n"); // ZHLT Common Settings if (DEFAULT_NUMTHREADS == -1) { Log("threads [ %9d ] [ Varies ]\n", g_numthreads); } else { Log("threads [ %9d ] [ %9d ]\n", g_numthreads, DEFAULT_NUMTHREADS); } Log("verbose [ %9s ] [ %9s ]\n", g_verbose ? "on" : "off", DEFAULT_VERBOSE ? "on" : "off"); Log("log [ %9s ] [ %9s ]\n", g_log ? "on" : "off", DEFAULT_LOG ? "on" : "off"); Log("developer [ %9d ] [ %9d ]\n", g_developer, DEFAULT_DEVELOPER); Log("chart [ %9s ] [ %9s ]\n", g_chart ? "on" : "off", DEFAULT_CHART ? "on" : "off"); Log("estimate [ %9s ] [ %9s ]\n", g_estimate ? "on" : "off", DEFAULT_ESTIMATE ? "on" : "off"); Log("max texture memory [ %9d ] [ %9d ]\n", g_max_map_miptex, DEFAULT_MAX_MAP_MIPTEX); Log("max lighting memory [ %9d ] [ %9d ]\n", g_max_map_lightdata, DEFAULT_MAX_MAP_LIGHTDATA); switch (g_threadpriority) { case eThreadPriorityNormal: default: tmp = "Normal"; break; case eThreadPriorityLow: tmp = "Low"; break; case eThreadPriorityHigh: tmp = "High"; break; } Log("priority [ %9s ] [ %9s ]\n", tmp, "Normal"); Log("\n"); // HLCSG Specific Settings Log("noclip [ %9s ] [ %9s ]\n", g_noclip ? "on" : "off", DEFAULT_NOCLIP ? "on" : "off"); #ifdef ZHLT_NULLTEX // AJM: Log("null texture stripping[ %9s ] [ %9s ]\n", g_bUseNullTex ? "on" : "off", DEFAULT_NULLTEX ? "on" : "off"); #endif #ifdef ZHLT_DETAIL // AJM Log("detail brushes [ %9s ] [ %9s ]\n", g_bDetailBrushes ? "on" : "off", DEFAULT_DETAIL ? "on" : "off"); #endif #ifdef HLCSG_CLIPECONOMY // AJM Log("clipnode economy mode [ %9s ] [ %9s ]\n", g_bClipNazi ? "on" : "off", DEFAULT_CLIPNAZI ? "on" : "off"); #endif #ifdef HLCSG_PRECISIONCLIP // KGP Log("clip hull type [ %9s ] [ %9s ]\n", GetClipTypeString(g_cliptype), GetClipTypeString(DEFAULT_CLIPTYPE)); #endif Log("onlyents [ %9s ] [ %9s ]\n", g_onlyents ? "on" : "off", DEFAULT_ONLYENTS ? "on" : "off"); Log("wadtextures [ %9s ] [ %9s ]\n", g_wadtextures ? "on" : "off", DEFAULT_WADTEXTURES ? "on" : "off"); Log("skyclip [ %9s ] [ %9s ]\n", g_skyclip ? "on" : "off", DEFAULT_SKYCLIP ? "on" : "off"); Log("hullfile [ %9s ] [ %9s ]\n", g_hullfile ? g_hullfile : "None", "None"); #ifdef HLCSG_NULLIFY_INVISIBLE // KGP Log("nullfile [ %9s ] [ %9s ]\n", g_nullfile ? g_nullfile : "None", "None"); #endif // calc min surface area { char tiny_penetration[10]; char default_tiny_penetration[10]; safe_snprintf(tiny_penetration, sizeof(tiny_penetration), "%3.3f", g_tiny_threshold); safe_snprintf(default_tiny_penetration, sizeof(default_tiny_penetration), "%3.3f", DEFAULT_TINY_THRESHOLD); Log("min surface area [ %9s ] [ %9s ]\n", tiny_penetration, default_tiny_penetration); } // calc union threshold { char brush_union[10]; char default_brush_union[10]; safe_snprintf(brush_union, sizeof(brush_union), "%3.3f", g_BrushUnionThreshold); safe_snprintf(default_brush_union, sizeof(default_brush_union), "%3.3f", DEFAULT_BRUSH_UNION_THRESHOLD); Log("brush union threshold [ %9s ] [ %9s ]\n", brush_union, default_brush_union); } Log("\n"); } // AJM: added in // ===================================================================================== // CSGCleanup // ===================================================================================== void CSGCleanup() { //Log("CSGCleanup\n"); #ifdef HLCSG_AUTOWAD autowad_cleanup(); #endif #ifdef HLCSG_WADCFG WadCfg_cleanup(); #endif #ifdef HLCSG_NULLIFY_TEXTURES properties_cleanup(); #endif FreeWadPaths(); } // ===================================================================================== // Main // Oh, come on. // ===================================================================================== int main(const int argc, char** argv) { int i; char name[_MAX_PATH]; // mapanme double start, end; // start/end time log const char* mapname_from_arg = NULL; // mapname path from passed argvar g_Program = "hlcsg"; if (argc == 1) Usage(); // Hard coded list of -wadinclude files, used for HINT texture brushes so lazy // mapmakers wont cause beta testers (or possibly end users) to get a wad // error on zhlt.wad etc g_WadInclude.push_back("zhlt.wad"); memset(wadconfigname, 0, sizeof(wadconfigname));//AJM // detect argv for (i = 1; i < argc; i++) { if (!strcasecmp(argv[i], "-threads")) { if (i < argc) { g_numthreads = atoi(argv[++i]); if (g_numthreads < 1) { Log("Expected value of at least 1 for '-threads'\n"); Usage(); } } else { Usage(); } } #ifdef SYSTEM_WIN32 else if (!strcasecmp(argv[i], "-estimate")) { g_estimate = true; } #endif #ifdef SYSTEM_POSIX else if (!strcasecmp(argv[i], "-noestimate")) { g_estimate = false; } #endif else if (!strcasecmp(argv[i], "-dev")) { if (i < argc) { g_developer = (developer_level_t)atoi(argv[++i]); } else { Usage(); } } else if (!strcasecmp(argv[i], "-verbose")) { g_verbose = true; } else if (!strcasecmp(argv[i], "-noinfo")) { g_info = false; } else if (!strcasecmp(argv[i], "-chart")) { g_chart = true; } else if (!strcasecmp(argv[i], "-low")) { g_threadpriority = eThreadPriorityLow; } else if (!strcasecmp(argv[i], "-high")) { g_threadpriority = eThreadPriorityHigh; } else if (!strcasecmp(argv[i], "-nolog")) { g_log = false; } else if (!strcasecmp(argv[i], "-skyclip")) { g_skyclip = true; } else if (!strcasecmp(argv[i], "-noskyclip")) { g_skyclip = false; } else if (!strcasecmp(argv[i], "-noclip")) { g_noclip = true; } else if (!strcasecmp(argv[i], "-onlyents")) { g_onlyents = true; } #ifdef ZHLT_NULLTEX // AJM: added in -nonulltex else if (!strcasecmp(argv[i], "-nonulltex")) { g_bUseNullTex = false; } #endif #ifdef HLCSG_CLIPECONOMY // AJM: added in -noclipeconomy else if (!strcasecmp(argv[i], "-noclipeconomy")) { g_bClipNazi = false; } #endif #ifdef HLCSG_PRECISIONCLIP // KGP: added in -cliptype else if (!strcasecmp(argv[i], "-cliptype")) { if (i < argc) { ++i; if(!strcasecmp(argv[i],"smallest")) { g_cliptype = clip_smallest; } else if(!strcasecmp(argv[i],"normalized")) { g_cliptype = clip_normalized; } else if(!strcasecmp(argv[i],"simple")) { g_cliptype = clip_simple; } else if(!strcasecmp(argv[i],"precise")) { g_cliptype = clip_precise; } else if(!strcasecmp(argv[i],"legacy")) { g_cliptype = clip_legacy; } } else { Log("Error: -cliptype: incorrect usage of parameter\n"); Usage(); } } #endif #ifdef HLCSG_WADCFG // AJM: added in -wadconfig else if (!strcasecmp(argv[i], "-wadconfig")) { if (i < argc) { safe_strncpy(wadconfigname, argv[++i], MAX_WAD_CFG_NAME); if (strlen(argv[i]) > MAX_WAD_CFG_NAME) { Warning("wad configuration name was truncated to %i chars", MAX_WAD_CFG_NAME); wadconfigname[MAX_WAD_CFG_NAME] = 0; } } else { Log("Error: -wadconfig: incorrect usage of parameter\n"); Usage(); } } //JK: added in -wadcfgfile else if (!strcasecmp(argv[i], "-wadcfgfile")) { if (i < argc) { g_wadcfgfile = argv[++i]; } else { Log("Error: -wadcfgfile: incorrect usage of parameter\n"); Usage(); } } #endif #ifdef HLCSG_NULLIFY_INVISIBLE else if (!strcasecmp(argv[i], "-nullfile")) { if (i < argc) { g_nullfile = argv[++i]; } else { Log("Error: -nullfile: expected path to null ent file following parameter\n"); Usage(); } } #endif #ifdef HLCSG_AUTOWAD // AJM else if (!strcasecmp(argv[i], "-wadautodetect")) { g_bWadAutoDetect = true; } #endif #ifdef ZHLT_DETAIL // AJM else if (!strcasecmp(argv[i], "-nodetail")) { g_bDetailBrushes = false; } #endif #ifdef ZHLT_PROGRESSFILE // AJM else if (!strcasecmp(argv[i], "-progressfile")) { if (i < argc) { g_progressfile = argv[++i]; } else { Log("Error: -progressfile: expected path to progress file following parameter\n"); Usage(); } } #endif else if (!strcasecmp(argv[i], "-nowadtextures")) { g_wadtextures = false; } else if (!strcasecmp(argv[i], "-wadinclude")) { if (i < argc) { g_WadInclude.push_back(argv[++i]); } else { Usage(); } } else if (!strcasecmp(argv[i], "-texdata")) { if (i < argc) { int x = atoi(argv[++i]) * 1024; if (x > g_max_map_miptex) { g_max_map_miptex = x; } } else { Usage(); } } else if (!strcasecmp(argv[i], "-lightdata")) { if (i < argc) { int x = atoi(argv[++i]) * 1024; if (x > g_max_map_lightdata) { g_max_map_lightdata = x; } } else { Usage(); } } else if (!strcasecmp(argv[i], "-brushunion")) { if (i < argc) { g_BrushUnionThreshold = (float)atof(argv[++i]); } else { Usage(); } } else if (!strcasecmp(argv[i], "-tiny")) { if (i < argc) { g_tiny_threshold = (float)atof(argv[++i]); } else { Usage(); } } else if (!strcasecmp(argv[i], "-hullfile")) { if (i < argc) { g_hullfile = argv[++i]; } else { Usage(); } } // SILENCER --> else if (!strcasecmp(argv[i], "-noconcolors")) { g_noConColors = true; } // <-- SILENCER else if (argv[i][0] == '-') { Log("Unknown option \"%s\"\n", argv[i]); Usage(); } else if (!mapname_from_arg) { mapname_from_arg = argv[i]; } else { Log("Unknown option \"%s\"\n", argv[i]); Usage(); } } // no mapfile? if (!mapname_from_arg) { // what a shame. Log("No mapfile specified\n"); Usage(); } // handle mapname safe_strncpy(g_Mapname, mapname_from_arg, _MAX_PATH); FlipSlashes(g_Mapname); StripExtension(g_Mapname); // onlyents if (!g_onlyents) ResetTmpFiles(); // other stuff ResetErrorLog(); ResetLog(); OpenLog(g_clientid); atexit(CloseLog); LogStart(argc, argv); atexit(CSGCleanup); // AJM dtexdata_init(); atexit(dtexdata_free); // START CSG // AJM: re-arranged some stuff up here so that the mapfile is loaded // before settings are finalised and printed out, so that the info_compile_parameters // entity can be dealt with effectively start = I_FloatTime(); LoadHullfile(g_hullfile); // if the user specified a hull file, load it now #ifdef HLCSG_NULLIFY_INVISIBLE if(g_bUseNullTex) { properties_initialize(g_nullfile); } #endif safe_strncpy(name, mapname_from_arg, _MAX_PATH); // make a copy of the nap name DefaultExtension(name, ".map"); // might be .reg LoadMapFile(name); ThreadSetDefault(); ThreadSetPriority(g_threadpriority); Settings(); #ifdef HLCSG_WADCFG // AJM // figure out what to do with the texture settings if (wadconfigname[0]) // custom wad configuations will take precedence { LoadWadConfigFile(); ProcessWadConfiguration(); } else { Log("Using mapfile wad configuration\n"); } if (!g_bWadConfigsLoaded) // dont try and override wad.cfg #endif { GetUsedWads(); } #ifdef HLCSG_AUTOWAD if (g_bWadAutoDetect) { Log("Wadfiles not in use by the map will be excluded\n"); } #endif DumpWadinclude(); Log("\n"); // if onlyents, just grab the entites and resave if (g_onlyents) { char out[_MAX_PATH]; safe_snprintf(out, _MAX_PATH, "%s.bsp", g_Mapname); LoadBSPFile(out); LoadWadincludeFile(g_Mapname); HandleWadinclude(); // Write it all back out again. if (g_chart) { PrintBSPFileSizes(); } WriteBSP(g_Mapname); end = I_FloatTime(); LogTimeElapsed(end - start); return 0; } else { SaveWadincludeFile(g_Mapname); } #ifdef HLCSG_CLIPECONOMY // AJM CheckForNoClip(); #endif // createbrush NamedRunThreadsOnIndividual(g_nummapbrushes, g_estimate, CreateBrush); CheckFatal(); #ifdef HLCSG_PRECISIONCLIP // KGP - drop TEX_BEVEL flag FixBevelTextures(); #endif // boundworld BoundWorld(); Verbose("%5i map planes\n", g_nummapplanes); // Set model centers NamedRunThreadsOnIndividual(g_numentities, g_estimate, SetModelCenters); // Calc brush unions if ((g_BrushUnionThreshold > 0.0) && (g_BrushUnionThreshold <= 100.0)) { NamedRunThreadsOnIndividual(g_nummapbrushes, g_estimate, CalculateBrushUnions); } // open hull files for (i = 0; i < NUM_HULLS; i++) { char name[_MAX_PATH]; safe_snprintf(name, _MAX_PATH, "%s.p%i", g_Mapname, i); out[i] = fopen(name, "w"); if (!out[i]) Error("Couldn't open %s", name); } ProcessModels(); Verbose("%5i csg faces\n", c_csgfaces); Verbose("%5i used faces\n", c_outfaces); Verbose("%5i tiny faces\n", c_tiny); Verbose("%5i tiny clips\n", c_tiny_clip); // close hull files for (i = 0; i < NUM_HULLS; i++) fclose(out[i]); EmitPlanes(); if (g_chart) PrintBSPFileSizes(); WriteBSP(g_Mapname); // AJM: debug #if 0 Log("\n---------------------------------------\n" "Map Plane Usage:\n" " # normal origin dist type\n" " ( x, y, z) ( x, y, z) ( )\n" ); for (i = 0; i < g_nummapplanes; i++) { plane_t* p = &g_mapplanes[i]; Log( "%3i (%4.0f, %4.0f, %4.0f) (%4.0f, %4.0f, %4.0f) (%5.0f) %i\n", i, p->normal[1], p->normal[2], p->normal[3], p->origin[1], p->origin[2], p->origin[3], p->dist, p->type ); } Log("---------------------------------------\n\n"); #endif // elapsed time end = I_FloatTime(); LogTimeElapsed(end - start); return 0; }