diff options
Diffstat (limited to 'utils/vbsp')
40 files changed, 21352 insertions, 0 deletions
diff --git a/utils/vbsp/boundbox.cpp b/utils/vbsp/boundbox.cpp new file mode 100644 index 0000000..a62e9e2 --- /dev/null +++ b/utils/vbsp/boundbox.cpp @@ -0,0 +1,285 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "BoundBox.h" +//#include "hammer_mathlib.h" +//#include "MapDefs.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +float V_rint(float f) +{ + if (f > 0.0f) { + return (float) floor(f + 0.5f); + } else if (f < 0.0f) { + return (float) ceil(f - 0.5f); + } else + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +BoundBox::BoundBox(void) +{ + ResetBounds(); +} + +BoundBox::BoundBox(const Vector &mins, const Vector &maxs) +{ + bmins = mins; + bmaxs = maxs; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the box to an uninitialized state, so that calls to UpdateBounds +// will properly set the mins and maxs. +//----------------------------------------------------------------------------- +void BoundBox::ResetBounds(void) +{ + bmins[0] = bmins[1] = bmins[2] = COORD_NOTINIT; + bmaxs[0] = bmaxs[1] = bmaxs[2] = -COORD_NOTINIT; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pt - +//----------------------------------------------------------------------------- +void BoundBox::UpdateBounds(const Vector& pt) +{ + if(pt[0] < bmins[0]) + bmins[0] = pt[0]; + if(pt[1] < bmins[1]) + bmins[1] = pt[1]; + if(pt[2] < bmins[2]) + bmins[2] = pt[2]; + + if(pt[0] > bmaxs[0]) + bmaxs[0] = pt[0]; + if(pt[1] > bmaxs[1]) + bmaxs[1] = pt[1]; + if(pt[2] > bmaxs[2]) + bmaxs[2] = pt[2]; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bmins - +// bmaxs - +//----------------------------------------------------------------------------- +void BoundBox::UpdateBounds(const Vector& mins, const Vector& maxs) +{ + if(mins[0] < bmins[0]) + bmins[0] = mins[0]; + if(mins[1] < bmins[1]) + bmins[1] = mins[1]; + if(mins[2] < bmins[2]) + bmins[2] = mins[2]; + + if(maxs[0] > bmaxs[0]) + bmaxs[0] = maxs[0]; + if(maxs[1] > bmaxs[1]) + bmaxs[1] = maxs[1]; + if(maxs[2] > bmaxs[2]) + bmaxs[2] = maxs[2]; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pBox - +//----------------------------------------------------------------------------- +void BoundBox::UpdateBounds(const BoundBox *pBox) +{ + UpdateBounds(pBox->bmins, pBox->bmaxs); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : ptdest - +//----------------------------------------------------------------------------- +void BoundBox::GetBoundsCenter(Vector& ptdest) +{ + ptdest = (bmins + bmaxs)/2.0f; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pt - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool BoundBox::ContainsPoint(const Vector& pt) const +{ + for (int i = 0; i < 3; i++) + { + if (pt[i] < bmins[i] || pt[i] > bmaxs[i]) + { + return(false); + } + } + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pfMins - +// pfMaxs - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool BoundBox::IsIntersectingBox(const Vector& pfMins, const Vector& pfMaxs) const +{ + if ((bmins[0] >= pfMaxs[0]) || (bmaxs[0] <= pfMins[0])) + { + return(false); + + } + if ((bmins[1] >= pfMaxs[1]) || (bmaxs[1] <= pfMins[1])) + { + return(false); + } + + if ((bmins[2] >= pfMaxs[2]) || (bmaxs[2] <= pfMins[2])) + { + return(false); + } + + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pfMins - +// pfMaxs - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool BoundBox::IsInsideBox(const Vector& pfMins, const Vector& pfMaxs) const +{ + if ((bmins[0] < pfMins[0]) || (bmaxs[0] > pfMaxs[0])) + { + return(false); + } + + if ((bmins[1] < pfMins[1]) || (bmaxs[1] > pfMaxs[1])) + { + return(false); + } + + if ((bmins[2] < pfMins[2]) || (bmaxs[2] > pfMaxs[2])) + { + return(false); + } + + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns whether this bounding box is valid, ie maxs >= mins. +//----------------------------------------------------------------------------- +bool BoundBox::IsValidBox(void) const +{ + for (int i = 0; i < 3; i++) + { + if (bmins[i] > bmaxs[i]) + { + return(false); + } + } + + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : size - +//----------------------------------------------------------------------------- +void BoundBox::GetBoundsSize(Vector& size) +{ + size[0] = bmaxs[0] - bmins[0]; + size[1] = bmaxs[1] - bmins[1]; + size[2] = bmaxs[2] - bmins[2]; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iValue - +// iGridSize - +// Output : +//----------------------------------------------------------------------------- +static int Snap(/*int*/ float iValue, int iGridSize) +{ + return (int)(V_rint(iValue/iGridSize) * iGridSize); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iGridSize - +//----------------------------------------------------------------------------- +void BoundBox::SnapToGrid(int iGridSize) +{ + // does not alter the size of the box .. snaps its minimal coordinates + // to the grid size specified in iGridSize + Vector size; + GetBoundsSize(size); + + for(int i = 0; i < 3; i++) + { + bmins[i] = (float)Snap(/* YWB (int)*/bmins[i], iGridSize); + bmaxs[i] = bmins[i] + size[i]; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : axis - +//----------------------------------------------------------------------------- +void BoundBox::Rotate90(int axis) +{ + int e1 = AXIS_X, e2 = AXIS_Y; + + // get bounds center first + Vector center; + GetBoundsCenter(center); + + switch(axis) + { + case AXIS_Z: + e1 = AXIS_X; + e2 = AXIS_Y; + break; + case AXIS_X: + e1 = AXIS_Y; + e2 = AXIS_Z; + break; + case AXIS_Y: + e1 = AXIS_X; + e2 = AXIS_Z; + break; + } + + float tmp1, tmp2; + tmp1 = bmins[e1] - center[e1] + center[e2]; + tmp2 = bmaxs[e1] - center[e1] + center[e2]; + bmins[e1] = bmins[e2] - center[e2] + center[e1]; + bmaxs[e1] = bmaxs[e2] - center[e2] + center[e1]; + bmins[e2] = tmp1; + bmaxs[e2] = tmp2; +} + diff --git a/utils/vbsp/boundbox.h b/utils/vbsp/boundbox.h new file mode 100644 index 0000000..4720a40 --- /dev/null +++ b/utils/vbsp/boundbox.h @@ -0,0 +1,79 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: An axis aligned bounding box class. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef BOUNDBOX_H +#define BOUNDBOX_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "mathlib/vector.h" + +#define COORD_NOTINIT ((float)(99999.0)) + +enum +{ + AXIS_X = 0, + AXIS_Y, + AXIS_Z +}; + +class BoundBox +{ + public: + + BoundBox(void); + BoundBox(const Vector &mins, const Vector &maxs); + + void ResetBounds(void); + inline void SetBounds(const Vector &mins, const Vector &maxs); + + void UpdateBounds(const Vector& bmins, const Vector& bmaxs); + void UpdateBounds(const Vector& pt); + void UpdateBounds(const BoundBox *pBox); + void GetBoundsCenter(Vector& ptdest); + inline void GetBounds(Vector& Mins, Vector& Maxs); + + virtual bool IsIntersectingBox(const Vector& pfMins, const Vector& pfMaxs) const; + bool IsInsideBox(const Vector& pfMins, const Vector& pfMaxs) const; + bool ContainsPoint(const Vector& pt) const; + bool IsValidBox(void) const; + void GetBoundsSize(Vector& size); + void SnapToGrid(int iGridSize); + void Rotate90(int axis); + + Vector bmins; + Vector bmaxs; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Gets the bounding box as two vectors, a min and a max. +// Input : Mins - Receives the box's minima. +// Maxs - Receives the box's maxima. +//----------------------------------------------------------------------------- +void BoundBox::GetBounds(Vector &Mins, Vector &Maxs) +{ + Mins = bmins; + Maxs = bmaxs; +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the box outright, equivalent to ResetBounds + UpdateBounds. +// Input : mins - Minima to set. +// maxs - Maxima to set. +//----------------------------------------------------------------------------- +void BoundBox::SetBounds(const Vector &mins, const Vector &maxs) +{ + bmins = mins; + bmaxs = maxs; +} + + +#endif // BOUNDBOX_H diff --git a/utils/vbsp/brushbsp.cpp b/utils/vbsp/brushbsp.cpp new file mode 100644 index 0000000..d455f30 --- /dev/null +++ b/utils/vbsp/brushbsp.cpp @@ -0,0 +1,1469 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" + + +int c_nodes; +int c_nonvis; +int c_active_brushes; + +// if a brush just barely pokes onto the other side, +// let it slide by without chopping +#define PLANESIDE_EPSILON 0.001 +//0.1 + + +void FindBrushInTree (node_t *node, int brushnum) +{ + bspbrush_t *b; + + if (node->planenum == PLANENUM_LEAF) + { + for (b=node->brushlist ; b ; b=b->next) + if (b->original->brushnum == brushnum) + Msg("here\n"); + return; + } + FindBrushInTree (node->children[0], brushnum); + FindBrushInTree (node->children[1], brushnum); +} + +//================================================== + +/* +================ +DrawBrushList +================ +*/ +void DrawBrushList (bspbrush_t *brush, node_t *node) +{ + int i; + side_t *s; + + GLS_BeginScene (); + for ( ; brush ; brush=brush->next) + { + for (i=0 ; i<brush->numsides ; i++) + { + s = &brush->sides[i]; + if (!s->winding) + continue; + if (s->texinfo == TEXINFO_NODE) + GLS_Winding (s->winding, 1); + else if (!s->visible) + GLS_Winding (s->winding, 2); + else + GLS_Winding (s->winding, 0); + } + } + GLS_EndScene (); +} + +/* +================ +WriteBrushList +================ +*/ +void WriteBrushList (char *name, bspbrush_t *brush, qboolean onlyvis) +{ + int i; + side_t *s; + + qprintf ("writing %s\n", name); + FileHandle_t f = g_pFileSystem->Open(name, "w"); + + for ( ; brush ; brush=brush->next) + { + for (i=0 ; i<brush->numsides ; i++) + { + s = &brush->sides[i]; + if (!s->winding) + continue; + if (onlyvis && !s->visible) + continue; + OutputWinding (brush->sides[i].winding, f); + } + } + + g_pFileSystem->Close (f); +} + +void PrintBrush (bspbrush_t *brush) +{ + int i; + + Msg("brush: %p\n", brush); + for (i=0;i<brush->numsides ; i++) + { + pw(brush->sides[i].winding); + Msg("\n"); + } +} + +/* +================== +BoundBrush + +Sets the mins/maxs based on the windings +================== +*/ +void BoundBrush (bspbrush_t *brush) +{ + int i, j; + winding_t *w; + + ClearBounds (brush->mins, brush->maxs); + for (i=0 ; i<brush->numsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; j<w->numpoints ; j++) + AddPointToBounds (w->p[j], brush->mins, brush->maxs); + } +} + +Vector PointInsideBrush( bspbrush_t *brush ) +{ + Vector insidePoint = vec3_origin; + + bool bInside = false; + for ( int k = 0; k < 4 && !bInside; k++ ) + { + bInside = true; + for (int i = 0; i < brush->numsides; i++) + { + side_t *side = &brush->sides[i]; + plane_t *plane = &g_MainMap->mapplanes[side->planenum]; + float d = DotProduct( plane->normal, insidePoint ) - plane->dist; + if ( d < 0 ) + { + bInside = false; + insidePoint -= d * plane->normal; + } + } + } + return insidePoint; +} + +/* +================== +CreateBrushWindings + +================== +*/ +void CreateBrushWindings (bspbrush_t *brush) +{ + int i, j; + winding_t *w; + side_t *side; + plane_t *plane; + + // translate the CSG problem to improve precision + Vector insidePoint = PointInsideBrush( brush ); + Vector offset = -insidePoint; + + for (i=0 ; i<brush->numsides ; i++) + { + side = &brush->sides[i]; + plane = &g_MainMap->mapplanes[side->planenum]; + w = BaseWindingForPlane (plane->normal, plane->dist + DotProduct(plane->normal, offset)); + for (j=0 ; j<brush->numsides && w; j++) + { + if (i == j) + continue; + if (brush->sides[j].bevel) + continue; + plane = &g_MainMap->mapplanes[brush->sides[j].planenum^1]; + ChopWindingInPlace (&w, plane->normal, plane->dist + DotProduct(plane->normal, offset), 0); //CLIP_EPSILON); + } + + TranslateWinding( w, -offset ); + side->winding = w; + } + + BoundBrush (brush); +} + +/* +================== +BrushFromBounds + +Creates a new axial brush +================== +*/ +bspbrush_t *BrushFromBounds (Vector& mins, Vector& maxs) +{ + bspbrush_t *b; + int i; + Vector normal; + vec_t dist; + + b = AllocBrush (6); + b->numsides = 6; + for (i=0 ; i<3 ; i++) + { + VectorClear (normal); + normal[i] = 1; + dist = maxs[i]; + b->sides[i].planenum = g_MainMap->FindFloatPlane (normal, dist); + + normal[i] = -1; + dist = -mins[i]; + b->sides[3+i].planenum = g_MainMap->FindFloatPlane (normal, dist); + } + + CreateBrushWindings (b); + + return b; +} + +/* +================== +BrushVolume + +================== +*/ +vec_t BrushVolume (bspbrush_t *brush) +{ + int i; + winding_t *w; + Vector corner; + vec_t d, area, volume; + plane_t *plane; + + if (!brush) + return 0; + + // grab the first valid point as the corner + + w = NULL; + for (i=0 ; i<brush->numsides ; i++) + { + w = brush->sides[i].winding; + if (w) + break; + } + if (!w) + return 0; + VectorCopy (w->p[0], corner); + + // make tetrahedrons to all other faces + + volume = 0; + for ( ; i<brush->numsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + plane = &g_MainMap->mapplanes[brush->sides[i].planenum]; + d = -(DotProduct (corner, plane->normal) - plane->dist); + area = WindingArea (w); + volume += d*area; + } + + volume /= 3; + return volume; +} + +/* +================ +CountBrushList +================ +*/ +int CountBrushList (bspbrush_t *brushes) +{ + int c; + + c = 0; + for ( ; brushes ; brushes = brushes->next) + c++; + return c; +} + +/* +================ +AllocTree +================ +*/ +tree_t *AllocTree (void) +{ + tree_t *tree; + + tree = (tree_t*)malloc(sizeof(*tree)); + memset (tree, 0, sizeof(*tree)); + ClearBounds (tree->mins, tree->maxs); + + return tree; +} + +/* +================ +AllocNode +================ +*/ +node_t *AllocNode (void) +{ + static int s_NodeCount = 0; + + node_t *node; + + node = (node_t*)malloc(sizeof(*node)); + memset (node, 0, sizeof(*node)); + node->id = s_NodeCount; + node->diskId = -1; + + s_NodeCount++; + + return node; +} + + +/* +================ +AllocBrush +================ +*/ +bspbrush_t *AllocBrush (int numsides) +{ + static int s_BrushId = 0; + + bspbrush_t *bb; + int c; + + c = (int)&(((bspbrush_t *)0)->sides[numsides]); + bb = (bspbrush_t*)malloc(c); + memset (bb, 0, c); + bb->id = s_BrushId++; + if (numthreads == 1) + c_active_brushes++; + return bb; +} + +/* +================ +FreeBrush +================ +*/ +void FreeBrush (bspbrush_t *brushes) +{ + int i; + + for (i=0 ; i<brushes->numsides ; i++) + if (brushes->sides[i].winding) + FreeWinding(brushes->sides[i].winding); + free (brushes); + if (numthreads == 1) + c_active_brushes--; +} + + +/* +================ +FreeBrushList +================ +*/ +void FreeBrushList (bspbrush_t *brushes) +{ + bspbrush_t *next; + + for ( ; brushes ; brushes = next) + { + next = brushes->next; + + FreeBrush (brushes); + } +} + +/* +================== +CopyBrush + +Duplicates the brush, the sides, and the windings +================== +*/ +bspbrush_t *CopyBrush (bspbrush_t *brush) +{ + bspbrush_t *newbrush; + int size; + int i; + + size = (int)&(((bspbrush_t *)0)->sides[brush->numsides]); + + newbrush = AllocBrush (brush->numsides); + memcpy (newbrush, brush, size); + + for (i=0 ; i<brush->numsides ; i++) + { + if (brush->sides[i].winding) + newbrush->sides[i].winding = CopyWinding (brush->sides[i].winding); + } + + return newbrush; +} + + +/* +================== +PointInLeaf + +================== +*/ +node_t *PointInLeaf (node_t *node, Vector& point) +{ + vec_t d; + plane_t *plane; + + while (node->planenum != PLANENUM_LEAF) + { + plane = &g_MainMap->mapplanes[node->planenum]; + if (plane->type < 3) + { + d = point[plane->type] - plane->dist; + } + else + { + d = DotProduct (point, plane->normal) - plane->dist; + } + + if (d >= 0) + node = node->children[0]; + else + node = node->children[1]; + } + + return node; +} + +//======================================================== + +/* +============== +BoxOnPlaneSide + +Returns PSIDE_FRONT, PSIDE_BACK, or PSIDE_BOTH +============== +*/ +int BrushBspBoxOnPlaneSide (const Vector& mins, const Vector& maxs, dplane_t *plane) +{ + int side; + int i; + Vector corners[2]; + vec_t dist1, dist2; + + // axial planes are easy + if (plane->type < 3) + { + side = 0; + if (maxs[plane->type] > plane->dist+PLANESIDE_EPSILON) + side |= PSIDE_FRONT; + if (mins[plane->type] < plane->dist-PLANESIDE_EPSILON) + side |= PSIDE_BACK; + return side; + } + + // create the proper leading and trailing verts for the box + + for (i=0 ; i<3 ; i++) + { + if (plane->normal[i] < 0) + { + corners[0][i] = mins[i]; + corners[1][i] = maxs[i]; + } + else + { + corners[1][i] = mins[i]; + corners[0][i] = maxs[i]; + } + } + + dist1 = DotProduct (plane->normal, corners[0]) - plane->dist; + dist2 = DotProduct (plane->normal, corners[1]) - plane->dist; + side = 0; + if (dist1 >= PLANESIDE_EPSILON) + side = PSIDE_FRONT; + if (dist2 < PLANESIDE_EPSILON) + side |= PSIDE_BACK; + + return side; +} + +/* +============ +QuickTestBrushToPlanenum + +============ +*/ +int QuickTestBrushToPlanenum (bspbrush_t *brush, int planenum, int *numsplits) +{ + int i, num; + plane_t *plane; + int s; + + *numsplits = 0; + + // if the brush actually uses the planenum, + // we can tell the side for sure + for (i=0 ; i<brush->numsides ; i++) + { + num = brush->sides[i].planenum; + if (num >= 0x10000) + Error ("bad planenum"); + if (num == planenum) + return PSIDE_BACK|PSIDE_FACING; + if (num == (planenum ^ 1) ) + return PSIDE_FRONT|PSIDE_FACING; + } + + // box on plane side + plane = &g_MainMap->mapplanes[planenum]; + s = BrushBspBoxOnPlaneSide (brush->mins, brush->maxs, plane); + + // if both sides, count the visible faces split + if (s == PSIDE_BOTH) + { + *numsplits += 3; + } + + return s; +} + +/* +============ +TestBrushToPlanenum + +============ +*/ +int TestBrushToPlanenum (bspbrush_t *brush, int planenum, + int *numsplits, qboolean *hintsplit, int *epsilonbrush) +{ + int i, j, num; + plane_t *plane; + int s; + winding_t *w; + vec_t d, d_front, d_back; + int front, back; + + *numsplits = 0; + *hintsplit = false; + + // if the brush actually uses the planenum, + // we can tell the side for sure + for (i=0 ; i<brush->numsides ; i++) + { + num = brush->sides[i].planenum; + if (num >= 0x10000) + Error ("bad planenum"); + if (num == planenum) + return PSIDE_BACK|PSIDE_FACING; + if (num == (planenum ^ 1) ) + return PSIDE_FRONT|PSIDE_FACING; + } + + // box on plane side + plane = &g_MainMap->mapplanes[planenum]; + s = BrushBspBoxOnPlaneSide (brush->mins, brush->maxs, plane); + + if (s != PSIDE_BOTH) + return s; + +// if both sides, count the visible faces split + d_front = d_back = 0; + + for (i=0 ; i<brush->numsides ; i++) + { + if (brush->sides[i].texinfo == TEXINFO_NODE) + continue; // on node, don't worry about splits + if (!brush->sides[i].visible) + continue; // we don't care about non-visible + w = brush->sides[i].winding; + if (!w) + continue; + + front = back = 0; + for (j=0 ; j<w->numpoints; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + + if (d > d_front) + d_front = d; + if (d < d_back) + d_back = d; + + if (d > 0.1) // PLANESIDE_EPSILON) + front = 1; + if (d < -0.1) // PLANESIDE_EPSILON) + back = 1; + } + + if (front && back) + { + if ( !(brush->sides[i].surf & SURF_SKIP) ) + { + (*numsplits)++; + if (brush->sides[i].surf & SURF_HINT) + *hintsplit = true; + } + } + } + + if ( (d_front > 0.0 && d_front < 1.0) + || (d_back < 0.0 && d_back > -1.0) ) + (*epsilonbrush)++; + +#if 0 + if (*numsplits == 0) + { // didn't really need to be split + if (front) + s = PSIDE_FRONT; + else if (back) + s = PSIDE_BACK; + else + s = 0; + } +#endif + + return s; +} + +//======================================================== + +/* +================ +WindingIsTiny + +Returns true if the winding would be crunched out of +existance by the vertex snapping. +================ +*/ +#define EDGE_LENGTH 0.2 +qboolean WindingIsTiny (winding_t *w) +{ + int i, j; + vec_t len; + Vector delta; + int edges; + + edges = 0; + for (i=0 ; i<w->numpoints ; i++) + { + j = i == w->numpoints - 1 ? 0 : i+1; + VectorSubtract (w->p[j], w->p[i], delta); + len = VectorLength (delta); + if (len > EDGE_LENGTH) + { + if (++edges == 3) + return false; + } + } + return true; +} + + +// UNDONE: JAY: This should be a slightly better heuristic - it builds an OBB +// around the winding and tests planar dimensions. NOTE: This can fail when a +// winding normal cannot be constructed (or is degenerate), but that is probably +// desired in this case. +// UNDONE: Test & use this instead. +#if 0 +qboolean WindingIsTiny2 (winding_t *w) +{ + int i, j; + vec_t len; + Vector delta; + int edges; + + vec_t maxLen = 0; + Vector maxEdge = vec3_origin; + + edges = 0; + for (i=0 ; i<w->numpoints ; i++) + { + j = i == w->numpoints - 1 ? 0 : i+1; + VectorSubtract (w->p[j], w->p[i], delta); + len = VectorLength (delta); + if (len > maxLen) + { + maxEdge = delta; + maxLen = len; + } + } + Vector normal; + vec_t dist; + WindingPlane (w, normal, &dist); // normal can come back vec3_origin in some cases + VectorNormalize(maxEdge); + Vector cross = CrossProduct(normal, maxEdge); + VectorNormalize(cross); + Vector mins, maxs; + ClearBounds( mins, maxs ); + for (i=0 ; i<w->numpoints ; i++) + { + Vector point; + point.x = DotProduct( w->p[i], maxEdge ); + point.y = DotProduct( w->p[i], cross ); + point.z = DotProduct( w->p[i], normal ); + AddPointToBounds( point, mins, maxs ); + } + + // check to see if the size in the plane is too small in either dimension + Vector size = maxs - mins; + for ( i = 0; i < 2; i++ ) + { + if ( size[i] < EDGE_LENGTH ) + return true; + } + return false; +} +#endif + + +/* +================ +WindingIsHuge + +Returns true if the winding still has one of the points +from basewinding for plane +================ +*/ +qboolean WindingIsHuge (winding_t *w) +{ + int i, j; + + for (i=0 ; i<w->numpoints ; i++) + { + for (j=0 ; j<3 ; j++) + if (w->p[i][j] < MIN_COORD_INTEGER || w->p[i][j] > MAX_COORD_INTEGER) + return true; + } + return false; +} + +//============================================================ + +/* +================ +Leafnode +================ +*/ +void LeafNode (node_t *node, bspbrush_t *brushes) +{ + bspbrush_t *b; + int i; + + node->planenum = PLANENUM_LEAF; + node->contents = 0; + + for (b=brushes ; b ; b=b->next) + { + // if the brush is solid and all of its sides are on nodes, + // it eats everything + if (b->original->contents & CONTENTS_SOLID) + { + for (i=0 ; i<b->numsides ; i++) + if (b->sides[i].texinfo != TEXINFO_NODE) + break; + if (i == b->numsides) + { + node->contents = CONTENTS_SOLID; + break; + } + } + node->contents |= b->original->contents; + } + + node->brushlist = brushes; +} + + +void RemoveAreaPortalBrushes_R( node_t *node ) +{ + if( node->planenum == PLANENUM_LEAF ) + { + // Remove any CONTENTS_AREAPORTAL brushes we added. We don't want them in the engine + // at runtime but we do want their flags in the leaves. + bspbrush_t **pPrev = &node->brushlist; + for( bspbrush_t *b=node->brushlist; b; b=b->next ) + { + if( b->original->contents == CONTENTS_AREAPORTAL ) + { + *pPrev = b->next; + } + else + { + pPrev = &b->next; + } + } + } + else + { + RemoveAreaPortalBrushes_R( node->children[0] ); + RemoveAreaPortalBrushes_R( node->children[1] ); + } +} + + +//============================================================ + +void CheckPlaneAgainstParents (int pnum, node_t *node) +{ + node_t *p; + + for (p=node->parent ; p ; p=p->parent) + { + if (p->planenum == pnum) + Error ("Tried parent"); + } +} + +qboolean CheckPlaneAgainstVolume (int pnum, node_t *node) +{ + bspbrush_t *front, *back; + qboolean good; + + SplitBrush (node->volume, pnum, &front, &back); + + good = (front && back); + + if (front) + FreeBrush (front); + if (back) + FreeBrush (back); + + return good; +} + +/* +================ +SelectSplitSide + +Using a hueristic, choses one of the sides out of the brushlist +to partition the brushes with. +Returns NULL if there are no valid planes to split with.. +================ +*/ + +side_t *SelectSplitSide (bspbrush_t *brushes, node_t *node) +{ + int value, bestvalue; + bspbrush_t *brush, *test; + side_t *side, *bestside; + int i, j, pass, numpasses; + int pnum; + int s; + int front, back, both, facing, splits; + int bsplits; + int bestsplits; + int epsilonbrush; + qboolean hintsplit = false; + + bestside = NULL; + bestvalue = -99999; + bestsplits = 0; + + // the search order goes: visible-structural, nonvisible-structural + // If any valid plane is available in a pass, no further + // passes will be tried. + numpasses = 2; + for (pass = 0 ; pass < numpasses ; pass++) + { + for (brush = brushes ; brush ; brush=brush->next) + { + for (i=0 ; i<brush->numsides ; i++) + { + side = brush->sides + i; + + if (side->bevel) + continue; // never use a bevel as a spliter + if (!side->winding) + continue; // nothing visible, so it can't split + if (side->texinfo == TEXINFO_NODE) + continue; // allready a node splitter + if (side->tested) + continue; // we allready have metrics for this plane + if (side->surf & SURF_SKIP) + continue; // skip surfaces are never chosen + if ( side->visible ^ (pass<1) ) + continue; // only check visible faces on first pass + + pnum = side->planenum; + pnum &= ~1; // allways use positive facing plane + + CheckPlaneAgainstParents (pnum, node); + + if (!CheckPlaneAgainstVolume (pnum, node)) + continue; // would produce a tiny volume + + front = 0; + back = 0; + both = 0; + facing = 0; + splits = 0; + epsilonbrush = 0; + + for (test = brushes ; test ; test=test->next) + { + s = TestBrushToPlanenum (test, pnum, &bsplits, &hintsplit, &epsilonbrush); + + splits += bsplits; + if (bsplits && (s&PSIDE_FACING) ) + Error ("PSIDE_FACING with splits"); + + test->testside = s; + // if the brush shares this face, don't bother + // testing that facenum as a splitter again + if (s & PSIDE_FACING) + { + facing++; + for (j=0 ; j<test->numsides ; j++) + { + if ( (test->sides[j].planenum&~1) == pnum) + test->sides[j].tested = true; + } + } + if (s & PSIDE_FRONT) + front++; + if (s & PSIDE_BACK) + back++; + if (s == PSIDE_BOTH) + both++; + } + + // give a value estimate for using this plane + value = 5*facing - 5*splits - abs(front-back); +// value = -5*splits; +// value = 5*facing - 5*splits; + if (g_MainMap->mapplanes[pnum].type < 3) + value+=5; // axial is better + value -= epsilonbrush*1000; // avoid! + + // trans should split last + if ( side->surf & SURF_TRANS ) + { + value -= 500; + } + + // never split a hint side except with another hint + if (hintsplit && !(side->surf & SURF_HINT) ) + value = -9999999; + + // water should split first + if (side->contents & (CONTENTS_WATER | CONTENTS_SLIME)) + value = 9999999; + + // save off the side test so we don't need + // to recalculate it when we actually seperate + // the brushes + if (value > bestvalue) + { + bestvalue = value; + bestside = side; + bestsplits = splits; + for (test = brushes ; test ; test=test->next) + test->side = test->testside; + } + } + } + + // if we found a good plane, don't bother trying any + // other passes + if (bestside) + { + if (pass > 0) + { + if (numthreads == 1) + c_nonvis++; + } + break; + } + } + + // + // clear all the tested flags we set + // + for (brush = brushes ; brush ; brush=brush->next) + { + for (i=0 ; i<brush->numsides ; i++) + brush->sides[i].tested = false; + } + + return bestside; +} + + +/* +================== +BrushMostlyOnSide + +================== +*/ +int BrushMostlyOnSide (bspbrush_t *brush, plane_t *plane) +{ + int i, j; + winding_t *w; + vec_t d, max; + int side; + + max = 0; + side = PSIDE_FRONT; + for (i=0 ; i<brush->numsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; j<w->numpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > max) + { + max = d; + side = PSIDE_FRONT; + } + if (-d > max) + { + max = -d; + side = PSIDE_BACK; + } + } + } + return side; +} + +/* +================ +SplitBrush + +Generates two new brushes, leaving the original +unchanged +================ +*/ + + +void SplitBrush( bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back ) +{ + bspbrush_t *b[2]; + int i, j; + winding_t *w, *cw[2], *midwinding; + plane_t *plane, *plane2; + side_t *s, *cs; + float d, d_front, d_back; + + *front = *back = NULL; + plane = &g_MainMap->mapplanes[planenum]; + + // check all points + d_front = d_back = 0; + for (i=0 ; i<brush->numsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; j<w->numpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > 0 && d > d_front) + d_front = d; + if (d < 0 && d < d_back) + d_back = d; + } + } + + if (d_front < 0.1) // PLANESIDE_EPSILON) + { // only on back + *back = CopyBrush (brush); + return; + } + if (d_back > -0.1) // PLANESIDE_EPSILON) + { // only on front + *front = CopyBrush (brush); + return; + } + + + // Move the CSG problem so that offset is at the origin + // This gives us much better floating point precision in the clipping operations + Vector offset = -0.5f * (brush->mins + brush->maxs); + // create a new winding from the split plane + + w = BaseWindingForPlane (plane->normal, plane->dist + DotProduct(plane->normal,offset)); + for (i=0 ; i<brush->numsides && w ; i++) + { + plane2 = &g_MainMap->mapplanes[brush->sides[i].planenum ^ 1]; + ChopWindingInPlace (&w, plane2->normal, plane2->dist+DotProduct(plane2->normal,offset), 0); // PLANESIDE_EPSILON); + } + + if (!w || WindingIsTiny (w) ) + { // the brush isn't really split + int side; + + side = BrushMostlyOnSide (brush, plane); + if (side == PSIDE_FRONT) + *front = CopyBrush (brush); + if (side == PSIDE_BACK) + *back = CopyBrush (brush); + return; + } + + if (WindingIsHuge (w)) + { + qprintf ("WARNING: huge winding\n"); + } + + TranslateWinding( w, -offset ); + midwinding = w; + + // + // + // split it for real + // + // + + // + // allocate two new brushes referencing the original + // + for( i = 0; i < 2; i++ ) + { + b[i] = AllocBrush( brush->numsides + 1 ); + b[i]->original = brush->original; + } + + // + // split all the current windings + // + for( i = 0; i < brush->numsides; i++ ) + { + // get the current side + s = &brush->sides[i]; + + // get the sides winding + w = s->winding; + if( !w ) + continue; + + // clip the winding + ClipWindingEpsilon_Offset( w, plane->normal, plane->dist, 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1], offset ); + + for( j = 0; j < 2; j++ ) + { + // does winding exist? + if( !cw[j] ) + continue; +#if 0 + if (WindingIsTiny (cw[j])) + { + FreeWinding (cw[j]); + continue; + } +#endif + + // + // create a clipped "side" with the new winding + // + cs = &b[j]->sides[b[j]->numsides]; + b[j]->numsides++; + *cs = *s; + cs->winding = cw[j]; + cs->tested = false; + // save the original side information + //cs->original = s->original; + } + } + + + // see if we have valid polygons on both sides + + for (i=0 ; i<2 ; i++) + { + BoundBrush (b[i]); + for (j=0 ; j<3 ; j++) + { + if (b[i]->mins[j] < MIN_COORD_INTEGER || b[i]->maxs[j] > MAX_COORD_INTEGER) + { + qprintf ("bogus brush after clip\n"); + break; + } + } + + if (b[i]->numsides < 3 || j < 3) + { + FreeBrush (b[i]); + b[i] = NULL; + } + } + + if ( !(b[0] && b[1]) ) + { + if (!b[0] && !b[1]) + qprintf ("split removed brush\n"); + else + qprintf ("split not on both sides\n"); + if (b[0]) + { + FreeBrush (b[0]); + *front = CopyBrush (brush); + } + if (b[1]) + { + FreeBrush (b[1]); + *back = CopyBrush (brush); + } + return; + } + + // add the midwinding to both sides + for (i=0 ; i<2 ; i++) + { + cs = &b[i]->sides[b[i]->numsides]; + b[i]->numsides++; + + cs->planenum = planenum^i^1; + cs->texinfo = TEXINFO_NODE; + + // initialize the displacement map index + cs->pMapDisp = NULL; + + cs->visible = false; + cs->tested = false; + if (i==0) + cs->winding = CopyWinding (midwinding); + else + cs->winding = midwinding; + } + +{ + vec_t v1; + int i; + + for (i=0 ; i<2 ; i++) + { + v1 = BrushVolume (b[i]); + if (v1 < 1.0) + { + FreeBrush (b[i]); + b[i] = NULL; +// qprintf ("tiny volume after clip\n"); + } + } +} + + *front = b[0]; + *back = b[1]; +} + + +/* +================ +SplitBrushList +================ +*/ +void SplitBrushList (bspbrush_t *brushes, + node_t *node, bspbrush_t **front, bspbrush_t **back) +{ + bspbrush_t *brush, *newbrush, *newbrush2; + side_t *side; + int sides; + int i; + + *front = *back = NULL; + + for (brush = brushes ; brush ; brush=brush->next) + { + sides = brush->side; + + if (sides == PSIDE_BOTH) + { // split into two brushes + SplitBrush (brush, node->planenum, &newbrush, &newbrush2); + if (newbrush) + { + newbrush->next = *front; + *front = newbrush; + } + if (newbrush2) + { + newbrush2->next = *back; + *back = newbrush2; + } + continue; + } + + newbrush = CopyBrush (brush); + + // if the planenum is actualy a part of the brush + // find the plane and flag it as used so it won't be tried + // as a splitter again + if (sides & PSIDE_FACING) + { + for (i=0 ; i<newbrush->numsides ; i++) + { + side = newbrush->sides + i; + if ( (side->planenum& ~1) == node->planenum) + side->texinfo = TEXINFO_NODE; + } + } + + + if (sides & PSIDE_FRONT) + { + newbrush->next = *front; + *front = newbrush; + continue; + } + if (sides & PSIDE_BACK) + { + newbrush->next = *back; + *back = newbrush; + continue; + } + } +} + + +/* +================ +BuildTree_r +================ +*/ + + +node_t *BuildTree_r (node_t *node, bspbrush_t *brushes) +{ + node_t *newnode; + side_t *bestside; + int i; + bspbrush_t *children[2]; + + if (numthreads == 1) + c_nodes++; + + // find the best plane to use as a splitter + bestside = SelectSplitSide (brushes, node); + + if (!bestside) + { + // leaf node + node->side = NULL; + node->planenum = -1; + LeafNode (node, brushes); + return node; + } + + // this is a splitplane node + node->side = bestside; + node->planenum = bestside->planenum & ~1; // always use front facing + + SplitBrushList (brushes, node, &children[0], &children[1]); + FreeBrushList (brushes); + + // allocate children before recursing + for (i=0 ; i<2 ; i++) + { + newnode = AllocNode (); + newnode->parent = node; + node->children[i] = newnode; + } + + SplitBrush (node->volume, node->planenum, &node->children[0]->volume, + &node->children[1]->volume); + + // recursively process children + for (i=0 ; i<2 ; i++) + { + node->children[i] = BuildTree_r (node->children[i], children[i]); + } + + return node; +} + + +//=========================================================== + +/* +================= +BrushBSP + +The incoming list will be freed before exiting +================= +*/ +tree_t *BrushBSP (bspbrush_t *brushlist, Vector& mins, Vector& maxs) +{ + node_t *node; + bspbrush_t *b; + int c_faces, c_nonvisfaces; + int c_brushes; + tree_t *tree; + int i; + vec_t volume; + + qprintf ("--- BrushBSP ---\n"); + + tree = AllocTree (); + + c_faces = 0; + c_nonvisfaces = 0; + c_brushes = 0; + for (b=brushlist ; b ; b=b->next) + { + c_brushes++; + + volume = BrushVolume (b); + if (volume < microvolume) + { + Warning("Brush %i: WARNING, microbrush\n", b->original->id); + } + + for (i=0 ; i<b->numsides ; i++) + { + if (b->sides[i].bevel) + continue; + if (!b->sides[i].winding) + continue; + if (b->sides[i].texinfo == TEXINFO_NODE) + continue; + if (b->sides[i].visible) + c_faces++; + else + c_nonvisfaces++; + } + + AddPointToBounds (b->mins, tree->mins, tree->maxs); + AddPointToBounds (b->maxs, tree->mins, tree->maxs); + } + + qprintf ("%5i brushes\n", c_brushes); + qprintf ("%5i visible faces\n", c_faces); + qprintf ("%5i nonvisible faces\n", c_nonvisfaces); + + c_nodes = 0; + c_nonvis = 0; + node = AllocNode (); + + node->volume = BrushFromBounds (mins, maxs); + + tree->headnode = node; + + node = BuildTree_r (node, brushlist); + qprintf ("%5i visible nodes\n", c_nodes/2 - c_nonvis); + qprintf ("%5i nonvis nodes\n", c_nonvis); + qprintf ("%5i leafs\n", (c_nodes+1)/2); +#if 0 +{ // debug code +static node_t *tnode; +Vector p; + +p[0] = -1469; +p[1] = -118; +p[2] = 119; +tnode = PointInLeaf (tree->headnode, p); +Msg("contents: %i\n", tnode->contents); +p[0] = 0; +} +#endif + return tree; +} + diff --git a/utils/vbsp/csg.cpp b/utils/vbsp/csg.cpp new file mode 100644 index 0000000..5de1d68 --- /dev/null +++ b/utils/vbsp/csg.cpp @@ -0,0 +1,784 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" + +/* + +tag all brushes with original contents +brushes may contain multiple contents +there will be no brush overlap after csg phase + + + + +each side has a count of the other sides it splits + +the best split will be the one that minimizes the total split counts +of all remaining sides + +precalc side on plane table + +evaluate split side +{ +cost = 0 +for all sides + for all sides + get + if side splits side and splitside is on same child + cost++; +} + + + */ + +void SplitBrush2( bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back ) +{ + SplitBrush( brush, planenum, front, back ); +#if 0 + if (*front && (*front)->sides[(*front)->numsides-1].texinfo == -1) + (*front)->sides[(*front)->numsides-1].texinfo = (*front)->sides[0].texinfo; // not -1 + if (*back && (*back)->sides[(*back)->numsides-1].texinfo == -1) + (*back)->sides[(*back)->numsides-1].texinfo = (*back)->sides[0].texinfo; // not -1 +#endif +} + +/* +=============== +SubtractBrush + +Returns a list of brushes that remain after B is subtracted from A. +May by empty if A is contained inside B. + +The originals are undisturbed. +=============== +*/ +bspbrush_t *SubtractBrush (bspbrush_t *a, bspbrush_t *b) +{ // a - b = out (list) + int i; + bspbrush_t *front, *back; + bspbrush_t *out, *in; + + in = a; + out = NULL; + for (i=0 ; i<b->numsides && in ; i++) + { + SplitBrush2 (in, b->sides[i].planenum, &front, &back); + if (in != a) + FreeBrush (in); + if (front) + { // add to list + front->next = out; + out = front; + } + in = back; + } + if (in) + FreeBrush (in); + else + { // didn't really intersect + FreeBrushList (out); + return a; + } + return out; +} + +/* +=============== +IntersectBrush + +Returns a single brush made up by the intersection of the +two provided brushes, or NULL if they are disjoint. + +The originals are undisturbed. +=============== +*/ +bspbrush_t *IntersectBrush (bspbrush_t *a, bspbrush_t *b) +{ + int i; + bspbrush_t *front, *back; + bspbrush_t *in; + + in = a; + for (i=0 ; i<b->numsides && in ; i++) + { + SplitBrush2 (in, b->sides[i].planenum, &front, &back); + if (in != a) + FreeBrush (in); + if (front) + FreeBrush (front); + in = back; + } + + if (in == a || !in) + return NULL; + + in->next = NULL; + return in; +} + + +/* +=============== +BrushesDisjoint + +Returns true if the two brushes definately do not intersect. +There will be false negatives for some non-axial combinations. +=============== +*/ +qboolean BrushesDisjoint (bspbrush_t *a, bspbrush_t *b) +{ + int i, j; + + // check bounding boxes + for (i=0 ; i<3 ; i++) + if (a->mins[i] >= b->maxs[i] + || a->maxs[i] <= b->mins[i]) + return true; // bounding boxes don't overlap + + // check for opposing planes + for (i=0 ; i<a->numsides ; i++) + { + for (j=0 ; j<b->numsides ; j++) + { + if (a->sides[i].planenum == + (b->sides[j].planenum^1) ) + return true; // opposite planes, so not touching + } + } + + return false; // might intersect +} + + +int minplanenums[3]; +int maxplanenums[3]; + +/* +=============== +ClipBrushToBox + +Any planes shared with the box edge will be set to no texinfo +=============== +*/ +bspbrush_t *ClipBrushToBox (bspbrush_t *brush, const Vector& clipmins, const Vector& clipmaxs) +{ + int i, j; + bspbrush_t *front, *back; + int p; + + for (j=0 ; j<2 ; j++) + { + if (brush->maxs[j] > clipmaxs[j]) + { + SplitBrush (brush, maxplanenums[j], &front, &back); + if (front) + FreeBrush (front); + brush = back; + if (!brush) + return NULL; + } + if (brush->mins[j] < clipmins[j]) + { + SplitBrush (brush, minplanenums[j], &front, &back); + if (back) + FreeBrush (back); + brush = front; + if (!brush) + return NULL; + } + } + + // remove any colinear faces + + for (i=0 ; i<brush->numsides ; i++) + { + p = brush->sides[i].planenum & ~1; + if (p == maxplanenums[0] || p == maxplanenums[1] + || p == minplanenums[0] || p == minplanenums[1]) + { + brush->sides[i].texinfo = TEXINFO_NODE; + brush->sides[i].visible = false; + } + } + return brush; +} + + +//----------------------------------------------------------------------------- +// Creates a clipped brush from a map brush +//----------------------------------------------------------------------------- +static bspbrush_t *CreateClippedBrush( mapbrush_t *mb, const Vector& clipmins, const Vector& clipmaxs ) +{ + int nNumSides = mb->numsides; + if (!nNumSides) + return NULL; + + // if the brush is outside the clip area, skip it + for (int j=0 ; j<3 ; j++) + { + if (mb->mins[j] >= clipmaxs[j] || mb->maxs[j] <= clipmins[j]) + { + return NULL; + } + } + + // make a copy of the brush + bspbrush_t *newbrush = AllocBrush( nNumSides ); + newbrush->original = mb; + newbrush->numsides = nNumSides; + memcpy (newbrush->sides, mb->original_sides, nNumSides*sizeof(side_t)); + + for (int j=0 ; j<nNumSides; j++) + { + if (newbrush->sides[j].winding) + { + newbrush->sides[j].winding = CopyWinding (newbrush->sides[j].winding); + } + + if (newbrush->sides[j].surf & SURF_HINT) + { + newbrush->sides[j].visible = true; // hints are always visible + } + + // keep a pointer to the original map brush side -- use to create the original face later!! + //newbrush->sides[j].original = &mb->original_sides[j]; + } + + VectorCopy (mb->mins, newbrush->mins); + VectorCopy (mb->maxs, newbrush->maxs); + + // carve off anything outside the clip box + newbrush = ClipBrushToBox (newbrush, clipmins, clipmaxs); + return newbrush; +} + + +//----------------------------------------------------------------------------- +// Creates a clipped brush from a map brush +//----------------------------------------------------------------------------- +static void ComputeBoundingPlanes( const Vector& clipmins, const Vector& clipmaxs ) +{ + Vector normal; + float dist; + for (int i=0 ; i<2 ; i++) + { + VectorClear (normal); + normal[i] = 1; + dist = clipmaxs[i]; + maxplanenums[i] = g_MainMap->FindFloatPlane (normal, dist); + dist = clipmins[i]; + minplanenums[i] = g_MainMap->FindFloatPlane (normal, dist); + } +} + + +//----------------------------------------------------------------------------- +// This forces copies of texinfo data for matching sides of a brush +//----------------------------------------------------------------------------- +void CopyMatchingTexinfos( side_t *pDestSides, int numDestSides, const bspbrush_t *pSource ) +{ + for ( int i = 0; i < numDestSides; i++ ) + { + side_t *pSide = &pDestSides[i]; + plane_t *pPlane = &g_MainMap->mapplanes[pSide->planenum]; + + // We have to use the *original sides* because MapBSPBrushList could have generated + // splits when cutting the original brush to the block being processed. This + // will generate faces that use TEXINFO_NODE, which is definitely *not* what we want. + // If we end up with faces using TEXINFO_NODE here, the area portal will flood into + // the entire water volume intersecting the areaportal. + + mapbrush_t *pSourceBrush = pSource->original; + Assert( pSourceBrush ); + + const side_t *pSourceSide = pSourceBrush->original_sides; + const side_t *pBestSide = NULL; + float flBestDot = -1.0f; + for ( int j = 0; j < pSourceBrush->numsides; ++j, ++pSourceSide ) + { + if ( pSourceSide->texinfo == TEXINFO_NODE ) + continue; + + plane_t *pSourcePlane = &g_MainMap->mapplanes[pSourceSide->planenum]; + float flDot = DotProduct( pPlane->normal, pSourcePlane->normal ); + if ( flDot == 1.0f || pSide->planenum == pSourceSide->planenum ) + { + pBestSide = pSourceSide; + break; + } + else if ( flDot > flBestDot ) + { + pBestSide = pSourceSide; + flBestDot = flDot; + } + } + + if ( pBestSide ) + { + pSide->texinfo = pBestSide->texinfo; + if ( pSide->original ) + { + pSide->original->texinfo = pSide->texinfo; + } + } + else + { + texinfo_t *pTexInfo = &texinfo[pSide->texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + Msg("Found no matching plane for %s\n", TexDataStringTable_GetString( pTexData->nameStringTableID ) ); + } + } +} + +// This is a hack to allow areaportals to work in water +// It was done this way for ease of implementation. +// This searches a brush list to find intersecting areaportals and water +// If an areaportal is found inside water, then the water contents and +// texture information is copied over to the areaportal so that the +// resulting space has the same properties as the water (normal areaportals assume "empty" surroundings) +void FixupAreaportalWaterBrushes( bspbrush_t *pList ) +{ + for ( bspbrush_t *pAreaportal = pList; pAreaportal; pAreaportal = pAreaportal->next ) + { + if ( !(pAreaportal->original->contents & CONTENTS_AREAPORTAL) ) + continue; + + for ( bspbrush_t *pWater = pList; pWater; pWater = pWater->next ) + { + // avoid using areaportal/water combo brushes that have already been fixed up + if ( pWater->original->contents & CONTENTS_AREAPORTAL ) + continue; + + if ( !(pWater->original->contents & MASK_SPLITAREAPORTAL) ) + continue; + + if ( BrushesDisjoint( pAreaportal, pWater ) ) + continue; + + bspbrush_t *pIntersect = IntersectBrush( pAreaportal, pWater ); + if ( !pIntersect ) + continue; + FreeBrush( pIntersect ); + pAreaportal->original->contents |= pWater->original->contents; + + // HACKHACK: Ideally, this should have been done before the bspbrush_t was + // created from the map brush. But since it hasn't been, retexture the original map + // brush's sides + CopyMatchingTexinfos( pAreaportal->sides, pAreaportal->numsides, pWater ); + CopyMatchingTexinfos( pAreaportal->original->original_sides, pAreaportal->original->numsides, pWater ); + } + } +} + + +//----------------------------------------------------------------------------- +// MakeBspBrushList +//----------------------------------------------------------------------------- +// UNDONE: Put detail brushes in a separate brush array and pass that instead of "onlyDetail" ? +bspbrush_t *MakeBspBrushList (int startbrush, int endbrush, const Vector& clipmins, const Vector& clipmaxs, int detailScreen) +{ + ComputeBoundingPlanes( clipmins, clipmaxs ); + + bspbrush_t *pBrushList = NULL; + + int i; + for (i=startbrush ; i<endbrush ; i++) + { + mapbrush_t *mb = &g_MainMap->mapbrushes[i]; + if ( detailScreen != FULL_DETAIL ) + { + bool onlyDetail = (detailScreen == ONLY_DETAIL); + bool detail = (mb->contents & CONTENTS_DETAIL) != 0; + if ( onlyDetail ^ detail ) + { + // both of these must have the same value or we're not interested in this brush + continue; + } + } + + bspbrush_t *pNewBrush = CreateClippedBrush( mb, clipmins, clipmaxs ); + if ( pNewBrush ) + { + pNewBrush->next = pBrushList; + pBrushList = pNewBrush; + } + } + + return pBrushList; +} + + +//----------------------------------------------------------------------------- +// A version which uses a passed-in list of brushes +//----------------------------------------------------------------------------- +bspbrush_t *MakeBspBrushList (mapbrush_t **pBrushes, int nBrushCount, const Vector& clipmins, const Vector& clipmaxs) +{ + ComputeBoundingPlanes( clipmins, clipmaxs ); + + bspbrush_t *pBrushList = NULL; + for ( int i=0; i < nBrushCount; ++i ) + { + bspbrush_t *pNewBrush = CreateClippedBrush( pBrushes[i], clipmins, clipmaxs ); + if ( pNewBrush ) + { + pNewBrush->next = pBrushList; + pBrushList = pNewBrush; + } + } + + return pBrushList; +} + + +/* +=============== +AddBspBrushListToTail +=============== +*/ +bspbrush_t *AddBrushListToTail (bspbrush_t *list, bspbrush_t *tail) +{ + bspbrush_t *walk, *next; + + for (walk=list ; walk ; walk=next) + { // add to end of list + next = walk->next; + walk->next = NULL; + tail->next = walk; + tail = walk; + } + + return tail; +} + +/* +=========== +CullList + +Builds a new list that doesn't hold the given brush +=========== +*/ +bspbrush_t *CullList (bspbrush_t *list, bspbrush_t *skip1) +{ + bspbrush_t *newlist; + bspbrush_t *next; + + newlist = NULL; + + for ( ; list ; list = next) + { + next = list->next; + if (list == skip1) + { + FreeBrush (list); + continue; + } + list->next = newlist; + newlist = list; + } + return newlist; +} + + +/* +================== +WriteBrushMap +================== +*/ +void WriteBrushMap (char *name, bspbrush_t *list) +{ + FILE *f; + side_t *s; + int i; + winding_t *w; + + Msg("writing %s\n", name); + f = fopen (name, "w"); + if (!f) + Error ("Can't write %s\b", name); + + fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); + + for ( ; list ; list=list->next ) + { + fprintf (f, "{\n"); + for (i=0,s=list->sides ; i<list->numsides ; i++,s++) + { + w = BaseWindingForPlane (g_MainMap->mapplanes[s->planenum].normal, g_MainMap->mapplanes[s->planenum].dist); + + fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); + + fprintf (f, "%s 0 0 0 1 1\n", TexDataStringTable_GetString( GetTexData( texinfo[s->texinfo].texdata )->nameStringTableID ) ); + FreeWinding (w); + } + fprintf (f, "}\n"); + } + fprintf (f, "}\n"); + + fclose (f); + +} + +// UNDONE: This isn't quite working yet +#if 0 +void WriteBrushVMF(char *name, bspbrush_t *list) +{ + FILE *f; + side_t *s; + int i; + winding_t *w; + Vector u, v; + + Msg("writing %s\n", name); + f = fopen (name, "w"); + if (!f) + Error ("Can't write %s\b", name); + + fprintf (f, "world\n{\n\"classname\" \"worldspawn\"\n"); + + for ( ; list ; list=list->next ) + { + fprintf (f, "\tsolid\n\t{\n"); + for (i=0,s=list->sides ; i<list->numsides ; i++,s++) + { + fprintf( f, "\t\tside\n\t\t{\n" ); + fprintf( f, "\t\t\t\"plane\" \"" ); + w = BaseWindingForPlane (mapplanes[s->planenum].normal, mapplanes[s->planenum].dist); + + fprintf (f,"(%i %i %i) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); + fprintf (f,"(%i %i %i) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); + fprintf (f,"(%i %i %i)", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); + fprintf( f, "\"\n" ); + fprintf( f, "\t\t\t\"material\" \"%s\"\n", GetTexData( texinfo[s->texinfo].texdata )->name ); + // UNDONE: recreate correct texture axes + BasisForPlane( mapplanes[s->planenum].normal, u, v ); + fprintf( f, "\t\t\t\"uaxis\" \"[%.3f %.3f %.3f 0] 1.0\"\n", u[0], u[1], u[2] ); + fprintf( f, "\t\t\t\"vaxis\" \"[%.3f %.3f %.3f 0] 1.0\"\n", v[0], v[1], v[2] ); + + fprintf( f, "\t\t\t\"rotation\" \"0.0\"\n" ); + fprintf( f, "\t\t\t\"lightmapscale\" \"16.0\"\n" ); + + FreeWinding (w); + fprintf (f, "\t\t}\n"); + } + fprintf (f, "\t}\n"); + } + fprintf (f, "}\n"); + + fclose (f); + +} +#endif + +void PrintBrushContentsToString( int contents, char *pOut, int nMaxChars ) +{ + #define ADD_CONTENTS( flag ) \ + if ( contents & flag ) \ + Q_strncat( pOut, #flag " ", nMaxChars, COPY_ALL_CHARACTERS ); + + pOut[0] = 0; + + ADD_CONTENTS(CONTENTS_SOLID) + ADD_CONTENTS(CONTENTS_WINDOW) + ADD_CONTENTS(CONTENTS_AUX) + ADD_CONTENTS(CONTENTS_GRATE) + ADD_CONTENTS(CONTENTS_SLIME) + ADD_CONTENTS(CONTENTS_WATER) + ADD_CONTENTS(CONTENTS_BLOCKLOS) + ADD_CONTENTS(CONTENTS_OPAQUE) + ADD_CONTENTS(CONTENTS_TESTFOGVOLUME) + ADD_CONTENTS(CONTENTS_MOVEABLE) + ADD_CONTENTS(CONTENTS_AREAPORTAL) + ADD_CONTENTS(CONTENTS_PLAYERCLIP) + ADD_CONTENTS(CONTENTS_MONSTERCLIP) + ADD_CONTENTS(CONTENTS_CURRENT_0) + ADD_CONTENTS(CONTENTS_CURRENT_90) + ADD_CONTENTS(CONTENTS_CURRENT_180) + ADD_CONTENTS(CONTENTS_CURRENT_270) + ADD_CONTENTS(CONTENTS_CURRENT_UP) + ADD_CONTENTS(CONTENTS_CURRENT_DOWN) + ADD_CONTENTS(CONTENTS_ORIGIN) + ADD_CONTENTS(CONTENTS_MONSTER) + ADD_CONTENTS(CONTENTS_DEBRIS) + ADD_CONTENTS(CONTENTS_DETAIL) + ADD_CONTENTS(CONTENTS_TRANSLUCENT) + ADD_CONTENTS(CONTENTS_LADDER) + ADD_CONTENTS(CONTENTS_HITBOX) +} + +void PrintBrushContents( int contents ) +{ + char str[1024]; + PrintBrushContentsToString( contents, str, sizeof( str ) ); + Msg( "%s", str ); +} + +/* +================== +BrushGE + +Returns true if b1 is allowed to bite b2 +================== +*/ +qboolean BrushGE (bspbrush_t *b1, bspbrush_t *b2) +{ + // Areaportals are allowed to bite water + slime + // NOTE: This brush combo should have been fixed up + // in a first pass (FixupAreaportalWaterBrushes) + if( (b2->original->contents & MASK_SPLITAREAPORTAL) && + (b1->original->contents & CONTENTS_AREAPORTAL) ) + { + return true; + } + + // detail brushes never bite structural brushes + if ( (b1->original->contents & CONTENTS_DETAIL) + && !(b2->original->contents & CONTENTS_DETAIL) ) + return false; + if (b1->original->contents & CONTENTS_SOLID) + return true; + // Transparent brushes are not marked as detail anymore, so let them cut each other. + if ( (b1->original->contents & TRANSPARENT_CONTENTS) && (b2->original->contents & TRANSPARENT_CONTENTS) ) + return true; + + return false; +} + +/* +================= +ChopBrushes + +Carves any intersecting solid brushes into the minimum number +of non-intersecting brushes. +================= +*/ +bspbrush_t *ChopBrushes (bspbrush_t *head) +{ + bspbrush_t *b1, *b2, *next; + bspbrush_t *tail; + bspbrush_t *keep; + bspbrush_t *sub, *sub2; + int c1, c2; + + qprintf ("---- ChopBrushes ----\n"); + qprintf ("original brushes: %i\n", CountBrushList (head)); + +#if DEBUG_BRUSHMODEL + if (entity_num == DEBUG_BRUSHMODEL) + WriteBrushList ("before.gl", head, false); +#endif + keep = NULL; + +newlist: + // find tail + if (!head) + return NULL; + for (tail=head ; tail->next ; tail=tail->next) + ; + + for (b1=head ; b1 ; b1=next) + { + next = b1->next; + for (b2=b1->next ; b2 ; b2 = b2->next) + { + if (BrushesDisjoint (b1, b2)) + continue; + + sub = NULL; + sub2 = NULL; + c1 = 999999; + c2 = 999999; + + if ( BrushGE (b2, b1) ) + { +// printf( "b2 bites b1\n" ); + sub = SubtractBrush (b1, b2); + if (sub == b1) + continue; // didn't really intersect + if (!sub) + { // b1 is swallowed by b2 + head = CullList (b1, b1); + goto newlist; + } + c1 = CountBrushList (sub); + } + + if ( BrushGE (b1, b2) ) + { +// printf( "b1 bites b2\n" ); + sub2 = SubtractBrush (b2, b1); + if (sub2 == b2) + continue; // didn't really intersect + if (!sub2) + { // b2 is swallowed by b1 + FreeBrushList (sub); + head = CullList (b1, b2); + goto newlist; + } + c2 = CountBrushList (sub2); + } + + if (!sub && !sub2) + continue; // neither one can bite + + // only accept if it didn't fragment + // (commening this out allows full fragmentation) + if (c1 > 1 && c2 > 1) + { + const int contents1 = b1->original->contents; + const int contents2 = b2->original->contents; + // if both detail, allow fragmentation + if ( !((contents1&contents2) & CONTENTS_DETAIL) && !((contents1|contents2) & CONTENTS_AREAPORTAL) ) + { + if (sub2) + FreeBrushList (sub2); + if (sub) + FreeBrushList (sub); + continue; + } + } + + if (c1 < c2) + { + if (sub2) + FreeBrushList (sub2); + tail = AddBrushListToTail (sub, tail); + head = CullList (b1, b1); + goto newlist; + } + else + { + if (sub) + FreeBrushList (sub); + tail = AddBrushListToTail (sub2, tail); + head = CullList (b1, b2); + goto newlist; + } + } + + if (!b2) + { // b1 is no longer intersecting anything, so keep it + b1->next = keep; + keep = b1; + } + } + + qprintf ("output brushes: %i\n", CountBrushList (keep)); +#if DEBUG_BRUSHMODEL + if ( entity_num == DEBUG_BRUSHMODEL ) + { + WriteBrushList ("after.gl", keep, false); + WriteBrushMap ("after.map", keep); + } +#endif + return keep; +} + + diff --git a/utils/vbsp/csg.h b/utils/vbsp/csg.h new file mode 100644 index 0000000..158dcf4 --- /dev/null +++ b/utils/vbsp/csg.h @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef CSG_H +#define CSG_H +#ifdef _WIN32 +#pragma once +#endif + + +// Print a CONTENTS_ mask to a string. +void PrintBrushContentsToString( int contents, char *pOut, int nMaxChars ); + +// Print a CONTENTS_ mask with Msg(). +void PrintBrushContents( int contents ); + +void FixupAreaportalWaterBrushes( bspbrush_t *pList ); + +bspbrush_t *MakeBspBrushList (int startbrush, int endbrush, + const Vector& clipmins, const Vector& clipmaxs, int detailScreen); +bspbrush_t *MakeBspBrushList (mapbrush_t **pBrushes, int nBrushCount, const Vector& clipmins, const Vector& clipmaxs); + +void WriteBrushMap (char *name, bspbrush_t *list); + +bspbrush_t *ChopBrushes (bspbrush_t *head); +bspbrush_t *IntersectBrush (bspbrush_t *a, bspbrush_t *b); +qboolean BrushesDisjoint (bspbrush_t *a, bspbrush_t *b); + +#endif // CSG_H diff --git a/utils/vbsp/cubemap.cpp b/utils/vbsp/cubemap.cpp new file mode 100644 index 0000000..829efdf --- /dev/null +++ b/utils/vbsp/cubemap.cpp @@ -0,0 +1,995 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "bsplib.h" +#include "tier1/UtlBuffer.h" +#include "tier1/utlvector.h" +#include "bitmap/imageformat.h" +#include <KeyValues.h> +#include "tier1/strtools.h" +#include "tier1/utlsymbol.h" +#include "vtf/vtf.h" +#include "materialpatch.h" +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/imaterialvar.h" + + +/* + Meager documentation for how the cubemaps are assigned. + + + While loading the map, it calls: + *** Cubemap_SaveBrushSides + Builds a list of what cubemaps manually were assigned to what faces + in s_EnvCubemapToBrushSides. + + Immediately after loading the map, it calls: + *** Cubemap_FixupBrushSidesMaterials + Goes through s_EnvCubemapToBrushSides and does Cubemap_CreateTexInfo for each + side referenced by an env_cubemap manually. + + Then it calls Cubemap_AttachDefaultCubemapToSpecularSides: + *** Cubemap_InitCubemapSideData: + Setup s_aCubemapSideData.bHasEnvMapInMaterial and bManuallyPickedByAnEnvCubemap for each side. + bHasEnvMapInMaterial is set if the side's material has $envmap. + bManuallyPickedByAnEnvCubemap is true if the side was in s_EnvCubemapToBrushSides. + + Then, for each bHasEnvMapInMaterial and !bManuallyPickedByAnEnvCubemap (ie: every specular surface that wasn't + referenced by some env_cubemap), it does Cubemap_CreateTexInfo. +*/ + +struct PatchInfo_t +{ + char *m_pMapName; + int m_pOrigin[3]; +}; + +struct CubemapInfo_t +{ + int m_nTableId; + bool m_bSpecular; +}; + +static bool CubemapLessFunc( const CubemapInfo_t &lhs, const CubemapInfo_t &rhs ) +{ + return ( lhs.m_nTableId < rhs.m_nTableId ); +} + + +typedef CUtlVector<int> IntVector_t; +static CUtlVector<IntVector_t> s_EnvCubemapToBrushSides; + +static CUtlVector<char *> s_DefaultCubemapNames; +static char g_IsCubemapTexData[MAX_MAP_TEXDATA]; + + +struct CubemapSideData_t +{ + bool bHasEnvMapInMaterial; + bool bManuallyPickedByAnEnvCubemap; +}; + +static CubemapSideData_t s_aCubemapSideData[MAX_MAP_BRUSHSIDES]; + + + +inline bool SideHasCubemapAndWasntManuallyReferenced( int iSide ) +{ + return s_aCubemapSideData[iSide].bHasEnvMapInMaterial && !s_aCubemapSideData[iSide].bManuallyPickedByAnEnvCubemap; +} + + +void Cubemap_InsertSample( const Vector& origin, int size ) +{ + dcubemapsample_t *pSample = &g_CubemapSamples[g_nCubemapSamples]; + pSample->origin[0] = ( int )origin[0]; + pSample->origin[1] = ( int )origin[1]; + pSample->origin[2] = ( int )origin[2]; + pSample->size = size; + g_nCubemapSamples++; +} + +static const char *FindSkyboxMaterialName( void ) +{ + for( int i = 0; i < g_MainMap->num_entities; i++ ) + { + char* pEntity = ValueForKey(&g_MainMap->entities[i], "classname"); + if (!strcmp(pEntity, "worldspawn")) + { + return ValueForKey( &g_MainMap->entities[i], "skyname" ); + } + } + return NULL; +} + +static void BackSlashToForwardSlash( char *pname ) +{ + while ( *pname ) { + if ( *pname == '\\' ) + *pname = '/'; + pname++; + } +} + +static void ForwardSlashToBackSlash( char *pname ) +{ + while ( *pname ) { + if ( *pname == '/' ) + *pname = '\\'; + pname++; + } +} + + +//----------------------------------------------------------------------------- +// Finds materials that are used by a particular material +//----------------------------------------------------------------------------- +#define MAX_MATERIAL_NAME 512 + +// This is the list of materialvars which are used in our codebase to look up dependent materials +static const char *s_pDependentMaterialVar[] = +{ + "$bottommaterial", // Used by water materials + "$crackmaterial", // Used by shattered glass materials + "$fallbackmaterial", // Used by all materials + + "", // Always must be last +}; + +static const char *FindDependentMaterial( const char *pMaterialName, const char **ppMaterialVar = NULL ) +{ + // FIXME: This is a terrible way of doing this! It creates a dependency + // between vbsp and *all* code which reads dependent materials from materialvars + // At the time of writing this function, that means the engine + studiorender. + // We need a better way of figuring out how to do this, but for now I'm trying to do + // the fastest solution possible since it's close to ship + + static char pDependentMaterialName[MAX_MATERIAL_NAME]; + for( int i = 0; s_pDependentMaterialVar[i][0]; ++i ) + { + if ( !GetValueFromMaterial( pMaterialName, s_pDependentMaterialVar[i], pDependentMaterialName, MAX_MATERIAL_NAME - 1 ) ) + continue; + + if ( !Q_stricmp( pDependentMaterialName, pMaterialName ) ) + { + Warning( "Material %s is depending on itself through materialvar %s! Ignoring...\n", pMaterialName, s_pDependentMaterialVar[i] ); + continue; + } + + // Return the material var that caused the dependency + if ( ppMaterialVar ) + { + *ppMaterialVar = s_pDependentMaterialVar[i]; + } + +#ifdef _DEBUG + // FIXME: Note that this code breaks if a material has more than 1 dependent material + ++i; + static char pDependentMaterialName2[MAX_MATERIAL_NAME]; + while( s_pDependentMaterialVar[i][0] ) + { + Assert( !GetValueFromMaterial( pMaterialName, s_pDependentMaterialVar[i], pDependentMaterialName2, MAX_MATERIAL_NAME - 1 ) ); + ++i; + } +#endif + + return pDependentMaterialName; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Loads VTF files +//----------------------------------------------------------------------------- +static bool LoadSrcVTFFiles( IVTFTexture *pSrcVTFTextures[6], const char *pSkyboxMaterialBaseName, + int *pUnionTextureFlags, bool bHDR ) +{ + const char *facingName[6] = { "rt", "lf", "bk", "ft", "up", "dn" }; + int i; + for( i = 0; i < 6; i++ ) + { + char srcMaterialName[1024]; + sprintf( srcMaterialName, "%s%s", pSkyboxMaterialBaseName, facingName[i] ); + + IMaterial *pSkyboxMaterial = g_pMaterialSystem->FindMaterial( srcMaterialName, "skybox" ); + //IMaterialVar *pSkyTextureVar = pSkyboxMaterial->FindVar( bHDR ? "$hdrbasetexture" : "$basetexture", NULL ); //, bHDR ? false : true ); + IMaterialVar *pSkyTextureVar = pSkyboxMaterial->FindVar( "$basetexture", NULL ); // Since we're setting it to black anyway, just use $basetexture for HDR + const char *vtfName = pSkyTextureVar->GetStringValue(); + char srcVTFFileName[MAX_PATH]; + Q_snprintf( srcVTFFileName, MAX_PATH, "materials/%s.vtf", vtfName ); + + CUtlBuffer buf; + if ( !g_pFullFileSystem->ReadFile( srcVTFFileName, NULL, buf ) ) + { + // Try looking for a compressed HDR texture + if ( bHDR ) + { + /* // FIXME: We need a way to uncompress this format! + bool bHDRCompressed = true; + + pSkyTextureVar = pSkyboxMaterial->FindVar( "$hdrcompressedTexture", NULL ); + vtfName = pSkyTextureVar->GetStringValue(); + Q_snprintf( srcVTFFileName, MAX_PATH, "materials/%s.vtf", vtfName ); + + if ( !g_pFullFileSystem->ReadFile( srcVTFFileName, NULL, buf ) ) + */ + { + return false; + } + } + else + { + return false; + } + } + + pSrcVTFTextures[i] = CreateVTFTexture(); + if (!pSrcVTFTextures[i]->Unserialize(buf)) + { + Warning("*** Error unserializing skybox texture: %s\n", pSkyboxMaterialBaseName ); + return false; + } + + *pUnionTextureFlags |= pSrcVTFTextures[i]->Flags(); + int flagsNoAlpha = pSrcVTFTextures[i]->Flags() & ~( TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_ONEBITALPHA ); + int flagsFirstNoAlpha = pSrcVTFTextures[0]->Flags() & ~( TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_ONEBITALPHA ); + + // NOTE: texture[0] is a side texture that could be 1/2 height, so allow this and also allow 4x4 faces + if ( ( ( pSrcVTFTextures[i]->Width() != pSrcVTFTextures[0]->Width() ) && ( pSrcVTFTextures[i]->Width() != 4 ) ) || + ( ( pSrcVTFTextures[i]->Height() != pSrcVTFTextures[0]->Height() ) && ( pSrcVTFTextures[i]->Height() != pSrcVTFTextures[0]->Height()*2 ) && ( pSrcVTFTextures[i]->Height() != 4 ) ) || + ( flagsNoAlpha != flagsFirstNoAlpha ) ) + { + Warning("*** Error: Skybox vtf files for %s weren't compiled with the same size texture and/or same flags!\n", pSkyboxMaterialBaseName ); + return false; + } + + if ( bHDR ) + { + pSrcVTFTextures[i]->ConvertImageFormat( IMAGE_FORMAT_RGB323232F, false ); + pSrcVTFTextures[i]->GenerateMipmaps(); + pSrcVTFTextures[i]->ConvertImageFormat( IMAGE_FORMAT_RGBA16161616F, false ); + } + } + + return true; +} + +void VTFNameToHDRVTFName( const char *pSrcName, char *pDest, int maxLen, bool bHDR ) +{ + Q_strncpy( pDest, pSrcName, maxLen ); + if( !bHDR ) + { + return; + } + char *pDot = Q_stristr( pDest, ".vtf" ); + if( !pDot ) + { + return; + } + Q_strncpy( pDot, ".hdr.vtf", maxLen - ( pDot - pDest ) ); +} + +#define DEFAULT_CUBEMAP_SIZE 32 + +void CreateDefaultCubemaps( bool bHDR ) +{ + memset( g_IsCubemapTexData, 0, sizeof(g_IsCubemapTexData) ); + + // NOTE: This implementation depends on the fact that all VTF files contain + // all mipmap levels + const char *pSkyboxBaseName = FindSkyboxMaterialName(); + char skyboxMaterialName[MAX_PATH]; + Q_snprintf( skyboxMaterialName, MAX_PATH, "skybox/%s", pSkyboxBaseName ); + + IVTFTexture *pSrcVTFTextures[6]; + + if( !skyboxMaterialName ) + { + if( s_DefaultCubemapNames.Count() ) + { + Warning( "This map uses env_cubemap, and you don't have a skybox, so no default env_cubemaps will be generated.\n" ); + } + return; + } + + int unionTextureFlags = 0; + if( !LoadSrcVTFFiles( pSrcVTFTextures, skyboxMaterialName, &unionTextureFlags, bHDR ) ) + { + Warning( "Can't load skybox file %s to build the default cubemap!\n", skyboxMaterialName ); + return; + } + Msg( "Creating default %scubemaps for env_cubemap using skybox materials:\n %s*.vmt\n" + " ! Run buildcubemaps in the engine to get the correct cube maps.\n", bHDR ? "HDR " : "LDR ", skyboxMaterialName ); + + // Figure out the mip differences between the two textures + int iMipLevelOffset = 0; + int tmp = pSrcVTFTextures[0]->Width(); + while( tmp > DEFAULT_CUBEMAP_SIZE ) + { + iMipLevelOffset++; + tmp >>= 1; + } + + // Create the destination cubemap + IVTFTexture *pDstCubemap = CreateVTFTexture(); + pDstCubemap->Init( DEFAULT_CUBEMAP_SIZE, DEFAULT_CUBEMAP_SIZE, 1, + pSrcVTFTextures[0]->Format(), unionTextureFlags | TEXTUREFLAGS_ENVMAP, + pSrcVTFTextures[0]->FrameCount() ); + + // First iterate over all frames + for (int iFrame = 0; iFrame < pDstCubemap->FrameCount(); ++iFrame) + { + // Next iterate over all normal cube faces (we know there's 6 cause it's an envmap) + for (int iFace = 0; iFace < 6; ++iFace ) + { + // Finally, iterate over all mip levels in the *destination* + for (int iMip = 0; iMip < pDstCubemap->MipCount(); ++iMip ) + { + // Copy the bits from the source images into the cube faces + unsigned char *pSrcBits = pSrcVTFTextures[iFace]->ImageData( iFrame, 0, iMip + iMipLevelOffset ); + unsigned char *pDstBits = pDstCubemap->ImageData( iFrame, iFace, iMip ); + int iSize = pDstCubemap->ComputeMipSize( iMip ); + int iSrcMipSize = pSrcVTFTextures[iFace]->ComputeMipSize( iMip + iMipLevelOffset ); + + // !!! FIXME: Set this to black until HDR cubemaps are built properly! + memset( pDstBits, 0, iSize ); + continue; + + if ( ( pSrcVTFTextures[iFace]->Width() == 4 ) && ( pSrcVTFTextures[iFace]->Height() == 4 ) ) // If texture is 4x4 square + { + // Force mip level 2 to get the 1x1 face + unsigned char *pSrcBits = pSrcVTFTextures[iFace]->ImageData( iFrame, 0, 2 ); + int iSrcMipSize = pSrcVTFTextures[iFace]->ComputeMipSize( 2 ); + + // Replicate 1x1 mip level across entire face + //memset( pDstBits, 0, iSize ); + for ( int i = 0; i < ( iSize / iSrcMipSize ); i++ ) + { + memcpy( pDstBits + ( i * iSrcMipSize ), pSrcBits, iSrcMipSize ); + } + } + else if ( pSrcVTFTextures[iFace]->Width() == pSrcVTFTextures[iFace]->Height() ) // If texture is square + { + if ( iSrcMipSize != iSize ) + { + Warning( "%s - ERROR! Cannot copy square face for default cubemap! iSrcMipSize(%d) != iSize(%d)\n", skyboxMaterialName, iSrcMipSize, iSize ); + memset( pDstBits, 0, iSize ); + } + else + { + // Just copy the mip level + memcpy( pDstBits, pSrcBits, iSize ); + } + } + else if ( pSrcVTFTextures[iFace]->Width() == pSrcVTFTextures[iFace]->Height()*2 ) // If texture is rectangle 2x wide + { + int iMipWidth, iMipHeight, iMipDepth; + pDstCubemap->ComputeMipLevelDimensions( iMip, &iMipWidth, &iMipHeight, &iMipDepth ); + if ( ( iMipHeight > 1 ) && ( iSrcMipSize*2 != iSize ) ) + { + Warning( "%s - ERROR building default cube map! %d*2 != %d\n", skyboxMaterialName, iSrcMipSize, iSize ); + memset( pDstBits, 0, iSize ); + } + else + { + // Copy row at a time and repeat last row + memcpy( pDstBits, pSrcBits, iSize/2 ); + //memcpy( pDstBits + iSize/2, pSrcBits, iSize/2 ); + int nSrcRowSize = pSrcVTFTextures[iFace]->RowSizeInBytes( iMip + iMipLevelOffset ); + int nDstRowSize = pDstCubemap->RowSizeInBytes( iMip ); + if ( nSrcRowSize != nDstRowSize ) + { + Warning( "%s - ERROR building default cube map! nSrcRowSize(%d) != nDstRowSize(%d)!\n", skyboxMaterialName, nSrcRowSize, nDstRowSize ); + memset( pDstBits, 0, iSize ); + } + else + { + for ( int i = 0; i < ( iSize/2 / nSrcRowSize ); i++ ) + { + memcpy( pDstBits + iSize/2 + i*nSrcRowSize, pSrcBits + iSrcMipSize - nSrcRowSize, nSrcRowSize ); + } + } + } + } + else + { + // ERROR! This code only supports square and rectangluar 2x wide + Warning( "%s - Couldn't create default cubemap because texture res is %dx%d\n", skyboxMaterialName, pSrcVTFTextures[iFace]->Width(), pSrcVTFTextures[iFace]->Height() ); + memset( pDstBits, 0, iSize ); + return; + } + } + } + } + + ImageFormat originalFormat = pDstCubemap->Format(); + if( !bHDR ) + { + // Convert the cube to format that we can apply tools to it... + pDstCubemap->ConvertImageFormat( IMAGE_FORMAT_DEFAULT, false ); + } + + // Fixup the cubemap facing + pDstCubemap->FixCubemapFaceOrientation(); + + // Now that the bits are in place, compute the spheremaps... + pDstCubemap->GenerateSpheremap(); + + if( !bHDR ) + { + // Convert the cubemap to the final format + pDstCubemap->ConvertImageFormat( originalFormat, false ); + } + + // Write the puppy out! + char dstVTFFileName[1024]; + if( bHDR ) + { + sprintf( dstVTFFileName, "materials/maps/%s/cubemapdefault.hdr.vtf", mapbase ); + } + else + { + sprintf( dstVTFFileName, "materials/maps/%s/cubemapdefault.vtf", mapbase ); + } + + CUtlBuffer outputBuf; + if (!pDstCubemap->Serialize( outputBuf )) + { + Warning( "Error serializing default cubemap %s\n", dstVTFFileName ); + return; + } + + IZip *pak = GetPakFile(); + + // spit out the default one. + AddBufferToPak( pak, dstVTFFileName, outputBuf.Base(), outputBuf.TellPut(), false ); + + // spit out all of the ones that are attached to world geometry. + int i; + for( i = 0; i < s_DefaultCubemapNames.Count(); i++ ) + { + char vtfName[MAX_PATH]; + VTFNameToHDRVTFName( s_DefaultCubemapNames[i], vtfName, MAX_PATH, bHDR ); + if( FileExistsInPak( pak, vtfName ) ) + { + continue; + } + AddBufferToPak( pak, vtfName, outputBuf.Base(),outputBuf.TellPut(), false ); + } + + // Clean up the textures + for( i = 0; i < 6; i++ ) + { + DestroyVTFTexture( pSrcVTFTextures[i] ); + } + DestroyVTFTexture( pDstCubemap ); +} + +void Cubemap_CreateDefaultCubemaps( void ) +{ + CreateDefaultCubemaps( false ); + CreateDefaultCubemaps( true ); +} + +// Builds a list of what cubemaps manually were assigned to what faces +// in s_EnvCubemapToBrushSides. +void Cubemap_SaveBrushSides( const char *pSideListStr ) +{ + IntVector_t &brushSidesVector = s_EnvCubemapToBrushSides[s_EnvCubemapToBrushSides.AddToTail()]; + char *pTmp = ( char * )_alloca( strlen( pSideListStr ) + 1 ); + strcpy( pTmp, pSideListStr ); + const char *pScan = strtok( pTmp, " " ); + if( !pScan ) + { + return; + } + do + { + int brushSideID; + if( sscanf( pScan, "%d", &brushSideID ) == 1 ) + { + brushSidesVector.AddToTail( brushSideID ); + } + } while( ( pScan = strtok( NULL, " " ) ) ); +} + + +//----------------------------------------------------------------------------- +// Generate patched material name +//----------------------------------------------------------------------------- +static void GeneratePatchedName( const char *pMaterialName, const PatchInfo_t &info, bool bMaterialName, char *pBuffer, int nMaxLen ) +{ + const char *pSeparator = bMaterialName ? "_" : ""; + int nLen = Q_snprintf( pBuffer, nMaxLen, "maps/%s/%s%s%d_%d_%d", info.m_pMapName, + pMaterialName, pSeparator, info.m_pOrigin[0], info.m_pOrigin[1], info.m_pOrigin[2] ); + + if ( bMaterialName ) + { + Assert( nLen < TEXTURE_NAME_LENGTH - 1 ); + if ( nLen >= TEXTURE_NAME_LENGTH - 1 ) + { + Error( "Generated env_cubemap patch name : %s too long! (max = %d)\n", pBuffer, TEXTURE_NAME_LENGTH ); + } + } + + BackSlashToForwardSlash( pBuffer ); + Q_strlower( pBuffer ); +} + + +//----------------------------------------------------------------------------- +// Patches the $envmap for a material and all its dependents, returns true if any patching happened +//----------------------------------------------------------------------------- +static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, const PatchInfo_t &info, const char *pCubemapTexture ) +{ + // Do *NOT* patch the material if there is an $envmap specified and it's not 'env_cubemap' + + // FIXME: It's theoretically ok to patch the material if $envmap is not specified, + // because we're using the 'replace' block, which will only add the env_cubemap if + // $envmap is specified in the source material. But it will fail if someone adds + // a specific non-env_cubemap $envmap to the source material at a later point. Bleah + + // See if we have an $envmap to patch + bool bShouldPatchEnvCubemap = DoesMaterialHaveKeyValuePair( pMaterialName, "$envmap", "env_cubemap" ); + + // See if we have a dependent material to patch + bool bDependentMaterialPatched = false; + const char *pDependentMaterialVar = NULL; + const char *pDependentMaterial = FindDependentMaterial( pMaterialName, &pDependentMaterialVar ); + if ( pDependentMaterial ) + { + bDependentMaterialPatched = PatchEnvmapForMaterialAndDependents( pDependentMaterial, info, pCubemapTexture ); + } + + // If we have neither to patch, we're done + if ( !bShouldPatchEnvCubemap && !bDependentMaterialPatched ) + return false; + + // Otherwise we have to make a patched version of ourselves + char pPatchedMaterialName[1024]; + GeneratePatchedName( pMaterialName, info, true, pPatchedMaterialName, 1024 ); + + MaterialPatchInfo_t pPatchInfo[2]; + int nPatchCount = 0; + if ( bShouldPatchEnvCubemap ) + { + pPatchInfo[nPatchCount].m_pKey = "$envmap"; + pPatchInfo[nPatchCount].m_pRequiredOriginalValue = "env_cubemap"; + pPatchInfo[nPatchCount].m_pValue = pCubemapTexture; + ++nPatchCount; + } + + char pDependentPatchedMaterialName[1024]; + if ( bDependentMaterialPatched ) + { + // FIXME: Annoying! I either have to pass back the patched dependent material name + // or reconstruct it. Both are sucky. + GeneratePatchedName( pDependentMaterial, info, true, pDependentPatchedMaterialName, 1024 ); + pPatchInfo[nPatchCount].m_pKey = pDependentMaterialVar; + pPatchInfo[nPatchCount].m_pValue = pDependentPatchedMaterialName; + ++nPatchCount; + } + + CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_REPLACE ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Finds a texinfo that has a particular +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Create a VMT to override the specified texinfo which references the cubemap entity at the specified origin. +// Returns the index of the new (or preexisting) texinfo referencing that VMT. +// +// Also adds the new cubemap VTF filename to s_DefaultCubemapNames so it can copy the +// default (skybox) cubemap into this file so the cubemap doesn't have the pink checkerboard at +// runtime before they run buildcubemaps. +//----------------------------------------------------------------------------- +static int Cubemap_CreateTexInfo( int originalTexInfo, int origin[3] ) +{ + // Don't make cubemap tex infos for nodes + if ( originalTexInfo == TEXINFO_NODE ) + return originalTexInfo; + + texinfo_t *pTexInfo = &texinfo[originalTexInfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + if ( g_IsCubemapTexData[pTexInfo->texdata] ) + { + Warning("Multiple references for cubemap on texture %s!!!\n", pMaterialName ); + return originalTexInfo; + } + + // Get out of here if the originalTexInfo is already a generated material for this position. + char pStringToSearchFor[512]; + Q_snprintf( pStringToSearchFor, 512, "_%d_%d_%d", origin[0], origin[1], origin[2] ); + if ( Q_stristr( pMaterialName, pStringToSearchFor ) ) + return originalTexInfo; + + // Package up information needed to generate patch names + PatchInfo_t info; + info.m_pMapName = mapbase; + info.m_pOrigin[0] = origin[0]; + info.m_pOrigin[1] = origin[1]; + info.m_pOrigin[2] = origin[2]; + + // Generate the name of the patched material + char pGeneratedTexDataName[1024]; + GeneratePatchedName( pMaterialName, info, true, pGeneratedTexDataName, 1024 ); + + // Make sure the texdata doesn't already exist. + int nTexDataID = FindTexData( pGeneratedTexDataName ); + bool bHasTexData = (nTexDataID != -1); + if( !bHasTexData ) + { + // Generate the new "$envmap" texture name. + char pTextureName[1024]; + GeneratePatchedName( "c", info, false, pTextureName, 1024 ); + + // Hook the texture into the material and all dependent materials + // but if no hooking was necessary, exit out + if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName ) ) + return originalTexInfo; + + // Store off the name of the cubemap that we need to create since we successfully patched + char pFileName[1024]; + int nLen = Q_snprintf( pFileName, 1024, "materials/%s.vtf", pTextureName ); + int id = s_DefaultCubemapNames.AddToTail(); + s_DefaultCubemapNames[id] = new char[ nLen + 1 ]; + strcpy( s_DefaultCubemapNames[id], pFileName ); + + // Make a new texdata + nTexDataID = AddCloneTexData( pTexData, pGeneratedTexDataName ); + g_IsCubemapTexData[nTexDataID] = true; + } + + Assert( nTexDataID != -1 ); + + texinfo_t newTexInfo; + newTexInfo = *pTexInfo; + newTexInfo.texdata = nTexDataID; + + int nTexInfoID = -1; + + // See if we need to make a new texinfo + bool bHasTexInfo = false; + if( bHasTexData ) + { + nTexInfoID = FindTexInfo( newTexInfo ); + bHasTexInfo = (nTexInfoID != -1); + } + + // Make a new texinfo if we need to. + if( !bHasTexInfo ) + { + nTexInfoID = texinfo.AddToTail( newTexInfo ); + } + + Assert( nTexInfoID != -1 ); + return nTexInfoID; +} + +static int SideIDToIndex( int brushSideID ) +{ + int i; + for( i = 0; i < g_MainMap->nummapbrushsides; i++ ) + { + if( g_MainMap->brushsides[i].id == brushSideID ) + { + return i; + } + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Goes through s_EnvCubemapToBrushSides and does Cubemap_CreateTexInfo for each +// side referenced by an env_cubemap manually. +//----------------------------------------------------------------------------- +void Cubemap_FixupBrushSidesMaterials( void ) +{ + Msg( "fixing up env_cubemap materials on brush sides...\n" ); + Assert( s_EnvCubemapToBrushSides.Count() == g_nCubemapSamples ); + + int cubemapID; + for( cubemapID = 0; cubemapID < g_nCubemapSamples; cubemapID++ ) + { + IntVector_t &brushSidesVector = s_EnvCubemapToBrushSides[cubemapID]; + int i; + for( i = 0; i < brushSidesVector.Count(); i++ ) + { + int brushSideID = brushSidesVector[i]; + int sideIndex = SideIDToIndex( brushSideID ); + if( sideIndex < 0 ) + { + Warning("env_cubemap pointing at deleted brushside near (%d, %d, %d)\n", + g_CubemapSamples[cubemapID].origin[0], g_CubemapSamples[cubemapID].origin[1], g_CubemapSamples[cubemapID].origin[2] ); + + continue; + } + + side_t *pSide = &g_MainMap->brushsides[sideIndex]; + +#ifdef DEBUG + if ( pSide->pMapDisp ) + { + Assert( pSide->texinfo == pSide->pMapDisp->face.texinfo ); + } +#endif + + pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin ); + if ( pSide->pMapDisp ) + { + pSide->pMapDisp->face.texinfo = pSide->texinfo; + } + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void Cubemap_ResetCubemapSideData( void ) +{ + for ( int iSide = 0; iSide < MAX_MAP_BRUSHSIDES; ++iSide ) + { + s_aCubemapSideData[iSide].bHasEnvMapInMaterial = false; + s_aCubemapSideData[iSide].bManuallyPickedByAnEnvCubemap = false; + } +} + + +//----------------------------------------------------------------------------- +// Returns true if the material or any of its dependents use an $envmap +//----------------------------------------------------------------------------- +bool DoesMaterialOrDependentsUseEnvmap( const char *pPatchedMaterialName ) +{ + const char *pOriginalMaterialName = GetOriginalMaterialNameForPatchedMaterial( pPatchedMaterialName ); + if( DoesMaterialHaveKey( pOriginalMaterialName, "$envmap" ) ) + return true; + + const char *pDependentMaterial = FindDependentMaterial( pOriginalMaterialName ); + if ( !pDependentMaterial ) + return false; + + return DoesMaterialOrDependentsUseEnvmap( pDependentMaterial ); +} + + +//----------------------------------------------------------------------------- +// Builds a list of all texdatas which need fixing up +//----------------------------------------------------------------------------- +void Cubemap_InitCubemapSideData( void ) +{ + // This tree is used to prevent re-parsing material vars multiple times + CUtlRBTree<CubemapInfo_t> lookup( 0, g_MainMap->nummapbrushsides, CubemapLessFunc ); + + // Fill in specular data. + for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide ) + { + side_t *pSide = &g_MainMap->brushsides[iSide]; + if ( !pSide ) + continue; + + if ( pSide->texinfo == TEXINFO_NODE ) + continue; + + texinfo_t *pTex = &texinfo[pSide->texinfo]; + if ( !pTex ) + continue; + + dtexdata_t *pTexData = GetTexData( pTex->texdata ); + if ( !pTexData ) + continue; + + CubemapInfo_t info; + info.m_nTableId = pTexData->nameStringTableID; + + // Have we encountered this materal? If so, then copy the data we cached off before + int i = lookup.Find( info ); + if ( i != lookup.InvalidIndex() ) + { + s_aCubemapSideData[iSide].bHasEnvMapInMaterial = lookup[i].m_bSpecular; + continue; + } + + // First time we've seen this material. Figure out if it uses env_cubemap + const char *pPatchedMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + info.m_bSpecular = DoesMaterialOrDependentsUseEnvmap( pPatchedMaterialName ); + s_aCubemapSideData[ iSide ].bHasEnvMapInMaterial = info.m_bSpecular; + lookup.Insert( info ); + } + + // Fill in cube map data. + for ( int iCubemap = 0; iCubemap < g_nCubemapSamples; ++iCubemap ) + { + IntVector_t &sideList = s_EnvCubemapToBrushSides[iCubemap]; + int nSideCount = sideList.Count(); + for ( int iSide = 0; iSide < nSideCount; ++iSide ) + { + int nSideID = sideList[iSide]; + int nIndex = SideIDToIndex( nSideID ); + if ( nIndex < 0 ) + continue; + + s_aCubemapSideData[nIndex].bManuallyPickedByAnEnvCubemap = true; + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int Cubemap_FindClosestCubemap( const Vector &entityOrigin, side_t *pSide ) +{ + if ( !pSide ) + return -1; + + // Return a valid (if random) cubemap if there's no winding + if ( !pSide->winding ) + return 0; + + // Calculate the center point. + Vector vecCenter; + vecCenter.Init(); + + for ( int iPoint = 0; iPoint < pSide->winding->numpoints; ++iPoint ) + { + VectorAdd( vecCenter, pSide->winding->p[iPoint], vecCenter ); + } + VectorScale( vecCenter, 1.0f / pSide->winding->numpoints, vecCenter ); + vecCenter += entityOrigin; + plane_t *pPlane = &g_MainMap->mapplanes[pSide->planenum]; + + // Find the closest cubemap. + int iMinCubemap = -1; + float flMinDist = FLT_MAX; + + // Look for cubemaps in front of the surface first. + for ( int iCubemap = 0; iCubemap < g_nCubemapSamples; ++iCubemap ) + { + dcubemapsample_t *pSample = &g_CubemapSamples[iCubemap]; + Vector vecSampleOrigin( static_cast<float>( pSample->origin[0] ), + static_cast<float>( pSample->origin[1] ), + static_cast<float>( pSample->origin[2] ) ); + Vector vecDelta; + VectorSubtract( vecSampleOrigin, vecCenter, vecDelta ); + float flDist = vecDelta.NormalizeInPlace(); + float flDot = DotProduct( vecDelta, pPlane->normal ); + if ( ( flDot >= 0.0f ) && ( flDist < flMinDist ) ) + { + flMinDist = flDist; + iMinCubemap = iCubemap; + } + } + + // Didn't find anything in front search for closest. + if( iMinCubemap == -1 ) + { + for ( int iCubemap = 0; iCubemap < g_nCubemapSamples; ++iCubemap ) + { + dcubemapsample_t *pSample = &g_CubemapSamples[iCubemap]; + Vector vecSampleOrigin( static_cast<float>( pSample->origin[0] ), + static_cast<float>( pSample->origin[1] ), + static_cast<float>( pSample->origin[2] ) ); + Vector vecDelta; + VectorSubtract( vecSampleOrigin, vecCenter, vecDelta ); + float flDist = vecDelta.Length(); + if ( flDist < flMinDist ) + { + flMinDist = flDist; + iMinCubemap = iCubemap; + } + } + } + + return iMinCubemap; +} + + +//----------------------------------------------------------------------------- +// For every specular surface that wasn't referenced by some env_cubemap, call Cubemap_CreateTexInfo. +//----------------------------------------------------------------------------- +void Cubemap_AttachDefaultCubemapToSpecularSides( void ) +{ + Cubemap_ResetCubemapSideData(); + Cubemap_InitCubemapSideData(); + + // build a mapping from side to entity id so that we can get the entity origin + CUtlVector<int> sideToEntityIndex; + sideToEntityIndex.SetCount(g_MainMap->nummapbrushsides); + int i; + for ( i = 0; i < g_MainMap->nummapbrushsides; i++ ) + { + sideToEntityIndex[i] = -1; + } + + for ( i = 0; i < g_MainMap->nummapbrushes; i++ ) + { + int entityIndex = g_MainMap->mapbrushes[i].entitynum; + for ( int j = 0; j < g_MainMap->mapbrushes[i].numsides; j++ ) + { + side_t *side = &g_MainMap->mapbrushes[i].original_sides[j]; + int sideIndex = side - g_MainMap->brushsides; + sideToEntityIndex[sideIndex] = entityIndex; + } + } + + for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide ) + { + side_t *pSide = &g_MainMap->brushsides[iSide]; + if ( !SideHasCubemapAndWasntManuallyReferenced( iSide ) ) + continue; + + + int currentEntity = sideToEntityIndex[iSide]; + + int iCubemap = Cubemap_FindClosestCubemap( g_MainMap->entities[currentEntity].origin, pSide ); + if ( iCubemap == -1 ) + continue; + +#ifdef DEBUG + if ( pSide->pMapDisp ) + { + Assert( pSide->texinfo == pSide->pMapDisp->face.texinfo ); + } +#endif + pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin ); + if ( pSide->pMapDisp ) + { + pSide->pMapDisp->face.texinfo = pSide->texinfo; + } + } +} + +// Populate with cubemaps that were skipped +void Cubemap_AddUnreferencedCubemaps() +{ + char pTextureName[1024]; + char pFileName[1024]; + PatchInfo_t info; + dcubemapsample_t *pSample; + int i,j; + + for ( i=0; i<g_nCubemapSamples; ++i ) + { + pSample = &g_CubemapSamples[i]; + + // generate the formatted texture name based on cubemap origin + info.m_pMapName = mapbase; + info.m_pOrigin[0] = pSample->origin[0]; + info.m_pOrigin[1] = pSample->origin[1]; + info.m_pOrigin[2] = pSample->origin[2]; + GeneratePatchedName( "c", info, false, pTextureName, 1024 ); + + // find or add + for ( j=0; j<s_DefaultCubemapNames.Count(); ++j ) + { + if ( !stricmp( s_DefaultCubemapNames[j], pTextureName ) ) + { + // already added + break; + } + } + if ( j == s_DefaultCubemapNames.Count() ) + { + int nLen = Q_snprintf( pFileName, 1024, "materials/%s.vtf", pTextureName ); + + int id = s_DefaultCubemapNames.AddToTail(); + s_DefaultCubemapNames[id] = new char[nLen + 1]; + strcpy( s_DefaultCubemapNames[id], pFileName ); + } + } +} diff --git a/utils/vbsp/detail.cpp b/utils/vbsp/detail.cpp new file mode 100644 index 0000000..840068d --- /dev/null +++ b/utils/vbsp/detail.cpp @@ -0,0 +1,693 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Builds/merges the BSP tree of detail brushes +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "detail.h" +#include "utlvector.h" +#include <assert.h> + +face_t *NewFaceFromFace (face_t *f); +face_t *ComputeVisibleBrushSides( bspbrush_t *list ); + +//----------------------------------------------------------------------------- +// Purpose: Copies a face and its winding +// Input : *pFace - +// Output : face_t +//----------------------------------------------------------------------------- +face_t *CopyFace( face_t *pFace ) +{ + face_t *f = NewFaceFromFace( pFace ); + f->w = CopyWinding( pFace->w ); + + return f; +} + +//----------------------------------------------------------------------------- +// Purpose: Link this brush into the list for this leaf +// Input : *node - +// *brush - +//----------------------------------------------------------------------------- +void AddBrushToLeaf( node_t *node, bspbrush_t *brush ) +{ + brush->next = node->brushlist; + node->brushlist = brush; +} + +//----------------------------------------------------------------------------- +// Purpose: Recursively filter a brush through the tree +// Input : *node - +// *brush - +//----------------------------------------------------------------------------- +void MergeBrush_r( node_t *node, bspbrush_t *brush ) +{ + if ( node->planenum == PLANENUM_LEAF ) + { + if ( node->contents & CONTENTS_SOLID ) + { + FreeBrush( brush ); + } + else + { + AddBrushToLeaf( node, brush ); + } + return; + } + + bspbrush_t *front, *back; + SplitBrush( brush, node->planenum, &front, &back ); + FreeBrush( brush ); + + if ( front ) + { + MergeBrush_r( node->children[0], front ); + } + if ( back ) + { + MergeBrush_r( node->children[1], back ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Recursively filter a face into the tree leaving references to the +// original face in any visible leaves that a clipped fragment falls +// into. +// Input : *node - current head of tree +// *face - clipped face fragment +// *original - unclipped original face +// Output : Returns true if any references were left +//----------------------------------------------------------------------------- +bool MergeFace_r( node_t *node, face_t *face, face_t *original ) +{ + bool referenced = false; + + if ( node->planenum == PLANENUM_LEAF ) + { + if ( node->contents & CONTENTS_SOLID ) + { + FreeFace( face ); + return false; + } + + leafface_t *plist = new leafface_t; + plist->pFace = original; + plist->pNext = node->leaffacelist; + node->leaffacelist = plist; + + referenced = true; + } + else + { + // UNDONE: Don't copy the faces each time unless it's necessary!?!?! + plane_t *plane = &g_MainMap->mapplanes[node->planenum]; + winding_t *frontwinding, *backwinding, *onwinding; + + Vector offset; + WindingCenter( face->w, offset ); + + // UNDONE: Export epsilon from original face clipping code + ClassifyWindingEpsilon_Offset(face->w, plane->normal, plane->dist, 0.001, &frontwinding, &backwinding, &onwinding, -offset); + + if ( onwinding ) + { + // face is in the split plane, go down the appropriate side according to the facing direction + assert( frontwinding == NULL ); + assert( backwinding == NULL ); + + if ( DotProduct( g_MainMap->mapplanes[face->planenum].normal, g_MainMap->mapplanes[node->planenum].normal ) > 0 ) + { + frontwinding = onwinding; + } + else + { + backwinding = onwinding; + } + } + + if ( frontwinding ) + { + face_t *tmp = NewFaceFromFace( face ); + tmp->w = frontwinding; + referenced = MergeFace_r( node->children[0], tmp, original ); + } + if ( backwinding ) + { + face_t *tmp = NewFaceFromFace( face ); + tmp->w = backwinding; + bool test = MergeFace_r( node->children[1], tmp, original ); + referenced = referenced || test; + } + } + FreeFace( face ); + + return referenced; +} + +//----------------------------------------------------------------------------- +// Purpose: Loop through each face and filter it into the tree +// Input : *out - +// *pFaces - +//----------------------------------------------------------------------------- +face_t *FilterFacesIntoTree( tree_t *out, face_t *pFaces ) +{ + face_t *pLeafFaceList = NULL; + for ( face_t *f = pFaces; f; f = f->next ) + { + if( f->merged || f->split[0] || f->split[1] ) + continue; + + face_t *tmp = CopyFace( f ); + face_t *original = CopyFace( f ); + + if ( MergeFace_r( out->headnode, tmp, original ) ) + { + // clear out portal (comes from a different tree) + original->portal = NULL; + original->next = pLeafFaceList; + pLeafFaceList = original; + } + else + { + FreeFace( original ); + } + } + + return pLeafFaceList; +} + + +//----------------------------------------------------------------------------- +// Purpose: Splits the face list into faces from the same plane and tries to merge +// them if possible +// Input : **pFaceList - +//----------------------------------------------------------------------------- +void TryMergeFaceList( face_t **pFaceList ) +{ + face_t **pPlaneList = NULL; + + // divide the list into buckets by plane number + pPlaneList = new face_t *[g_MainMap->nummapplanes]; + memset( pPlaneList, 0, sizeof(face_t *) * g_MainMap->nummapplanes ); + + face_t *pFaces = *pFaceList; + face_t *pOutput = NULL; + + while ( pFaces ) + { + face_t *next = pFaces->next; + + // go ahead and delete the old split/merged faces + if ( pFaces->merged || pFaces->split[0] || pFaces->split[1] ) + { + Error("Split face in merge list!"); + } + else + { + // add to the list for this plane + pFaces->next = pPlaneList[pFaces->planenum]; + pPlaneList[pFaces->planenum] = pFaces; + } + + pFaces = next; + } + + // now merge each plane's list of faces + int merged = 0; + for ( int i = 0; i < g_MainMap->nummapplanes; i++ ) + { + if ( pPlaneList[i] ) + { + MergeFaceList( &pPlaneList[i] ); + } + + // move these over to the output face list + face_t *list = pPlaneList[i]; + while ( list ) + { + face_t *next = list->next; + + if ( list->merged ) + merged++; + + list->next = pOutput; + pOutput = list; + list = next; + } + } + + if ( merged ) + { + Msg("\nMerged %d detail faces...", merged ); + } + delete[] pPlaneList; + + *pFaceList = pOutput; +} + + +//----------------------------------------------------------------------------- +// Purpose: filter each brush in the list into the tree +// Input : *out - +// *brushes - +//----------------------------------------------------------------------------- +void FilterBrushesIntoTree( tree_t *out, bspbrush_t *brushes ) +{ + // Merge all of the brushes into the world tree + for ( bspbrush_t *plist = brushes; plist; plist = plist->next ) + { + MergeBrush_r( out->headnode, CopyBrush(plist) ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Build faces for the detail brushes and merge them into the BSP +// Input : *worldtree - +// brush_start - +// brush_end - +//----------------------------------------------------------------------------- +face_t *MergeDetailTree( tree_t *worldtree, int brush_start, int brush_end ) +{ + int start; + bspbrush_t *detailbrushes = NULL; + face_t *pFaces = NULL; + face_t *pLeafFaceList = NULL; + + // Grab the list of detail brushes + detailbrushes = MakeBspBrushList (brush_start, brush_end, g_MainMap->map_mins, g_MainMap->map_maxs, ONLY_DETAIL ); + if (detailbrushes) + { + start = Plat_FloatTime(); + Msg("Chop Details..."); + // if there are detail brushes, chop them against each other + if (!nocsg) + detailbrushes = ChopBrushes (detailbrushes); + + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); + // Now mark the visible sides so we can eliminate all detail brush sides + // that are covered by other detail brush sides + // NOTE: This still leaves detail brush sides that are covered by the world. (these are removed in the merge operation) + Msg("Find Visible Detail Sides..."); + pFaces = ComputeVisibleBrushSides( detailbrushes ); + TryMergeFaceList( &pFaces ); + SubdivideFaceList( &pFaces ); + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); + + start = Plat_FloatTime(); + Msg("Merging details..."); + // Merge the detail solids and faces into the world tree + // Merge all of the faces into the world tree + pLeafFaceList = FilterFacesIntoTree( worldtree, pFaces ); + FilterBrushesIntoTree( worldtree, detailbrushes ); + + FreeFaceList( pFaces ); + FreeBrushList(detailbrushes); + + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); + } + + return pLeafFaceList; +} + + +//----------------------------------------------------------------------------- +// Purpose: Quick overlap test for brushes +// Input : *p1 - +// *p2 - +// Output : Returns false if the brushes cannot intersect +//----------------------------------------------------------------------------- +bool BrushBoxOverlap( bspbrush_t *p1, bspbrush_t *p2 ) +{ + if ( p1 == p2 ) + return false; + + for ( int i = 0; i < 3; i++ ) + { + if ( p1->mins[i] > p2->maxs[i] || p1->maxs[i] < p2->mins[i] ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFace - input face to test +// *pbrush - brush to clip face against +// **pOutputList - list of faces clipped from pFace +// Output : Returns true if the brush completely clips the face +//----------------------------------------------------------------------------- +// NOTE: This assumes the brushes have already been chopped so that no solid space +// is enclosed by more than one brush!! +bool ClipFaceToBrush( face_t *pFace, bspbrush_t *pbrush, face_t **pOutputList ) +{ + int planenum = pFace->planenum & (~1); + int foundSide = -1; + + CUtlVector<int> sortedSides; + + int i; + for ( i = 0; i < pbrush->numsides && foundSide < 0; i++ ) + { + int bplane = pbrush->sides[i].planenum & (~1); + if ( bplane == planenum ) + foundSide = i; + } + + Vector offset = -0.5f * (pbrush->maxs + pbrush->mins); + face_t *currentface = CopyFace( pFace ); + + if ( foundSide >= 0 ) + { + sortedSides.RemoveAll(); + for ( i = 0; i < pbrush->numsides; i++ ) + { + // don't clip to bevels + if ( pbrush->sides[i].bevel ) + continue; + + if ( g_MainMap->mapplanes[pbrush->sides[i].planenum].type <= PLANE_Z ) + { + sortedSides.AddToHead( i ); + } + else + { + sortedSides.AddToTail( i ); + } + } + + for ( i = 0; i < sortedSides.Size(); i++ ) + { + int index = sortedSides[i]; + if ( index == foundSide ) + continue; + + plane_t *plane = &g_MainMap->mapplanes[pbrush->sides[index].planenum]; + winding_t *frontwinding, *backwinding; + ClipWindingEpsilon_Offset(currentface->w, plane->normal, plane->dist, 0.001, &frontwinding, &backwinding, offset); + + // only clip if some part of this face is on the back side of all brush sides + if ( !backwinding || WindingIsTiny(backwinding)) + { + FreeFaceList( *pOutputList ); + *pOutputList = NULL; + break; + } + if ( frontwinding && !WindingIsTiny(frontwinding) ) + { + // add this fragment to the return list + // make a face for the fragment + face_t *f = NewFaceFromFace( pFace ); + f->w = frontwinding; + + // link the fragment in + f->next = *pOutputList; + *pOutputList = f; + } + + // update the current winding to be the part behind each plane + FreeWinding( currentface->w ); + currentface->w = backwinding; + } + + // free the bit that is left in solid or not clipped (if we broke out early) + FreeFace( currentface ); + + // if we made it all the way through and didn't produce any fragments then the whole face was clipped away + if ( !*pOutputList && i == sortedSides.Size() ) + { + return true; + } + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Given an original side and chopped winding, make a face_t +// Input : *side - side of the original brush +// *winding - winding for this face (portion of the side) +// Output : face_t +//----------------------------------------------------------------------------- +face_t *MakeBrushFace( side_t *originalSide, winding_t *winding ) +{ + face_t *f = AllocFace(); + f->merged = NULL; + f->split[0] = f->split[1] = NULL; + f->w = CopyWinding( winding ); + f->originalface = originalSide; + // + // save material info + // + f->texinfo = originalSide->texinfo; + f->dispinfo = -1; + + // save plane info + f->planenum = originalSide->planenum; + f->contents = originalSide->contents; + + return f; +} + + +//----------------------------------------------------------------------------- +// Purpose: Chop away sides that are inside other brushes. +// Brushes have already been chopped up so that they do not overlap, +// they merely touch. +// Input : *list - list of brushes +// Output : face_t * - list of visible faces (some marked bad/split) +//----------------------------------------------------------------------------- +// assumes brushes were chopped! + + +side_t *FindOriginalSide( mapbrush_t *mb, side_t *pBspSide ) +{ + side_t *bestside = NULL; + float bestdot = 0; + + plane_t *p1 = g_MainMap->mapplanes + pBspSide->planenum; + + for (int i=0 ; i<mb->numsides ; i++) + { + side_t *side = &mb->original_sides[i]; + if (side->bevel) + continue; + if (side->texinfo == TEXINFO_NODE) + continue; // non-visible + if ((side->planenum&~1) == (pBspSide->planenum&~1)) + { // exact match + return mb->original_sides + i; + } + // see how close the match is + plane_t *p2 = &g_MainMap->mapplanes[side->planenum&~1]; + float dot = DotProduct (p1->normal, p2->normal); + if (dot > bestdot) + { + bestdot = dot; + bestside = side; + } + } + + if ( !bestside ) + { + Error( "Bad detail brush side\n" ); + } + return bestside; +} + +// Get a list of brushes from pBrushList that could cut faces on the source brush +int GetListOfCutBrushes( CUtlVector<bspbrush_t *> &out, bspbrush_t *pSourceBrush, bspbrush_t *pBrushList ) +{ + mapbrush_t *mb = pSourceBrush->original; + for ( bspbrush_t *walk = pBrushList; walk; walk = walk->next ) + { + if ( walk == pSourceBrush ) + continue; + + // only clip to transparent brushes if the original brush is transparent + if ( walk->original->contents & TRANSPARENT_CONTENTS ) + { + if ( !(mb->contents & TRANSPARENT_CONTENTS) ) + continue; + } + + // don't clip to clip brushes, etc. + if ( !(walk->original->contents & ALL_VISIBLE_CONTENTS) ) + continue; + + // brushes overlap, test faces + if ( !BrushBoxOverlap( pSourceBrush, walk ) ) + continue; + + out.AddToTail( walk ); + } + return out.Count(); +} + +// Count the number of real (unsplit) faces in the list +static int CountFaceList( face_t *f ) +{ + int count = 0; + for ( ; f; f = f->next ) + { + if ( f->split[0] ) + continue; + count++; + } + + return count; +} + +// Clips f to a list of potential cutting brushes +// If f clips into new faces, returns the list of new faces in pOutputList +static void ClipFaceToBrushList( face_t *f, const CUtlVector<bspbrush_t *> &cutBrushes, face_t **pOutputList ) +{ + *pOutputList = NULL; + + if ( f->split[0] ) + return; + + face_t *pClipList = CopyFace( f ); + pClipList->next = NULL; + bool clipped = false; + for ( int i = 0; i < cutBrushes.Count(); i++ ) + { + bspbrush_t *cut = cutBrushes[i]; + for ( face_t *pCutFace = pClipList; pCutFace; pCutFace = pCutFace->next ) + { + face_t *pClip = NULL; + // already split, no need to clip + if ( pCutFace->split[0] ) + continue; + + if ( ClipFaceToBrush( pCutFace, cut, &pClip ) ) + { + clipped = true; + // mark face bad, the brush clipped it away + pCutFace->split[0] = pCutFace; + } + else if ( pClip ) + { + clipped = true; + // mark this face as split + pCutFace->split[0] = pCutFace; + + // insert face fragments at head of list (UNDONE: reverses order, do we care?) + while ( pClip ) + { + face_t *next = pClip->next; + pClip->next = pClipList; + pClipList = pClip; + pClip = next; + } + } + } + } + if ( clipped ) + { + *pOutputList = pClipList; + } + else + { + // didn't do any clipping, go ahead and free the copy of the face here. + FreeFaceList( pClipList ); + } +} + +// Compute a list of faces that are visible on the detail brush sides +face_t *ComputeVisibleBrushSides( bspbrush_t *list ) +{ + face_t *pTotalFaces = NULL; + CUtlVector<bspbrush_t *> cutBrushes; + + // Go through the whole brush list + for ( bspbrush_t *pbrush = list; pbrush; pbrush = pbrush->next ) + { + face_t *pFaces = NULL; + mapbrush_t *mb = pbrush->original; + + if ( !(mb->contents & ALL_VISIBLE_CONTENTS) ) + continue; + + // Make a face for each brush side, then clip it by the other + // details to see if any fragments are visible + for ( int i = 0; i < pbrush->numsides; i++ ) + { + winding_t *winding = pbrush->sides[i].winding; + if ( !winding ) + continue; + + if (! (pbrush->sides[i].contents & ALL_VISIBLE_CONTENTS) ) + continue; + + side_t *side = FindOriginalSide( mb, pbrush->sides + i ); + face_t *f = MakeBrushFace( side, winding ); + + // link to head of face list + f->next = pFaces; + pFaces = f; + } + + // Make a list of brushes that can cut the face list for this brush + cutBrushes.RemoveAll(); + if ( GetListOfCutBrushes( cutBrushes, pbrush, list ) ) + { + // now cut each face to find visible fragments + for ( face_t *f = pFaces; f; f = f->next ) + { + // this will be a new list of faces that this face cuts into + face_t *pClip = NULL; + ClipFaceToBrushList( f, cutBrushes, &pClip ); + if ( pClip ) + { + int outCount = CountFaceList(pClip); + // it cut into more faces (or it was completely cut away) + if ( outCount <= 1 ) + { + // was removed or cut down, mark as split + f->split[0] = f; + // insert face fragments at head of list (UNDONE: reverses order, do we care?) + while ( pClip ) + { + face_t *next = pClip->next; + pClip->next = pFaces; + pFaces = pClip; + pClip = next; + } + } + else + { + // it cut into more than one visible fragment + // Don't fragment details + // UNDONE: Build 2d convex hull of this list and swap face winding + // with that polygon? That would fix the remaining issues. + FreeFaceList( pClip ); + pClip = NULL; + } + } + } + } + + // move visible fragments to global face list + while ( pFaces ) + { + face_t *next = pFaces->next; + if ( pFaces->split[0] ) + { + FreeFace( pFaces ); + } + else + { + pFaces->next = pTotalFaces; + pTotalFaces = pFaces; + } + pFaces = next; + } + } + + return pTotalFaces; +} diff --git a/utils/vbsp/detail.h b/utils/vbsp/detail.h new file mode 100644 index 0000000..64f02b3 --- /dev/null +++ b/utils/vbsp/detail.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef DETAIL_H +#define DETAIL_H + +#ifdef _WIN32 +#pragma once +#endif + +face_t *MergeDetailTree( tree_t *worldtree, int brush_start, int brush_end ); + + +#endif // DETAIL_H diff --git a/utils/vbsp/detailobjects.cpp b/utils/vbsp/detailobjects.cpp new file mode 100644 index 0000000..c6c1fc2 --- /dev/null +++ b/utils/vbsp/detailobjects.cpp @@ -0,0 +1,966 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Places "detail" objects which are client-only renderable things +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#include <windows.h> +#include "vbsp.h" +#include "bsplib.h" +#include "KeyValues.h" +#include "utlsymbol.h" +#include "utlvector.h" +#include <io.h> +#include "bspfile.h" +#include "utilmatlib.h" +#include "gamebspfile.h" +#include "mathlib/VMatrix.h" +#include "materialpatch.h" +#include "pacifier.h" +#include "vstdlib/random.h" +#include "builddisp.h" +#include "disp_vbsp.h" +#include "UtlBuffer.h" +#include "CollisionUtils.h" +#include <float.h> +#include "UtlLinkedList.h" +#include "byteswap.h" +#include "writebsp.h" + +//----------------------------------------------------------------------------- +// Information about particular detail object types +//----------------------------------------------------------------------------- +enum +{ + MODELFLAG_UPRIGHT = 0x1, +}; + +struct DetailModel_t +{ + CUtlSymbol m_ModelName; + float m_Amount; + float m_MinCosAngle; + float m_MaxCosAngle; + int m_Flags; + int m_Orientation; + int m_Type; + Vector2D m_Pos[2]; + Vector2D m_Tex[2]; + float m_flRandomScaleStdDev; + unsigned char m_ShapeSize; + unsigned char m_ShapeAngle; + unsigned char m_SwayAmount; +}; + +struct DetailObjectGroup_t +{ + float m_Alpha; + CUtlVector< DetailModel_t > m_Models; +}; + +struct DetailObject_t +{ + CUtlSymbol m_Name; + float m_Density; + CUtlVector< DetailObjectGroup_t > m_Groups; + + bool operator==(const DetailObject_t& src ) const + { + return src.m_Name == m_Name; + } +}; + +static CUtlVector<DetailObject_t> s_DetailObjectDict; + + +//----------------------------------------------------------------------------- +// Error checking.. make sure the model is valid + is a static prop +//----------------------------------------------------------------------------- +struct StaticPropLookup_t +{ + CUtlSymbol m_ModelName; + bool m_IsValid; +}; + +static bool StaticLess( StaticPropLookup_t const& src1, StaticPropLookup_t const& src2 ) +{ + return src1.m_ModelName < src2.m_ModelName; +} + +static CUtlRBTree< StaticPropLookup_t, unsigned short > s_StaticPropLookup( 0, 32, StaticLess ); + + +//----------------------------------------------------------------------------- +// These puppies are used to construct the game lumps +//----------------------------------------------------------------------------- +static CUtlVector<DetailObjectDictLump_t> s_DetailObjectDictLump; +static CUtlVector<DetailObjectLump_t> s_DetailObjectLump; +static CUtlVector<DetailSpriteDictLump_t> s_DetailSpriteDictLump; + + +//----------------------------------------------------------------------------- +// Parses the key-value pairs in the detail.rad file +//----------------------------------------------------------------------------- +static void ParseDetailGroup( int detailId, KeyValues* pGroupKeyValues ) +{ + // Sort the group by alpha + float alpha = pGroupKeyValues->GetFloat( "alpha", 1.0f ); + + int i = s_DetailObjectDict[detailId].m_Groups.Count(); + while ( --i >= 0 ) + { + if (alpha > s_DetailObjectDict[detailId].m_Groups[i].m_Alpha) + break; + } + + // Insert after the first guy who's more transparent that we are! + i = s_DetailObjectDict[detailId].m_Groups.InsertAfter(i); + DetailObjectGroup_t& group = s_DetailObjectDict[detailId].m_Groups[i]; + + group.m_Alpha = alpha; + + // Add in all the model groups + KeyValues* pIter = pGroupKeyValues->GetFirstSubKey(); + float totalAmount = 0.0f; + while( pIter ) + { + if (pIter->GetFirstSubKey()) + { + int i = group.m_Models.AddToTail(); + + DetailModel_t &model = group.m_Models[i]; + + model.m_ModelName = pIter->GetString( "model", 0 ); + if (model.m_ModelName != UTL_INVAL_SYMBOL) + { + model.m_Type = DETAIL_PROP_TYPE_MODEL; + } + else + { + const char *pSpriteData = pIter->GetString( "sprite", 0 ); + if (pSpriteData) + { + const char *pProcModelType = pIter->GetString( "sprite_shape", 0 ); + + if ( pProcModelType ) + { + if ( !Q_stricmp( pProcModelType, "cross" ) ) + { + model.m_Type = DETAIL_PROP_TYPE_SHAPE_CROSS; + } + else if ( !Q_stricmp( pProcModelType, "tri" ) ) + { + model.m_Type = DETAIL_PROP_TYPE_SHAPE_TRI; + } + else + model.m_Type = DETAIL_PROP_TYPE_SPRITE; + } + else + { + // card sprite + model.m_Type = DETAIL_PROP_TYPE_SPRITE; + } + + model.m_Tex[0].Init(); + model.m_Tex[1].Init(); + + float x = 0, y = 0, flWidth = 64, flHeight = 64, flTextureSize = 512; + int nValid = sscanf( pSpriteData, "%f %f %f %f %f", &x, &y, &flWidth, &flHeight, &flTextureSize ); + if ( (nValid != 5) || (flTextureSize == 0) ) + { + Error( "Invalid arguments to \"sprite\" in detail.vbsp (model %s)!\n", model.m_ModelName.String() ); + } + + model.m_Tex[0].x = ( x + 0.5f ) / flTextureSize; + model.m_Tex[0].y = ( y + 0.5f ) / flTextureSize; + model.m_Tex[1].x = ( x + flWidth - 0.5f ) / flTextureSize; + model.m_Tex[1].y = ( y + flHeight - 0.5f ) / flTextureSize; + + model.m_Pos[0].Init( -10, 20 ); + model.m_Pos[1].Init( 10, 0 ); + + pSpriteData = pIter->GetString( "spritesize", 0 ); + if (pSpriteData) + { + sscanf( pSpriteData, "%f %f %f %f", &x, &y, &flWidth, &flHeight ); + + float ox = flWidth * x; + float oy = flHeight * y; + + model.m_Pos[0].x = -ox; + model.m_Pos[0].y = flHeight - oy; + model.m_Pos[1].x = flWidth - ox; + model.m_Pos[1].y = -oy; + } + + model.m_flRandomScaleStdDev = pIter->GetFloat( "spriterandomscale", 0.0f ); + + // sway is a percent of max sway, cl_detail_max_sway + float flSway = clamp( pIter->GetFloat( "sway", 0.0f ), 0.0, 1.0 ); + model.m_SwayAmount = (unsigned char)( 255.0 * flSway ); + + // shape angle + // for the tri shape, this is the angle each side is fanned out + model.m_ShapeAngle = pIter->GetInt( "shape_angle", 0 ); + + // shape size + // for the tri shape, this is the distance from the origin to the center of a side + float flShapeSize = clamp( pIter->GetFloat( "shape_size", 0.0f ), 0.0, 1.0 ); + model.m_ShapeSize = (unsigned char)( 255.0 * flShapeSize ); + } + } + + model.m_Amount = pIter->GetFloat( "amount", 1.0 ) + totalAmount; + totalAmount = model.m_Amount; + + model.m_Flags = 0; + if (pIter->GetInt( "upright", 0 )) + { + model.m_Flags |= MODELFLAG_UPRIGHT; + } + + // These are used to prevent emission on steep surfaces + float minAngle = pIter->GetFloat( "minAngle", 180 ); + float maxAngle = pIter->GetFloat( "maxAngle", 180 ); + model.m_MinCosAngle = cos(minAngle * M_PI / 180.f); + model.m_MaxCosAngle = cos(maxAngle * M_PI / 180.f); + model.m_Orientation = pIter->GetInt( "detailOrientation", 0 ); + + // Make sure minAngle < maxAngle + if ( model.m_MinCosAngle < model.m_MaxCosAngle) + { + model.m_MinCosAngle = model.m_MaxCosAngle; + } + } + pIter = pIter->GetNextKey(); + } + + // renormalize the amount if the total > 1 + if (totalAmount > 1.0f) + { + for (i = 0; i < group.m_Models.Count(); ++i) + { + group.m_Models[i].m_Amount /= totalAmount; + } + } +} + + +//----------------------------------------------------------------------------- +// Parses the key-value pairs in the detail.vbsp file +//----------------------------------------------------------------------------- +static void ParseDetailObjectFile( KeyValues& keyValues ) +{ + // Iterate over all detail object groups... + KeyValues* pIter; + for( pIter = keyValues.GetFirstSubKey(); pIter; pIter = pIter->GetNextKey() ) + { + if (!pIter->GetFirstSubKey()) + continue; + + int i = s_DetailObjectDict.AddToTail( ); + s_DetailObjectDict[i].m_Name = pIter->GetName() ; + s_DetailObjectDict[i].m_Density = pIter->GetFloat( "density", 0.0f ); + + // Iterate over all detail object groups... + KeyValues* pIterGroups = pIter->GetFirstSubKey(); + while( pIterGroups ) + { + if (pIterGroups->GetFirstSubKey()) + { + ParseDetailGroup( i, pIterGroups ); + } + pIterGroups = pIterGroups->GetNextKey(); + } + } +} + + +//----------------------------------------------------------------------------- +// Finds the name of the detail.vbsp file to use +//----------------------------------------------------------------------------- +static const char *FindDetailVBSPName( void ) +{ + for( int i = 0; i < num_entities; i++ ) + { + char* pEntity = ValueForKey( &entities[i], "classname" ); + if ( !strcmp( pEntity, "worldspawn" ) ) + { + const char *pDetailVBSP = ValueForKey( &entities[i], "detailvbsp" ); + if ( !pDetailVBSP || !pDetailVBSP[0] ) + { + pDetailVBSP = "detail.vbsp"; + } + return pDetailVBSP; + } + } + return "detail.vbsp"; +} + + +//----------------------------------------------------------------------------- +// Loads up the detail object dictionary +//----------------------------------------------------------------------------- +void LoadEmitDetailObjectDictionary( const char* pGameDir ) +{ + // Set the required global lights filename and try looking in qproject + const char *pDetailVBSP = FindDetailVBSPName(); + KeyValues * values = new KeyValues( pDetailVBSP ); + if ( values->LoadFromFile( g_pFileSystem, pDetailVBSP ) ) + { + ParseDetailObjectFile( *values ); + } + values->deleteThis(); +} + + +//----------------------------------------------------------------------------- +// Selects a detail group +//----------------------------------------------------------------------------- +static int SelectGroup( const DetailObject_t& detail, float alpha ) +{ + // Find the two groups whose alpha we're between... + int start, end; + for ( start = 0; start < detail.m_Groups.Count() - 1; ++start ) + { + if (alpha < detail.m_Groups[start+1].m_Alpha) + break; + } + + end = start + 1; + if (end >= detail.m_Groups.Count()) + --end; + + if (start == end) + return start; + + // Figure out how far we are between start and end... + float dist = 0.0f; + float dAlpha = (detail.m_Groups[end].m_Alpha - detail.m_Groups[start].m_Alpha); + if (dAlpha != 0.0f) + { + dist = (alpha - detail.m_Groups[start].m_Alpha) / dAlpha; + } + + // Pick a number, any number... + float r = rand() / (float)VALVE_RAND_MAX; + + // When dist == 0, we *always* want start. + // When dist == 1, we *always* want end + // That's why this logic looks a little reversed + return (r > dist) ? start : end; +} + + +//----------------------------------------------------------------------------- +// Selects a detail object +//----------------------------------------------------------------------------- +static int SelectDetail( DetailObjectGroup_t const& group ) +{ + // Pick a number, any number... + float r = rand() / (float)VALVE_RAND_MAX; + + // Look through the list of models + pick the one associated with this number + for ( int i = 0; i < group.m_Models.Count(); ++i ) + { + if (r <= group.m_Models[i].m_Amount) + return i; + } + + return -1; +} + + +//----------------------------------------------------------------------------- +// Adds a detail dictionary element (expected to oftentimes be shared) +//----------------------------------------------------------------------------- +static int AddDetailDictLump( const char* pModelName ) +{ + DetailObjectDictLump_t dictLump; + strncpy( dictLump.m_Name, pModelName, DETAIL_NAME_LENGTH ); + + for (int i = s_DetailObjectDictLump.Count(); --i >= 0; ) + { + if (!memcmp(&s_DetailObjectDictLump[i], &dictLump, sizeof(dictLump) )) + return i; + } + + return s_DetailObjectDictLump.AddToTail( dictLump ); +} + +static int AddDetailSpriteDictLump( const Vector2D *pPos, const Vector2D *pTex ) +{ + DetailSpriteDictLump_t dictLump; + dictLump.m_UL = pPos[0]; + dictLump.m_LR = pPos[1]; + dictLump.m_TexUL = pTex[0]; + dictLump.m_TexLR = pTex[1]; + + for (int i = s_DetailSpriteDictLump.Count(); --i >= 0; ) + { + if (!memcmp(&s_DetailSpriteDictLump[i], &dictLump, sizeof(dictLump) )) + return i; + } + + return s_DetailSpriteDictLump.AddToTail( dictLump ); +} + + +//----------------------------------------------------------------------------- +// Computes the leaf that the detail lies in +//----------------------------------------------------------------------------- +static int ComputeDetailLeaf( const Vector& pt ) +{ + int node = 0; + while( node >= 0 ) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + if (DotProduct(pt, pPlane->normal) < pPlane->dist) + node = pNode->children[1]; + else + node = pNode->children[0]; + } + + return - node - 1; +} + + +//----------------------------------------------------------------------------- +// Make sure the details are compiled with static prop +//----------------------------------------------------------------------------- +static bool IsModelValid( const char* pModelName ) +{ + StaticPropLookup_t lookup; + lookup.m_ModelName = pModelName; + + int i = s_StaticPropLookup.Find( lookup ); + if (i != s_StaticPropLookup.InvalidIndex() ) + return s_StaticPropLookup[i].m_IsValid; + + CUtlBuffer buf; + lookup.m_IsValid = LoadStudioModel( pModelName, "detail_prop", buf ); + if (!lookup.m_IsValid) + { + Warning("Error loading studio model \"%s\"!\n", pModelName ); + } + + s_StaticPropLookup.Insert( lookup ); + return lookup.m_IsValid; +} + + +//----------------------------------------------------------------------------- +// Add a detail to the lump. +//----------------------------------------------------------------------------- +static int s_nDetailOverflow = 0; +static void AddDetailToLump( const char* pModelName, const Vector& pt, const QAngle& angles, int nOrientation ) +{ + Assert( pt.IsValid() && angles.IsValid() ); + + // Make sure the model is valid... + if (!IsModelValid(pModelName)) + return; + + if (s_DetailObjectLump.Count() == 65535) + { + ++s_nDetailOverflow; + return; + } + + // Insert an element into the object dictionary if it aint there... + int i = s_DetailObjectLump.AddToTail( ); + + DetailObjectLump_t& objectLump = s_DetailObjectLump[i]; + objectLump.m_DetailModel = AddDetailDictLump( pModelName ); + VectorCopy( angles, objectLump.m_Angles ); + VectorCopy( pt, objectLump.m_Origin ); + objectLump.m_Leaf = ComputeDetailLeaf(pt); + objectLump.m_Lighting.r = 255; + objectLump.m_Lighting.g = 255; + objectLump.m_Lighting.b = 255; + objectLump.m_Lighting.exponent = 0; + objectLump.m_LightStyles = 0; + objectLump.m_LightStyleCount = 0; + objectLump.m_Orientation = nOrientation; + objectLump.m_Type = DETAIL_PROP_TYPE_MODEL; +} + + +//----------------------------------------------------------------------------- +// Add a detail sprite to the lump. +//----------------------------------------------------------------------------- +static void AddDetailSpriteToLump( const Vector &vecOrigin, const QAngle &vecAngles, int nOrientation, + const Vector2D *pPos, const Vector2D *pTex, float flScale, int iType, + int iShapeAngle = 0, int iShapeSize = 0, int iSwayAmount = 0 ) +{ + // Insert an element into the object dictionary if it aint there... + int i = s_DetailObjectLump.AddToTail( ); + + if (i >= 65535) + { + Error( "Error! Too many detail props emitted on this map! (64K max!)n" ); + } + + DetailObjectLump_t& objectLump = s_DetailObjectLump[i]; + objectLump.m_DetailModel = AddDetailSpriteDictLump( pPos, pTex ); + VectorCopy( vecAngles, objectLump.m_Angles ); + VectorCopy( vecOrigin, objectLump.m_Origin ); + objectLump.m_Leaf = ComputeDetailLeaf(vecOrigin); + objectLump.m_Lighting.r = 255; + objectLump.m_Lighting.g = 255; + objectLump.m_Lighting.b = 255; + objectLump.m_Lighting.exponent = 0; + objectLump.m_LightStyles = 0; + objectLump.m_LightStyleCount = 0; + objectLump.m_Orientation = nOrientation; + objectLump.m_Type = iType; + objectLump.m_flScale = flScale; + objectLump.m_ShapeAngle = iShapeAngle; + objectLump.m_ShapeSize = iShapeSize; + objectLump.m_SwayAmount = iSwayAmount; +} + +static void AddDetailSpriteToLump( const Vector &vecOrigin, const QAngle &vecAngles, DetailModel_t const& model, float flScale ) +{ + AddDetailSpriteToLump( vecOrigin, + vecAngles, + model.m_Orientation, + model.m_Pos, + model.m_Tex, + flScale, + model.m_Type, + model.m_ShapeAngle, + model.m_ShapeSize, + model.m_SwayAmount ); +} + +//----------------------------------------------------------------------------- +// Got a detail! Place it on the surface... +//----------------------------------------------------------------------------- +// BUGBUG: When the global optimizer is on, "normal" gets trashed in this function +// (only when not in the debugger?) +// Printing the values of normal at the bottom of the function fixes it as does +// disabling global optimizations. +static void PlaceDetail( DetailModel_t const& model, const Vector& pt, const Vector& normal ) +{ + // But only place it on the surface if it meets the angle constraints... + float cosAngle = normal.z; + + // Never emit if the angle's too steep + if (cosAngle < model.m_MaxCosAngle) + return; + + // If it's between min + max, flip a coin... + if (cosAngle < model.m_MinCosAngle) + { + float probability = (cosAngle - model.m_MaxCosAngle) / + (model.m_MinCosAngle - model.m_MaxCosAngle); + + float t = rand() / (float)VALVE_RAND_MAX; + if (t > probability) + return; + } + + // Compute the orientation of the detail + QAngle angles; + if (model.m_Flags & MODELFLAG_UPRIGHT) + { + // If it's upright, we just select a random yaw + angles.Init( 0, 360.0f * rand() / (float)VALVE_RAND_MAX, 0.0f ); + } + else + { + // It's not upright, so it must conform to the ground. Choose + // a random orientation based on the surface normal + + Vector zaxis; + VectorCopy( normal, zaxis ); + VectorNormalize( zaxis ); + + // Choose any two arbitrary axes which are perpendicular to the normal + Vector xaxis( 1, 0, 0 ); + if (fabs(xaxis.Dot(zaxis)) - 1.0 > -1e-3) + xaxis.Init( 0, 1, 0 ); + Vector yaxis; + CrossProduct( zaxis, xaxis, yaxis ); + VectorNormalize( yaxis ); + CrossProduct( yaxis, zaxis, xaxis ); + VectorNormalize( xaxis ); + VMatrix matrix; + matrix.SetBasisVectors( xaxis, yaxis, zaxis ); + matrix.SetTranslation( vec3_origin ); + + float rotAngle = 360.0f * rand() / (float)VALVE_RAND_MAX; + VMatrix rot = SetupMatrixAxisRot( Vector( 0, 0, 1 ), rotAngle ); + matrix = matrix * rot; + + MatrixToAngles( matrix, angles ); + } + + // FIXME: We may also want a purely random rotation too + + // Insert an element into the object dictionary if it aint there... + switch ( model.m_Type ) + { + case DETAIL_PROP_TYPE_MODEL: + AddDetailToLump( model.m_ModelName.String(), pt, angles, model.m_Orientation ); + break; + + // Sprites and procedural models made from sprites + case DETAIL_PROP_TYPE_SPRITE: + default: + { + float flScale = 1.0f; + if ( model.m_flRandomScaleStdDev != 0.0f ) + { + flScale = fabs( RandomGaussianFloat( 1.0f, model.m_flRandomScaleStdDev ) ); + } + + AddDetailSpriteToLump( pt, angles, model, flScale ); + } + break; + } +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects on a face +//----------------------------------------------------------------------------- +static void EmitDetailObjectsOnFace( dface_t* pFace, DetailObject_t& detail ) +{ + if (pFace->numedges < 3) + return; + + // We're going to pick a bunch of random points, and then probabilistically + // decide whether or not to plant a detail object there. + + // Turn the face into a bunch of polygons, and compute the area of each + int* pSurfEdges = &dsurfedges[pFace->firstedge]; + int vertexIdx = (pSurfEdges[0] < 0); + int firstVertexIndex = dedges[abs(pSurfEdges[0])].v[vertexIdx]; + dvertex_t* pFirstVertex = &dvertexes[firstVertexIndex]; + for (int i = 1; i < pFace->numedges - 1; ++i ) + { + int vertexIdx = (pSurfEdges[i] < 0); + dedge_t* pEdge = &dedges[abs(pSurfEdges[i])]; + + // Compute two triangle edges + Vector e1, e2; + VectorSubtract( dvertexes[pEdge->v[vertexIdx]].point, pFirstVertex->point, e1 ); + VectorSubtract( dvertexes[pEdge->v[1 - vertexIdx]].point, pFirstVertex->point, e2 ); + + // Compute the triangle area + Vector areaVec; + CrossProduct( e1, e2, areaVec ); + float normalLength = areaVec.Length(); + float area = 0.5f * normalLength; + + // Compute the number of samples to take + int numSamples = area * detail.m_Density * 0.000001; + + // Now take a sample, and randomly place an object there + for (int i = 0; i < numSamples; ++i ) + { + // Create a random sample... + float u = rand() / (float)VALVE_RAND_MAX; + float v = rand() / (float)VALVE_RAND_MAX; + if (v > 1.0f - u) + { + u = 1.0f - u; + v = 1.0f - v; + assert( u + v <= 1.0f ); + } + + // Compute alpha + float alpha = 1.0f; + + // Select a group based on the alpha value + int group = SelectGroup( detail, alpha ); + + // Now that we've got a group, choose a detail + int model = SelectDetail( detail.m_Groups[group] ); + if (model < 0) + continue; + + // Got a detail! Place it on the surface... + Vector pt, normal; + VectorMA( pFirstVertex->point, u, e1, pt ); + VectorMA( pt, v, e2, pt ); + VectorDivide( areaVec, -normalLength, normal ); + + PlaceDetail( detail.m_Groups[group].m_Models[model], pt, normal ); + } + } +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects on a face +//----------------------------------------------------------------------------- +static float ComputeDisplacementFaceArea( dface_t* pFace ) +{ + float area = 0.0f; + + // Compute the area of the base face + int* pSurfEdges = &dsurfedges[pFace->firstedge]; + int vertexIdx = (pSurfEdges[0] < 0); + int firstVertexIndex = dedges[abs(pSurfEdges[0])].v[vertexIdx]; + dvertex_t* pFirstVertex = &dvertexes[firstVertexIndex]; + for (int i = 1; i <= 2; ++i ) + { + int vertexIdx = (pSurfEdges[i] < 0); + dedge_t* pEdge = &dedges[abs(pSurfEdges[i])]; + + // Compute two triangle edges + Vector e1, e2; + VectorSubtract( dvertexes[pEdge->v[vertexIdx]].point, pFirstVertex->point, e1 ); + VectorSubtract( dvertexes[pEdge->v[1 - vertexIdx]].point, pFirstVertex->point, e2 ); + + // Compute the triangle area + Vector areaVec; + CrossProduct( e1, e2, areaVec ); + float normalLength = areaVec.Length(); + area += 0.5f * normalLength; + } + + return area; +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects on a face +//----------------------------------------------------------------------------- +static void EmitDetailObjectsOnDisplacementFace( dface_t* pFace, + DetailObject_t& detail, CCoreDispInfo& coreDispInfo ) +{ + assert(pFace->numedges == 4); + + // We're going to pick a bunch of random points, and then probabilistically + // decide whether or not to plant a detail object there. + + // Compute the area of the base face + float area = ComputeDisplacementFaceArea( pFace ); + + // Compute the number of samples to take + int numSamples = area * detail.m_Density * 0.000001; + + // Now take a sample, and randomly place an object there + for (int i = 0; i < numSamples; ++i ) + { + // Create a random sample... + float u = rand() / (float)VALVE_RAND_MAX; + float v = rand() / (float)VALVE_RAND_MAX; + + // Compute alpha + float alpha; + Vector pt, normal; + coreDispInfo.GetPositionOnSurface( u, v, pt, &normal, &alpha ); + alpha /= 255.0f; + + // Select a group based on the alpha value + int group = SelectGroup( detail, alpha ); + + // Now that we've got a group, choose a detail + int model = SelectDetail( detail.m_Groups[group] ); + if (model < 0) + continue; + + // Got a detail! Place it on the surface... + PlaceDetail( detail.m_Groups[group].m_Models[model], pt, normal ); + } +} + + +//----------------------------------------------------------------------------- +// Sort detail objects by leaf +//----------------------------------------------------------------------------- +static int SortFunc( const void *arg1, const void *arg2 ) +{ + int nDelta = ((DetailObjectLump_t*)arg1)->m_Leaf - ((DetailObjectLump_t*)arg2)->m_Leaf; + if ( nDelta < 0 ) + return -1; + if ( nDelta > 0 ) + return 1; + return 0; +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects in the lump +//----------------------------------------------------------------------------- +static void SetLumpData( ) +{ + // Sort detail props by leaf + qsort( s_DetailObjectLump.Base(), s_DetailObjectLump.Count(), sizeof(DetailObjectLump_t), SortFunc ); + + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(GAMELUMP_DETAIL_PROPS); + if (handle != g_GameLumps.InvalidGameLump()) + { + g_GameLumps.DestroyGameLump(handle); + } + int nDictSize = s_DetailObjectDictLump.Count() * sizeof(DetailObjectDictLump_t); + int nSpriteDictSize = s_DetailSpriteDictLump.Count() * sizeof(DetailSpriteDictLump_t); + int nObjSize = s_DetailObjectLump.Count() * sizeof(DetailObjectLump_t); + int nSize = nDictSize + nSpriteDictSize + nObjSize + (3 * sizeof(int)); + + handle = g_GameLumps.CreateGameLump( GAMELUMP_DETAIL_PROPS, nSize, 0, GAMELUMP_DETAIL_PROPS_VERSION ); + + // Serialize the data + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), nSize ); + buf.PutInt( s_DetailObjectDictLump.Count() ); + if (nDictSize) + { + buf.Put( s_DetailObjectDictLump.Base(), nDictSize ); + } + buf.PutInt( s_DetailSpriteDictLump.Count() ); + if (nSpriteDictSize) + { + buf.Put( s_DetailSpriteDictLump.Base(), nSpriteDictSize ); + } + buf.PutInt( s_DetailObjectLump.Count() ); + if (nObjSize) + { + buf.Put( s_DetailObjectLump.Base(), nObjSize ); + } +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects in the level +//----------------------------------------------------------------------------- +void EmitDetailModels() +{ + StartPacifier("Placing detail props : "); + + // Place stuff on each face + dface_t* pFace = dfaces; + for (int j = 0; j < numfaces; ++j) + { + UpdatePacifier( (float)j / (float)numfaces ); + + // Get at the material associated with this face + texinfo_t* pTexInfo = &texinfo[pFace[j].texinfo]; + dtexdata_t* pTexData = GetTexData( pTexInfo->texdata ); + + // Try to get at the material + bool found; + MaterialSystemMaterial_t handle = + FindOriginalMaterial( TexDataStringTable_GetString( pTexData->nameStringTableID ), + &found, false ); + if (!found) + continue; + + // See if its got any detail objects on it + const char* pDetailType = GetMaterialVar( handle, "%detailtype" ); + if (!pDetailType) + continue; + + // Get the detail type... + DetailObject_t search; + search.m_Name = pDetailType; + int objectType = s_DetailObjectDict.Find(search); + if (objectType < 0) + { + Warning("Material %s uses unknown detail object type %s!\n", + TexDataStringTable_GetString( pTexData->nameStringTableID ), + pDetailType); + continue; + } + + // Emit objects on a particular face + DetailObject_t& detail = s_DetailObjectDict[objectType]; + + // Initialize the Random Number generators for detail prop placement based on the hammer Face num. + int detailpropseed = dfaceids[j].hammerfaceid; +#ifdef WARNSEEDNUMBER + Warning( "[%d]\n",detailpropseed ); +#endif + srand( detailpropseed ); + RandomSeed( detailpropseed ); + + if (pFace[j].dispinfo < 0) + { + EmitDetailObjectsOnFace( &pFace[j], detail ); + } + else + { + // Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates. + mapdispinfo_t *pMapDisp = &mapdispinfo[pFace[j].dispinfo]; + CCoreDispInfo coreDispInfo; + DispMapToCoreDispInfo( pMapDisp, &coreDispInfo, NULL, NULL ); + + EmitDetailObjectsOnDisplacementFace( &pFace[j], detail, coreDispInfo ); + } + } + + // Emit specifically specified detail props + Vector origin; + QAngle angles; + Vector2D pos[2]; + Vector2D tex[2]; + for (int i = 0; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!strcmp(pEntity, "detail_prop") || !strcmp(pEntity, "prop_detail")) + { + GetVectorForKey( &entities[i], "origin", origin ); + GetAnglesForKey( &entities[i], "angles", angles ); + char* pModelName = ValueForKey( &entities[i], "model" ); + int nOrientation = IntForKey( &entities[i], "detailOrientation" ); + + AddDetailToLump( pModelName, origin, angles, nOrientation ); + + // strip this ent from the .bsp file + entities[i].epairs = 0; + continue; + } + + if (!strcmp(pEntity, "prop_detail_sprite")) + { + GetVectorForKey( &entities[i], "origin", origin ); + GetAnglesForKey( &entities[i], "angles", angles ); + int nOrientation = IntForKey( &entities[i], "detailOrientation" ); + GetVector2DForKey( &entities[i], "position_ul", pos[0] ); + GetVector2DForKey( &entities[i], "position_lr", pos[1] ); + GetVector2DForKey( &entities[i], "tex_ul", tex[0] ); + GetVector2DForKey( &entities[i], "tex_size", tex[1] ); + float flTextureSize = FloatForKey( &entities[i], "tex_total_size" ); + + tex[1].x += tex[0].x - 0.5f; + tex[1].y += tex[0].y - 0.5f; + tex[0].x += 0.5f; + tex[0].y += 0.5f; + tex[0] /= flTextureSize; + tex[1] /= flTextureSize; + + AddDetailSpriteToLump( origin, angles, nOrientation, pos, tex, 1.0f, DETAIL_PROP_TYPE_SPRITE ); + + // strip this ent from the .bsp file + entities[i].epairs = 0; + continue; + } + } + + EndPacifier( true ); +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects in the level +//----------------------------------------------------------------------------- +void EmitDetailObjects() +{ + EmitDetailModels(); + + // Done! Now lets add the lumps (destroy previous ones) + SetLumpData( ); + + if ( s_nDetailOverflow != 0 ) + { + Warning( "Error! Too many detail props on this map. %d were not emitted!\n", s_nDetailOverflow ); + } +} diff --git a/utils/vbsp/disp_vbsp.cpp b/utils/vbsp/disp_vbsp.cpp new file mode 100644 index 0000000..f368859 --- /dev/null +++ b/utils/vbsp/disp_vbsp.cpp @@ -0,0 +1,675 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#include "disp_vbsp.h" +#include "tier0/dbg.h" +#include "vbsp.h" +#include "mstristrip.h" +#include "writebsp.h" +#include "pacifier.h" +#include "disp_ivp.h" +#include "builddisp.h" +#include "mathlib/vector.h" + +// map displacement info -- runs parallel to the dispinfos struct +int nummapdispinfo = 0; +mapdispinfo_t mapdispinfo[MAX_MAP_DISPINFO]; + +CUtlVector<CCoreDispInfo*> g_CoreDispInfos; + +//----------------------------------------------------------------------------- +// Computes the bounds for a disp info +//----------------------------------------------------------------------------- +void ComputeDispInfoBounds( int dispinfo, Vector& mins, Vector& maxs ) +{ + CDispBox box; + + // Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates. + mapdispinfo_t *pMapDisp = &mapdispinfo[dispinfo]; + + CCoreDispInfo coreDispInfo; + DispMapToCoreDispInfo( pMapDisp, &coreDispInfo, NULL, NULL ); + + GetDispBox( &coreDispInfo, box ); + mins = box.m_Min; + maxs = box.m_Max; +} + +// Gets the barycentric coordinates of the position on the triangle where the lightmap +// coordinates are equal to lmCoords. This always generates the coordinates but it +// returns false if the point containing them does not lie inside the triangle. +bool GetBarycentricCoordsFromLightmapCoords( Vector2D tri[3], Vector2D const &lmCoords, float bcCoords[3] ) +{ + GetBarycentricCoords2D( tri[0], tri[1], tri[2], lmCoords, bcCoords ); + + return + (bcCoords[0] >= 0.0f && bcCoords[0] <= 1.0f) && + (bcCoords[1] >= 0.0f && bcCoords[1] <= 1.0f) && + (bcCoords[2] >= 0.0f && bcCoords[2] <= 1.0f); +} + + +bool FindTriIndexMapByUV( CCoreDispInfo *pCoreDisp, Vector2D const &lmCoords, + int &iTriangle, float flBarycentric[3] ) +{ + const CPowerInfo *pPowerInfo = GetPowerInfo( pCoreDisp->GetPower() ); + + // Search all the triangles.. + int nTriCount= pCoreDisp->GetTriCount(); + for ( int iTri = 0; iTri < nTriCount; ++iTri ) + { + unsigned short iVerts[3]; +// pCoreDisp->GetTriIndices( iTri, iVerts[0], iVerts[1], iVerts[2] ); + CTriInfo *pTri = &pPowerInfo->m_pTriInfos[iTri]; + iVerts[0] = pTri->m_Indices[0]; + iVerts[1] = pTri->m_Indices[1]; + iVerts[2] = pTri->m_Indices[2]; + + // Get this triangle's UVs. + Vector2D vecUV[3]; + for ( int iCoord = 0; iCoord < 3; ++iCoord ) + { + pCoreDisp->GetLuxelCoord( 0, iVerts[iCoord], vecUV[iCoord] ); + } + + // See if the passed-in UVs are in this triangle's UVs. + if( GetBarycentricCoordsFromLightmapCoords( vecUV, lmCoords, flBarycentric ) ) + { + iTriangle = iTri; + return true; + } + } + + return false; +} + + +void CalculateLightmapSamplePositions( CCoreDispInfo *pCoreDispInfo, const dface_t *pFace, CUtlVector<unsigned char> &out ) +{ + int width = pFace->m_LightmapTextureSizeInLuxels[0] + 1; + int height = pFace->m_LightmapTextureSizeInLuxels[1] + 1; + + // For each lightmap sample, find the triangle it sits in. + Vector2D lmCoords; + for( int y=0; y < height; y++ ) + { + lmCoords.y = y + 0.5f; + + for( int x=0; x < width; x++ ) + { + lmCoords.x = x + 0.5f; + + float flBarycentric[3]; + int iTri; + + if( FindTriIndexMapByUV( pCoreDispInfo, lmCoords, iTri, flBarycentric ) ) + { + if( iTri < 255 ) + { + out.AddToTail( iTri ); + } + else + { + out.AddToTail( 255 ); + out.AddToTail( iTri - 255 ); + } + + out.AddToTail( (unsigned char)( flBarycentric[0] * 255.9f ) ); + out.AddToTail( (unsigned char)( flBarycentric[1] * 255.9f ) ); + out.AddToTail( (unsigned char)( flBarycentric[2] * 255.9f ) ); + } + else + { + out.AddToTail( 0 ); + out.AddToTail( 0 ); + out.AddToTail( 0 ); + out.AddToTail( 0 ); + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int GetDispInfoEntityNum( mapdispinfo_t *pDisp ) +{ + return pDisp->entitynum; +} + +// Setup a CCoreDispInfo given a mapdispinfo_t. +// If pFace is non-NULL, then lightmap texture coordinates will be generated. +void DispMapToCoreDispInfo( mapdispinfo_t *pMapDisp, CCoreDispInfo *pCoreDispInfo, dface_t *pFace, int *pSwappedTexInfos ) +{ + winding_t *pWinding = pMapDisp->face.originalface->winding; + + Assert( pWinding->numpoints == 4 ); + + // + // set initial surface data + // + CCoreDispSurface *pSurf = pCoreDispInfo->GetSurface(); + + texinfo_t *pTexInfo = &texinfo[ pMapDisp->face.texinfo ]; + Assert( pTexInfo != NULL ); + + // init material contents + pMapDisp->contents = pMapDisp->face.contents; + if (!(pMapDisp->contents & (ALL_VISIBLE_CONTENTS | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) ) + { + pMapDisp->contents |= CONTENTS_SOLID; + } + + pSurf->SetContents( pMapDisp->contents ); + + // Calculate the lightmap coordinates. + Vector2D tCoords[4] = {Vector2D(0,0),Vector2D(0,1),Vector2D(1,0),Vector2D(1,1)}; + if( pFace ) + { + Assert( pFace->numedges == 4 ); + + Vector pt[4]; + for( int i=0; i < 4; i++ ) + pt[i] = pWinding->p[i]; + + int zeroOffset[2] = {0,0}; + CalcTextureCoordsAtPoints( + pTexInfo->textureVecsTexelsPerWorldUnits, + zeroOffset, + pt, + 4, + tCoords ); + } + + // + // set face point data ... + // + pSurf->SetPointCount( 4 ); + for( int i = 0; i < 4; i++ ) + { + // position + pSurf->SetPoint( i, pWinding->p[i] ); + pSurf->SetTexCoord( i, tCoords[i] ); + } + + // reset surface given start info + pSurf->SetPointStart( pMapDisp->startPosition ); + pSurf->FindSurfPointStartIndex(); + pSurf->AdjustSurfPointData(); + + // Set the luxel coordinates on the base displacement surface. + Vector vecTmp( pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][0], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][1], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); + int nLuxelsPerWorldUnit = static_cast<int>( 1.0f / VectorLength( vecTmp ) ); + Vector vecU( pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][0], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][1], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); + Vector vecV( pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][0], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][1], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][2] ); + bool bSwap = pSurf->CalcLuxelCoords( nLuxelsPerWorldUnit, false, vecU, vecV ); + + // Set the face m_LightmapExtents + if ( pFace ) + { + pFace->m_LightmapTextureSizeInLuxels[0] = pSurf->GetLuxelU(); + pFace->m_LightmapTextureSizeInLuxels[1] = pSurf->GetLuxelV(); + if ( bSwap ) + { + if ( pSwappedTexInfos[ pMapDisp->face.texinfo ] < 0 ) + { + // Create a new texinfo to hold the swapped data. + // We must do this because other surfaces may want the non-swapped data + // This fixes a lighting bug in d2_prison_08 where many non-displacement surfaces + // were pitch black, in addition to bugs in other maps I bet. + + // NOTE: Copy here because adding a texinfo could realloc. + texinfo_t temp = *pTexInfo; + memcpy( temp.lightmapVecsLuxelsPerWorldUnits[0], pTexInfo->lightmapVecsLuxelsPerWorldUnits[1], 4 * sizeof(float) ); + memcpy( temp.lightmapVecsLuxelsPerWorldUnits[1], pTexInfo->lightmapVecsLuxelsPerWorldUnits[0], 4 * sizeof(float) ); + temp.lightmapVecsLuxelsPerWorldUnits[1][0] *= -1.0f; + temp.lightmapVecsLuxelsPerWorldUnits[1][1] *= -1.0f; + temp.lightmapVecsLuxelsPerWorldUnits[1][2] *= -1.0f; + temp.lightmapVecsLuxelsPerWorldUnits[1][3] *= -1.0f; + pSwappedTexInfos[ pMapDisp->face.texinfo ] = texinfo.AddToTail( temp ); + } + pMapDisp->face.texinfo = pSwappedTexInfos[ pMapDisp->face.texinfo ]; + } + + // NOTE: This is here to help future-proof code, since there are codepaths where + // pTexInfo can be made invalid (texinfo.AddToTail above). + pTexInfo = NULL; + } + + // Setup the displacement vectors and offsets. + int size = ( ( ( 1 << pMapDisp->power ) + 1 ) * ( ( 1 << pMapDisp->power ) + 1 ) ); + + Vector vectorDisps[2048]; + float dispDists[2048]; + Assert( size < sizeof(vectorDisps)/sizeof(vectorDisps[0]) ); + + for( int j = 0; j < size; j++ ) + { + Vector v; + float dist; + + VectorScale( pMapDisp->vectorDisps[j], pMapDisp->dispDists[j], v ); + VectorAdd( v, pMapDisp->vectorOffsets[j], v ); + + dist = VectorLength( v ); + VectorNormalize( v ); + + vectorDisps[j] = v; + dispDists[j] = dist; + } + + + // Use CCoreDispInfo to setup the actual vertex positions. + pCoreDispInfo->InitDispInfo( pMapDisp->power, pMapDisp->minTess, pMapDisp->smoothingAngle, + pMapDisp->alphaValues, vectorDisps, dispDists ); + pCoreDispInfo->Create(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void EmitInitialDispInfos( void ) +{ + int i; + mapdispinfo_t *pMapDisp; + ddispinfo_t *pDisp; + Vector v; + + // Calculate the total number of verts. + int nTotalVerts = 0; + int nTotalTris = 0; + for ( i=0; i < nummapdispinfo; i++ ) + { + nTotalVerts += NUM_DISP_POWER_VERTS( mapdispinfo[i].power ); + nTotalTris += NUM_DISP_POWER_TRIS( mapdispinfo[i].power ); + } + + // Clear the output arrays.. + g_dispinfo.Purge(); + g_dispinfo.SetSize( nummapdispinfo ); + g_DispVerts.SetSize( nTotalVerts ); + g_DispTris.SetSize( nTotalTris ); + + int iCurVert = 0; + int iCurTri = 0; + for( i = 0; i < nummapdispinfo; i++ ) + { + pDisp = &g_dispinfo[i]; + pMapDisp = &mapdispinfo[i]; + + CDispVert *pOutVerts = &g_DispVerts[iCurVert]; + CDispTri *pOutTris = &g_DispTris[iCurTri]; + + // Setup the vert pointers. + pDisp->m_iDispVertStart = iCurVert; + pDisp->m_iDispTriStart = iCurTri; + iCurVert += NUM_DISP_POWER_VERTS( pMapDisp->power ); + iCurTri += NUM_DISP_POWER_TRIS( pMapDisp->power ); + + // + // save power, minimum tesselation, and smoothing angle + // + pDisp->power = pMapDisp->power; + + // If the high bit is set - this is FLAGS! + pDisp->minTess = pMapDisp->flags; + pDisp->minTess |= 0x80000000; +// pDisp->minTess = pMapDisp->minTess; + pDisp->smoothingAngle = pMapDisp->smoothingAngle; + pDisp->m_iMapFace = (unsigned short)-2; + + // get surface contents + pDisp->contents = pMapDisp->face.contents; + + pDisp->startPosition = pMapDisp->startPosition; + // + // add up the vectorOffsets and displacements, save alphas (per vertex) + // + int size = ( ( ( 1 << pDisp->power ) + 1 ) * ( ( 1 << pDisp->power ) + 1 ) ); + for( int j = 0; j < size; j++ ) + { + VectorScale( pMapDisp->vectorDisps[j], pMapDisp->dispDists[j], v ); + VectorAdd( v, pMapDisp->vectorOffsets[j], v ); + + float dist = VectorLength( v ); + VectorNormalize( v ); + + VectorCopy( v, pOutVerts[j].m_vVector ); + pOutVerts[j].m_flDist = dist; + + pOutVerts[j].m_flAlpha = pMapDisp->alphaValues[j]; + } + + int nTriCount = ( (1 << (pDisp->power)) * (1 << (pDisp->power)) * 2 ); + for ( int iTri = 0; iTri< nTriCount; ++iTri ) + { + pOutTris[iTri].m_uiTags = pMapDisp->triTags[iTri]; + } + //=================================================================== + //=================================================================== + + // save the index for face data reference + pMapDisp->face.dispinfo = i; + } +} + + +void ExportCoreDispNeighborData( const CCoreDispInfo *pIn, ddispinfo_t *pOut ) +{ + for ( int i=0; i < 4; i++ ) + { + pOut->m_EdgeNeighbors[i] = *pIn->GetEdgeNeighbor( i ); + pOut->m_CornerNeighbors[i] = *pIn->GetCornerNeighbors( i ); + } +} + +void ExportNeighborData( CCoreDispInfo **ppListBase, ddispinfo_t *pBSPDispInfos, int listSize ) +{ + FindNeighboringDispSurfs( ppListBase, listSize ); + + // Export the neighbor data. + for ( int i=0; i < nummapdispinfo; i++ ) + { + ExportCoreDispNeighborData( g_CoreDispInfos[i], &pBSPDispInfos[i] ); + } +} + + +void ExportCoreDispAllowedVertList( const CCoreDispInfo *pIn, ddispinfo_t *pOut ) +{ + ErrorIfNot( + pIn->GetAllowedVerts().GetNumDWords() == sizeof( pOut->m_AllowedVerts ) / 4, + ("ExportCoreDispAllowedVertList: size mismatch") + ); + for ( int i=0; i < pIn->GetAllowedVerts().GetNumDWords(); i++ ) + pOut->m_AllowedVerts[i] = pIn->GetAllowedVerts().GetDWord( i ); +} + + +void ExportAllowedVertLists( CCoreDispInfo **ppListBase, ddispinfo_t *pBSPDispInfos, int listSize ) +{ + SetupAllowedVerts( ppListBase, listSize ); + + for ( int i=0; i < listSize; i++ ) + { + ExportCoreDispAllowedVertList( ppListBase[i], &pBSPDispInfos[i] ); + } +} + +bool FindEnclosingTri( + const Vector2D &vert, + CUtlVector<Vector2D> &vertCoords, + CUtlVector<unsigned short> &indices, + int *pStartVert, + float bcCoords[3] ) +{ + for ( int i=0; i < indices.Count(); i += 3 ) + { + GetBarycentricCoords2D( + vertCoords[indices[i+0]], + vertCoords[indices[i+1]], + vertCoords[indices[i+2]], + vert, + bcCoords ); + + if ( bcCoords[0] >= 0 && bcCoords[0] <= 1 && + bcCoords[1] >= 0 && bcCoords[1] <= 1 && + bcCoords[2] >= 0 && bcCoords[2] <= 1 ) + { + *pStartVert = i; + return true; + } + } + + return false; +} + +void SnapRemainingVertsToSurface( CCoreDispInfo *pCoreDisp, ddispinfo_t *pDispInfo ) +{ + // First, tesselate the displacement. + CUtlVector<unsigned short> indices; + CVBSPTesselateHelper helper; + helper.m_pIndices = &indices; + helper.m_pActiveVerts = pCoreDisp->GetAllowedVerts().Base(); + helper.m_pPowerInfo = pCoreDisp->GetPowerInfo(); + ::TesselateDisplacement( &helper ); + + // Figure out which verts are actually referenced in the tesselation. + CUtlVector<bool> vertsTouched; + vertsTouched.SetSize( pCoreDisp->GetSize() ); + memset( vertsTouched.Base(), 0, sizeof( bool ) * vertsTouched.Count() ); + + for ( int i=0; i < indices.Count(); i++ ) + vertsTouched[ indices[i] ] = true; + + // Generate 2D floating point coordinates for each vertex. We use these to generate + // barycentric coordinates, and the scale doesn't matter. + CUtlVector<Vector2D> vertCoords; + vertCoords.SetSize( pCoreDisp->GetSize() ); + for ( int y=0; y < pCoreDisp->GetHeight(); y++ ) + { + for ( int x=0; x < pCoreDisp->GetWidth(); x++ ) + vertCoords[y*pCoreDisp->GetWidth()+x].Init( x, y ); + } + + // Now, for each vert not touched, snap its position to the main surface. + for ( int y=0; y < pCoreDisp->GetHeight(); y++ ) + { + for ( int x=0; x < pCoreDisp->GetWidth(); x++ ) + { + int index = y * pCoreDisp->GetWidth() + x; + if ( !( vertsTouched[index] ) ) + { + float bcCoords[3]; + int iStartVert = -1; + if ( FindEnclosingTri( vertCoords[index], vertCoords, indices, &iStartVert, bcCoords ) ) + { + const Vector &A = pCoreDisp->GetVert( indices[iStartVert+0] ); + const Vector &B = pCoreDisp->GetVert( indices[iStartVert+1] ); + const Vector &C = pCoreDisp->GetVert( indices[iStartVert+2] ); + Vector vNewPos = A*bcCoords[0] + B*bcCoords[1] + C*bcCoords[2]; + + // This is kind of cheesy, but it gets the job done. Since the CDispVerts store the + // verts relative to some other offset, we'll just offset their position instead + // of setting it directly. + Vector vOffset = vNewPos - pCoreDisp->GetVert( index ); + + // Modify the mapfile vert. + CDispVert *pVert = &g_DispVerts[pDispInfo->m_iDispVertStart + index]; + pVert->m_vVector = (pVert->m_vVector * pVert->m_flDist) + vOffset; + pVert->m_flDist = 1; + + // Modify the CCoreDispInfo vert (although it probably won't be used later). + pCoreDisp->SetVert( index, vNewPos ); + } + else + { + // This shouldn't happen because it would mean that the triangulation that + // disp_tesselation.h produced was missing a chunk of the space that the + // displacement covers. + // It also could indicate a floating-point epsilon error.. check to see if + // FindEnclosingTri finds a triangle that -almost- encloses the vert. + Assert( false ); + } + } + } + } +} + +void SnapRemainingVertsToSurface( CCoreDispInfo **ppListBase, ddispinfo_t *pBSPDispInfos, int listSize ) +{ +//g_pPad = ScratchPad3D_Create(); + for ( int i=0; i < listSize; i++ ) + { + SnapRemainingVertsToSurface( ppListBase[i], &pBSPDispInfos[i] ); + } +} + +void EmitDispLMAlphaAndNeighbors() +{ + int i; + + Msg( "Finding displacement neighbors...\n" ); + + // Build the CCoreDispInfos. + CUtlVector<dface_t*> faces; + + // Create the core dispinfos and init them for use as CDispUtilsHelpers. + for ( int iDisp = 0; iDisp < nummapdispinfo; ++iDisp ) + { + CCoreDispInfo *pDisp = new CCoreDispInfo; + if ( !pDisp ) + { + g_CoreDispInfos.Purge(); + return; + } + + int nIndex = g_CoreDispInfos.AddToTail(); + pDisp->SetListIndex( nIndex ); + g_CoreDispInfos[nIndex] = pDisp; + } + + for ( i=0; i < nummapdispinfo; i++ ) + { + g_CoreDispInfos[i]->SetDispUtilsHelperInfo( g_CoreDispInfos.Base(), nummapdispinfo ); + } + + faces.SetSize( nummapdispinfo ); + + int nMemSize = texinfo.Count() * sizeof(int); + int *pSwappedTexInfos = (int*)stackalloc( nMemSize ); + memset( pSwappedTexInfos, 0xFF, nMemSize ); + for( i = 0; i < numfaces; i++ ) + { + dface_t *pFace = &dfaces[i]; + + if( pFace->dispinfo == -1 ) + continue; + + mapdispinfo_t *pMapDisp = &mapdispinfo[pFace->dispinfo]; + + // Set the displacement's face index. + ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; + pDisp->m_iMapFace = i; + + // Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates. + CCoreDispInfo *pCoreDispInfo = g_CoreDispInfos[pFace->dispinfo]; + DispMapToCoreDispInfo( pMapDisp, pCoreDispInfo, pFace, pSwappedTexInfos ); + + faces[pFace->dispinfo] = pFace; + } + stackfree( pSwappedTexInfos ); + + // Generate and export neighbor data. + ExportNeighborData( g_CoreDispInfos.Base(), g_dispinfo.Base(), nummapdispinfo ); + + // Generate and export the active vert lists. + ExportAllowedVertLists( g_CoreDispInfos.Base(), g_dispinfo.Base(), nummapdispinfo ); + + + // Now that we know which vertices are actually going to be around, snap the ones that won't + // be around onto the slightly-reduced mesh. This is so the engine's ray test code and + // overlay code works right. + SnapRemainingVertsToSurface( g_CoreDispInfos.Base(), g_dispinfo.Base(), nummapdispinfo ); + + Msg( "Finding lightmap sample positions...\n" ); + for ( i=0; i < nummapdispinfo; i++ ) + { + dface_t *pFace = faces[i]; + ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; + CCoreDispInfo *pCoreDispInfo = g_CoreDispInfos[i]; + + pDisp->m_iLightmapSamplePositionStart = g_DispLightmapSamplePositions.Count(); + + CalculateLightmapSamplePositions( pCoreDispInfo, pFace, g_DispLightmapSamplePositions ); + } + + StartPacifier( "Displacement Alpha : "); + + // Build lightmap alphas. + int dispCount = 0; // How many we've processed. + for( i = 0; i < nummapdispinfo; i++ ) + { + dface_t *pFace = faces[i]; + + Assert( pFace->dispinfo == i ); + ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; + + // Allocate space for the alpha values. + pDisp->m_iLightmapAlphaStart = 0; // not used anymore + + ++dispCount; + } + + EndPacifier(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void DispGetFaceInfo( mapbrush_t *pBrush ) +{ + int i; + side_t *pSide; + + // we don't support displacement on entities at the moment!! + if( pBrush->entitynum != 0 ) + { + char* pszEntityName = ValueForKey( &g_LoadingMap->entities[pBrush->entitynum], "classname" ); + Error( "Error: displacement found on a(n) %s entity - not supported (entity %d, brush %d)\n", pszEntityName, pBrush->entitynum, pBrush->brushnum ); + } + + for( i = 0; i < pBrush->numsides; i++ ) + { + pSide = &pBrush->original_sides[i]; + if( pSide->pMapDisp ) + { + // error checking!! + if( pSide->winding->numpoints != 4 ) + Error( "Trying to create a non-quad displacement! (entity %d, brush %d)\n", pBrush->entitynum, pBrush->brushnum ); + pSide->pMapDisp->face.originalface = pSide; + pSide->pMapDisp->face.texinfo = pSide->texinfo; + pSide->pMapDisp->face.dispinfo = -1; + pSide->pMapDisp->face.planenum = pSide->planenum; + pSide->pMapDisp->face.numpoints = pSide->winding->numpoints; + pSide->pMapDisp->face.w = CopyWinding( pSide->winding ); + pSide->pMapDisp->face.contents = pBrush->contents; + + pSide->pMapDisp->face.merged = FALSE; + pSide->pMapDisp->face.split[0] = FALSE; + pSide->pMapDisp->face.split[1] = FALSE; + + pSide->pMapDisp->entitynum = pBrush->entitynum; + pSide->pMapDisp->brushSideID = pSide->id; + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool HasDispInfo( mapbrush_t *pBrush ) +{ + int i; + side_t *pSide; + + for( i = 0; i < pBrush->numsides; i++ ) + { + pSide = &pBrush->original_sides[i]; + if( pSide->pMapDisp ) + return true; + } + + return false; +} diff --git a/utils/vbsp/disp_vbsp.h b/utils/vbsp/disp_vbsp.h new file mode 100644 index 0000000..5af7760 --- /dev/null +++ b/utils/vbsp/disp_vbsp.h @@ -0,0 +1,46 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VBSP_DISPINFO_H +#define VBSP_DISPINFO_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "vbsp.h" + + + +class CCoreDispInfo; + + +extern CUtlVector<CCoreDispInfo*> g_CoreDispInfos; + + +// Setup initial entries in g_dispinfo with some of the vertex data from the mapdisps. +void EmitInitialDispInfos(); + +// Resample vertex alpha into lightmap alpha for displacement surfaces so LOD popping artifacts are +// less noticeable on the mid-to-high end. +// +// Also builds neighbor data. +void EmitDispLMAlphaAndNeighbors(); + +// Setup a CCoreDispInfo given a mapdispinfo_t. +// If pFace is non-NULL, then lightmap texture coordinates will be generated. +void DispMapToCoreDispInfo( mapdispinfo_t *pMapDisp, + CCoreDispInfo *pCoreDispInfo, dface_t *pFace, int *pSwappedTexInfos ); + + +void DispGetFaceInfo( mapbrush_t *pBrush ); +bool HasDispInfo( mapbrush_t *pBrush ); + +// Computes the bounds for a disp info +void ComputeDispInfoBounds( int dispinfo, Vector& mins, Vector& maxs ); + +#endif // VBSP_DISPINFO_H diff --git a/utils/vbsp/faces.cpp b/utils/vbsp/faces.cpp new file mode 100644 index 0000000..f6ec3ee --- /dev/null +++ b/utils/vbsp/faces.cpp @@ -0,0 +1,1810 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// faces.c + +#include "vbsp.h" +#include "utlvector.h" +#include "utilmatlib.h" +#include <float.h> +#include "mstristrip.h" +#include "tier1/strtools.h" +#include "materialpatch.h" +/* + + some faces will be removed before saving, but still form nodes: + + the insides of sky volumes + meeting planes of different water current volumes + +*/ + +// undefine for dumb linear searches +#define USE_HASHING + +#define INTEGRAL_EPSILON 0.01 +#define POINT_EPSILON 0.1 +#define OFF_EPSILON 0.25 + +int c_merge; +int c_subdivide; + +int c_totalverts; +int c_uniqueverts; +int c_degenerate; +int c_tjunctions; +int c_faceoverflows; +int c_facecollapse; +int c_badstartverts; + +#define MAX_SUPERVERTS 512 +int superverts[MAX_SUPERVERTS]; +int numsuperverts; + +face_t *edgefaces[MAX_MAP_EDGES][2]; +int firstmodeledge = 1; +int firstmodelface; + +int c_tryedges; + +Vector edge_dir; +Vector edge_start; +vec_t edge_len; + +int num_edge_verts; +int edge_verts[MAX_MAP_VERTS]; + + +float g_maxLightmapDimension = 32; + + +face_t *NewFaceFromFace (face_t *f); + +// Used to speed up GetEdge2(). Holds a list of edges connected to each vert. +CUtlVector<int> g_VertEdgeList[MAX_MAP_VERTS]; + + +//=========================================================================== + +typedef struct hashvert_s +{ + struct hashvert_s *next; + int num; +} hashvert_t; + +#define HASH_BITS 7 +#define HASH_SIZE (COORD_EXTENT>>HASH_BITS) + + +int vertexchain[MAX_MAP_VERTS]; // the next vertex in a hash chain +int hashverts[HASH_SIZE*HASH_SIZE]; // a vertex number, or 0 for no verts + +//face_t *edgefaces[MAX_MAP_EDGES][2]; + +//============================================================================ + + +unsigned HashVec (Vector& vec) +{ + int x, y; + + x = (MAX_COORD_INTEGER + (int)(vec[0]+0.5)) >> HASH_BITS; + y = (MAX_COORD_INTEGER + (int)(vec[1]+0.5)) >> HASH_BITS; + + if ( x < 0 || x >= HASH_SIZE || y < 0 || y >= HASH_SIZE ) + Error ("HashVec: point outside valid range"); + + return y*HASH_SIZE + x; +} + +#ifdef USE_HASHING +/* +============= +GetVertex + +Uses hashing +============= +*/ +int GetVertexnum (Vector& in) +{ + int h; + int i; + Vector vert; + int vnum; + + c_totalverts++; + + for (i=0 ; i<3 ; i++) + { + if ( fabs(in[i] - (int)(in[i]+0.5)) < INTEGRAL_EPSILON) + vert[i] = (int)(in[i]+0.5); + else + vert[i] = in[i]; + } + + h = HashVec (vert); + + for (vnum=hashverts[h] ; vnum ; vnum=vertexchain[vnum]) + { + Vector& p = dvertexes[vnum].point; + if ( fabs(p[0]-vert[0])<POINT_EPSILON + && fabs(p[1]-vert[1])<POINT_EPSILON + && fabs(p[2]-vert[2])<POINT_EPSILON ) + return vnum; + } + +// emit a vertex + if (numvertexes == MAX_MAP_VERTS) + Error ("Too many unique verts, max = %d (map has too much brush geometry)\n", MAX_MAP_VERTS); + + dvertexes[numvertexes].point[0] = vert[0]; + dvertexes[numvertexes].point[1] = vert[1]; + dvertexes[numvertexes].point[2] = vert[2]; + + vertexchain[numvertexes] = hashverts[h]; + hashverts[h] = numvertexes; + + c_uniqueverts++; + + numvertexes++; + + return numvertexes-1; +} +#else +/* +================== +GetVertexnum + +Dumb linear search +================== +*/ +int GetVertexnum (Vector& v) +{ + int i, j; + dvertex_t *dv; + vec_t d; + + c_totalverts++; + + // make really close values exactly integral + for (i=0 ; i<3 ; i++) + { + if ( fabs(v[i] - (int)(v[i]+0.5)) < INTEGRAL_EPSILON ) + v[i] = (int)(v[i]+0.5); + if (v[i] < MIN_COORD_INTEGER || v[i] > MAX_COORD_INTEGER) + Error ("GetVertexnum: outside world, vertex %.1f %.1f %.1f", v.x, v.y, v.z); + } + + // search for an existing vertex match + for (i=0, dv=dvertexes ; i<numvertexes ; i++, dv++) + { + for (j=0 ; j<3 ; j++) + { + d = v[j] - dv->point[j]; + if ( d > POINT_EPSILON || d < -POINT_EPSILON) + break; + } + if (j == 3) + return i; // a match + } + + // new point + if (numvertexes == MAX_MAP_VERTS) + Error ("Too many unique verts, max = %d (map has too much brush geometry)\n", MAX_MAP_VERTS); + VectorCopy (v, dv->point); + numvertexes++; + c_uniqueverts++; + + return numvertexes-1; +} +#endif + + +/* +================== +FaceFromSuperverts + +The faces vertexes have beeb added to the superverts[] array, +and there may be more there than can be held in a face (MAXEDGES). + +If less, the faces vertexnums[] will be filled in, otherwise +face will reference a tree of split[] faces until all of the +vertexnums can be added. + +superverts[base] will become face->vertexnums[0], and the others +will be circularly filled in. +================== +*/ +void FaceFromSuperverts (face_t **pListHead, face_t *f, int base) +{ + face_t *newf; + int remaining; + int i; + + remaining = numsuperverts; + while (remaining > MAXEDGES) + { // must split into two faces, because of vertex overload + c_faceoverflows++; + + newf = NewFaceFromFace (f); + f->split[0] = newf; + + newf->next = *pListHead; + *pListHead = newf; + + newf->numpoints = MAXEDGES; + for (i=0 ; i<MAXEDGES ; i++) + newf->vertexnums[i] = superverts[(i+base)%numsuperverts]; + + f->split[1] = NewFaceFromFace (f); + f = f->split[1]; + + f->next = *pListHead; + *pListHead = f; + + remaining -= (MAXEDGES-2); + base = (base+MAXEDGES-1)%numsuperverts; + } + + // copy the vertexes back to the face + f->numpoints = remaining; + for (i=0 ; i<remaining ; i++) + f->vertexnums[i] = superverts[(i+base)%numsuperverts]; +} + + +/* +================== +EmitFaceVertexes +================== +*/ +void EmitFaceVertexes (face_t **pListHead, face_t *f) +{ + winding_t *w; + int i; + + if (f->merged || f->split[0] || f->split[1]) + return; + + w = f->w; + for (i=0 ; i<w->numpoints ; i++) + { + if (noweld) + { // make every point unique + if (numvertexes == MAX_MAP_VERTS) + Error ("Too many unique verts, max = %d (map has too much brush geometry)\n", MAX_MAP_VERTS); + superverts[i] = numvertexes; + VectorCopy (w->p[i], dvertexes[numvertexes].point); + numvertexes++; + c_uniqueverts++; + c_totalverts++; + } + else + superverts[i] = GetVertexnum (w->p[i]); + } + numsuperverts = w->numpoints; + + // this may fragment the face if > MAXEDGES + FaceFromSuperverts (pListHead, f, 0); +} + +/* +================== +EmitNodeFaceVertexes_r +================== +*/ +void EmitNodeFaceVertexes_r (node_t *node) +{ + int i; + face_t *f; + + if (node->planenum == PLANENUM_LEAF) + { + // leaf faces are emitted in second pass + return; + } + + for (f=node->faces ; f ; f=f->next) + { + EmitFaceVertexes (&node->faces, f); + } + + for (i=0 ; i<2 ; i++) + { + EmitNodeFaceVertexes_r (node->children[i]); + } +} + +void EmitLeafFaceVertexes( face_t **ppLeafFaceList ) +{ + face_t *f = *ppLeafFaceList; + + while ( f ) + { + EmitFaceVertexes( ppLeafFaceList, f ); + f = f->next; + } +} + + +#ifdef USE_HASHING +/* +========== +FindEdgeVerts + +Uses the hash tables to cut down to a small number +========== +*/ +void FindEdgeVerts (Vector& v1, Vector& v2) +{ + int x1, x2, y1, y2, t; + int x, y; + int vnum; + +#if 0 +{ + int i; + num_edge_verts = numvertexes-1; + for (i=0 ; i<numvertexes-1 ; i++) + edge_verts[i] = i+1; +} +#endif + + x1 = (MAX_COORD_INTEGER + (int)(v1[0]+0.5)) >> HASH_BITS; + y1 = (MAX_COORD_INTEGER + (int)(v1[1]+0.5)) >> HASH_BITS; + x2 = (MAX_COORD_INTEGER + (int)(v2[0]+0.5)) >> HASH_BITS; + y2 = (MAX_COORD_INTEGER + (int)(v2[1]+0.5)) >> HASH_BITS; + + if (x1 > x2) + { + t = x1; + x1 = x2; + x2 = t; + } + if (y1 > y2) + { + t = y1; + y1 = y2; + y2 = t; + } +#if 0 + x1--; + x2++; + y1--; + y2++; + if (x1 < 0) + x1 = 0; + if (x2 >= HASH_SIZE) + x2 = HASH_SIZE; + if (y1 < 0) + y1 = 0; + if (y2 >= HASH_SIZE) + y2 = HASH_SIZE; +#endif + num_edge_verts = 0; + for (x=x1 ; x <= x2 ; x++) + { + for (y=y1 ; y <= y2 ; y++) + { + for (vnum=hashverts[y*HASH_SIZE+x] ; vnum ; vnum=vertexchain[vnum]) + { + edge_verts[num_edge_verts++] = vnum; + } + } + } +} + +#else +/* +========== +FindEdgeVerts + +Forced a dumb check of everything +========== +*/ +void FindEdgeVerts (Vector& v1, Vector& v2) +{ + int i; + + num_edge_verts = numvertexes-1; + for (i=0 ; i<num_edge_verts ; i++) + edge_verts[i] = i+1; +} +#endif + +/* +========== +TestEdge + +Can be recursively reentered +========== +*/ +void TestEdge (vec_t start, vec_t end, int p1, int p2, int startvert) +{ + int j, k; + vec_t dist; + Vector delta; + Vector exact; + Vector off; + vec_t error; + Vector p; + + if (p1 == p2) + { + c_degenerate++; + return; // degenerate edge + } + + for (k=startvert ; k<num_edge_verts ; k++) + { + j = edge_verts[k]; + if (j==p1 || j == p2) + continue; + + VectorCopy (dvertexes[j].point, p); + + VectorSubtract (p, edge_start, delta); + dist = DotProduct (delta, edge_dir); + if (dist <=start || dist >= end) + continue; // off an end + VectorMA (edge_start, dist, edge_dir, exact); + VectorSubtract (p, exact, off); + error = off.Length(); + + if (error > OFF_EPSILON) + continue; // not on the edge + + // break the edge + c_tjunctions++; + TestEdge (start, dist, p1, j, k+1); + TestEdge (dist, end, j, p2, k+1); + return; + } + + // the edge p1 to p2 is now free of tjunctions + if (numsuperverts >= MAX_SUPERVERTS) + Error ("Edge with too many vertices due to t-junctions. Max %d verts along an edge!\n", MAX_SUPERVERTS); + superverts[numsuperverts] = p1; + numsuperverts++; +} + + +// stores the edges that each vert is part of +struct face_vert_table_t +{ + face_vert_table_t() + { + edge0 = -1; + edge1 = -1; + } + + void AddEdge( int edge ) + { + if ( edge0 == -1 ) + { + edge0 = edge; + } + else + { + // can only have two edges + Assert(edge1==-1); + edge1 = edge; + } + } + + bool HasEdge( int edge ) const + { + if ( edge >= 0 ) + { + if ( edge0 == edge || edge1 == edge ) + return true; + } + return false; + } + + int edge0; + int edge1; +}; + +// if these two verts share an edge, they must be collinear +bool IsDiagonal( const face_vert_table_t &v0, const face_vert_table_t &v1 ) +{ + if ( v1.HasEdge(v0.edge0) || v1.HasEdge(v0.edge1) ) + return false; + + return true; +} + + +void Triangulate_r( CUtlVector<int> &out, const CUtlVector<int> &inIndices, const CUtlVector<face_vert_table_t> &poly ) +{ + Assert( inIndices.Count() > 2 ); + + // one triangle left, return + if ( inIndices.Count() == 3 ) + { + for ( int i = 0; i < inIndices.Count(); i++ ) + { + out.AddToTail( inIndices[i] ); + } + return; + } + + // check each pair of verts and see if they are diagonal (not on a shared edge) + // if so, split & recurse + for ( int i = 0; i < inIndices.Count(); i++ ) + { + int count = inIndices.Count(); + + // i + count is myself, i + count-1 is previous, so we need to stop at i+count-2 + for ( int j = 2; j < count-1; j++ ) + { + // if these two form a diagonal, split the poly along + // the diagonal and triangulate the two sub-polys + int index = inIndices[i]; + int nextArray = (i+j)%count; + int nextIndex = inIndices[nextArray]; + if ( IsDiagonal(poly[index], poly[nextIndex]) ) + { + // add the poly up to the diagonal + CUtlVector<int> in1; + for ( int k = i; k != nextArray; k = (k+1)%count ) + { + in1.AddToTail(inIndices[k]); + } + in1.AddToTail(nextIndex); + + // add the rest of the poly starting with the diagonal + CUtlVector<int> in2; + in2.AddToTail(index); + for ( int l = nextArray; l != i; l = (l+1)%count ) + { + in2.AddToTail(inIndices[l]); + } + + // triangulate the sub-polys + Triangulate_r( out, in1, poly ); + Triangulate_r( out, in2, poly ); + return; + } + } + } + + // didn't find a diagonal + Assert(0); +} + +/* +================== +FixFaceEdges + +================== +*/ +void FixFaceEdges (face_t **pList, face_t *f) +{ + int p1, p2; + int i; + Vector e2; + vec_t len; + int count[MAX_SUPERVERTS], start[MAX_SUPERVERTS]; + int base; + + if (f->merged || f->split[0] || f->split[1]) + return; + + numsuperverts = 0; + + int originalPoints = f->numpoints; + for (i=0 ; i<f->numpoints ; i++) + { + p1 = f->vertexnums[i]; + p2 = f->vertexnums[(i+1)%f->numpoints]; + + VectorCopy (dvertexes[p1].point, edge_start); + VectorCopy (dvertexes[p2].point, e2); + + FindEdgeVerts (edge_start, e2); + + VectorSubtract (e2, edge_start, edge_dir); + len = VectorNormalize (edge_dir); + + start[i] = numsuperverts; + TestEdge (0, len, p1, p2, 0); + + count[i] = numsuperverts - start[i]; + } + + if (numsuperverts < 3) + { // entire face collapsed + f->numpoints = 0; + c_facecollapse++; + return; + } + + // we want to pick a vertex that doesn't have tjunctions + // on either side, which can cause artifacts on trifans, + // especially underwater + for (i=0 ; i<f->numpoints ; i++) + { + if (count[i] == 1 && count[(i+f->numpoints-1)%f->numpoints] == 1) + break; + } + if (i == f->numpoints) + { + f->badstartvert = true; + c_badstartverts++; + base = 0; + + } + else + { // rotate the vertex order + base = start[i]; + } + + // this may fragment the face if > MAXEDGES + FaceFromSuperverts (pList, f, base); + + // if this is the world, then re-triangulate to sew cracks + if ( f->badstartvert && entity_num == 0 ) + { + CUtlVector<face_vert_table_t> poly; + CUtlVector<int> inIndices; + CUtlVector<int> outIndices; + poly.AddMultipleToTail( numsuperverts ); + for ( i = 0; i < originalPoints; i++ ) + { + // edge may not have output any points. Don't mark + if ( !count[i] ) + continue; + // mark each edge the point is a member of + // we'll use this as a fast "is collinear" test + for ( int j = 0; j <= count[i]; j++ ) + { + int polyIndex = (start[i] + j) % numsuperverts; + poly[polyIndex].AddEdge( i ); + } + } + for ( i = 0; i < numsuperverts; i++ ) + { + inIndices.AddToTail( i ); + } + Triangulate_r( outIndices, inIndices, poly ); + dprimitive_t &newPrim = g_primitives[g_numprimitives]; + f->firstPrimID = g_numprimitives; + g_numprimitives++; + f->numPrims = 1; + newPrim.firstIndex = g_numprimindices; + newPrim.firstVert = g_numprimverts; + newPrim.indexCount = outIndices.Count(); + newPrim.vertCount = 0; + newPrim.type = PRIM_TRILIST; + g_numprimindices += newPrim.indexCount; + if ( g_numprimitives > MAX_MAP_PRIMITIVES || g_numprimindices > MAX_MAP_PRIMINDICES ) + { + Error("Too many t-junctions to fix up! (%d prims, max %d :: %d indices, max %d)\n", g_numprimitives, MAX_MAP_PRIMITIVES, g_numprimindices, MAX_MAP_PRIMINDICES ); + } + for ( i = 0; i < outIndices.Count(); i++ ) + { + g_primindices[newPrim.firstIndex + i] = outIndices[i]; + } + } +} + +/* +================== +FixEdges_r +================== +*/ +void FixEdges_r (node_t *node) +{ + int i; + face_t *f; + + if (node->planenum == PLANENUM_LEAF) + { + return; + } + + for (f=node->faces ; f ; f=f->next) + FixFaceEdges (&node->faces, f); + + for (i=0 ; i<2 ; i++) + FixEdges_r (node->children[i]); +} + + +//----------------------------------------------------------------------------- +// Purpose: Fix the t-junctions on detail faces +//----------------------------------------------------------------------------- +void FixLeafFaceEdges( face_t **ppLeafFaceList ) +{ + face_t *f; + + for ( f = *ppLeafFaceList; f; f = f->next ) + { + FixFaceEdges( ppLeafFaceList, f ); + } +} + +/* +=========== +FixTjuncs + +=========== +*/ + +face_t *FixTjuncs (node_t *headnode, face_t *pLeafFaceList) +{ + // snap and merge all vertexes + qprintf ("---- snap verts ----\n"); + memset (hashverts, 0, sizeof(hashverts)); + memset (vertexchain, 0, sizeof(vertexchain)); + c_totalverts = 0; + c_uniqueverts = 0; + c_faceoverflows = 0; + EmitNodeFaceVertexes_r (headnode); + + // UNDONE: This count is wrong with tjuncs off on details - since + + // break edges on tjunctions + qprintf ("---- tjunc ----\n"); + c_tryedges = 0; + c_degenerate = 0; + c_facecollapse = 0; + c_tjunctions = 0; + + if ( g_bAllowDetailCracks ) + { + FixEdges_r (headnode); + EmitLeafFaceVertexes( &pLeafFaceList ); + FixLeafFaceEdges( &pLeafFaceList ); + } + else + { + EmitLeafFaceVertexes( &pLeafFaceList ); + if (!notjunc) + { + FixEdges_r (headnode); + FixLeafFaceEdges( &pLeafFaceList ); + } + } + + + qprintf ("%i unique from %i\n", c_uniqueverts, c_totalverts); + qprintf ("%5i edges degenerated\n", c_degenerate); + qprintf ("%5i faces degenerated\n", c_facecollapse); + qprintf ("%5i edges added by tjunctions\n", c_tjunctions); + qprintf ("%5i faces added by tjunctions\n", c_faceoverflows); + qprintf ("%5i bad start verts\n", c_badstartverts); + + return pLeafFaceList; +} + + +//======================================================== + +int c_faces; + +face_t *AllocFace (void) +{ + static int s_FaceId = 0; + + face_t *f; + + f = (face_t*)malloc(sizeof(*f)); + memset (f, 0, sizeof(*f)); + f->id = s_FaceId; + ++s_FaceId; + + c_faces++; + + return f; +} + +face_t *NewFaceFromFace (face_t *f) +{ + face_t *newf; + + newf = AllocFace (); + *newf = *f; + newf->merged = NULL; + newf->split[0] = newf->split[1] = NULL; + newf->w = NULL; + return newf; +} + +void FreeFace (face_t *f) +{ + if (f->w) + FreeWinding (f->w); + free (f); + c_faces--; +} + + +void FreeFaceList( face_t *pFaces ) +{ + while ( pFaces ) + { + face_t *next = pFaces->next; + + FreeFace( pFaces ); + pFaces = next; + } +} + +//======================================================== + +void GetEdge2_InitOptimizedList() +{ + for( int i=0; i < MAX_MAP_VERTS; i++ ) + g_VertEdgeList[i].RemoveAll(); +} + + +void IntSort( CUtlVector<int> &theList ) +{ + for( int i=0; i < theList.Size()-1; i++ ) + { + if( theList[i] > theList[i+1] ) + { + int temp = theList[i]; + theList[i] = theList[i+1]; + theList[i+1] = temp; + if( i > 0 ) + i -= 2; + else + i = -1; + } + } +} + + +int AddEdge( int v1, int v2, face_t *f ) +{ + if (numedges >= MAX_MAP_EDGES) + Error ("Too many edges in map, max == %d", MAX_MAP_EDGES); + + g_VertEdgeList[v1].AddToTail( numedges ); + g_VertEdgeList[v2].AddToTail( numedges ); + IntSort( g_VertEdgeList[v1] ); + IntSort( g_VertEdgeList[v2] ); + + dedge_t *edge = &dedges[numedges]; + numedges++; + + edge->v[0] = v1; + edge->v[1] = v2; + edgefaces[numedges-1][0] = f; + return numedges - 1; +} + + +/* +================== +GetEdge + +Called by writebsp. +Don't allow four way edges +================== +*/ +int GetEdge2 (int v1, int v2, face_t *f) +{ + dedge_t *edge; + + c_tryedges++; + + if (!noshare) + { + // Check all edges connected to v1. + CUtlVector<int> &theList = g_VertEdgeList[v1]; + for( int i=0; i < theList.Size(); i++ ) + { + int iEdge = theList[i]; + edge = &dedges[iEdge]; + if (v1 == edge->v[1] && v2 == edge->v[0] && edgefaces[iEdge][0]->contents == f->contents) + { + if (edgefaces[iEdge][1]) + continue; + + edgefaces[iEdge][1] = f; + return -iEdge; + } + } + } + + return AddEdge( v1, v2, f ); +} + +/* +=========================================================================== + +FACE MERGING + +=========================================================================== +*/ + +#define CONTINUOUS_EPSILON 0.001 + +/* +============= +TryMergeWinding + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the faces couldn't be merged, or the new face. +The originals will NOT be freed. +============= +*/ +winding_t *TryMergeWinding (winding_t *f1, winding_t *f2, Vector& planenormal) +{ + Vector *p1, *p2, *p3, *p4, *back; + winding_t *newf; + int i, j, k, l; + Vector normal, delta; + vec_t dot; + qboolean keep1, keep2; + + + // + // find a common edge + // + p1 = p2 = NULL; // stop compiler warning + j = 0; // + + for (i=0 ; i<f1->numpoints ; i++) + { + p1 = &f1->p[i]; + p2 = &f1->p[(i+1)%f1->numpoints]; + for (j=0 ; j<f2->numpoints ; j++) + { + p3 = &f2->p[j]; + p4 = &f2->p[(j+1)%f2->numpoints]; + for (k=0 ; k<3 ; k++) + { + if (fabs((*p1)[k] - (*p4)[k]) > EQUAL_EPSILON) + break; + if (fabs((*p2)[k] - (*p3)[k]) > EQUAL_EPSILON) + break; + } + if (k==3) + break; + } + if (j < f2->numpoints) + break; + } + + if (i == f1->numpoints) + return NULL; // no matching edges + + // + // check slope of connected lines + // if the slopes are colinear, the point can be removed + // + back = &f1->p[(i+f1->numpoints-1)%f1->numpoints]; + VectorSubtract (*p1, *back, delta); + CrossProduct (planenormal, delta, normal); + VectorNormalize (normal); + + back = &f2->p[(j+2)%f2->numpoints]; + VectorSubtract (*back, *p1, delta); + dot = DotProduct (delta, normal); + if (dot > CONTINUOUS_EPSILON) + return NULL; // not a convex polygon + keep1 = (qboolean)(dot < -CONTINUOUS_EPSILON); + + back = &f1->p[(i+2)%f1->numpoints]; + VectorSubtract (*back, *p2, delta); + CrossProduct (planenormal, delta, normal); + VectorNormalize (normal); + + back = &f2->p[(j+f2->numpoints-1)%f2->numpoints]; + VectorSubtract (*back, *p2, delta); + dot = DotProduct (delta, normal); + if (dot > CONTINUOUS_EPSILON) + return NULL; // not a convex polygon + keep2 = (qboolean)(dot < -CONTINUOUS_EPSILON); + + // + // build the new polygon + // + newf = AllocWinding (f1->numpoints + f2->numpoints); + + // copy first polygon + for (k=(i+1)%f1->numpoints ; k != i ; k=(k+1)%f1->numpoints) + { + if (k==(i+1)%f1->numpoints && !keep2) + continue; + + VectorCopy (f1->p[k], newf->p[newf->numpoints]); + newf->numpoints++; + } + + // copy second polygon + for (l= (j+1)%f2->numpoints ; l != j ; l=(l+1)%f2->numpoints) + { + if (l==(j+1)%f2->numpoints && !keep1) + continue; + VectorCopy (f2->p[l], newf->p[newf->numpoints]); + newf->numpoints++; + } + + return newf; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool OverlaysAreEqual( face_t *f1, face_t *f2 ) +{ + // Check the overlay ids - see if they are the same. + if ( f1->originalface->aOverlayIds.Count() != f2->originalface->aOverlayIds.Count() ) + return false; + + int nOverlayCount = f1->originalface->aOverlayIds.Count(); + for ( int iOverlay = 0; iOverlay < nOverlayCount; ++iOverlay ) + { + int nOverlayId = f1->originalface->aOverlayIds[iOverlay]; + if ( f2->originalface->aOverlayIds.Find( nOverlayId ) == -1 ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool FaceOnWaterBrush( face_t *face ) +{ + side_t *pSide = face->originalface; + if ( !pSide ) + return false; + + if ( pSide->contents & ( CONTENTS_WATER | CONTENTS_SLIME ) ) + return true; + + return false; +} + +/* +============= +TryMerge + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the faces couldn't be merged, or the new face. +The originals will NOT be freed. +============= +*/ +face_t *TryMerge (face_t *f1, face_t *f2, Vector& planenormal) +{ + face_t *newf; + winding_t *nw; + + if (!f1->w || !f2->w) + return NULL; + if (f1->texinfo != f2->texinfo) + return NULL; + if (f1->planenum != f2->planenum) // on front and back sides + return NULL; + if (f1->contents != f2->contents) + return NULL; + if ( f1->originalface->smoothingGroups != f2->originalface->smoothingGroups ) + return NULL; + if ( !OverlaysAreEqual( f1, f2 ) ) + return NULL; + if ( nomergewater && ( FaceOnWaterBrush( f1 ) || FaceOnWaterBrush( f2 ) ) ) + return NULL; + + nw = TryMergeWinding (f1->w, f2->w, planenormal); + if (!nw) + return NULL; + + c_merge++; + newf = NewFaceFromFace (f1); + newf->w = nw; + + f1->merged = newf; + f2->merged = newf; + + return newf; +} + +/* +=============== +MergeFaceList +=============== +*/ +void MergeFaceList(face_t **pList) +{ + face_t *f1, *f2, *end; + face_t *merged; + plane_t *plane; + + merged = NULL; + + for (f1 = *pList; f1 ; f1 = f1->next) + { + if (f1->merged || f1->split[0] || f1->split[1]) + continue; + for (f2 = *pList; f2 != f1 ; f2=f2->next) + { + if (f2->merged || f2->split[0] || f2->split[1]) + continue; + + plane = &g_MainMap->mapplanes[f1->planenum]; + merged = TryMerge (f1, f2, plane->normal); + if (!merged) + continue; + + // add merged to the end of the face list + // so it will be checked against all the faces again + for (end = *pList; end->next ; end = end->next) + ; + merged->next = NULL; + end->next = merged; + break; + } + } +} + +//===================================================================== + +/* +=============== +SubdivideFace + +Chop up faces that are larger than we want in the surface cache +=============== +*/ +void SubdivideFace (face_t **pFaceList, face_t *f) +{ + float mins, maxs; + vec_t v; + vec_t luxelsPerWorldUnit; + int axis, i; + texinfo_t *tex; + Vector temp; + vec_t dist; + winding_t *w, *frontw, *backw; + + if ( f->merged || f->split[0] || f->split[1] ) + return; + +// special (non-surface cached) faces don't need subdivision + tex = &texinfo[f->texinfo]; + + if( tex->flags & SURF_NOLIGHT ) + { + return; + } + + for (axis = 0 ; axis < 2 ; axis++) + { + while (1) + { + mins = 999999; + maxs = -999999; + + VECTOR_COPY (tex->lightmapVecsLuxelsPerWorldUnits[axis], temp); + w = f->w; + for (i=0 ; i<w->numpoints ; i++) + { + v = DotProduct (w->p[i], temp); + if (v < mins) + mins = v; + if (v > maxs) + maxs = v; + } +#if 0 + if (maxs - mins <= 0) + Error ("zero extents"); +#endif + if (maxs - mins <= g_maxLightmapDimension) + break; + + // split it + c_subdivide++; + + luxelsPerWorldUnit = VectorNormalize (temp); + + dist = ( mins + g_maxLightmapDimension - 1 ) / luxelsPerWorldUnit; + + ClipWindingEpsilon (w, temp, dist, ON_EPSILON, &frontw, &backw); + if (!frontw || !backw) + Error ("SubdivideFace: didn't split the polygon"); + + f->split[0] = NewFaceFromFace (f); + f->split[0]->w = frontw; + f->split[0]->next = *pFaceList; + *pFaceList = f->split[0]; + + f->split[1] = NewFaceFromFace (f); + f->split[1]->w = backw; + f->split[1]->next = *pFaceList; + *pFaceList = f->split[1]; + + SubdivideFace (pFaceList, f->split[0]); + SubdivideFace (pFaceList, f->split[1]); + return; + } + } +} + +void SubdivideFaceList(face_t **pFaceList) +{ + face_t *f; + + for (f = *pFaceList ; f ; f=f->next) + { + SubdivideFace (pFaceList, f); + } +} + + +//----------------------------------------------------------------------------- +// Assigns the bottom material to the bottom face +//----------------------------------------------------------------------------- +static bool AssignBottomWaterMaterialToFace( face_t *f ) +{ + // NOTE: This happens *after* cubemap fixup occurs, so we need to get the + // fixed-up bottom material for this + texinfo_t *pTexInfo = &texinfo[f->texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + + char pBottomMatName[512]; + if ( !GetValueFromPatchedMaterial( pMaterialName, "$bottommaterial", pBottomMatName, 512 ) ) + { + if( !Q_stristr( pMaterialName, "nodraw" ) && !Q_stristr( pMaterialName, "toolsskip" ) ) + { + Warning("error: material %s doesn't have a $bottommaterial\n", pMaterialName ); + } + return false; + } + + //Assert( mapplanes[f->planenum].normal.z < 0 ); + texinfo_t newTexInfo; + newTexInfo.flags = pTexInfo->flags; + int j, k; + for (j=0 ; j<2 ; j++) + { + for (k=0 ; k<4 ; k++) + { + newTexInfo.textureVecsTexelsPerWorldUnits[j][k] = pTexInfo->textureVecsTexelsPerWorldUnits[j][k]; + newTexInfo.lightmapVecsLuxelsPerWorldUnits[j][k] = pTexInfo->lightmapVecsLuxelsPerWorldUnits[j][k]; + } + } + newTexInfo.texdata = FindOrCreateTexData( pBottomMatName ); + f->texinfo = FindOrCreateTexInfo( newTexInfo ); + + return true; +} + + +//=========================================================================== + +int c_nodefaces; + +static void SubdivideFaceBySubdivSize( face_t *f, float subdivsize ); +void SubdivideFaceBySubdivSize( face_t *f ); + +/* +============ +FaceFromPortal + +============ +*/ +extern int FindOrCreateTexInfo( const texinfo_t &searchTexInfo ); + +face_t *FaceFromPortal (portal_t *p, int pside) +{ + face_t *f; + side_t *side; + int deltaContents; + + // portal does not bridge different visible contents + side = p->side; + if (!side) + return NULL; + + // allocate a new face + f = AllocFace(); + + // save the original "side" from the map brush -- portal->side + // see FindPortalSide(...) + f->originalface = side; + + // + // save material info + // + f->texinfo = side->texinfo; + f->dispinfo = -1; // all faces with displacement info are created elsewhere + f->smoothingGroups = side->smoothingGroups; + + // save plane info + f->planenum = (side->planenum & ~1) | pside; + if ( entity_num != 0 ) + { + // the brush model renderer doesn't use PLANEBACK, so write the real plane + // inside water faces can be flipped because they are generated on the inside of the brush + if ( p->nodes[pside]->contents & (CONTENTS_WATER|CONTENTS_SLIME) ) + { + f->planenum = (side->planenum & ~1) | pside; + } + else + { + f->planenum = side->planenum; + } + } + + // save portal info + f->portal = p; + f->fogVolumeLeaf = NULL; + + deltaContents = VisibleContents(p->nodes[!pside]->contents^p->nodes[pside]->contents); + + // don't show insides of windows or grates + if ( ((p->nodes[pside]->contents & CONTENTS_WINDOW) && deltaContents == CONTENTS_WINDOW) || + ((p->nodes[pside]->contents & CONTENTS_GRATE) && deltaContents == CONTENTS_GRATE) ) + { + FreeFace( f ); + return NULL; + } + + if ( p->nodes[pside]->contents & MASK_WATER ) + { + f->fogVolumeLeaf = p->nodes[pside]; + } + else if ( p->nodes[!pside]->contents & MASK_WATER ) + { + f->fogVolumeLeaf = p->nodes[!pside]; + } + + // If it's the underside of water, we need to figure out what material to use, etc. + if( ( p->nodes[pside]->contents & CONTENTS_WATER ) && deltaContents == CONTENTS_WATER ) + { + if ( !AssignBottomWaterMaterialToFace( f ) ) + { + FreeFace( f ); + return NULL; + } + } + + // + // generate the winding for the face and save face contents + // + if( pside ) + { + f->w = ReverseWinding(p->winding); + f->contents = p->nodes[1]->contents; + } + else + { + f->w = CopyWinding(p->winding); + f->contents = p->nodes[0]->contents; + } + + f->numPrims = 0; + f->firstPrimID = 0; + + // return the created face + return f; +} + +/* +=============== +MakeFaces_r + +If a portal will make a visible face, +mark the side that originally created it + + solid / empty : solid + solid / water : solid + water / empty : water + water / water : none +=============== +*/ +void MakeFaces_r (node_t *node) +{ + portal_t *p; + int s; + + // recurse down to leafs + if (node->planenum != PLANENUM_LEAF) + { + MakeFaces_r (node->children[0]); + MakeFaces_r (node->children[1]); + + // merge together all visible faces on the node + if (!nomerge) + MergeFaceList(&node->faces); + if (!nosubdiv) + SubdivideFaceList(&node->faces); + + return; + } + + // solid leafs never have visible faces + if (node->contents & CONTENTS_SOLID) + return; + + // see which portals are valid + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + + p->face[s] = FaceFromPortal (p, s); + if (p->face[s]) + { + c_nodefaces++; + p->face[s]->next = p->onnode->faces; + p->onnode->faces = p->face[s]; + } + } +} + +typedef winding_t *pwinding_t; + +static void PrintWinding( winding_t *w ) +{ + int i; + Msg( "\t---\n" ); + for( i = 0; i < w->numpoints; i++ ) + { + Msg( "\t%f %f %f\n", w->p[i].x, w->p[i].y, w->p[i].z ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a winding to the current list of primverts +// Input : *w - the winding +// *pIndices - The output indices +// vertStart - the starting vert index +// vertCount - current count +// Output : int - output count including new verts from this winding +//----------------------------------------------------------------------------- +int AddWindingToPrimverts( const winding_t *w, unsigned short *pIndices, int vertStart, int vertCount ) +{ + for( int i = 0; i < w->numpoints; i++ ) + { + int j; + for( j = vertStart; j < vertStart + vertCount; j++ ) + { + Vector tmp = g_primverts[j].pos - w->p[i]; + + if( tmp.LengthSqr() < POINT_EPSILON*POINT_EPSILON ) + { + pIndices[i] = j; + break; + } + } + if ( j >= vertStart + vertCount ) + { + pIndices[i] = j; + g_primverts[j].pos = w->p[i]; + vertCount++; + g_numprimverts++; + if ( g_numprimverts > MAX_MAP_PRIMVERTS ) + { + Error( "Exceeded max water verts.\nIncrease surface subdivision size or lower your subdivision size in vmt files! (%d>%d)\n", + ( int )g_numprimverts, ( int )MAX_MAP_PRIMVERTS ); + } + } + } + + return vertCount; +} + + + +#pragma optimize( "g", off ) +#define USE_TRISTRIPS + +// UNDONE: Should split this function into subdivide and primitive building parts +// UNDONE: We should try building strips of shared verts for all water faces in a leaf +// since those will be drawn concurrently anyway. It should be more efficient. +static void SubdivideFaceBySubdivSize( face_t *f, float subdivsize ) +{ + // garymcthack - REFACTOR ME!!! + + vec_t dummy; + Vector hackNormal; + WindingPlane( f->w, hackNormal, &dummy ); + + // HACK - only subdivide stuff that is facing up or down (for water) + if( fabs(hackNormal[2]) < .9f ) + { + return; + } + + // Get the extents of the surface. + // garymcthack - this assumes a surface of constant z for now (for water). . can generalize later. + subdivsize = ( int )subdivsize; + winding_t *w; + w = CopyWinding( f->w ); + + Vector min, max; + WindingBounds( w, min, max ); + +#if 0 + Msg( "START WINDING: \n" ); + PrintWinding( w ); +#endif + int xStart, yStart, xEnd, yEnd, xSteps, ySteps; + xStart = ( int )subdivsize * ( int )( ( min[0] - subdivsize ) / subdivsize ); + xEnd = ( int )subdivsize * ( int )( ( max[0] + subdivsize ) / subdivsize ); + yStart = ( int )subdivsize * ( int )( ( min[1] - subdivsize ) / subdivsize ); + yEnd = ( int )subdivsize * ( int )( ( max[1] + subdivsize ) / subdivsize ); + xSteps = ( xEnd - xStart ) / subdivsize; + ySteps = ( yEnd - yStart ) / subdivsize; + int x, y; + int xi, yi; + winding_t **windings = ( winding_t ** )new pwinding_t[xSteps * ySteps]; + memset( windings, 0, sizeof( winding_t * ) * xSteps * ySteps ); + + for( yi = 0, y = yStart; y < yEnd; y += ( int )subdivsize, yi++ ) + { + for( xi = 0, x = xStart; x < xEnd; x += ( int )subdivsize, xi++ ) + { + winding_t *tempWinding, *frontWinding, *backWinding; + float planeDist; + Vector normal; + normal.Init( 1.0f, 0.0f, 0.0f ); + planeDist = ( float )x; + tempWinding = CopyWinding( w ); + ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, + &frontWinding, &backWinding ); + if( tempWinding ) + { + FreeWinding( tempWinding ); + } + if( backWinding ) + { + FreeWinding( backWinding ); + } + if( !frontWinding ) + { + continue; + } + tempWinding = frontWinding; + + normal.Init( -1.0f, 0.0f, 0.0f ); + planeDist = -( float )( x + subdivsize ); + ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, + &frontWinding, &backWinding ); + if( tempWinding ) + { + FreeWinding( tempWinding ); + } + if( backWinding ) + { + FreeWinding( backWinding ); + } + if( !frontWinding ) + { + continue; + } + tempWinding = frontWinding; + + normal.Init( 0.0f, 1.0f, 0.0f ); + planeDist = ( float )y; + ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, + &frontWinding, &backWinding ); + if( tempWinding ) + { + FreeWinding( tempWinding ); + } + if( backWinding ) + { + FreeWinding( backWinding ); + } + if( !frontWinding ) + { + continue; + } + tempWinding = frontWinding; + + normal.Init( 0.0f, -1.0f, 0.0f ); + planeDist = -( float )( y + subdivsize ); + ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, + &frontWinding, &backWinding ); + if( tempWinding ) + { + FreeWinding( tempWinding ); + } + if( backWinding ) + { + FreeWinding( backWinding ); + } + if( !frontWinding ) + { + continue; + } + +#if 0 + Msg( "output winding:\n" ); + PrintWinding( frontWinding ); +#endif + + if( frontWinding ) + { + windings[xi + yi * xSteps] = frontWinding; + } + } + } + FreeWinding( w ); + dprimitive_t &newPrim = g_primitives[g_numprimitives]; + f->firstPrimID = g_numprimitives; + f->numPrims = 1; + newPrim.firstIndex = g_numprimindices; + newPrim.firstVert = g_numprimverts; + newPrim.indexCount = 0; + newPrim.vertCount = 0; +#ifdef USE_TRISTRIPS + newPrim.type = PRIM_TRISTRIP; +#else + newPrim.type = PRIM_TRILIST; +#endif + + CUtlVector<WORD> triListIndices; + int i; + for( i = 0; i < xSteps * ySteps; i++ ) + { + if( !windings[i] ) + { + continue; + } + unsigned short *pIndices = + ( unsigned short * )_alloca( windings[i]->numpoints * sizeof( unsigned short ) ); + // find indices for the verts. + newPrim.vertCount = AddWindingToPrimverts( windings[i], pIndices, newPrim.firstVert, newPrim.vertCount ); + + // Now that we have indices for the verts, fan-tesselate the polygon and spit out tris. + for( int j = 0; j < windings[i]->numpoints - 2; j++ ) + { + triListIndices.AddToTail( pIndices[0] ); + triListIndices.AddToTail( pIndices[j+1] ); + triListIndices.AddToTail( pIndices[j+2] ); + } + } + + delete [] windings; + // We've already updated the verts and have a trilist. . let's strip it! + if( !triListIndices.Size() ) + { + return; + } + +#ifdef USE_TRISTRIPS + int numTristripIndices; + WORD *pStripIndices = NULL; + Stripify( triListIndices.Size() / 3, triListIndices.Base(), &numTristripIndices, + &pStripIndices ); + Assert( pStripIndices ); + + // FIXME: Should also call ComputeVertexPermutation and reorder the verts. + + for( i = 0; i < numTristripIndices; i++ ) + { + Assert( pStripIndices[i] >= newPrim.firstVert && + pStripIndices[i] < newPrim.firstVert + newPrim.vertCount ); + g_primindices[newPrim.firstIndex + newPrim.indexCount] = pStripIndices[i]; + newPrim.indexCount++; + g_numprimindices++; + if( g_numprimindices > MAX_MAP_PRIMINDICES ) + { + Error( "Exceeded max water indicies.\nIncrease surface subdivision size! (%d>%d)\n", g_numprimindices, MAX_MAP_PRIMINDICES ); + } + } + delete [] pStripIndices; +#else + for( i = 0; i < triListIndices.Size(); i++ ) + { + g_primindices[newPrim.firstIndex + newPrim.indexCount] = triListIndices[i]; + newPrim.indexCount++; + g_numprimindices++; + if( g_numprimindices > MAX_MAP_PRIMINDICES ) + { + Error( "Exceeded max water indicies.\nIncrease surface subdivision size! (%d>%d)\n", g_numprimindices, MAX_MAP_PRIMINDICES ); + } + } +#endif + g_numprimitives++; // don't increment until we get here and are sure that we have a primitive. + if( g_numprimitives > MAX_MAP_PRIMITIVES ) + { + Error( "Exceeded max water primitives.\nIncrease surface subdivision size! (%d>%d)\n", ( int )g_numprimitives, ( int )MAX_MAP_PRIMITIVES ); + } +} + +void SubdivideFaceBySubdivSize( face_t *f ) +{ + if( f->numpoints == 0 || f->split[0] || f->split[1] || f->merged || !f->w ) + { + return; + } + // see if the face needs to be subdivided. + texinfo_t *pTexInfo = &texinfo[f->texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + bool bFound; + const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + MaterialSystemMaterial_t matID = + FindOriginalMaterial( pMaterialName, &bFound, false ); + + if( !bFound ) + { + return; + } + const char *subdivsizeString = GetMaterialVar( matID, "$subdivsize" ); + if( subdivsizeString ) + { + float subdivSize = atof( subdivsizeString ); + if( subdivSize > 0.0f ) + { + // NOTE: Subdivision is unsupported and should be phased out + Warning("Using subdivision on %s\n", pMaterialName ); + SubdivideFaceBySubdivSize( f, subdivSize ); + } + } +} + +void SplitSubdividedFaces_Node_r( node_t *node ) +{ + if (node->planenum == PLANENUM_LEAF) + { + return; + } + face_t *f; + for( f = node->faces; f ;f = f->next ) + { + SubdivideFaceBySubdivSize( f ); + } + + // + // recursively output the other nodes + // + SplitSubdividedFaces_Node_r( node->children[0] ); + SplitSubdividedFaces_Node_r( node->children[1] ); +} + +void SplitSubdividedFaces( face_t *pLeafFaceList, node_t *headnode ) +{ + // deal with leaf faces. + face_t *f = pLeafFaceList; + while ( f ) + { + SubdivideFaceBySubdivSize( f ); + f = f->next; + } + + // deal with node faces. + SplitSubdividedFaces_Node_r( headnode ); +} + +#pragma optimize( "", on ) + +/* +============ +MakeFaces +============ +*/ +void MakeFaces (node_t *node) +{ + qprintf ("--- MakeFaces ---\n"); + c_merge = 0; + c_subdivide = 0; + c_nodefaces = 0; + + MakeFaces_r (node); + + qprintf ("%5i makefaces\n", c_nodefaces); + qprintf ("%5i merged\n", c_merge); + qprintf ("%5i subdivided\n", c_subdivide); +}
\ No newline at end of file diff --git a/utils/vbsp/faces.h b/utils/vbsp/faces.h new file mode 100644 index 0000000..79d7895 --- /dev/null +++ b/utils/vbsp/faces.h @@ -0,0 +1,20 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef FACES_H +#define FACES_H +#ifdef _WIN32 +#pragma once +#endif + + +void GetEdge2_InitOptimizedList(); // Call this before calling GetEdge2() on a bunch of edges. +int AddEdge( int v1, int v2, face_t *f ); +int GetEdge2(int v1, int v2, face_t *f); + + +#endif // FACES_H diff --git a/utils/vbsp/glfile.cpp b/utils/vbsp/glfile.cpp new file mode 100644 index 0000000..361d40b --- /dev/null +++ b/utils/vbsp/glfile.cpp @@ -0,0 +1,219 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" + +int c_glfaces; + +int PortalVisibleSides (portal_t *p) +{ + int fcon, bcon; + + if (!p->onnode) + return 0; // outside + + fcon = p->nodes[0]->contents; + bcon = p->nodes[1]->contents; + + // same contents never create a face + if (fcon == bcon) + return 0; + + // FIXME: is this correct now? + if (!fcon) + return 1; + if (!bcon) + return 2; + return 0; +} + +void OutputWinding (winding_t *w, FileHandle_t glview) +{ + static int level = 128; + vec_t light; + int i; + + CmdLib_FPrintf( glview, "%i\n", w->numpoints); + level+=28; + light = (level&255)/255.0; + for (i=0 ; i<w->numpoints ; i++) + { + CmdLib_FPrintf(glview, "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n", + w->p[i][0], + w->p[i][1], + w->p[i][2], + light, + light, + light); + } + //CmdLib_FPrintf(glview, "\n"); +} + +void OutputWindingColor (winding_t *w, FileHandle_t glview, int r, int g, int b) +{ + int i; + + CmdLib_FPrintf( glview, "%i\n", w->numpoints); + float lr = r * (1.0f/255.0f); + float lg = g * (1.0f/255.0f); + float lb = b * (1.0f/255.0f); + for (i=0 ; i<w->numpoints ; i++) + { + CmdLib_FPrintf(glview, "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n", + w->p[i][0], + w->p[i][1], + w->p[i][2], + lr, + lg, + lb); + } + //CmdLib_FPrintf(glview, "\n"); +} + +/* +============= +OutputPortal +============= +*/ +void OutputPortal (portal_t *p, FileHandle_t glview) +{ + winding_t *w; + int sides; + + sides = PortalVisibleSides (p); + if (!sides) + return; + + c_glfaces++; + + w = p->winding; + + if (sides == 2) // back side + w = ReverseWinding (w); + + OutputWinding (w, glview); + + if (sides == 2) + FreeWinding(w); +} + +/* +============= +WriteGLView_r +============= +*/ +void WriteGLView_r (node_t *node, FileHandle_t glview) +{ + portal_t *p, *nextp; + + if (node->planenum != PLANENUM_LEAF) + { + WriteGLView_r (node->children[0], glview); + WriteGLView_r (node->children[1], glview); + return; + } + + // write all the portals + for (p=node->portals ; p ; p=nextp) + { + if (p->nodes[0] == node) + { + OutputPortal (p, glview); + nextp = p->next[0]; + } + else + nextp = p->next[1]; + } +} + + +void WriteGLViewFaces_r( node_t *node, FileHandle_t glview ) +{ + portal_t *p, *nextp; + + if (node->planenum != PLANENUM_LEAF) + { + WriteGLViewFaces_r (node->children[0], glview); + WriteGLViewFaces_r (node->children[1], glview); + return; + } + + // write all the portals + for (p=node->portals ; p ; p=nextp) + { + int s = (p->nodes[1] == node); + + if ( p->face[s] ) + { + OutputWinding( p->face[s]->w, glview ); + } + nextp = p->next[s]; + } +} + +/* +============= +WriteGLView +============= +*/ +void WriteGLView (tree_t *tree, char *source) +{ + char name[1024]; + FileHandle_t glview; + + c_glfaces = 0; + sprintf (name, "%s%s.gl",outbase, source); + Msg("Writing %s\n", name); + + glview = g_pFileSystem->Open( name, "w" ); + if (!glview) + Error ("Couldn't open %s", name); + WriteGLView_r (tree->headnode, glview); + g_pFileSystem->Close( glview ); + + Msg("%5i c_glfaces\n", c_glfaces); +} + + +void WriteGLViewFaces( tree_t *tree, const char *pName ) +{ + char name[1024]; + FileHandle_t glview; + + c_glfaces = 0; + sprintf (name, "%s%s.gl", outbase, pName); + Msg("Writing %s\n", name); + + glview = g_pFileSystem->Open( name, "w" ); + if (!glview) + Error ("Couldn't open %s", name); + WriteGLViewFaces_r (tree->headnode, glview); + g_pFileSystem->Close( glview ); + + Msg("%5i c_glfaces\n", c_glfaces); +} + + +void WriteGLViewBrushList( bspbrush_t *pList, const char *pName ) +{ + char name[1024]; + FileHandle_t glview; + + sprintf (name, "%s%s.gl", outbase, pName ); + Msg("Writing %s\n", name); + + glview = g_pFileSystem->Open( name, "w" ); + if (!glview) + Error ("Couldn't open %s", name); + for ( bspbrush_t *pBrush = pList; pBrush; pBrush = pBrush->next ) + { + for (int i = 0; i < pBrush->numsides; i++ ) + OutputWinding( pBrush->sides[i].winding, glview ); + } + g_pFileSystem->Close( glview ); +} diff --git a/utils/vbsp/leakfile.cpp b/utils/vbsp/leakfile.cpp new file mode 100644 index 0000000..d603883 --- /dev/null +++ b/utils/vbsp/leakfile.cpp @@ -0,0 +1,168 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" +#include "color.h" + +/* +============================================================================== + +LEAF FILE GENERATION + +Save out name.line for qe3 to read +============================================================================== +*/ + + +/* +============= +LeakFile + +Finds the shortest possible chain of portals +that leads from the outside leaf to a specifically +occupied leaf +============= +*/ +void LeakFile (tree_t *tree) +{ + Vector mid; + FILE *linefile; + char filename[1024]; + node_t *node; + int count; + + if (!tree->outside_node.occupied) + return; + + tree->leaked = true; + qprintf ("--- LeakFile ---\n"); + + // + // write the points to the file + // + sprintf (filename, "%s.lin", source); + linefile = fopen (filename, "w"); + if (!linefile) + Error ("Couldn't open %s\n", filename); + + count = 0; + node = &tree->outside_node; + while (node->occupied > 1) + { + portal_t *nextportal = NULL; + node_t *nextnode = NULL; + int s = 0; + + // find the best portal exit + int next = node->occupied; + for (portal_t *p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (p->nodes[s]->occupied + && p->nodes[s]->occupied < next) + { + nextportal = p; + nextnode = p->nodes[s]; + next = nextnode->occupied; + } + } + node = nextnode; + WindingCenter (nextportal->winding, mid); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + count++; + } + + // Add the occupant's origin to the leakfile. + Vector origin; + GetVectorForKey (node->occupant, "origin", origin); + + fprintf (linefile, "%f %f %f\n", origin[0], origin[1], origin[2]); + qprintf ("%5i point linefile\n", count+1); + + fclose (linefile); + + // Emit a leak warning. + const char *cl = ValueForKey (node->occupant, "classname"); + Color red(255,0,0,255); + ColorSpewMessage( SPEW_MESSAGE, &red, "Entity %s (%.2f %.2f %.2f) leaked!\n", cl, origin[0], origin[1], origin[2] ); +} + +void AreaportalLeakFile( tree_t *tree, portal_t *pStartPortal, portal_t *pEndPortal, node_t *pStart ) +{ + Vector mid; + FILE *linefile; + char filename[1024]; + node_t *node; + int count; + + // wrote a leak line file already, don't overwrite it with the areaportal leak file + if ( tree->leaked ) + return; + + tree->leaked = true; + qprintf ("--- LeakFile ---\n"); + + // + // write the points to the file + // + sprintf (filename, "%s.lin", source); + linefile = fopen (filename, "w"); + if (!linefile) + Error ("Couldn't open %s\n", filename); + + count = 2; + WindingCenter (pEndPortal->winding, mid); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + mid = 0.5 * (pStart->mins + pStart->maxs); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + + node = pStart; + while (node->occupied >= 1) + { + portal_t *nextportal = NULL; + node_t *nextnode = NULL; + int s = 0; + + // find the best portal exit + int next = node->occupied; + for (portal_t *p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (p->nodes[s]->occupied + && p->nodes[s]->occupied < next) + { + nextportal = p; + nextnode = p->nodes[s]; + next = nextnode->occupied; + } + } + if ( !nextnode ) + break; + node = nextnode; + WindingCenter (nextportal->winding, mid); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + count++; + } + // add the occupant center + if ( node ) + { + mid = 0.5 * (node->mins + node->maxs); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + count++; + } + WindingCenter (pStartPortal->winding, mid); + count++; + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + + qprintf ("%5i point linefile\n", count); + + fclose (linefile); + Warning( "Wrote %s\n", filename ); + Color red(255,0,0,255); + ColorSpewMessage( SPEW_MESSAGE, &red, "Areaportal leak ! File: %s ", filename ); +}
\ No newline at end of file diff --git a/utils/vbsp/manifest.cpp b/utils/vbsp/manifest.cpp new file mode 100644 index 0000000..c72a956 --- /dev/null +++ b/utils/vbsp/manifest.cpp @@ -0,0 +1,568 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +#include "vbsp.h" +#include "map_shared.h" +#include "fgdlib/fgdlib.h" +#include "manifest.h" +#include "windows.h" + +//----------------------------------------------------------------------------- +// Purpose: default constructor +//----------------------------------------------------------------------------- +CManifestMap::CManifestMap( void ) +{ + m_RelativeMapFileName[ 0 ] = 0; + m_bTopLevelMap = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: default constructor +//----------------------------------------------------------------------------- +CManifest::CManifest( void ) +{ + m_InstancePath[ 0 ] = 0; + m_bIsCordoning = false; + m_CordoningMapEnt = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will parse through the known keys for the manifest map entry +// Input : szKey - the key name +// szValue - the value +// pManifestMap - the manifest map this belongs to +// Output : ChunkFileResult_t - result of the parsing +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadManifestMapKeyCallback( const char *szKey, const char *szValue, CManifestMap *pManifestMap ) +{ + if ( !stricmp( szKey, "Name" ) ) + { + // pManifestMap->m_FriendlyName = szValue; + } + else if ( !stricmp( szKey, "File" ) ) + { + strcpy( pManifestMap->m_RelativeMapFileName, szValue ); + } + else if ( !stricmp( szKey, "IsPrimary" ) ) + { + // pManifestMap->m_bPrimaryMap = ( atoi( szValue ) == 1 ); + } + else if ( !stricmp( szKey, "IsProtected" ) ) + { + // pManifestMap->m_bCanBeModified = ( atoi( szValue ) != 1 ); + } + else if ( !stricmp( szKey, "TopLevel" ) ) + { + pManifestMap->m_bTopLevelMap = ( atoi( szValue ) == 1 ); + } + + return ChunkFile_Ok; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function is responsible for setting up the manifest map about to be read in +// Input : pFile - the chunk file being read +// pDoc - the owning manifest document +// Output : ChunkFileResult_t - result of the parsing +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadManifestVMFCallback( CChunkFile *pFile, CManifest *pManifest ) +{ + CManifestMap *pManifestMap = new CManifestMap(); + + pManifest->m_Maps.AddToTail( pManifestMap ); + + ChunkFileResult_t eResult = pFile->ReadChunk( ( KeyHandler_t )LoadManifestMapKeyCallback, pManifestMap ); + + return( eResult ); +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will load the VMF chunk +// Input : pFile - the chunk file being read +// pDoc - the owning manifest document +// Output : ChunkFileResult_t - result of the parsing +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadManifestMapsCallback( CChunkFile *pFile, CManifest *pManifest ) +{ + CChunkHandlerMap Handlers; + Handlers.AddHandler( "VMF", ( ChunkHandler_t )LoadManifestVMFCallback, pManifest ); + pFile->PushHandlers(&Handlers); + + ChunkFileResult_t eResult = ChunkFile_Ok; + + eResult = pFile->ReadChunk(); + + pFile->PopHandlers(); + + return( eResult ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonBoxCallback( CChunkFile *pFile, Cordon_t *pCordon ) +{ + // Add a box to this cordon. + pCordon->m_Boxes.AddToTail(); + BoundBox &box = pCordon->m_Boxes.Tail(); + + // Fill it in with the data from the VMF. + return pFile->ReadChunk( (KeyHandler_t)LoadCordonBoxKeyCallback, (void *)&box ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonBoxKeyCallback( const char *szKey, const char *szValue, BoundBox *pBox ) +{ + if (!stricmp(szKey, "mins")) + { + CChunkFile::ReadKeyValuePoint(szValue, pBox->bmins); + } + else if (!stricmp(szKey, "maxs")) + { + CChunkFile::ReadKeyValuePoint(szValue, pBox->bmaxs); + } + + return ChunkFile_Ok; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonKeyCallback( const char *szKey, const char *szValue, Cordon_t *pCordon ) +{ + if (!stricmp(szKey, "name")) + { + pCordon->m_szName.Set( szValue ); + } + // Whether this particular cordon volume is active. + else if (!stricmp(szKey, "active")) + { + CChunkFile::ReadKeyValueBool(szValue, pCordon->m_bActive); + } + + return ChunkFile_Ok; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonCallback( CChunkFile *pFile, CManifest *pManifest ) +{ + // Add a new cordon which will be filled in by the key callback + pManifest->m_Cordons.AddToTail(); + Cordon_t &cordon = pManifest->m_Cordons.Tail(); + + CChunkHandlerMap Handlers; + Handlers.AddHandler( "box", (ChunkHandler_t)CManifest::LoadCordonBoxCallback, (void *)&cordon ); + + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk( (KeyHandler_t)LoadCordonKeyCallback, (void *)&cordon ); + pFile->PopHandlers(); + + return(eResult); +} + + +//----------------------------------------------------------------------------------------------------------- +// Parses keys that are applicable to all cordons in the map. +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonsKeyCallback( const char *szKey, const char *szValue, CManifest *pManifest ) +{ + // Whether the cordoning system is enabled or disabled. + if ( !stricmp( szKey, "active" ) ) + { + CChunkFile::ReadKeyValueBool( szValue, pManifest->m_bIsCordoning ); + } + + return ChunkFile_Ok; +} + + +//----------------------------------------------------------------------------- +// Parses the VMF chunk that pertains to all the cordons in the map: +// +// cordons +// { +// "active" "true" +// cordon +// { +// "active" "true" +// "box" +// { +// "mins" "-1024, -1024, -1024" +// "maxs" "1024, 1024, 1024" +// } +// ...may be more boxes... +// } +// ...may be more cordons... +// } +// +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonsCallback( CChunkFile *pFile, CManifest *pManifest ) +{ + CChunkHandlerMap Handlers; + Handlers.AddHandler( "cordon", (ChunkHandler_t)CManifest::LoadCordonCallback, pManifest ); + + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk( (KeyHandler_t)LoadCordonsKeyCallback, pManifest ); + pFile->PopHandlers(); + + return(eResult); +} + +extern ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); + +ChunkFileResult_t CManifest::LoadManifestCordoningPrefsCallback( CChunkFile *pFile, CManifest *pDoc ) +{ + pDoc->m_CordoningMapEnt = &g_MainMap->entities[g_MainMap->num_entities]; + g_MainMap->num_entities++; + memset( pDoc->m_CordoningMapEnt, 0, sizeof( *pDoc->m_CordoningMapEnt ) ); + pDoc->m_CordoningMapEnt->firstbrush = g_MainMap->nummapbrushes; + pDoc->m_CordoningMapEnt->numbrushes = 0; + + LoadEntity_t LoadEntity; + LoadEntity.pEntity = pDoc->m_CordoningMapEnt; + + // No default flags/contents + LoadEntity.nBaseFlags = 0; + LoadEntity.nBaseContents = 0; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler( "cordons", ( ChunkHandler_t )CManifest::LoadCordonsCallback, pDoc ); + Handlers.AddHandler("solid", (ChunkHandler_t)::LoadSolidCallback, &LoadEntity); + pFile->PushHandlers(&Handlers); + + ChunkFileResult_t eResult = ChunkFile_Ok; + + eResult = pFile->ReadChunk(); + + pFile->PopHandlers(); + + return( eResult ); +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will create a new entity pair +// Input : pKey - the key of the pair +// pValue - the value of the pair +// Output : returns a newly created epair structure +//----------------------------------------------------------------------------- +epair_t *CManifest::CreateEPair( char *pKey, char *pValue ) +{ + epair_t *pEPair = new epair_t; + + pEPair->key = new char[ strlen( pKey ) + 1 ]; + pEPair->value = new char[ strlen( pValue ) + 1 ]; + + strcpy( pEPair->key, pKey ); + strcpy( pEPair->value, pValue ); + + return pEPair; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will load in all of the submaps belonging to this manifest, +// except for the top level map, which is loaded separately. +// Input : pMapFile - the top level map that was previously loaded +// pszFileName - the absolute file name of the top level map file +// Output : returns true if all submaps were loaded +//----------------------------------------------------------------------------- +bool CManifest::LoadSubMaps( CMapFile *pMapFile, const char *pszFileName ) +{ + entity_t *InstanceEntity; + epair_t *pEPair; + + InstanceEntity = &pMapFile->entities[ pMapFile->num_entities ]; + pMapFile->num_entities++; + memset( InstanceEntity, 0, sizeof( *InstanceEntity ) ); + + InstanceEntity->origin.Init( 0.0f, 0.0f, 0.0f ); + pEPair = CreateEPair( "classname", "worldspawn" ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + + for( int i = 0; i < m_Maps.Count(); i++ ) + { + // if ( m_Maps[ i ]->m_bTopLevelMap == false ) + { + char FileName[ MAX_PATH ]; + + sprintf( FileName, "%s%s", m_InstancePath, m_Maps[ i ]->m_RelativeMapFileName ); + + InstanceEntity = &pMapFile->entities[ pMapFile->num_entities ]; + pMapFile->num_entities++; + + memset( InstanceEntity, 0, sizeof( *InstanceEntity ) ); + InstanceEntity->origin.Init( 0.0f, 0.0f, 0.0f ); + + pEPair = CreateEPair( "angles", "0 0 0" ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + + char temp[ 128 ]; + sprintf( temp, "%d", GameData::NAME_FIXUP_NONE ); + + pEPair = CreateEPair( "fixup_style", temp ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + + pEPair = CreateEPair( "classname", "func_instance" ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + + pEPair = CreateEPair( "file", m_Maps[ i ]->m_RelativeMapFileName ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + + if ( m_Maps[ i ]->m_bTopLevelMap == true ) + { + pEPair = CreateEPair( "toplevel", "1" ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + } + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CManifest::LoadVMFManifestUserPrefs( const char *pszFileName ) +{ + char UserName[ MAX_PATH ], FileName[ MAX_PATH ], UserPrefsFileName[ MAX_PATH ]; + DWORD UserNameSize; + + UserNameSize = sizeof( UserName ); + if ( GetUserName( UserName, &UserNameSize ) == 0 ) + { + strcpy( UserPrefsFileName, "default" ); + } + + sprintf( UserPrefsFileName, "\\%s.vmm_prefs", UserName ); + V_StripExtension( pszFileName, FileName, sizeof( FileName ) ); + strcat( FileName, UserPrefsFileName ); + + FILE *fp = fopen( FileName, "rb" ); + if ( !fp ) + { + return false; + } + + CChunkFile File; + ChunkFileResult_t eResult = File.Open( FileName, ChunkFile_Read ); + + if ( eResult == ChunkFile_Ok ) + { + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler( "cordoning", ( ChunkHandler_t )CManifest::LoadManifestCordoningPrefsCallback, this ); + + // Handlers.SetErrorHandler( ( ChunkErrorHandler_t )CMapDoc::HandleLoadError, this); + + File.PushHandlers(&Handlers); + + while( eResult == ChunkFile_Ok ) + { + eResult = File.ReadChunk(); + } + + if ( eResult == ChunkFile_EOF ) + { + eResult = ChunkFile_Ok; + } + + File.PopHandlers(); + } + + if ( eResult == ChunkFile_Ok ) + { + } + else + { + // no pref message for now + // GetMainWnd()->MessageBox( File.GetErrorText( eResult ), "Error loading manifest!", MB_OK | MB_ICONEXCLAMATION ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Loads a .VMM file. +// Input : pszFileName - Full path of the map file to load. +//----------------------------------------------------------------------------- +bool CManifest::LoadVMFManifest( const char *pszFileName ) +{ + V_StripExtension( pszFileName, m_InstancePath, sizeof( m_InstancePath ) ); + strcat( m_InstancePath, "\\" ); + + CChunkFile File; + ChunkFileResult_t eResult = File.Open( pszFileName, ChunkFile_Read ); + if ( eResult != ChunkFile_Ok ) + { + g_MapError.ReportError( File.GetErrorText( eResult ) ); + return false; + } + + CChunkHandlerMap Handlers; + Handlers.AddHandler( "Maps", ( ChunkHandler_t )LoadManifestMapsCallback, this ); + + File.PushHandlers(&Handlers); + + while (eResult == ChunkFile_Ok) + { + eResult = File.ReadChunk(); + } + + if (eResult == ChunkFile_EOF) + { + eResult = ChunkFile_Ok; + } + + File.PopHandlers(); + + if ( eResult == ChunkFile_Ok ) + { + int index = g_Maps.AddToTail( new CMapFile() ); + g_LoadingMap = g_Maps[ index ]; + if ( g_MainMap == NULL ) + { + g_MainMap = g_LoadingMap; + } + + LoadSubMaps( g_LoadingMap, pszFileName ); + + LoadVMFManifestUserPrefs( pszFileName ); + } + + return ( eResult == ChunkFile_Ok ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CManifest::CordonWorld( ) +{ + if ( m_bIsCordoning == false ) + { + return; + } + + for ( int i = 0; i < g_MainMap->num_entities; i++ ) + { + if ( i == 0 ) + { // for world spawn, we look at brushes + for( int nBrushNum = 0; nBrushNum < g_MainMap->entities[ i ].numbrushes; nBrushNum++ ) + { + int nIndex = g_MainMap->entities[ i ].firstbrush + nBrushNum; + + bool bRemove = true; + + for( int nCordon = 0; nCordon < m_Cordons.Count(); nCordon++ ) + { + if ( m_Cordons[ nCordon ].m_bActive == false ) + { + continue; + } + + for( int nBox = 0; nBox < m_Cordons[ nCordon ].m_Boxes.Count(); nBox++ ) + { + if ( m_Cordons[ nCordon ].m_Boxes[ nBox ].IsIntersectingBox( g_MainMap->mapbrushes[ nIndex ].mins, g_MainMap->mapbrushes[ nIndex ].maxs ) == true ) + { + bRemove = false; + break; + } + } + + if ( bRemove == false ) + { + break; + } + } + + if ( bRemove ) + { + int nSize = ( g_MainMap->entities[ i ].numbrushes - nBrushNum - 1 ) * sizeof( g_MainMap->mapbrushes[ 0 ] ); + memmove( &g_MainMap->mapbrushes[ nIndex ], &g_MainMap->mapbrushes[ nIndex + 1 ], nSize ); + g_MainMap->entities[ i ].numbrushes--; + nBrushNum--; + } + } + } + else if ( &g_MainMap->entities[ i ] != m_CordoningMapEnt ) + { // for all other entities, even if they include brushes, we look at origin + if ( g_MainMap->entities[ i ].numbrushes == 0 && g_MainMap->entities[ i ].epairs == NULL ) + { + continue; + } + + bool bRemove = true; + + for( int nCordon = 0; nCordon < m_Cordons.Count(); nCordon++ ) + { + if ( m_Cordons[ nCordon ].m_bActive == false ) + { + continue; + } + + for( int nBox = 0; nBox < m_Cordons[ nCordon ].m_Boxes.Count(); nBox++ ) + { + if ( m_Cordons[ nCordon ].m_Boxes[ nBox ].ContainsPoint( g_MainMap->entities[ i ].origin ) == true ) + { + bRemove = false; + break; + } + } + + if ( bRemove == false ) + { + break; + } + } + + if ( bRemove ) + { + g_MainMap->entities[ i ].numbrushes = 0; + g_MainMap->entities[ i ].epairs = NULL; + } + } + } + + if ( m_CordoningMapEnt ) + { + g_MainMap->MoveBrushesToWorldGeneral( m_CordoningMapEnt ); + m_CordoningMapEnt->numbrushes = 0; + m_CordoningMapEnt->epairs = NULL; + } +} diff --git a/utils/vbsp/manifest.h b/utils/vbsp/manifest.h new file mode 100644 index 0000000..e7b801e --- /dev/null +++ b/utils/vbsp/manifest.h @@ -0,0 +1,73 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=====================================================================================// + +#ifndef __MANIFEST_H +#define __MANIFEST_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "boundbox.h" + +// +// Each cordon is a named collection of bounding boxes. +// +struct Cordon_t +{ + inline Cordon_t() + { + m_bActive = false; + } + + CUtlString m_szName; + bool m_bActive; // True means cull using this cordon when cordoning is enabled. + CUtlVector<BoundBox> m_Boxes; +}; + +class CManifestMap +{ +public: + CManifestMap( void ); + char m_RelativeMapFileName[ MAX_PATH ]; + bool m_bTopLevelMap; +}; + +class CManifest +{ +public: + CManifest( void ); + + static ChunkFileResult_t LoadManifestMapKeyCallback( const char *szKey, const char *szValue, CManifestMap *pManifestMap ); + static ChunkFileResult_t LoadManifestVMFCallback( CChunkFile *pFile, CManifest *pManifest ); + static ChunkFileResult_t LoadManifestMapsCallback( CChunkFile *pFile, CManifest *pManifest ); + static ChunkFileResult_t LoadCordonBoxCallback( CChunkFile *pFile, Cordon_t *pCordon ); + static ChunkFileResult_t LoadCordonBoxKeyCallback( const char *szKey, const char *szValue, BoundBox *pBox ); + static ChunkFileResult_t LoadCordonKeyCallback( const char *szKey, const char *szValue, Cordon_t *pCordon ); + static ChunkFileResult_t LoadCordonCallback( CChunkFile *pFile, CManifest *pManifest ); + static ChunkFileResult_t LoadCordonsKeyCallback( const char *pszKey, const char *pszValue, CManifest *pManifest ); + static ChunkFileResult_t LoadCordonsCallback( CChunkFile *pFile, CManifest *pManifest ); + static ChunkFileResult_t LoadManifestCordoningPrefsCallback( CChunkFile *pFile, CManifest *pManifest ); + + bool LoadSubMaps( CMapFile *pMapFile, const char *pszFileName ); + epair_t *CreateEPair( char *pKey, char *pValue ); + bool LoadVMFManifest( const char *pszFileName ); + const char *GetInstancePath( ) { return m_InstancePath; } + + void CordonWorld( ); + +private: + bool LoadVMFManifestUserPrefs( const char *pszFileName ); + + + CUtlVector< CManifestMap * > m_Maps; + char m_InstancePath[ MAX_PATH ]; + bool m_bIsCordoning; + CUtlVector< Cordon_t > m_Cordons; + entity_t *m_CordoningMapEnt; +}; + +#endif // #ifndef __MANIFEST_H diff --git a/utils/vbsp/map.cpp b/utils/vbsp/map.cpp new file mode 100644 index 0000000..2221c79 --- /dev/null +++ b/utils/vbsp/map.cpp @@ -0,0 +1,3304 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "map_shared.h" +#include "disp_vbsp.h" +#include "tier1/strtools.h" +#include "builddisp.h" +#include "tier0/icommandline.h" +#include "KeyValues.h" +#include "materialsub.h" +#include "fgdlib/fgdlib.h" +#include "manifest.h" + +#ifdef VSVMFIO +#include "VmfImport.h" +#endif // VSVMFIO + + +// undefine to make plane finding use linear sort +#define USE_HASHING + +#define RENDER_NORMAL_EPSILON 0.00001 +#define RENDER_DIST_EPSILON 0.01f + +#define BRUSH_CLIP_EPSILON 0.01f // this should probably be the same + // as clip epsilon, but it is 0.1f and I + // currently don't know how that number was + // come to (cab) - this is 0.01 of an inch + // for clipping brush solids +struct LoadSide_t +{ + mapbrush_t *pBrush; + side_t *pSide; + int nSideIndex; + int nBaseFlags; + int nBaseContents; + Vector planepts[3]; + brush_texture_t td; +}; + + +extern qboolean onlyents; + + +CUtlVector< CMapFile * > g_Maps; +CMapFile *g_MainMap = NULL; +CMapFile *g_LoadingMap = NULL; + +char CMapFile::m_InstancePath[ MAX_PATH ] = ""; +int CMapFile::m_InstanceCount = 0; +int CMapFile::c_areaportals = 0; + +void CMapFile::Init( void ) +{ + entity_num = 0; + num_entities = 0; + + nummapplanes = 0; + memset( mapplanes, 0, sizeof( mapplanes ) ); + + nummapbrushes = 0; + memset( mapbrushes, 0, sizeof( mapbrushes ) ); + + nummapbrushsides = 0; + memset( brushsides, 0, sizeof( brushsides ) ); + + memset( side_brushtextures, 0, sizeof( side_brushtextures ) ); + + memset( planehash, 0, sizeof( planehash ) ); + + m_ConnectionPairs = NULL; + + m_StartMapOverlays = g_aMapOverlays.Count(); + m_StartMapWaterOverlays = g_aMapWaterOverlays.Count(); + + c_boxbevels = 0; + c_edgebevels = 0; + c_clipbrushes = 0; + g_ClipTexinfo = -1; +} + + +// All the brush sides referenced by info_no_dynamic_shadow entities. +CUtlVector<int> g_NoDynamicShadowSides; + + +void TestExpandBrushes (void); + +ChunkFileResult_t LoadDispDistancesCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispDistancesKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispInfoCallback(CChunkFile *pFile, mapdispinfo_t **ppMapDispInfo ); +ChunkFileResult_t LoadDispInfoKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispOffsetsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispOffsetsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispAlphasCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispAlphasKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispTriangleTagsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispTriangleTagsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); + +#ifdef VSVMFIO +ChunkFileResult_t LoadDispOffsetNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispOffsetNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +#endif // VSVMFIO + +ChunkFileResult_t LoadEntityCallback(CChunkFile *pFile, int nParam); +ChunkFileResult_t LoadEntityKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity); + +ChunkFileResult_t LoadConnectionsCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); +ChunkFileResult_t LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity); + +ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); +ChunkFileResult_t LoadSolidKeyCallback(const char *szKey, const char *szValue, mapbrush_t *pLoadBrush); + +ChunkFileResult_t LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo); +ChunkFileResult_t LoadSideKeyCallback(const char *szKey, const char *szValue, LoadSide_t *pSideInfo); + + + +/* +============================================================================= + +PLANE FINDING + +============================================================================= +*/ + + +/* +================= +PlaneTypeForNormal +================= +*/ +int PlaneTypeForNormal (Vector& normal) +{ + vec_t ax, ay, az; + +// NOTE: should these have an epsilon around 1.0? + if (normal[0] == 1.0 || normal[0] == -1.0) + return PLANE_X; + if (normal[1] == 1.0 || normal[1] == -1.0) + return PLANE_Y; + if (normal[2] == 1.0 || normal[2] == -1.0) + return PLANE_Z; + + ax = fabs(normal[0]); + ay = fabs(normal[1]); + az = fabs(normal[2]); + + if (ax >= ay && ax >= az) + return PLANE_ANYX; + if (ay >= ax && ay >= az) + return PLANE_ANYY; + return PLANE_ANYZ; +} + +/* +================ +PlaneEqual +================ +*/ +qboolean PlaneEqual (plane_t *p, Vector& normal, vec_t dist, float normalEpsilon, float distEpsilon) +{ +#if 1 + if ( + fabs(p->normal[0] - normal[0]) < normalEpsilon + && fabs(p->normal[1] - normal[1]) < normalEpsilon + && fabs(p->normal[2] - normal[2]) < normalEpsilon + && fabs(p->dist - dist) < distEpsilon ) + return true; +#else + if (p->normal[0] == normal[0] + && p->normal[1] == normal[1] + && p->normal[2] == normal[2] + && p->dist == dist) + return true; +#endif + return false; +} + +/* +================ +AddPlaneToHash +================ +*/ +void CMapFile::AddPlaneToHash (plane_t *p) +{ + int hash; + + hash = (int)fabs(p->dist) / 8; + hash &= (PLANE_HASHES-1); + + p->hash_chain = planehash[hash]; + planehash[hash] = p; +} + +/* +================ +CreateNewFloatPlane +================ +*/ +int CMapFile::CreateNewFloatPlane (Vector& normal, vec_t dist) +{ + plane_t *p, temp; + + if (VectorLength(normal) < 0.5) + g_MapError.ReportError ("FloatPlane: bad normal"); + // create a new plane + if (nummapplanes+2 > MAX_MAP_PLANES) + g_MapError.ReportError ("MAX_MAP_PLANES"); + + p = &mapplanes[nummapplanes]; + VectorCopy (normal, p->normal); + p->dist = dist; + p->type = (p+1)->type = PlaneTypeForNormal (p->normal); + + VectorSubtract (vec3_origin, normal, (p+1)->normal); + (p+1)->dist = -dist; + + nummapplanes += 2; + + // allways put axial planes facing positive first + if (p->type < 3) + { + if (p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0) + { + // flip order + temp = *p; + *p = *(p+1); + *(p+1) = temp; + + AddPlaneToHash (p); + AddPlaneToHash (p+1); + return nummapplanes - 1; + } + } + + AddPlaneToHash (p); + AddPlaneToHash (p+1); + return nummapplanes - 2; +} + + +/* +============== +SnapVector +============== +*/ +bool SnapVector (Vector& normal) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if ( fabs(normal[i] - 1) < RENDER_NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = 1; + return true; + } + + if ( fabs(normal[i] - -1) < RENDER_NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = -1; + return true; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Snaps normal to axis-aligned if it is within an epsilon of axial. +// Rounds dist to integer if it is within an epsilon of integer. +// Input : normal - Plane normal vector (assumed to be unit length). +// dist - Plane constant. +//----------------------------------------------------------------------------- +void SnapPlane(Vector &normal, vec_t &dist) +{ + SnapVector(normal); + + if (fabs(dist - RoundInt(dist)) < RENDER_DIST_EPSILON) + { + dist = RoundInt(dist); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Snaps normal to axis-aligned if it is within an epsilon of axial. +// Recalculates dist if the normal was snapped. Rounds dist to integer +// if it is within an epsilon of integer. +// Input : normal - Plane normal vector (assumed to be unit length). +// dist - Plane constant. +// p0, p1, p2 - Three points on the plane. +//----------------------------------------------------------------------------- +void SnapPlane(Vector &normal, vec_t &dist, const Vector &p0, const Vector &p1, const Vector &p2) +{ + if (SnapVector(normal)) + { + // + // Calculate a new plane constant using the snapped normal. Use the + // centroid of the three plane points to minimize error. This is like + // rotating the plane around the centroid. + // + Vector p3 = (p0 + p1 + p2) / 3.0f; + dist = normal.Dot(p3); + if ( g_snapAxialPlanes ) + { + dist = RoundInt(dist); + } + } + + if (fabs(dist - RoundInt(dist)) < RENDER_DIST_EPSILON) + { + dist = RoundInt(dist); + } +} + + +/* +============= +FindFloatPlane + +============= +*/ +#ifndef USE_HASHING +int CMapFile::FindFloatPlane (Vector& normal, vec_t dist) +{ + int i; + plane_t *p; + + SnapPlane(normal, dist); + for (i=0, p=mapplanes ; i<nummapplanes ; i++, p++) + { + if (PlaneEqual (p, normal, dist, RENDER_NORMAL_EPSILON, RENDER_DIST_EPSILON)) + return i; + } + + return CreateNewFloatPlane (normal, dist); +} +#else +int CMapFile::FindFloatPlane (Vector& normal, vec_t dist) +{ + int i; + plane_t *p; + int hash, h; + + SnapPlane(normal, dist); + hash = (int)fabs(dist) / 8; + hash &= (PLANE_HASHES-1); + + // search the border bins as well + for (i=-1 ; i<=1 ; i++) + { + h = (hash+i)&(PLANE_HASHES-1); + for (p = planehash[h] ; p ; p=p->hash_chain) + { + if (PlaneEqual (p, normal, dist, RENDER_NORMAL_EPSILON, RENDER_DIST_EPSILON)) + return p-mapplanes; + } + } + + return CreateNewFloatPlane (normal, dist); +} +#endif + + +//----------------------------------------------------------------------------- +// Purpose: Builds a plane normal and distance from three points on the plane. +// If the normal is nearly axial, it will be snapped to be axial. Looks +// up the plane in the unique planes. +// Input : p0, p1, p2 - Three points on the plane. +// Output : Returns the index of the plane in the planes list. +//----------------------------------------------------------------------------- +int CMapFile::PlaneFromPoints(const Vector &p0, const Vector &p1, const Vector &p2) +{ + Vector t1, t2, normal; + vec_t dist; + + VectorSubtract (p0, p1, t1); + VectorSubtract (p2, p1, t2); + CrossProduct (t1, t2, normal); + VectorNormalize (normal); + + dist = DotProduct (p0, normal); + + SnapPlane(normal, dist, p0, p1, p2); + + return FindFloatPlane (normal, dist); +} + + +/* +=========== +BrushContents +=========== +*/ +int BrushContents (mapbrush_t *b) +{ + int contents; + int unionContents = 0; + side_t *s; + int i; + + s = &b->original_sides[0]; + contents = s->contents; + unionContents = contents; + for (i=1 ; i<b->numsides ; i++, s++) + { + s = &b->original_sides[i]; + + unionContents |= s->contents; +#if 0 + if (s->contents != contents) + { + Msg("Brush %i: mixed face contents\n", b->id); + break; + } +#endif + } + + // NOTE: we're making slime translucent so that it doesn't block lighting on things floating on its surface + int transparentContents = unionContents & (CONTENTS_WINDOW|CONTENTS_GRATE|CONTENTS_WATER|CONTENTS_SLIME); + if ( transparentContents ) + { + contents |= transparentContents | CONTENTS_TRANSLUCENT; + contents &= ~CONTENTS_SOLID; + } + + return contents; +} + + +//============================================================================ + +bool IsAreaPortal( char const *pClassName ) +{ + // If the class name starts with "func_areaportal", then it's considered an area portal. + char const *pBaseName = "func_areaportal"; + char const *pCur = pBaseName; + while( *pCur && *pClassName ) + { + if( *pCur != *pClassName ) + break; + + ++pCur; + ++pClassName; + } + + return *pCur == 0; +} + + +/* +================= +AddBrushBevels + +Adds any additional planes necessary to allow the brush to be expanded +against axial bounding boxes +================= +*/ +void CMapFile::AddBrushBevels (mapbrush_t *b) +{ + int axis, dir; + int i, j, k, l, order; + side_t sidetemp; + brush_texture_t tdtemp; + side_t *s, *s2; + Vector normal; + float dist; + winding_t *w, *w2; + Vector vec, vec2; + float d; + + // + // add the axial planes + // + order = 0; + for (axis=0 ; axis <3 ; axis++) + { + for (dir=-1 ; dir <= 1 ; dir+=2, order++) + { + // see if the plane is allready present + for (i=0, s=b->original_sides ; i<b->numsides ; i++,s++) + { + if (mapplanes[s->planenum].normal[axis] == dir) + break; + } + + if (i == b->numsides) + { // add a new side + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); + nummapbrushsides++; + b->numsides++; + VectorClear (normal); + normal[axis] = dir; + if (dir == 1) + dist = b->maxs[axis]; + else + dist = -b->mins[axis]; + s->planenum = FindFloatPlane (normal, dist); + s->texinfo = b->original_sides[0].texinfo; + s->contents = b->original_sides[0].contents; + s->bevel = true; + c_boxbevels++; + } + + // if the plane is not in it canonical order, swap it + if (i != order) + { + sidetemp = b->original_sides[order]; + b->original_sides[order] = b->original_sides[i]; + b->original_sides[i] = sidetemp; + + j = b->original_sides - brushsides; + tdtemp = side_brushtextures[j+order]; + side_brushtextures[j+order] = side_brushtextures[j+i]; + side_brushtextures[j+i] = tdtemp; + } + } + } + + // + // add the edge bevels + // + if (b->numsides == 6) + return; // pure axial + + // test the non-axial plane edges + for (i=6 ; i<b->numsides ; i++) + { + s = b->original_sides + i; + w = s->winding; + if (!w) + continue; + for (j=0 ; j<w->numpoints ; j++) + { + k = (j+1)%w->numpoints; + VectorSubtract (w->p[j], w->p[k], vec); + if (VectorNormalize (vec) < 0.5) + continue; + SnapVector (vec); + for (k=0 ; k<3 ; k++) + if ( vec[k] == -1 || vec[k] == 1) + break; // axial + if (k != 3) + continue; // only test non-axial edges + + // try the six possible slanted axials from this edge + for (axis=0 ; axis <3 ; axis++) + { + for (dir=-1 ; dir <= 1 ; dir+=2) + { + // construct a plane + VectorClear (vec2); + vec2[axis] = dir; + CrossProduct (vec, vec2, normal); + if (VectorNormalize (normal) < 0.5) + continue; + dist = DotProduct (w->p[j], normal); + + // if all the points on all the sides are + // behind this plane, it is a proper edge bevel + for (k=0 ; k<b->numsides ; k++) + { + // if this plane has allready been used, skip it + // NOTE: Use a larger tolerance for collision planes than for rendering planes + if ( PlaneEqual(&mapplanes[b->original_sides[k].planenum], normal, dist, 0.01f, 0.01f ) ) + break; + + w2 = b->original_sides[k].winding; + if (!w2) + continue; + for (l=0 ; l<w2->numpoints ; l++) + { + d = DotProduct (w2->p[l], normal) - dist; + if (d > 0.1) + break; // point in front + } + if (l != w2->numpoints) + break; + } + + if (k != b->numsides) + continue; // wasn't part of the outer hull + // add this plane + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); + nummapbrushsides++; + s2 = &b->original_sides[b->numsides]; + s2->planenum = FindFloatPlane (normal, dist); + s2->texinfo = b->original_sides[0].texinfo; + s2->contents = b->original_sides[0].contents; + s2->bevel = true; + c_edgebevels++; + b->numsides++; + } + } + } + } +} + +/* +================ +MakeBrushWindings + +makes basewindigs for sides and mins / maxs for the brush +================ +*/ +qboolean CMapFile::MakeBrushWindings (mapbrush_t *ob) +{ + int i, j; + winding_t *w; + side_t *side; + plane_t *plane; + + ClearBounds (ob->mins, ob->maxs); + + for (i=0 ; i<ob->numsides ; i++) + { + plane = &mapplanes[ob->original_sides[i].planenum]; + w = BaseWindingForPlane (plane->normal, plane->dist); + for (j=0 ; j<ob->numsides && w; j++) + { + if (i == j) + continue; + if (ob->original_sides[j].bevel) + continue; + plane = &mapplanes[ob->original_sides[j].planenum^1]; +// ChopWindingInPlace (&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); + // adding an epsilon here, due to precision issues creating complex + // displacement surfaces (cab) + ChopWindingInPlace( &w, plane->normal, plane->dist, BRUSH_CLIP_EPSILON ); + } + + side = &ob->original_sides[i]; + side->winding = w; + if (w) + { + side->visible = true; + for (j=0 ; j<w->numpoints ; j++) + AddPointToBounds (w->p[j], ob->mins, ob->maxs); + } + } + + for (i=0 ; i<3 ; i++) + { + if (ob->mins[i] < MIN_COORD_INTEGER || ob->maxs[i] > MAX_COORD_INTEGER) + Msg("Brush %i: bounds out of range\n", ob->id); + if (ob->mins[i] > MAX_COORD_INTEGER || ob->maxs[i] < MIN_COORD_INTEGER) + Msg("Brush %i: no visible sides on brush\n", ob->id); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Takes all of the brushes from the current entity and adds them to the +// world's brush list. Used by func_detail and func_areaportal. +// THIS ROUTINE MAY ONLY BE USED DURING ENTITY LOADING. +// Input : mapent - Entity whose brushes are to be moved to the world. +//----------------------------------------------------------------------------- +void CMapFile::MoveBrushesToWorld( entity_t *mapent ) +{ + int newbrushes; + int worldbrushes; + mapbrush_t *temp; + int i; + + // this is pretty gross, because the brushes are expected to be + // in linear order for each entity + + newbrushes = mapent->numbrushes; + worldbrushes = entities[0].numbrushes; + + temp = (mapbrush_t *)malloc(newbrushes*sizeof(mapbrush_t)); + memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); + +#if 0 // let them keep their original brush numbers + for (i=0 ; i<newbrushes ; i++) + temp[i].entitynum = 0; +#endif + + // make space to move the brushes (overlapped copy) + memmove (mapbrushes + worldbrushes + newbrushes, + mapbrushes + worldbrushes, + sizeof(mapbrush_t) * (nummapbrushes - worldbrushes - newbrushes) ); + + // copy the new brushes down + memcpy (mapbrushes + worldbrushes, temp, sizeof(mapbrush_t) * newbrushes); + + // fix up indexes + entities[0].numbrushes += newbrushes; + for (i=1 ; i<num_entities ; i++) + entities[i].firstbrush += newbrushes; + free (temp); + + mapent->numbrushes = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Takes all of the brushes from the current entity and adds them to the +// world's brush list. Used by func_detail and func_areaportal. +// Input : mapent - Entity whose brushes are to be moved to the world. +//----------------------------------------------------------------------------- +void CMapFile::MoveBrushesToWorldGeneral( entity_t *mapent ) +{ + int newbrushes; + int worldbrushes; + mapbrush_t *temp; + int i; + + for( i = 0; i < nummapdispinfo; i++ ) + { + if ( mapdispinfo[ i ].entitynum == ( mapent - entities ) ) + { + mapdispinfo[ i ].entitynum = 0; + } + } + + // this is pretty gross, because the brushes are expected to be + // in linear order for each entity + newbrushes = mapent->numbrushes; + worldbrushes = entities[0].numbrushes; + + temp = (mapbrush_t *)malloc(newbrushes*sizeof(mapbrush_t)); + memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); + +#if 0 // let them keep their original brush numbers + for (i=0 ; i<newbrushes ; i++) + temp[i].entitynum = 0; +#endif + + // make space to move the brushes (overlapped copy) + memmove (mapbrushes + worldbrushes + newbrushes, + mapbrushes + worldbrushes, + sizeof(mapbrush_t) * (mapent->firstbrush - worldbrushes) ); + + + // wwwxxxmmyyy + + // copy the new brushes down + memcpy (mapbrushes + worldbrushes, temp, sizeof(mapbrush_t) * newbrushes); + + // fix up indexes + entities[0].numbrushes += newbrushes; + for (i=1 ; i<num_entities ; i++) + { + if ( entities[ i ].firstbrush < mapent->firstbrush ) // if we use <=, then we'll remap the passed in ent, which we don't want to + { + entities[ i ].firstbrush += newbrushes; + } + } + free (temp); + + mapent->numbrushes = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Iterates the sides of brush and removed CONTENTS_DETAIL from each side +// Input : *brush - +//----------------------------------------------------------------------------- +void RemoveContentsDetailFromBrush( mapbrush_t *brush ) +{ + // Only valid on non-world brushes + Assert( brush->entitynum != 0 ); + + side_t *s; + int i; + + s = &brush->original_sides[0]; + for ( i=0 ; i<brush->numsides ; i++, s++ ) + { + if ( s->contents & CONTENTS_DETAIL ) + { + s->contents &= ~CONTENTS_DETAIL; + } + } + +} + +//----------------------------------------------------------------------------- +// Purpose: Iterates all brushes in an entity and removes CONTENTS_DETAIL from all brushes +// Input : *mapent - +//----------------------------------------------------------------------------- +void CMapFile::RemoveContentsDetailFromEntity( entity_t *mapent ) +{ + int i; + for ( i = 0; i < mapent->numbrushes; i++ ) + { + int brushnum = mapent->firstbrush + i; + + mapbrush_t *brush = &mapbrushes[ brushnum ]; + RemoveContentsDetailFromBrush( brush ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFile - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispDistancesCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispDistancesKeyCallback, pMapDispInfo)); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : szKey - +// szValue - +// pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispDistancesKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!strnicmp(szKey, "row", 3)) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy(szBuf, szValue); + + int nCols = (1 << pMapDispInfo->power) + 1; + int nRow = atoi(&szKey[3]); + + char *pszNext = strtok(szBuf, " "); + int nIndex = nRow * nCols; + + while (pszNext != NULL) + { + pMapDispInfo->dispDists[nIndex] = (float)atof(pszNext); + pszNext = strtok(NULL, " "); + nIndex++; + } + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: load in the displacement info "chunk" from the .map file into the +// vbsp map displacement info data structure +// Output : return the index of the map displacement info +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispInfoCallback(CChunkFile *pFile, mapdispinfo_t **ppMapDispInfo ) +{ + // + // check to see if we exceeded the maximum displacement info list size + // + if (nummapdispinfo > MAX_MAP_DISPINFO) + { + g_MapError.ReportError( "ParseDispInfoChunk: nummapdispinfo > MAX_MAP_DISPINFO" ); + } + + // get a pointer to the next available displacement info slot + mapdispinfo_t *pMapDispInfo = &mapdispinfo[nummapdispinfo]; + nummapdispinfo++; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler("normals", (ChunkHandler_t)LoadDispNormalsCallback, pMapDispInfo); + Handlers.AddHandler("distances", (ChunkHandler_t)LoadDispDistancesCallback, pMapDispInfo); + Handlers.AddHandler("offsets", (ChunkHandler_t)LoadDispOffsetsCallback, pMapDispInfo); + Handlers.AddHandler("alphas", (ChunkHandler_t)LoadDispAlphasCallback, pMapDispInfo); + Handlers.AddHandler("triangle_tags", (ChunkHandler_t)LoadDispTriangleTagsCallback, pMapDispInfo); + +#ifdef VSVMFIO + Handlers.AddHandler("offset_normals", (ChunkHandler_t)LoadDispOffsetNormalsCallback, pMapDispInfo); +#endif // VSVMFIO + + // + // Read the displacement chunk. + // + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadDispInfoKeyCallback, pMapDispInfo); + pFile->PopHandlers(); + + if (eResult == ChunkFile_Ok) + { + // return a pointer to the displacement info + *ppMapDispInfo = pMapDispInfo; + } + + return(eResult); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *mapent - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispInfoKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!stricmp(szKey, "power")) + { + CChunkFile::ReadKeyValueInt(szValue, pMapDispInfo->power); + } +#ifdef VSVMFIO + else if (!stricmp(szKey, "elevation")) + { + CChunkFile::ReadKeyValueFloat(szValue, pMapDispInfo->m_elevation); + } +#endif // VSVMFIO + else if (!stricmp(szKey, "uaxis")) + { + CChunkFile::ReadKeyValueVector3(szValue, pMapDispInfo->uAxis); + } + else if (!stricmp(szKey, "vaxis")) + { + CChunkFile::ReadKeyValueVector3(szValue, pMapDispInfo->vAxis); + } + else if( !stricmp( szKey, "startposition" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pMapDispInfo->startPosition ); + } + else if( !stricmp( szKey, "flags" ) ) + { + CChunkFile::ReadKeyValueInt( szValue, pMapDispInfo->flags ); + } +#if 0 // old data + else if (!stricmp( szKey, "alpha" ) ) + { + CChunkFile::ReadKeyValueVector4( szValue, pMapDispInfo->alphaValues ); + } +#endif + else if (!stricmp(szKey, "mintess")) + { + CChunkFile::ReadKeyValueInt(szValue, pMapDispInfo->minTess); + } + else if (!stricmp(szKey, "smooth")) + { + CChunkFile::ReadKeyValueFloat(szValue, pMapDispInfo->smoothingAngle); + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFile - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispNormalsKeyCallback, pMapDispInfo)); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!strnicmp(szKey, "row", 3)) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy(szBuf, szValue); + + int nCols = (1 << pMapDispInfo->power) + 1; + int nRow = atoi(&szKey[3]); + + char *pszNext0 = strtok(szBuf, " "); + char *pszNext1 = strtok(NULL, " "); + char *pszNext2 = strtok(NULL, " "); + + int nIndex = nRow * nCols; + + while ((pszNext0 != NULL) && (pszNext1 != NULL) && (pszNext2 != NULL)) + { + pMapDispInfo->vectorDisps[nIndex][0] = (float)atof(pszNext0); + pMapDispInfo->vectorDisps[nIndex][1] = (float)atof(pszNext1); + pMapDispInfo->vectorDisps[nIndex][2] = (float)atof(pszNext2); + + pszNext0 = strtok(NULL, " "); + pszNext1 = strtok(NULL, " "); + pszNext2 = strtok(NULL, " "); + + nIndex++; + } + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispOffsetsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispOffsetsKeyCallback, pMapDispInfo)); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispOffsetsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!strnicmp(szKey, "row", 3)) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy(szBuf, szValue); + + int nCols = (1 << pMapDispInfo->power) + 1; + int nRow = atoi(&szKey[3]); + + char *pszNext0 = strtok(szBuf, " "); + char *pszNext1 = strtok(NULL, " "); + char *pszNext2 = strtok(NULL, " "); + + int nIndex = nRow * nCols; + + while ((pszNext0 != NULL) && (pszNext1 != NULL) && (pszNext2 != NULL)) + { + pMapDispInfo->vectorOffsets[nIndex][0] = (float)atof(pszNext0); + pMapDispInfo->vectorOffsets[nIndex][1] = (float)atof(pszNext1); + pMapDispInfo->vectorOffsets[nIndex][2] = (float)atof(pszNext2); + + pszNext0 = strtok(NULL, " "); + pszNext1 = strtok(NULL, " "); + pszNext2 = strtok(NULL, " "); + + nIndex++; + } + } + + return(ChunkFile_Ok); +} + + +#ifdef VSVMFIO +ChunkFileResult_t LoadDispOffsetNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispOffsetNormalsKeyCallback, pMapDispInfo)); +} + + +ChunkFileResult_t LoadDispOffsetNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!strnicmp(szKey, "row", 3)) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy(szBuf, szValue); + + int nCols = (1 << pMapDispInfo->power) + 1; + int nRow = atoi(&szKey[3]); + + char *pszNext0 = strtok(szBuf, " "); + char *pszNext1 = strtok(NULL, " "); + char *pszNext2 = strtok(NULL, " "); + + int nIndex = nRow * nCols; + + while ((pszNext0 != NULL) && (pszNext1 != NULL) && (pszNext2 != NULL)) + { + pMapDispInfo->m_offsetNormals[nIndex][0] = (float)atof(pszNext0); + pMapDispInfo->m_offsetNormals[nIndex][1] = (float)atof(pszNext1); + pMapDispInfo->m_offsetNormals[nIndex][2] = (float)atof(pszNext2); + + pszNext0 = strtok(NULL, " "); + pszNext1 = strtok(NULL, " "); + pszNext2 = strtok(NULL, " "); + + nIndex++; + } + } + + return(ChunkFile_Ok); +} +#endif // VSVMFIO + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispAlphasCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispAlphasKeyCallback, pMapDispInfo)); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispAlphasKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!strnicmp(szKey, "row", 3)) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy(szBuf, szValue); + + int nCols = (1 << pMapDispInfo->power) + 1; + int nRow = atoi(&szKey[3]); + + char *pszNext0 = strtok(szBuf, " "); + + int nIndex = nRow * nCols; + + while (pszNext0 != NULL) + { + pMapDispInfo->alphaValues[nIndex] = (float)atof(pszNext0); + pszNext0 = strtok(NULL, " "); + nIndex++; + } + } + + return(ChunkFile_Ok); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispTriangleTagsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispTriangleTagsKeyCallback, pMapDispInfo)); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispTriangleTagsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if ( !strnicmp( szKey, "row", 3 ) ) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy( szBuf, szValue ); + + int nCols = ( 1 << pMapDispInfo->power ); + int nRow = atoi( &szKey[3] ); + + char *pszNext = strtok( szBuf, " " ); + + int nIndex = nRow * nCols; + int iTri = nIndex * 2; + + while ( pszNext != NULL ) + { + // Collapse the tags here! + unsigned short nTriTags = ( unsigned short )atoi( pszNext ); + + // Walkable + bool bWalkable = ( ( nTriTags & COREDISPTRI_TAG_WALKABLE ) != 0 ); + if ( ( ( nTriTags & COREDISPTRI_TAG_FORCE_WALKABLE_BIT ) != 0 ) ) + { + bWalkable = ( ( nTriTags & COREDISPTRI_TAG_FORCE_WALKABLE_VAL ) != 0 ); + } + + // Buildable + bool bBuildable = ( ( nTriTags & COREDISPTRI_TAG_BUILDABLE ) != 0 ); + if ( ( ( nTriTags & COREDISPTRI_TAG_FORCE_BUILDABLE_BIT ) != 0 ) ) + { + bBuildable = ( ( nTriTags & COREDISPTRI_TAG_FORCE_BUILDABLE_VAL ) != 0 ); + } + + nTriTags = 0; + if ( bWalkable ) + { + nTriTags |= DISPTRI_TAG_WALKABLE; + } + + if ( bBuildable ) + { + nTriTags |= DISPTRI_TAG_BUILDABLE; + } + + pMapDispInfo->triTags[iTri] = nTriTags; + pszNext = strtok( NULL, " " ); + iTri++; + } + } + + return( ChunkFile_Ok ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : brushSideID - +// Output : int +//----------------------------------------------------------------------------- +int CMapFile::SideIDToIndex( int brushSideID ) +{ + int i; + for ( i = 0; i < nummapbrushsides; i++ ) + { + if ( brushsides[i].id == brushSideID ) + { + return i; + } + } + Assert( 0 ); + return -1; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mapent - +// *key - +//----------------------------------------------------------------------------- +void ConvertSideList( entity_t *mapent, char *key ) +{ + char *pszSideList = ValueForKey( mapent, key ); + + if (pszSideList) + { + char *pszTmpList = ( char* )_alloca( strlen( pszSideList ) + 1 ); + strcpy( pszTmpList, pszSideList ); + + bool bFirst = true; + char szNewValue[1024]; + szNewValue[0] = '\0'; + + const char *pszScan = strtok( pszTmpList, " " ); + if ( !pszScan ) + return; + do + { + int nSideID; + + if ( sscanf( pszScan, "%d", &nSideID ) == 1 ) + { + int nIndex = g_LoadingMap->SideIDToIndex(nSideID); + if (nIndex != -1) + { + if (!bFirst) + { + strcat( szNewValue, " " ); + } + else + { + bFirst = false; + } + + char szIndex[15]; + itoa( nIndex, szIndex, 10 ); + strcat( szNewValue, szIndex ); + } + } + } while ( ( pszScan = strtok( NULL, " " ) ) ); + + SetKeyValue( mapent, key, szNewValue ); + } +} + + +// Add all the sides referenced by info_no_dynamic_shadows entities to g_NoDynamicShadowSides. +ChunkFileResult_t HandleNoDynamicShadowsEnt( entity_t *pMapEnt ) +{ + // Get the list of the sides. + char *pSideList = ValueForKey( pMapEnt, "sides" ); + + // Parse the side list. + char *pScan = strtok( pSideList, " " ); + if( pScan ) + { + do + { + int brushSideID; + if( sscanf( pScan, "%d", &brushSideID ) == 1 ) + { + if ( g_NoDynamicShadowSides.Find( brushSideID ) == -1 ) + g_NoDynamicShadowSides.AddToTail( brushSideID ); + } + } while( ( pScan = strtok( NULL, " " ) ) ); + } + + // Clear out this entity. + pMapEnt->epairs = NULL; + return ( ChunkFile_Ok ); +} + + +static ChunkFileResult_t LoadOverlayDataTransitionKeyCallback( const char *szKey, const char *szValue, mapoverlay_t *pOverlay ) +{ + if ( !stricmp( szKey, "material" ) ) + { + // Get the material name. + const char *pMaterialName = szValue; + if( g_ReplaceMaterials ) + { + pMaterialName = ReplaceMaterialName( szValue ); + } + + Assert( strlen( pMaterialName ) < OVERLAY_MAP_STRLEN ); + if ( strlen( pMaterialName ) >= OVERLAY_MAP_STRLEN ) + { + Error( "Overlay Material Name (%s) > OVERLAY_MAP_STRLEN (%d)", pMaterialName, OVERLAY_MAP_STRLEN ); + return ChunkFile_Fail; + } + strcpy( pOverlay->szMaterialName, pMaterialName ); + } + else if ( !stricmp( szKey, "StartU") ) + { + CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flU[0] ); + } + else if ( !stricmp( szKey, "EndU" ) ) + { + CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flU[1] ); + } + else if ( !stricmp( szKey, "StartV" ) ) + { + CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flV[0] ); + } + else if ( !stricmp( szKey, "EndV" ) ) + { + CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flV[1] ); + } + else if ( !stricmp( szKey, "BasisOrigin" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecOrigin ); + } + else if ( !stricmp( szKey, "BasisU" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecBasis[0] ); + } + else if ( !stricmp( szKey, "BasisV" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecBasis[1] ); + } + else if ( !stricmp( szKey, "BasisNormal" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecBasis[2] ); + } + else if ( !stricmp( szKey, "uv0" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[0] ); + } + else if ( !stricmp( szKey, "uv1" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[1] ); + } + else if ( !stricmp( szKey, "uv2" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[2] ); + } + else if ( !stricmp( szKey, "uv3" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[3] ); + } + else if ( !stricmp( szKey, "sides" ) ) + { + const char *pSideList = szValue; + char *pTmpList = ( char* )_alloca( strlen( pSideList ) + 1 ); + strcpy( pTmpList, pSideList ); + const char *pScan = strtok( pTmpList, " " ); + if ( !pScan ) + return ChunkFile_Fail; + + pOverlay->aSideList.Purge(); + pOverlay->aFaceList.Purge(); + + do + { + int nSideId; + if ( sscanf( pScan, "%d", &nSideId ) == 1 ) + { + pOverlay->aSideList.AddToTail( nSideId ); + } + } while ( ( pScan = strtok( NULL, " " ) ) ); + } + + return ChunkFile_Ok; +} + +static ChunkFileResult_t LoadOverlayDataTransitionCallback( CChunkFile *pFile, int nParam ) +{ + int iOverlay = g_aMapWaterOverlays.AddToTail(); + mapoverlay_t *pOverlay = &g_aMapWaterOverlays[iOverlay]; + if ( !pOverlay ) + return ChunkFile_Fail; + + pOverlay->nId = ( MAX_MAP_OVERLAYS + 1 ) + g_aMapWaterOverlays.Count() - 1; + pOverlay->m_nRenderOrder = 0; + + ChunkFileResult_t eResult = pFile->ReadChunk( ( KeyHandler_t )LoadOverlayDataTransitionKeyCallback, pOverlay ); + return eResult; +} + +static ChunkFileResult_t LoadOverlayTransitionCallback( CChunkFile *pFile, int nParam ) +{ + CChunkHandlerMap Handlers; + Handlers.AddHandler( "overlaydata", ( ChunkHandler_t )LoadOverlayDataTransitionCallback, 0 ); + pFile->PushHandlers( &Handlers ); + + ChunkFileResult_t eResult = pFile->ReadChunk( NULL, NULL ); + + pFile->PopHandlers(); + + return eResult; +} + +//----------------------------------------------------------------------------- +// Purpose: Iterates all brushes in a ladder entity, generates its mins and maxs. +// These are stored in the object, since the brushes are going to go away. +// Input : *mapent - +//----------------------------------------------------------------------------- +void CMapFile::AddLadderKeys( entity_t *mapent ) +{ + Vector mins, maxs; + ClearBounds( mins, maxs ); + + int i; + for ( i = 0; i < mapent->numbrushes; i++ ) + { + int brushnum = mapent->firstbrush + i; + mapbrush_t *brush = &mapbrushes[ brushnum ]; + + AddPointToBounds( brush->mins, mins, maxs ); + AddPointToBounds( brush->maxs, mins, maxs ); + } + + char buf[16]; + + Q_snprintf( buf, sizeof(buf), "%2.2f", mins.x ); + SetKeyValue( mapent, "mins.x", buf ); + + Q_snprintf( buf, sizeof(buf), "%2.2f", mins.y ); + SetKeyValue( mapent, "mins.y", buf ); + + Q_snprintf( buf, sizeof(buf), "%2.2f", mins.z ); + SetKeyValue( mapent, "mins.z", buf ); + + Q_snprintf( buf, sizeof(buf), "%2.2f", maxs.x ); + SetKeyValue( mapent, "maxs.x", buf ); + + Q_snprintf( buf, sizeof(buf), "%2.2f", maxs.y ); + SetKeyValue( mapent, "maxs.y", buf ); + + Q_snprintf( buf, sizeof(buf), "%2.2f", maxs.z ); + SetKeyValue( mapent, "maxs.z", buf ); +} + +ChunkFileResult_t LoadEntityCallback(CChunkFile *pFile, int nParam) +{ + return g_LoadingMap->LoadEntityCallback( pFile, nParam ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFile - +// ulParam - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapFile::LoadEntityCallback(CChunkFile *pFile, int nParam) +{ + if (num_entities == MAX_MAP_ENTITIES) + { + // Exits. + g_MapError.ReportError ("num_entities == MAX_MAP_ENTITIES"); + } + + entity_t *mapent = &entities[num_entities]; + num_entities++; + memset(mapent, 0, sizeof(*mapent)); + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; + //mapent->portalareas[0] = -1; + //mapent->portalareas[1] = -1; + + LoadEntity_t LoadEntity; + LoadEntity.pEntity = mapent; + + // No default flags/contents + LoadEntity.nBaseFlags = 0; + LoadEntity.nBaseContents = 0; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler("solid", (ChunkHandler_t)::LoadSolidCallback, &LoadEntity); + Handlers.AddHandler("connections", (ChunkHandler_t)LoadConnectionsCallback, &LoadEntity); + Handlers.AddHandler( "overlaytransition", ( ChunkHandler_t )LoadOverlayTransitionCallback, 0 ); + + // + // Read the entity chunk. + // + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadEntityKeyCallback, &LoadEntity); + pFile->PopHandlers(); + + if (eResult == ChunkFile_Ok) + { + GetVectorForKey (mapent, "origin", mapent->origin); + + const char *pMinDXLevelStr = ValueForKey( mapent, "mindxlevel" ); + const char *pMaxDXLevelStr = ValueForKey( mapent, "maxdxlevel" ); + if( *pMinDXLevelStr != '\0' || *pMaxDXLevelStr != '\0' ) + { + int min = 0; + int max = 0; + if( *pMinDXLevelStr ) + { + min = atoi( pMinDXLevelStr ); + } + if( *pMaxDXLevelStr ) + { + max = atoi( pMaxDXLevelStr ); + } + + // Set min and max to default values. + if( min == 0 ) + { + min = g_nDXLevel; + } + if( max == 0 ) + { + max = g_nDXLevel; + } + if( ( g_nDXLevel != 0 ) && ( g_nDXLevel < min || g_nDXLevel > max ) ) + { + mapent->numbrushes = 0; + mapent->epairs = NULL; + return(ChunkFile_Ok); + } + } + + // offset all of the planes and texinfo + if ( mapent->origin[0] || mapent->origin[1] || mapent->origin[2] ) + { + for (int i=0 ; i<mapent->numbrushes ; i++) + { + mapbrush_t *b = &mapbrushes[mapent->firstbrush + i]; + for (int j=0 ; j<b->numsides ; j++) + { + side_t *s = &b->original_sides[j]; + vec_t newdist = mapplanes[s->planenum].dist - DotProduct (mapplanes[s->planenum].normal, mapent->origin); + s->planenum = FindFloatPlane (mapplanes[s->planenum].normal, newdist); + if ( !onlyents ) + { + s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], &side_brushtextures[s-brushsides], mapent->origin); + } + } + MakeBrushWindings (b); + } + } + + // + // func_detail brushes are moved into the world entity. The CONTENTS_DETAIL flag was set by the loader. + // + const char *pClassName = ValueForKey( mapent, "classname" ); + + if ( !strcmp( "func_detail", pClassName ) ) + { + MoveBrushesToWorld (mapent); + mapent->numbrushes = 0; + + // clear out this entity + mapent->epairs = NULL; + return(ChunkFile_Ok); + } + + // these get added to a list for processing the portal file + // but aren't necessary to emit to the BSP + if ( !strcmp( "func_viscluster", pClassName ) ) + { + AddVisCluster(mapent); + return(ChunkFile_Ok); + } + + // + // func_ladder brushes are moved into the world entity. We convert the func_ladder to an info_ladder + // that holds the ladder's mins and maxs, and leave the entity. This helps the bots figure out ladders. + // + if ( !strcmp( "func_ladder", pClassName ) ) + { + AddLadderKeys( mapent ); + + MoveBrushesToWorld (mapent); + + // Convert to info_ladder entity + SetKeyValue( mapent, "classname", "info_ladder" ); + + return(ChunkFile_Ok); + } + + if( !strcmp( "env_cubemap", pClassName ) ) + { + if( ( g_nDXLevel == 0 ) || ( g_nDXLevel >= 70 ) ) + { + const char *pSideListStr = ValueForKey( mapent, "sides" ); + int size; + size = IntForKey( mapent, "cubemapsize" ); + Cubemap_InsertSample( mapent->origin, size ); + Cubemap_SaveBrushSides( pSideListStr ); + } + // clear out this entity + mapent->epairs = NULL; + return(ChunkFile_Ok); + } + + if ( !strcmp( "test_sidelist", pClassName ) ) + { + ConvertSideList(mapent, "sides"); + return ChunkFile_Ok; + } + + if ( !strcmp( "info_overlay", pClassName ) ) + { + int iAccessorID = Overlay_GetFromEntity( mapent ); + + if ( iAccessorID < 0 ) + { + // Clear out this entity. + mapent->epairs = NULL; + } + else + { + // Convert to info_overlay_accessor entity + SetKeyValue( mapent, "classname", "info_overlay_accessor" ); + + // Remember the id for accessing the overlay + char buf[16]; + Q_snprintf( buf, sizeof(buf), "%i", iAccessorID ); + SetKeyValue( mapent, "OverlayID", buf ); + } + + return ( ChunkFile_Ok ); + } + + if ( !strcmp( "info_overlay_transition", pClassName ) ) + { + // Clear out this entity. + mapent->epairs = NULL; + return ( ChunkFile_Ok ); + } + + if ( Q_stricmp( pClassName, "info_no_dynamic_shadow" ) == 0 ) + { + return HandleNoDynamicShadowsEnt( mapent ); + } + + if ( Q_stricmp( pClassName, "func_instance_parms" ) == 0 ) + { + // Clear out this entity. + mapent->epairs = NULL; + return ( ChunkFile_Ok ); + } + + // areaportal entities move their brushes, but don't eliminate + // the entity + if( IsAreaPortal( pClassName ) ) + { + char str[128]; + + if (mapent->numbrushes != 1) + { + Error ("Entity %i: func_areaportal can only be a single brush", num_entities-1); + } + + mapbrush_t *b = &mapbrushes[nummapbrushes-1]; + b->contents = CONTENTS_AREAPORTAL; + c_areaportals++; + mapent->areaportalnum = c_areaportals; + + // set the portal number as "portalnumber" + sprintf (str, "%i", c_areaportals); + SetKeyValue (mapent, "portalnumber", str); + + MoveBrushesToWorld (mapent); + return(ChunkFile_Ok); + } + +#ifdef VSVMFIO + if ( !Q_stricmp( pClassName, "light" ) ) + { + CVmfImport::GetVmfImporter()->ImportLightCallback( + ValueForKey( mapent, "hammerid" ), + ValueForKey( mapent, "origin" ), + ValueForKey( mapent, "_light" ), + ValueForKey( mapent, "_lightHDR" ), + ValueForKey( mapent, "_lightscaleHDR" ), + ValueForKey( mapent, "_quadratic_attn" ) ); + } + + if ( !Q_stricmp( pClassName, "light_spot" ) ) + { + CVmfImport::GetVmfImporter()->ImportLightSpotCallback( + ValueForKey( mapent, "hammerid" ), + ValueForKey( mapent, "origin" ), + ValueForKey( mapent, "angles" ), + ValueForKey( mapent, "pitch" ), + ValueForKey( mapent, "_light" ), + ValueForKey( mapent, "_lightHDR" ), + ValueForKey( mapent, "_lightscaleHDR" ), + ValueForKey( mapent, "_quadratic_attn" ), + ValueForKey( mapent, "_inner_cone" ), + ValueForKey( mapent, "_cone" ), + ValueForKey( mapent, "_exponent" ) ); + } + + if ( !Q_stricmp( pClassName, "light_dynamic" ) ) + { + CVmfImport::GetVmfImporter()->ImportLightDynamicCallback( + ValueForKey( mapent, "hammerid" ), + ValueForKey( mapent, "origin" ), + ValueForKey( mapent, "angles" ), + ValueForKey( mapent, "pitch" ), + ValueForKey( mapent, "_light" ), + ValueForKey( mapent, "_quadratic_attn" ), + ValueForKey( mapent, "_inner_cone" ), + ValueForKey( mapent, "_cone" ), + ValueForKey( mapent, "brightness" ), + ValueForKey( mapent, "distance" ), + ValueForKey( mapent, "spotlight_radius" ) ); + } + + if ( !Q_stricmp( pClassName, "light_environment" ) ) + { + CVmfImport::GetVmfImporter()->ImportLightEnvironmentCallback( + ValueForKey( mapent, "hammerid" ), + ValueForKey( mapent, "origin" ), + ValueForKey( mapent, "angles" ), + ValueForKey( mapent, "pitch" ), + ValueForKey( mapent, "_light" ), + ValueForKey( mapent, "_lightHDR" ), + ValueForKey( mapent, "_lightscaleHDR" ), + ValueForKey( mapent, "_ambient" ), + ValueForKey( mapent, "_ambientHDR" ), + ValueForKey( mapent, "_AmbientScaleHDR" ), + ValueForKey( mapent, "SunSpreadAngle" ) ); + } + + const char *pModel = ValueForKey( mapent, "model" ); + if ( pModel && Q_strlen( pModel ) ) + { + CVmfImport::GetVmfImporter()->ImportModelCallback( + pModel, + ValueForKey( mapent, "hammerid" ), + ValueForKey( mapent, "angles" ), + ValueForKey( mapent, "origin" ), + MDagPath() ); + } +#endif // VSVMFIO + + // If it's not in the world at this point, unmark CONTENTS_DETAIL from all sides... + if ( mapent != &entities[ 0 ] ) + { + RemoveContentsDetailFromEntity( mapent ); + } + + return(ChunkFile_Ok); + } + + return(eResult); +} + + +entity_t* EntityByName( char const *pTestName ) +{ + if( !pTestName ) + return 0; + + for( int i=0; i < g_MainMap->num_entities; i++ ) + { + entity_t *e = &g_MainMap->entities[i]; + + const char *pName = ValueForKey( e, "targetname" ); + if( stricmp( pName, pTestName ) == 0 ) + return e; + } + + return 0; +} + + +void CMapFile::ForceFuncAreaPortalWindowContents() +{ + // Now go through all areaportal entities and force CONTENTS_WINDOW + // on the brushes of the bmodels they point at. + char *targets[] = {"target", "BackgroundBModel"}; + int nTargets = sizeof(targets) / sizeof(targets[0]); + + for( int i=0; i < num_entities; i++ ) + { + entity_t *e = &entities[i]; + + const char *pClassName = ValueForKey( e, "classname" ); + + // Don't do this on "normal" func_areaportal entities. Those are tied to doors + // and should be opaque when closed. But areaportal windows (and any other + // distance-based areaportals) should be windows because they are normally open/transparent + if( !IsAreaPortal( pClassName ) || !Q_stricmp( pClassName, "func_areaportal" ) ) + continue; + +// const char *pTestEntName = ValueForKey( e, "targetname" ); + + for( int iTarget=0; iTarget < nTargets; iTarget++ ) + { + char const *pEntName = ValueForKey( e, targets[iTarget] ); + if( !pEntName[0] ) + continue; + + entity_t *pBrushEnt = EntityByName( pEntName ); + if( !pBrushEnt ) + continue; + + for( int iBrush=0; iBrush < pBrushEnt->numbrushes; iBrush++ ) + { + mapbrushes[pBrushEnt->firstbrush + iBrush].contents &= ~CONTENTS_SOLID; + mapbrushes[pBrushEnt->firstbrush + iBrush].contents |= CONTENTS_TRANSLUCENT | CONTENTS_WINDOW; + } + } + } +} + + +// ============ Instancing ============ + +// #define MERGE_INSTANCE_DEBUG_INFO 1 + +#define INSTANCE_VARIABLE_KEY "replace" + +static GameData GD; + +//----------------------------------------------------------------------------- +// Purpose: this function will read in a standard key / value file +// Input : pFilename - the absolute name of the file to read +// Output : returns the KeyValues of the file, NULL if the file could not be read. +//----------------------------------------------------------------------------- +static KeyValues *ReadKeyValuesFile( const char *pFilename ) +{ + // Read in the gameinfo.txt file and null-terminate it. + FILE *fp = fopen( pFilename, "rb" ); + if ( !fp ) + return NULL; + CUtlVector<char> buf; + fseek( fp, 0, SEEK_END ); + buf.SetSize( ftell( fp ) + 1 ); + fseek( fp, 0, SEEK_SET ); + fread( buf.Base(), 1, buf.Count()-1, fp ); + fclose( fp ); + buf[buf.Count()-1] = 0; + + KeyValues *kv = new KeyValues( "" ); + if ( !kv->LoadFromBuffer( pFilename, buf.Base() ) ) + { + kv->deleteThis(); + return NULL; + } + + return kv; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will set a secondary lookup path for instances. +// Input : pszInstancePath - the secondary lookup path +//----------------------------------------------------------------------------- +void CMapFile::SetInstancePath( const char *pszInstancePath ) +{ + strcpy( m_InstancePath, pszInstancePath ); + V_strlower( m_InstancePath ); + V_FixSlashes( m_InstancePath ); +} + + +//----------------------------------------------------------------------------- +// Purpose: This function will attempt to find a full path given the base and relative names. +// Input : pszBaseFileName - the base file that referenced this instance +// pszInstanceFileName - the relative file name of this instance +// Output : Returns true if it was able to locate the file +// pszOutFileName - the full path to the file name if located +//----------------------------------------------------------------------------- +bool CMapFile::DeterminePath( const char *pszBaseFileName, const char *pszInstanceFileName, char *pszOutFileName ) +{ + char szInstanceFileNameFixed[ MAX_PATH ]; + const char *pszMapPath = "\\maps\\"; + + strcpy( szInstanceFileNameFixed, pszInstanceFileName ); + V_SetExtension( szInstanceFileNameFixed, ".vmf", sizeof( szInstanceFileNameFixed ) ); + V_FixSlashes( szInstanceFileNameFixed ); + + // first, try to find a relative location based upon the Base file name + strcpy( pszOutFileName, pszBaseFileName ); + V_StripFilename( pszOutFileName ); + + strcat( pszOutFileName, "\\" ); + strcat( pszOutFileName, szInstanceFileNameFixed ); + + if ( g_pFullFileSystem->FileExists( pszOutFileName ) ) + { + return true; + } + + // second, try to find the master 'maps' directory and make it relative from that + strcpy( pszOutFileName, pszBaseFileName ); + V_StripFilename( pszOutFileName ); + V_RemoveDotSlashes( pszOutFileName ); + V_FixDoubleSlashes( pszOutFileName ); + V_strlower( pszOutFileName ); + strcat( pszOutFileName, "\\" ); + + char *pos = strstr( pszOutFileName, pszMapPath ); + if ( pos ) + { + pos += strlen( pszMapPath ); + *pos = 0; + strcat( pszOutFileName, szInstanceFileNameFixed ); + + if ( g_pFullFileSystem->FileExists( pszOutFileName ) ) + { + return true; + } + } + + if ( m_InstancePath[ 0 ] != 0 ) + { + sprintf( szInstanceFileNameFixed, "%s%s", m_InstancePath, pszInstanceFileName ); + + if ( g_pFullFileSystem->FileExists( szInstanceFileNameFixed, "GAME" ) ) + { + char FullPath[ MAX_PATH ]; + g_pFullFileSystem->RelativePathToFullPath( szInstanceFileNameFixed, "GAME", FullPath, sizeof( FullPath ) ); + strcpy( pszOutFileName, FullPath ); + + return true; + } + } + + pszOutFileName[ 0 ] = 0; + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will check the main map for any func_instances. It will +// also attempt to load in the gamedata file for instancing remapping help. +// Input : none +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::CheckForInstances( const char *pszFileName ) +{ + if ( this != g_MainMap ) + { // all sub-instances will be appended to the main map master list as they are read in + // so the main loop below will naturally get to the appended ones. + return; + } + + char GameInfoPath[ MAX_PATH ]; + + g_pFullFileSystem->RelativePathToFullPath( "gameinfo.txt", "MOD", GameInfoPath, sizeof( GameInfoPath ) ); + KeyValues *GameInfoKV = ReadKeyValuesFile( GameInfoPath ); + if ( !GameInfoKV ) + { + Msg( "Could not locate gameinfo.txt for Instance Remapping at %s\n", GameInfoPath ); + return; + } + + const char *InstancePath = GameInfoKV->GetString( "InstancePath", NULL ); + if ( InstancePath ) + { + CMapFile::SetInstancePath( InstancePath ); + } + + const char *GameDataFile = GameInfoKV->GetString( "GameData", NULL ); + if ( !GameDataFile ) + { + Msg( "Could not locate 'GameData' key in %s\n", GameInfoPath ); + return; + } + + char FDGPath[ MAX_PATH ]; + if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, "EXECUTABLE_PATH", FDGPath, sizeof( FDGPath ) ) ) + { + if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, NULL, FDGPath, sizeof( FDGPath ) ) ) + { + Msg( "Could not locate GameData file %s\n", GameDataFile ); + } + } + + GD.Load( FDGPath ); + + // this list will grow as instances are merged onto it. sub-instances are merged and + // automatically done in this processing. + for ( int i = 0; i < num_entities; i++ ) + { + char *pEntity = ValueForKey( &entities[ i ], "classname" ); + if ( !strcmp( pEntity, "func_instance" ) ) + { + char *pInstanceFile = ValueForKey( &entities[ i ], "file" ); + if ( pInstanceFile[ 0 ] ) + { + char InstancePath[ MAX_PATH ]; + bool bLoaded = false; + + if ( DeterminePath( pszFileName, pInstanceFile, InstancePath ) ) + { + if ( LoadMapFile( InstancePath ) ) + { + MergeInstance( &entities[ i ], g_LoadingMap ); + delete g_LoadingMap; + bLoaded = true; + } + } + + if ( bLoaded == false ) + { + Color red( 255, 0, 0, 255 ); + + ColorSpewMessage( SPEW_ERROR, &red, "Could not open instance file %s\n", pInstanceFile ); + } + } + + entities[ i ].numbrushes = 0; + entities[ i ].epairs = NULL; + } + } + + g_LoadingMap = this; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will do all of the necessary work to merge the instance +// into the main map. +// Input : pInstanceEntity - the entity of the func_instance +// Instance - the map file of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergeInstance( entity_t *pInstanceEntity, CMapFile *Instance ) +{ + matrix3x4_t mat; + QAngle angles; + Vector OriginOffset = pInstanceEntity->origin; + + m_InstanceCount++; + + GetAnglesForKey( pInstanceEntity, "angles", angles ); + AngleMatrix( angles, OriginOffset, mat ); + +#ifdef MERGE_INSTANCE_DEBUG_INFO + Msg( "Instance Remapping: O:( %g, %g, %g ) A:( %g, %g, %g )\n", OriginOffset.x, OriginOffset.y, OriginOffset.z, angles.x, angles.y, angles.z ); +#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO + MergePlanes( pInstanceEntity, Instance, OriginOffset, angles, mat ); + MergeBrushes( pInstanceEntity, Instance, OriginOffset, angles, mat ); + MergeBrushSides( pInstanceEntity, Instance, OriginOffset, angles, mat ); + MergeEntities( pInstanceEntity, Instance, OriginOffset, angles, mat ); + MergeOverlays( pInstanceEntity, Instance, OriginOffset, angles, mat ); +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will merge in the map planes from the instance into +// the main map. +// Input : pInstanceEntity - the entity of the func_instance +// Instance - the map file of the instance +// InstanceOrigin - the translation of the instance +// InstanceAngle - the rotation of the instance +// InstanceMatrix - the translation / rotation matrix of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergePlanes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) +{ + // Each pair of planes needs to be added to the main map + for ( int i = 0; i < Instance->nummapplanes; i += 2 ) + { + FindFloatPlane( Instance->mapplanes[i].normal, Instance->mapplanes[i].dist ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will merge in the map brushes from the instance into +// the main map. +// Input : pInstanceEntity - the entity of the func_instance +// Instance - the map file of the instance +// InstanceOrigin - the translation of the instance +// InstanceAngle - the rotation of the instance +// InstanceMatrix - the translation / rotation matrix of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergeBrushes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) +{ + int max_brush_id = 0; + + for( int i = 0; i < nummapbrushes; i++ ) + { + if ( mapbrushes[ i ].id > max_brush_id ) + { + max_brush_id = mapbrushes[ i ].id; + } + } + + for( int i = 0; i < Instance->nummapbrushes; i++ ) + { + mapbrushes[ nummapbrushes + i ] = Instance->mapbrushes[ i ]; + + mapbrush_t *brush = &mapbrushes[ nummapbrushes + i ]; + brush->entitynum += num_entities; + brush->brushnum += nummapbrushes; + + if ( i < Instance->entities[ 0 ].numbrushes || ( brush->contents & CONTENTS_LADDER ) != 0 ) + { // world spawn brushes as well as ladders we physically move + Vector minsIn = brush->mins; + Vector maxsIn = brush->maxs; + + TransformAABB( InstanceMatrix, minsIn, maxsIn, brush->mins, brush->maxs ); + } + else + { + } + brush->id += max_brush_id; + + int index = brush->original_sides - Instance->brushsides; + brush->original_sides = &brushsides[ nummapbrushsides + index ]; + } + + nummapbrushes += Instance->nummapbrushes; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will merge in the map sides from the instance into +// the main map. +// Input : pInstanceEntity - the entity of the func_instance +// Instance - the map file of the instance +// InstanceOrigin - the translation of the instance +// InstanceAngle - the rotation of the instance +// InstanceMatrix - the translation / rotation matrix of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergeBrushSides( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) +{ + int max_side_id = 0; + + for( int i = 0; i < nummapbrushsides; i++ ) + { + if ( brushsides[ i ].id > max_side_id ) + { + max_side_id = brushsides[ i ].id; + } + } + + for( int i = 0; i < Instance->nummapbrushsides; i++ ) + { + brushsides[ nummapbrushsides + i ] = Instance->brushsides[ i ]; + + side_t *side = &brushsides[ nummapbrushsides + i ]; + // The planes got merged & remapped. So you need to search for the output plane index on each side + // NOTE: You could optimize this by saving off an index map in MergePlanes + side->planenum = FindFloatPlane( Instance->mapplanes[side->planenum].normal, Instance->mapplanes[side->planenum].dist ); + side->id += max_side_id; + + // this could be pre-processed into a list for quicker checking + bool bNeedsTranslation = ( side->pMapDisp && side->pMapDisp->entitynum == 0 ); + if ( !bNeedsTranslation ) + { // check for sides that are part of the world spawn - those need translating + for( int j = 0; j < Instance->entities[ 0 ].numbrushes; j++ ) + { + int loc = Instance->mapbrushes[ j ].original_sides - Instance->brushsides; + + if ( i >= loc && i < ( loc + Instance->mapbrushes[ j ].numsides ) ) + { + bNeedsTranslation = true; + break; + } + } + } + if ( !bNeedsTranslation ) + { // sides for ladders are outside of the world spawn, but also need translating + for( int j = Instance->entities[ 0 ].numbrushes; j < Instance->nummapbrushes; j++ ) + { + int loc = Instance->mapbrushes[ j ].original_sides - Instance->brushsides; + + if ( i >= loc && i < ( loc + Instance->mapbrushes[ j ].numsides ) && ( Instance->mapbrushes[ j ].contents & CONTENTS_LADDER ) != 0 ) + { + bNeedsTranslation = true; + break; + } + } + } + if ( bNeedsTranslation ) + { // we only want to do the adjustment on world spawn brushes, not entity brushes + if ( side->winding ) + { + for( int point = 0; point < side->winding->numpoints; point++ ) + { + Vector inPoint = side->winding->p[ point ]; + VectorTransform( inPoint, InstanceMatrix, side->winding->p[ point ] ); + } + } + + int planenum = side->planenum; + cplane_t inPlane, outPlane; + inPlane.normal = mapplanes[ planenum ].normal; + inPlane.dist = mapplanes[ planenum ].dist; + + MatrixTransformPlane( InstanceMatrix, inPlane, outPlane ); + planenum = FindFloatPlane( outPlane.normal, outPlane.dist ); + side->planenum = planenum; + + brush_texture_t bt = Instance->side_brushtextures[ i ]; + + VectorRotate( Instance->side_brushtextures[ i ].UAxis, InstanceMatrix, bt.UAxis ); + VectorRotate( Instance->side_brushtextures[ i ].VAxis, InstanceMatrix, bt.VAxis ); + bt.shift[ 0 ] -= InstanceOrigin.Dot( bt.UAxis ) / bt.textureWorldUnitsPerTexel[ 0 ]; + bt.shift[ 1 ] -= InstanceOrigin.Dot( bt.VAxis ) / bt.textureWorldUnitsPerTexel[ 1 ]; + + if ( !onlyents ) + { + side->texinfo = TexinfoForBrushTexture ( &mapplanes[ side->planenum ], &bt, vec3_origin ); + } + } + + if ( side->pMapDisp ) + { + mapdispinfo_t *disp = side->pMapDisp; + + disp->brushSideID = side->id; + Vector inPoint = disp->startPosition; + VectorTransform( inPoint, InstanceMatrix, disp->startPosition ); + + disp->face.originalface = side; + disp->face.texinfo = side->texinfo; + disp->face.planenum = side->planenum; + disp->entitynum += num_entities; + + for( int point = 0; point < disp->face.w->numpoints; point++ ) + { + Vector inPoint = disp->face.w->p[ point ]; + VectorTransform( inPoint, InstanceMatrix, disp->face.w->p[ point ] ); + } + + } + } + + nummapbrushsides += Instance->nummapbrushsides; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will look for replace parameters in the function instance +// to see if there is anything in the epair that should be replaced. +// Input : pPair - the epair with the value +// pInstanceEntity - the func_instance that may ahve replace keywords +// Output : pPair - the value field may be updated +//----------------------------------------------------------------------------- +void CMapFile::ReplaceInstancePair( epair_t *pPair, entity_t *pInstanceEntity ) +{ + char Value[ MAX_KEYVALUE_LEN ], NewValue[ MAX_KEYVALUE_LEN ]; + bool Overwritten = false; + + strcpy( NewValue, pPair->value ); + for ( epair_t *epInstance = pInstanceEntity->epairs; epInstance != NULL; epInstance = epInstance->next ) + { + if ( strnicmp( epInstance->key, INSTANCE_VARIABLE_KEY, strlen( INSTANCE_VARIABLE_KEY ) ) == 0 ) + { + char InstanceVariable[ MAX_KEYVALUE_LEN ]; + + strcpy( InstanceVariable, epInstance->value ); + + char *ValuePos = strchr( InstanceVariable, ' ' ); + if ( !ValuePos ) + { + continue; + } + *ValuePos = 0; + ValuePos++; + + strcpy( Value, NewValue ); + if ( !V_StrSubst( Value, InstanceVariable, ValuePos, NewValue, sizeof( NewValue ), false ) ) + { + Overwritten = true; + break; + } + } + } + + if ( !Overwritten && strcmp( pPair->value, NewValue ) != 0 ) + { + free( pPair->value ); + pPair->value = copystring( NewValue ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will merge in the entities from the instance into +// the main map. +// Input : pInstanceEntity - the entity of the func_instance +// Instance - the map file of the instance +// InstanceOrigin - the translation of the instance +// InstanceAngle - the rotation of the instance +// InstanceMatrix - the translation / rotation matrix of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) +{ + int max_entity_id = 0; + char temp[ 2048 ]; + char NameFixup[ 128 ]; + entity_t *WorldspawnEnt = NULL; + GameData::TNameFixup FixupStyle; + + char *pTargetName = ValueForKey( pInstanceEntity, "targetname" ); + char *pName = ValueForKey( pInstanceEntity, "name" ); + if ( pTargetName[ 0 ] ) + { + sprintf( NameFixup, "%s", pTargetName ); + } + else if ( pName[ 0 ] ) + { + sprintf( NameFixup, "%s", pName ); + } + else + { + sprintf( NameFixup, "InstanceAuto%d", m_InstanceCount ); + } + + for( int i = 0; i < num_entities; i++ ) + { + char *pID = ValueForKey( &entities[ i ], "hammerid" ); + if ( pID[ 0 ] ) + { + int value = atoi( pID ); + if ( value > max_entity_id ) + { + max_entity_id = value; + } + } + } + + FixupStyle = ( GameData::TNameFixup )( IntForKey( pInstanceEntity, "fixup_style" ) ); + + for( int i = 0; i < Instance->num_entities; i++ ) + { + entities[ num_entities + i ] = Instance->entities[ i ]; + + entity_t *entity = &entities[ num_entities + i ]; + entity->firstbrush += ( nummapbrushes - Instance->nummapbrushes ); + + char *pID = ValueForKey( entity, "hammerid" ); + if ( pID[ 0 ] ) + { + int value = atoi( pID ); + value += max_entity_id; + sprintf( temp, "%d", value ); + + SetKeyValue( entity, "hammerid", temp ); + } + + char *pEntity = ValueForKey( entity, "classname" ); + if ( strcmpi( pEntity, "worldspawn" ) == 0 ) + { + WorldspawnEnt = entity; + } + else + { + Vector inOrigin = entity->origin; + VectorTransform( inOrigin, InstanceMatrix, entity->origin ); + + // search for variables coming from the func_instance to replace inside of the instance + // this is done before entity fixup, so fixup may occur on the replaced value. Not sure if this is a desired order of operation yet. + for ( epair_t *ep = entity->epairs; ep != NULL; ep = ep->next ) + { + ReplaceInstancePair( ep, pInstanceEntity ); + } + +#ifdef MERGE_INSTANCE_DEBUG_INFO + Msg( "Remapping class %s\n", pEntity ); +#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO + GDclass *EntClass = GD.BeginInstanceRemap( pEntity, NameFixup, InstanceOrigin, InstanceAngle ); + if ( EntClass ) + { + for( int i = 0; i < EntClass->GetVariableCount(); i++ ) + { + GDinputvariable *EntVar = EntClass->GetVariableAt( i ); + char *pValue = ValueForKey( entity, ( char * )EntVar->GetName() ); + if ( GD.RemapKeyValue( EntVar->GetName(), pValue, temp, FixupStyle ) ) + { +#ifdef MERGE_INSTANCE_DEBUG_INFO + Msg( " %d. Remapped %s: from %s to %s\n", i, EntVar->GetName(), pValue, temp ); +#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO + SetKeyValue( entity, EntVar->GetName(), temp ); + } + else + { +#ifdef MERGE_INSTANCE_DEBUG_INFO + Msg( " %d. Ignored %s: %s\n", i, EntVar->GetName(), pValue ); +#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO + } + } + } + + if ( strcmpi( pEntity, "func_simpleladder" ) == 0 ) + { // hate having to do this, but the key values are so screwed up + AddLadderKeys( entity ); +/* Vector vInNormal, vOutNormal; + + vInNormal.x = FloatForKey( entity, "normal.x" ); + vInNormal.y = FloatForKey( entity, "normal.y" ); + vInNormal.z = FloatForKey( entity, "normal.z" ); + VectorRotate( vInNormal, InstanceMatrix, vOutNormal ); + + Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.x ); + SetKeyValue( entity, "normal.x", temp ); + + Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.y ); + SetKeyValue( entity, "normal.y", temp ); + + Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.z ); + SetKeyValue( entity, "normal.z", temp );*/ + } + } + +#ifdef MERGE_INSTANCE_DEBUG_INFO + Msg( "Instance Entity %d remapped to %d\n", i, num_entities + i ); + Msg( " FirstBrush: from %d to %d\n", Instance->entities[ i ].firstbrush, entity->firstbrush ); + Msg( " KV Pairs:\n" ); + for ( epair_t *ep = entity->epairs; ep->next != NULL; ep = ep->next ) + { + Msg( " %s %s\n", ep->key, ep->value ); + } +#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO + } + + // search for variables coming from the func_instance to replace inside of the instance + // this is done before connection fix up, so fix up may occur on the replaced value. Not sure if this is a desired order of operation yet. + for( CConnectionPairs *Connection = Instance->m_ConnectionPairs; Connection; Connection = Connection->m_Next ) + { + ReplaceInstancePair( Connection->m_Pair, pInstanceEntity ); + } + + for( CConnectionPairs *Connection = Instance->m_ConnectionPairs; Connection; Connection = Connection->m_Next ) + { + char *newValue, *oldValue; + char origValue[ 4096 ]; + int extraLen = 0; + + oldValue = Connection->m_Pair->value; + strcpy( origValue, oldValue ); + char *pos = strchr( origValue, ',' ); + if ( pos ) + { // null terminate the first field + *pos = NULL; + extraLen = strlen( pos + 1) + 1; // for the comma we just null'd + } + + if ( GD.RemapNameField( origValue, temp, FixupStyle ) ) + { + newValue = new char [ strlen( temp ) + extraLen + 1 ]; + strcpy( newValue, temp ); + if ( pos ) + { + strcat( newValue, "," ); + strcat( newValue, pos + 1 ); + } + + Connection->m_Pair->value = newValue; + delete oldValue; + } + } + + num_entities += Instance->num_entities; + + MoveBrushesToWorldGeneral( WorldspawnEnt ); + WorldspawnEnt->numbrushes = 0; + WorldspawnEnt->epairs = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will translate overlays from the instance into +// the main map. +// Input : InstanceEntityNum - the entity number of the func_instance +// Instance - the map file of the instance +// InstanceOrigin - the translation of the instance +// InstanceAngle - the rotation of the instance +// InstanceMatrix - the translation / rotation matrix of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergeOverlays( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) +{ + for( int i = Instance->m_StartMapOverlays; i < g_aMapOverlays.Count(); i++ ) + { + Overlay_Translate( &g_aMapOverlays[ i ], InstanceOrigin, InstanceAngle, InstanceMatrix ); + } + for( int i = Instance->m_StartMapWaterOverlays; i < g_aMapWaterOverlays.Count(); i++ ) + { + Overlay_Translate( &g_aMapWaterOverlays[ i ], InstanceOrigin, InstanceAngle, InstanceMatrix ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Loads a VMF or MAP file. If the file has a .MAP extension, the MAP +// loader is used, otherwise the file is assumed to be in VMF format. +// Input : pszFileName - Full path of the map file to load. +//----------------------------------------------------------------------------- +bool LoadMapFile( const char *pszFileName ) +{ + bool bLoadingManifest = false; + CManifest *pMainManifest = NULL; + ChunkFileResult_t eResult; + + // + // Dummy this up for the texture handling. This can be removed when old .MAP file + // support is removed. + // + g_nMapFileVersion = 400; + + const char *pszExtension =V_GetFileExtension( pszFileName ); + if ( pszExtension && strcmpi( pszExtension, "vmm" ) == 0 ) + { + pMainManifest = new CManifest(); + if ( pMainManifest->LoadVMFManifest( pszFileName ) ) + { + eResult = ChunkFile_Ok; + pszFileName = pMainManifest->GetInstancePath(); + } + else + { + eResult = ChunkFile_Fail; + } + bLoadingManifest = true; + } + else + { + // + // Open the file. + // + CChunkFile File; + eResult = File.Open(pszFileName, ChunkFile_Read); + + // + // Read the file. + // + if (eResult == ChunkFile_Ok) + { + int index = g_Maps.AddToTail( new CMapFile() ); + g_LoadingMap = g_Maps[ index ]; + if ( g_MainMap == NULL ) + { + g_MainMap = g_LoadingMap; + } + + if ( g_MainMap == g_LoadingMap || verbose ) + { + Msg( "Loading %s\n", pszFileName ); + } + + + // reset the displacement info count + // nummapdispinfo = 0; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler("world", (ChunkHandler_t)LoadEntityCallback, 0); + Handlers.AddHandler("entity", (ChunkHandler_t)LoadEntityCallback, 0); + + File.PushHandlers(&Handlers); + + // + // Read the sub-chunks. We ignore keys in the root of the file. + // + while (eResult == ChunkFile_Ok) + { + eResult = File.ReadChunk(); + } + + File.PopHandlers(); + } + else + { + Error("Error opening %s: %s.\n", pszFileName, File.GetErrorText(eResult)); + } + } + + if ((eResult == ChunkFile_Ok) || (eResult == ChunkFile_EOF)) + { + // Update the overlay/side list(s). + Overlay_UpdateSideLists( g_LoadingMap->m_StartMapOverlays ); + OverlayTransition_UpdateSideLists( g_LoadingMap->m_StartMapWaterOverlays ); + + g_LoadingMap->CheckForInstances( pszFileName ); + + if ( pMainManifest ) + { + pMainManifest->CordonWorld(); + } + + ClearBounds (g_LoadingMap->map_mins, g_LoadingMap->map_maxs); + for (int i=0 ; i<g_MainMap->entities[0].numbrushes ; i++) + { + // HLTOOLS: Raise map limits + if (g_LoadingMap->mapbrushes[i].mins[0] > MAX_COORD_INTEGER) + { + continue; // no valid points + } + + AddPointToBounds (g_LoadingMap->mapbrushes[i].mins, g_LoadingMap->map_mins, g_LoadingMap->map_maxs); + AddPointToBounds (g_LoadingMap->mapbrushes[i].maxs, g_LoadingMap->map_mins, g_LoadingMap->map_maxs); + } + + qprintf ("%5i brushes\n", g_LoadingMap->nummapbrushes); + qprintf ("%5i clipbrushes\n", g_LoadingMap->c_clipbrushes); + qprintf ("%5i total sides\n", g_LoadingMap->nummapbrushsides); + qprintf ("%5i boxbevels\n", g_LoadingMap->c_boxbevels); + qprintf ("%5i edgebevels\n", g_LoadingMap->c_edgebevels); + qprintf ("%5i entities\n", g_LoadingMap->num_entities); + qprintf ("%5i planes\n", g_LoadingMap->nummapplanes); + qprintf ("%5i areaportals\n", g_LoadingMap->c_areaportals); + qprintf ("size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", g_LoadingMap->map_mins[0],g_LoadingMap->map_mins[1],g_LoadingMap->map_mins[2], + g_LoadingMap->map_maxs[0],g_LoadingMap->map_maxs[1],g_LoadingMap->map_maxs[2]); + + //TestExpandBrushes(); + + // Clear the error reporting + g_MapError.ClearState(); + } + + if ( g_MainMap == g_LoadingMap ) + { + num_entities = g_MainMap->num_entities; + memcpy( entities, g_MainMap->entities, sizeof( g_MainMap->entities ) ); + } + g_LoadingMap->ForceFuncAreaPortalWindowContents(); + + return ( ( eResult == ChunkFile_Ok ) || ( eResult == ChunkFile_EOF ) ); +} + +ChunkFileResult_t LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo) +{ + return g_LoadingMap->LoadSideCallback( pFile, pSideInfo ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pFile - +// pParent - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapFile::LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo) +{ + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + { + g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); + } + + pSideInfo->pSide = &brushsides[nummapbrushsides]; + + side_t *side = pSideInfo->pSide; + mapbrush_t *b = pSideInfo->pBrush; + g_MapError.BrushSide( pSideInfo->nSideIndex++ ); + + // initialize the displacement info + pSideInfo->pSide->pMapDisp = NULL; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler( "dispinfo", ( ChunkHandler_t )LoadDispInfoCallback, &side->pMapDisp ); + + // + // Read the side chunk. + // + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadSideKeyCallback, pSideInfo); + pFile->PopHandlers(); + + if (eResult == ChunkFile_Ok) + { + side->contents |= pSideInfo->nBaseContents; + side->surf |= pSideInfo->nBaseFlags; + pSideInfo->td.flags |= pSideInfo->nBaseFlags; + + if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + { + side->contents |= CONTENTS_DETAIL; + } + + if (fulldetail ) + { + side->contents &= ~CONTENTS_DETAIL; + } + + if (!(side->contents & (ALL_VISIBLE_CONTENTS | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) ) + { + side->contents |= CONTENTS_SOLID; + } + + // hints and skips are never detail, and have no content + if (side->surf & (SURF_HINT|SURF_SKIP) ) + { + side->contents = 0; + } + + // + // find the plane number + // + int planenum = PlaneFromPoints(pSideInfo->planepts[0], pSideInfo->planepts[1], pSideInfo->planepts[2]); + if (planenum != -1) + { + // + // See if the plane has been used already. + // + int k; + for ( k = 0; k < b->numsides; k++) + { + side_t *s2 = b->original_sides + k; + if (s2->planenum == planenum) + { + g_MapError.ReportWarning("duplicate plane"); + break; + } + if ( s2->planenum == (planenum^1) ) + { + g_MapError.ReportWarning("mirrored plane"); + break; + } + } + + // + // If the plane hasn't been used already, keep this side. + // + if (k == b->numsides) + { + side = b->original_sides + b->numsides; + side->planenum = planenum; + if ( !onlyents ) + { + side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], &pSideInfo->td, vec3_origin); + } + + // save the td off in case there is an origin brush and we + // have to recalculate the texinfo + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); + side_brushtextures[nummapbrushsides] = pSideInfo->td; + nummapbrushsides++; + b->numsides++; + +#ifdef VSVMFIO + // Tell Maya We Have Another Side + if ( CVmfImport::GetVmfImporter() ) + { + CVmfImport::GetVmfImporter()->AddSideCallback( + b, side, pSideInfo->td, + pSideInfo->planepts[ 0 ], pSideInfo->planepts[ 1 ], pSideInfo->planepts[ 2 ] ); + } +#endif // VSVMFIO + + } + } + else + { + g_MapError.ReportWarning("plane with no normal"); + } + } + + return(eResult); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : szKey - +// szValue - +// pSideInfo - +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadSideKeyCallback(const char *szKey, const char *szValue, LoadSide_t *pSideInfo) +{ + if (!stricmp(szKey, "plane")) + { + int nRead = sscanf(szValue, "(%f %f %f) (%f %f %f) (%f %f %f)", + &pSideInfo->planepts[0][0], &pSideInfo->planepts[0][1], &pSideInfo->planepts[0][2], + &pSideInfo->planepts[1][0], &pSideInfo->planepts[1][1], &pSideInfo->planepts[1][2], + &pSideInfo->planepts[2][0], &pSideInfo->planepts[2][1], &pSideInfo->planepts[2][2]); + + if (nRead != 9) + { + g_MapError.ReportError("parsing plane definition"); + } + } + else if (!stricmp(szKey, "material")) + { + // Get the material name. + if( g_ReplaceMaterials ) + { + szValue = ReplaceMaterialName( szValue ); + } + + strcpy(pSideInfo->td.name, szValue); + g_MapError.TextureState(szValue); + + // Find default flags and values for this material. + int mt = FindMiptex(pSideInfo->td.name); + pSideInfo->td.flags = textureref[mt].flags; + pSideInfo->td.lightmapWorldUnitsPerLuxel = textureref[mt].lightmapWorldUnitsPerLuxel; + + pSideInfo->pSide->contents = textureref[mt].contents; + pSideInfo->pSide->surf = pSideInfo->td.flags; + } + else if (!stricmp(szKey, "uaxis")) + { + int nRead = sscanf(szValue, "[%f %f %f %f] %f", &pSideInfo->td.UAxis[0], &pSideInfo->td.UAxis[1], &pSideInfo->td.UAxis[2], &pSideInfo->td.shift[0], &pSideInfo->td.textureWorldUnitsPerTexel[0]); + if (nRead != 5) + { + g_MapError.ReportError("parsing U axis definition"); + } + } + else if (!stricmp(szKey, "vaxis")) + { + int nRead = sscanf(szValue, "[%f %f %f %f] %f", &pSideInfo->td.VAxis[0], &pSideInfo->td.VAxis[1], &pSideInfo->td.VAxis[2], &pSideInfo->td.shift[1], &pSideInfo->td.textureWorldUnitsPerTexel[1]); + if (nRead != 5) + { + g_MapError.ReportError("parsing V axis definition"); + } + } + else if (!stricmp(szKey, "lightmapscale")) + { + pSideInfo->td.lightmapWorldUnitsPerLuxel = atoi(szValue); + if (pSideInfo->td.lightmapWorldUnitsPerLuxel == 0.0f) + { + g_MapError.ReportWarning("luxel size of 0"); + pSideInfo->td.lightmapWorldUnitsPerLuxel = g_defaultLuxelSize; + } + pSideInfo->td.lightmapWorldUnitsPerLuxel *= g_luxelScale; + if (pSideInfo->td.lightmapWorldUnitsPerLuxel < g_minLuxelScale) + { + pSideInfo->td.lightmapWorldUnitsPerLuxel = g_minLuxelScale; + } + } + else if (!stricmp(szKey, "contents")) + { + pSideInfo->pSide->contents |= atoi(szValue); + } + else if (!stricmp(szKey, "flags")) + { + pSideInfo->td.flags |= atoi(szValue); + pSideInfo->pSide->surf = pSideInfo->td.flags; + } + else if (!stricmp(szKey, "id")) + { + pSideInfo->pSide->id = atoi( szValue ); + } + else if (!stricmp(szKey, "smoothing_groups")) + { + pSideInfo->pSide->smoothingGroups = atoi( szValue ); + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: Reads the connections chunk of the entity. +// Input : pFile - Chunk file to load from. +// pLoadEntity - Structure to receive loaded entity information. +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadConnectionsCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadConnectionsKeyCallback, pLoadEntity)); +} + + +//----------------------------------------------------------------------------- +// Purpose: Parses a key/value pair from the entity connections chunk. +// Input : szKey - Key indicating the name of the entity output. +// szValue - Comma delimited fields in the following format: +// <target>,<input>,<parameter>,<delay>,<times to fire> +// pLoadEntity - Structure to receive loaded entity information. +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity) +{ + return g_LoadingMap->LoadConnectionsKeyCallback( szKey, szValue, pLoadEntity ); +} + +ChunkFileResult_t CMapFile::LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity) +{ + // + // Create new input and fill it out. + // + epair_t *pOutput = new epair_t; + + pOutput->key = new char [strlen(szKey) + 1]; + pOutput->value = new char [strlen(szValue) + 1]; + + strcpy(pOutput->key, szKey); + strcpy(pOutput->value, szValue); + + m_ConnectionPairs = new CConnectionPairs( pOutput, m_ConnectionPairs ); + + // + // Append it to the end of epairs list. + // + pOutput->next = NULL; + + if (!pLoadEntity->pEntity->epairs) + { + pLoadEntity->pEntity->epairs = pOutput; + } + else + { + epair_t *ep; + for ( ep = pLoadEntity->pEntity->epairs; ep->next != NULL; ep = ep->next ) + { + } + ep->next = pOutput; + } + + return(ChunkFile_Ok); +} + + +ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity) +{ + return g_LoadingMap->LoadSolidCallback( pFile, pLoadEntity ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pFile - +// pParent - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapFile::LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity) +{ + if (nummapbrushes == MAX_MAP_BRUSHES) + { + g_MapError.ReportError ("nummapbrushes == MAX_MAP_BRUSHES"); + } + + mapbrush_t *b = &mapbrushes[nummapbrushes]; + b->original_sides = &brushsides[nummapbrushsides]; + b->entitynum = num_entities-1; + b->brushnum = nummapbrushes - pLoadEntity->pEntity->firstbrush; + + LoadSide_t SideInfo; + SideInfo.pBrush = b; + SideInfo.nSideIndex = 0; + SideInfo.nBaseContents = pLoadEntity->nBaseContents; + SideInfo.nBaseFlags = pLoadEntity->nBaseFlags; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler("side", (ChunkHandler_t)::LoadSideCallback, &SideInfo); + + // + // Read the solid chunk. + // + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadSolidKeyCallback, b); + pFile->PopHandlers(); + + if (eResult == ChunkFile_Ok) + { + // get the content for the entire brush + b->contents = BrushContents (b); + + // allow detail brushes to be removed + if (nodetail && (b->contents & CONTENTS_DETAIL) && !HasDispInfo( b ) ) + { + b->numsides = 0; + return(ChunkFile_Ok); + } + + // allow water brushes to be removed + if (nowater && (b->contents & MASK_WATER) ) + { + b->numsides = 0; + return(ChunkFile_Ok); + } + + // create windings for sides and bounds for brush + MakeBrushWindings (b); + + // + // brushes that will not be visible at all will never be + // used as bsp splitters + // + // only do this on the world entity + // + if ( b->entitynum == 0 ) + { + if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + { + if ( g_ClipTexinfo < 0 ) + { + g_ClipTexinfo = b->original_sides[0].texinfo; + } + c_clipbrushes++; + for (int i=0 ; i<b->numsides ; i++) + { + b->original_sides[i].texinfo = TEXINFO_NODE; + } + } + } + + // + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity. After the entire entity is parsed, + // the planenums and texinfos will be adjusted for + // the origin brush + // + if (b->contents & CONTENTS_ORIGIN) + { + char string[32]; + Vector origin; + + if (num_entities == 1) + { + Error("Brush %i: origin brushes not allowed in world", b->id); + } + + VectorAdd (b->mins, b->maxs, origin); + VectorScale (origin, 0.5, origin); + + sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); + SetKeyValue (&entities[b->entitynum], "origin", string); + + VectorCopy (origin, entities[b->entitynum].origin); + + // don't keep this brush + b->numsides = 0; + + return(ChunkFile_Ok); + } + +#ifdef VSVMFIO + if ( CVmfImport::GetVmfImporter() ) + { + CVmfImport::GetVmfImporter()->MapBrushToMayaCallback( b ); + } +#endif // VSVMFIO + + // + // find a map brushes with displacement surfaces and remove them from the "world" + // + if( HasDispInfo( b ) ) + { + // add the base face data to the displacement surface + DispGetFaceInfo( b ); + + // don't keep this brush + b->numsides = 0; + + return( ChunkFile_Ok ); + } + + AddBrushBevels (b); + + nummapbrushes++; + pLoadEntity->pEntity->numbrushes++; + } + else + { + return eResult; + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pFile - +// parent - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadSolidKeyCallback(const char *szKey, const char *szValue, mapbrush_t *pLoadBrush) +{ + if (!stricmp(szKey, "id")) + { + pLoadBrush->id = atoi(szValue); + g_MapError.BrushState(pLoadBrush->id); + } + + return ChunkFile_Ok; +} + + +/* +================ +TestExpandBrushes + +Expands all the brush planes and saves a new map out +================ +*/ +void CMapFile::TestExpandBrushes (void) +{ + FILE *f; + side_t *s; + int i, j, bn; + winding_t *w; + char *name = "expanded.map"; + mapbrush_t *brush; + vec_t dist; + + Msg ("writing %s\n", name); + f = fopen (name, "wb"); + if (!f) + Error ("Can't write %s\b", name); + + fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); + fprintf( f, "\"mapversion\" \"220\"\n\"sounds\" \"1\"\n\"MaxRange\" \"4096\"\n\"mapversion\" \"220\"\n\"wad\" \"vert.wad;dev.wad;generic.wad;spire.wad;urb.wad;cit.wad;water.wad\"\n" ); + + + for (bn=0 ; bn<nummapbrushes ; bn++) + { + brush = &mapbrushes[bn]; + fprintf (f, "{\n"); + for (i=0 ; i<brush->numsides ; i++) + { + s = brush->original_sides + i; + dist = mapplanes[s->planenum].dist; + for (j=0 ; j<3 ; j++) + dist += fabs( 16 * mapplanes[s->planenum].normal[j] ); + + w = BaseWindingForPlane (mapplanes[s->planenum].normal, dist); + + fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); + + fprintf (f, "%s [ 0 0 1 -512 ] [ 0 -1 0 -256 ] 0 1 1 \n", + TexDataStringTable_GetString( GetTexData( texinfo[s->texinfo].texdata )->nameStringTableID ) ); + + FreeWinding (w); + } + fprintf (f, "}\n"); + } + fprintf (f, "}\n"); + + fclose (f); + + Error ("can't proceed after expanding brushes"); +} + + +//----------------------------------------------------------------------------- +// Purpose: load in the displacement info "chunk" from the .map file into the +// vbsp map displacement info data structure +// Output: return the pointer to the displacement map +//----------------------------------------------------------------------------- +mapdispinfo_t *ParseDispInfoChunk( void ) +{ + int i, j; + int vertCount; + mapdispinfo_t *pMapDispInfo; + + // + // check to see if we exceeded the maximum displacement info list size + // + if( nummapdispinfo > MAX_MAP_DISPINFO ) + g_MapError.ReportError( "ParseDispInfoChunk: nummapdispinfo > MAX_MAP_DISPINFO"); + + // get a pointer to the next available displacement info slot + pMapDispInfo = &mapdispinfo[nummapdispinfo]; + nummapdispinfo++; + + // + // get the chunk opener - "{" + // + GetToken( false ); + if( strcmp( token, "{" ) ) + g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - {" ); + + // + // + // get the displacement info attribs + // + // + + // power + GetToken( true ); + pMapDispInfo->power = atoi( token ); + + // u and v mapping axes + for( i = 0; i < 2; i++ ) + { + GetToken( false ); + if( strcmp( token, "[" ) ) + g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - [" ); + + for( j = 0; j < 3; j++ ) + { + GetToken( false ); + + if( i == 0 ) + { + pMapDispInfo->uAxis[j] = atof( token ); + } + else + { + pMapDispInfo->vAxis[j] = atof( token ); + } + } + + GetToken( false ); + if( strcmp( token, "]" ) ) + g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - ]" ); + } + + // max displacement value + if( g_nMapFileVersion < 350 ) + { + GetToken( false ); + pMapDispInfo->maxDispDist = atof( token ); + } + + // minimum tesselation value + GetToken( false ); + pMapDispInfo->minTess = atoi( token ); + + // light smoothing angle + GetToken( false ); + pMapDispInfo->smoothingAngle = atof( token ); + + // + // get the displacement info displacement normals + // + GetToken( true ); + pMapDispInfo->vectorDisps[0][0] = atof( token ); + GetToken( false ); + pMapDispInfo->vectorDisps[0][1] = atof( token ); + GetToken( false ); + pMapDispInfo->vectorDisps[0][2] = atof( token ); + + vertCount = ( ( ( 1 << pMapDispInfo->power ) + 1 ) * ( ( 1 << pMapDispInfo->power ) + 1 ) ); + for( i = 1; i < vertCount; i++ ) + { + GetToken( false ); + pMapDispInfo->vectorDisps[i][0] = atof( token ); + GetToken( false ); + pMapDispInfo->vectorDisps[i][1] = atof( token ); + GetToken( false ); + pMapDispInfo->vectorDisps[i][2] = atof( token ); + } + + // + // get the displacement info displacement values + // + GetToken( true ); + pMapDispInfo->dispDists[0] = atof( token ); + + for( i = 1; i < vertCount; i++ ) + { + GetToken( false ); + pMapDispInfo->dispDists[i] = atof( token ); + } + + // + // get the chunk closer - "}" + // + GetToken( true ); + if( strcmp( token, "}" ) ) + g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - }" ); + + // return the index of the displacement info slot + return pMapDispInfo; +} + + diff --git a/utils/vbsp/map.h b/utils/vbsp/map.h new file mode 100644 index 0000000..c7b0e5b --- /dev/null +++ b/utils/vbsp/map.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MAP_H +#define MAP_H +#ifdef _WIN32 +#pragma once +#endif + + +// All the brush sides referenced by info_no_dynamic_shadow entities. +extern CUtlVector<int> g_NoDynamicShadowSides; + + +#endif // MAP_H diff --git a/utils/vbsp/materialpatch.cpp b/utils/vbsp/materialpatch.cpp new file mode 100644 index 0000000..96a30ed --- /dev/null +++ b/utils/vbsp/materialpatch.cpp @@ -0,0 +1,440 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "vbsp.h" +#include "UtlBuffer.h" +#include "utlsymbol.h" +#include "utlrbtree.h" +#include "KeyValues.h" +#include "bsplib.h" +#include "materialpatch.h" +#include "tier1/strtools.h" + +// case insensitive +static CUtlSymbolTable s_SymbolTable( 0, 32, true ); + +struct NameTranslationLookup_t +{ + CUtlSymbol m_OriginalFileName; + CUtlSymbol m_PatchFileName; +}; + +static bool NameTranslationLessFunc( NameTranslationLookup_t const& src1, + NameTranslationLookup_t const& src2 ) +{ + return src1.m_PatchFileName < src2.m_PatchFileName; +} + +CUtlRBTree<NameTranslationLookup_t, int> s_MapPatchedMatToOriginalMat( 0, 256, NameTranslationLessFunc ); + +void AddNewTranslation( const char *pOriginalMaterialName, const char *pNewMaterialName ) +{ + NameTranslationLookup_t newEntry; + + newEntry.m_OriginalFileName = s_SymbolTable.AddString( pOriginalMaterialName ); + newEntry.m_PatchFileName = s_SymbolTable.AddString( pNewMaterialName ); + + s_MapPatchedMatToOriginalMat.Insert( newEntry ); +} + +const char *GetOriginalMaterialNameForPatchedMaterial( const char *pPatchMaterialName ) +{ + const char *pRetName = NULL; + int id; + NameTranslationLookup_t lookup; + lookup.m_PatchFileName = s_SymbolTable.AddString( pPatchMaterialName ); + do + { + id = s_MapPatchedMatToOriginalMat.Find( lookup ); + if( id >= 0 ) + { + NameTranslationLookup_t &found = s_MapPatchedMatToOriginalMat[id]; + lookup.m_PatchFileName = found.m_OriginalFileName; + pRetName = s_SymbolTable.String( found.m_OriginalFileName ); + } + } while( id >= 0 ); + if( !pRetName ) + { + // This isn't a patched material, so just return the original name. + return pPatchMaterialName; + } + return pRetName; +} + + +void CreateMaterialPatchRecursive( KeyValues *pOriginalKeyValues, KeyValues *pPatchKeyValues, int nKeys, const MaterialPatchInfo_t *pInfo ) +{ + int i; + for( i = 0; i < nKeys; i++ ) + { + const char *pVal = pOriginalKeyValues->GetString( pInfo[i].m_pKey, NULL ); + if( !pVal ) + continue; + if( pInfo[i].m_pRequiredOriginalValue && Q_stricmp( pVal, pInfo[i].m_pRequiredOriginalValue ) != 0 ) + continue; + pPatchKeyValues->SetString( pInfo[i].m_pKey, pInfo[i].m_pValue ); + } + KeyValues *pScan; + for( pScan = pOriginalKeyValues->GetFirstTrueSubKey(); pScan; pScan = pScan->GetNextTrueSubKey() ) + { + CreateMaterialPatchRecursive( pScan, pPatchKeyValues->FindKey( pScan->GetName(), true ), nKeys, pInfo ); + } +} + +//----------------------------------------------------------------------------- +// A version which allows you to patch multiple key values +//----------------------------------------------------------------------------- +void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, + int nKeys, const MaterialPatchInfo_t *pInfo, MaterialPatchType_t nPatchType ) +{ + char pOldVMTFile[ 512 ]; + char pNewVMTFile[ 512 ]; + + AddNewTranslation( pOriginalMaterialName, pNewMaterialName ); + + Q_snprintf( pOldVMTFile, 512, "materials/%s.vmt", pOriginalMaterialName ); + Q_snprintf( pNewVMTFile, 512, "materials/%s.vmt", pNewMaterialName ); + +// printf( "Creating material patch file %s which points at %s\n", newVMTFile, oldVMTFile ); + + KeyValues *kv = new KeyValues( "patch" ); + if ( !kv ) + { + Error( "Couldn't allocate KeyValues for %s!!!", pNewMaterialName ); + } + + kv->SetString( "include", pOldVMTFile ); + + const char *pSectionName = (nPatchType == PATCH_INSERT) ? "insert" : "replace"; + KeyValues *section = kv->FindKey( pSectionName, true ); + + if( nPatchType == PATCH_REPLACE ) + { + char name[512]; + Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pOriginalMaterialName ) ); + KeyValues *origkv = new KeyValues( "blah" ); + + if ( !origkv->LoadFromFile( g_pFileSystem, name ) ) + { + origkv->deleteThis(); + Assert( 0 ); + return; + } + + CreateMaterialPatchRecursive( origkv, section, nKeys, pInfo ); + origkv->deleteThis(); + } + else + { + for ( int i = 0; i < nKeys; ++i ) + { + section->SetString( pInfo[i].m_pKey, pInfo[i].m_pValue ); + } + } + + // Write patched .vmt into a memory buffer + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + kv->RecursiveSaveToFile( buf, 0 ); + + // Add to pak file for this .bsp + AddBufferToPak( GetPakFile(), pNewVMTFile, (void*)buf.Base(), buf.TellPut(), true ); + + // Cleanup + kv->deleteThis(); +} + + +//----------------------------------------------------------------------------- +// Patches a single keyvalue in a material +//----------------------------------------------------------------------------- +void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, + const char *pNewKey, const char *pNewValue, MaterialPatchType_t nPatchType ) +{ + MaterialPatchInfo_t info; + info.m_pKey = pNewKey; + info.m_pValue = pNewValue; + CreateMaterialPatch( pOriginalMaterialName, pNewMaterialName, 1, &info, nPatchType ); +} + + +//----------------------------------------------------------------------------- +// Scan material + all subsections for key +//----------------------------------------------------------------------------- +static bool DoesMaterialHaveKey( KeyValues *pKeyValues, const char *pKeyName ) +{ + const char *pVal; + pVal = pKeyValues->GetString( pKeyName, NULL ); + if ( pVal != NULL ) + return true; + + for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() ) + { + if ( DoesMaterialHaveKey( pSubKey, pKeyName) ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Scan material + all subsections for key/value pair +//----------------------------------------------------------------------------- +static bool DoesMaterialHaveKeyValuePair( KeyValues *pKeyValues, const char *pKeyName, const char *pSearchValue ) +{ + const char *pVal; + pVal = pKeyValues->GetString( pKeyName, NULL ); + if ( pVal != NULL && ( Q_stricmp( pSearchValue, pVal ) == 0 ) ) + return true; + + for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() ) + { + if ( DoesMaterialHaveKeyValuePair( pSubKey, pKeyName, pSearchValue ) ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Scan material + all subsections for key +//----------------------------------------------------------------------------- +bool DoesMaterialHaveKey( const char *pMaterialName, const char *pKeyName ) +{ + char name[512]; + Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) ); + KeyValues *kv = new KeyValues( "blah" ); + + if ( !kv->LoadFromFile( g_pFileSystem, name ) ) + { + kv->deleteThis(); + return NULL; + } + + bool retVal = DoesMaterialHaveKey( kv, pKeyName ); + + kv->deleteThis(); + return retVal; +} + +//----------------------------------------------------------------------------- +// Scan material + all subsections for key/value pair +//----------------------------------------------------------------------------- +bool DoesMaterialHaveKeyValuePair( const char *pMaterialName, const char *pKeyName, const char *pSearchValue ) +{ + char name[512]; + Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) ); + KeyValues *kv = new KeyValues( "blah" ); + + if ( !kv->LoadFromFile( g_pFileSystem, name ) ) + { + kv->deleteThis(); + return NULL; + } + + bool retVal = DoesMaterialHaveKeyValuePair( kv, pKeyName, pSearchValue ); + + kv->deleteThis(); + return retVal; +} + +//----------------------------------------------------------------------------- +// Gets a material value from a material. Ignores all patches +//----------------------------------------------------------------------------- +bool GetValueFromMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ) +{ + char name[512]; + Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) ); + KeyValues *kv = new KeyValues( "blah" ); + + if ( !kv->LoadFromFile( g_pFileSystem, name ) ) + { +// Assert( 0 ); + kv->deleteThis(); + return NULL; + } + + const char *pTmpValue = kv->GetString( pKey, NULL ); + if( pTmpValue ) + { + Q_strncpy( pValue, pTmpValue, len ); + } + + kv->deleteThis(); + return ( pTmpValue != NULL ); +} + + +//----------------------------------------------------------------------------- +// Finds the original material associated with a patched material +//----------------------------------------------------------------------------- +MaterialSystemMaterial_t FindOriginalMaterial( const char *materialName, bool *pFound, bool bComplain ) +{ + MaterialSystemMaterial_t matID; + matID = FindMaterial( GetOriginalMaterialNameForPatchedMaterial( materialName ), pFound, bComplain ); + return matID; +} + + +//----------------------------------------------------------------------------- +// Load keyvalues from the local pack file, or from a file +//----------------------------------------------------------------------------- +bool LoadKeyValuesFromPackOrFile( const char *pFileName, KeyValues *pKeyValues ) +{ + CUtlBuffer buf; + if ( ReadFileFromPak( GetPakFile(), pFileName, true, buf ) ) + { + return pKeyValues->LoadFromBuffer( pFileName, buf ); + } + + return pKeyValues->LoadFromFile( g_pFileSystem, pFileName ); +} + + +//----------------------------------------------------------------------------- +// VMT parser +//----------------------------------------------------------------------------- +static void InsertKeyValues( KeyValues &dst, KeyValues& src, bool bCheckForExistence ) +{ + KeyValues *pSrcVar = src.GetFirstSubKey(); + while( pSrcVar ) + { + if ( !bCheckForExistence || dst.FindKey( pSrcVar->GetName() ) ) + { + switch( pSrcVar->GetDataType() ) + { + case KeyValues::TYPE_STRING: + dst.SetString( pSrcVar->GetName(), pSrcVar->GetString() ); + break; + case KeyValues::TYPE_INT: + dst.SetInt( pSrcVar->GetName(), pSrcVar->GetInt() ); + break; + case KeyValues::TYPE_FLOAT: + dst.SetFloat( pSrcVar->GetName(), pSrcVar->GetFloat() ); + break; + case KeyValues::TYPE_PTR: + dst.SetPtr( pSrcVar->GetName(), pSrcVar->GetPtr() ); + break; + } + } + pSrcVar = pSrcVar->GetNextKey(); + } +} + +static void ExpandPatchFile( KeyValues &keyValues ) +{ + int nCount = 0; + while( nCount < 10 && stricmp( keyValues.GetName(), "patch" ) == 0 ) + { +// WriteKeyValuesToFile( "patch.txt", keyValues ); + const char *pIncludeFileName = keyValues.GetString( "include" ); + if( !pIncludeFileName ) + return; + + KeyValues * includeKeyValues = new KeyValues( "vmt" ); + int nBufLen = Q_strlen( pIncludeFileName ) + Q_strlen( "materials/.vmt" ) + 1; + char *pFileName = ( char * )stackalloc( nBufLen ); + Q_strncpy( pFileName, pIncludeFileName, nBufLen ); + bool bSuccess = LoadKeyValuesFromPackOrFile( pFileName, includeKeyValues ); + if ( !bSuccess ) + { + includeKeyValues->deleteThis(); + return; + } + + KeyValues *pInsertSection = keyValues.FindKey( "insert" ); + if( pInsertSection ) + { + InsertKeyValues( *includeKeyValues, *pInsertSection, false ); + keyValues = *includeKeyValues; + } + + KeyValues *pReplaceSection = keyValues.FindKey( "replace" ); + if( pReplaceSection ) + { + InsertKeyValues( *includeKeyValues, *pReplaceSection, true ); + keyValues = *includeKeyValues; + } + + // Could add other commands here, like "delete", "rename", etc. + + includeKeyValues->deleteThis(); + nCount++; + } + + if( nCount >= 10 ) + { + Warning( "Infinite recursion in patch file?\n" ); + } +} + +KeyValues *LoadMaterialKeyValues( const char *pMaterialName, unsigned int nFlags ) +{ + // Load the underlying file + KeyValues *kv = new KeyValues( "blah" ); + + char pFullMaterialName[512]; + Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName ); + if ( !LoadKeyValuesFromPackOrFile( pFullMaterialName, kv ) ) + { + // Assert( 0 ); + kv->deleteThis(); + return NULL; + } + + if( nFlags & LOAD_MATERIAL_KEY_VALUES_FLAGS_EXPAND_PATCH ) + { + ExpandPatchFile( *kv ); + } + + return kv; +} + +void WriteMaterialKeyValuesToPak( const char *pMaterialName, KeyValues *kv ) +{ + char pFullMaterialName[512]; + Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName ); + + // Write patched .vmt into a memory buffer + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + kv->RecursiveSaveToFile( buf, 0 ); + + // Add to pak file for this .bsp + AddBufferToPak( GetPakFile(), pFullMaterialName, (void*)buf.Base(), buf.TellPut(), true ); + + // Cleanup + kv->deleteThis(); +} + + +//----------------------------------------------------------------------------- +// Gets a keyvalue from a *patched* material +//----------------------------------------------------------------------------- +bool GetValueFromPatchedMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ) +{ + // Load the underlying file so that we can check if env_cubemap is in there. + KeyValues *kv = new KeyValues( "blah" ); + + char pFullMaterialName[512]; + Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName ); + if ( !LoadKeyValuesFromPackOrFile( pFullMaterialName, kv ) ) + { +// Assert( 0 ); + kv->deleteThis(); + return NULL; + } + + ExpandPatchFile( *kv ); + + const char *pTmpValue = kv->GetString( pKey, NULL ); + if( pTmpValue ) + { + Q_strncpy( pValue, pTmpValue, len ); + } + + kv->deleteThis(); + return ( pTmpValue != NULL ); +} diff --git a/utils/vbsp/materialpatch.h b/utils/vbsp/materialpatch.h new file mode 100644 index 0000000..f04a95d --- /dev/null +++ b/utils/vbsp/materialpatch.h @@ -0,0 +1,60 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MATERIALPATCH_H +#define MATERIALPATCH_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utilmatlib.h" + +struct MaterialPatchInfo_t +{ + const char *m_pKey; + const char *m_pRequiredOriginalValue; // NULL if you don't require one. + const char *m_pValue; + MaterialPatchInfo_t() + { + memset( this, 0, sizeof( *this ) ); + } +}; + +enum MaterialPatchType_t +{ + PATCH_INSERT = 0, // Add the key no matter what + PATCH_REPLACE, // Add the key only if it exists +}; + +void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, + const char *pNewKey, const char *pNewValue, MaterialPatchType_t nPatchType ); + +// A version which allows you to use multiple key values +void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, + int nKeys, const MaterialPatchInfo_t *pInfo, MaterialPatchType_t nPatchType ); + +// This gets a keyvalue from the *unpatched* version of the passed-in material +bool GetValueFromMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ); + +// Gets a keyvalue from a *patched* material +bool GetValueFromPatchedMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ); + +const char *GetOriginalMaterialNameForPatchedMaterial( const char *pPatchMaterialName ); + +MaterialSystemMaterial_t FindOriginalMaterial( const char *materialName, bool *pFound, bool bComplain = true ); + +bool DoesMaterialHaveKeyValuePair( const char *pMaterialName, const char *pKeyName, const char *pSearchValue ); +bool DoesMaterialHaveKey( const char *pMaterialName, const char *pKeyName ); + +enum LoadMaterialKeyValuesFlags_t +{ + LOAD_MATERIAL_KEY_VALUES_FLAGS_EXPAND_PATCH = 1, +}; + +KeyValues *LoadMaterialKeyValues( const char *pMaterialName, unsigned int nFlags ); +void WriteMaterialKeyValuesToPak( const char *pMaterialName, KeyValues *kv ); + +#endif // MATERIALPATCH_H diff --git a/utils/vbsp/materialsub.cpp b/utils/vbsp/materialsub.cpp new file mode 100644 index 0000000..b02063e --- /dev/null +++ b/utils/vbsp/materialsub.cpp @@ -0,0 +1,90 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: This file loads a KeyValues file containing material name mappings. +// When the bsp is compiled, all materials listed in the file will +// be replaced by the second material in the pair. +// +//============================================================================= + +#include "vbsp.h" +#include "materialsub.h" +#include "KeyValues.h" +#include "tier1/strtools.h" + +bool g_ReplaceMaterials = false; + +static KeyValues *kv = 0; +static KeyValues *allMapKeys = 0; +static KeyValues *curMapKeys = 0; + +//----------------------------------------------------------------------------- +// Purpose: Loads the KeyValues file for materials replacements +//----------------------------------------------------------------------------- +void LoadMaterialReplacementKeys( const char *gamedir, const char *mapname ) +{ + // Careful with static variables + if( kv ) + { + kv->deleteThis(); + kv = 0; + } + if( allMapKeys ) + allMapKeys = 0; + if( curMapKeys ) + curMapKeys = 0; + + Msg( "Loading Replacement Keys\n" ); + + // Attach the path to the keyValues file + char path[1024]; + Q_snprintf( path, sizeof( path ), "%scfg\\materialsub.cfg", gamedir ); + + // Load the keyvalues file + kv = new KeyValues( "MaterialReplacements" ); + + Msg( "File path: %s", path ); + if( !kv->LoadFromFile( g_pFileSystem, path ) ) + { + Msg( "Failed to load KeyValues file!\n" ); + g_ReplaceMaterials = false; + kv->deleteThis(); + kv = 0; + return; + } + + // Load global replace keys + allMapKeys = kv->FindKey( "AllMaps", true ); + + // Load keys for the current map + curMapKeys = kv->FindKey( mapname ); + + allMapKeys->ChainKeyValue( curMapKeys ); +} + +//----------------------------------------------------------------------------- +// Purpose: Deletes all keys +//----------------------------------------------------------------------------- +void DeleteMaterialReplacementKeys( void ) +{ + if( kv ) + { + kv->deleteThis(); + kv = 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Replace the passed-in material name with a replacement name, if one exists +//----------------------------------------------------------------------------- +const char* ReplaceMaterialName( const char *name ) +{ + // Look for the material name in the global and map KeyValues + // If it's not there, just return the original name + + // HACK: This stinks - KeyValues won't take a string with '/' in it. + // If they did, this could be a simple pointer swap. + char newName[1024]; + Q_strncpy( newName, name, sizeof( newName ) ); + Q_FixSlashes( newName ); + return allMapKeys->GetString( newName, name ); +}
\ No newline at end of file diff --git a/utils/vbsp/materialsub.h b/utils/vbsp/materialsub.h new file mode 100644 index 0000000..ce6cac4 --- /dev/null +++ b/utils/vbsp/materialsub.h @@ -0,0 +1,25 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: This file loads a KeyValues file containing material name mappings. +// When the bsp is compiled, all materials listed in the file will +// be replaced by the second material in the pair. +// +//============================================================================= + +#ifndef MATERIALSUB_H +#define MATERIALSUB_H +#ifdef _WIN32 +#pragma once +#endif + +extern bool g_ReplaceMaterials; + +// Setup / Cleanup +void LoadMaterialReplacementKeys( const char *gamedir, const char *mapname ); +void DeleteMaterialReplacementKeys( void ); + +// Takes a material name and returns it's replacement, if there is one. +// If there isn't a replacement, it returns the original. +const char* ReplaceMaterialName( const char *name ); + +#endif // MATERIALSUB_H
\ No newline at end of file diff --git a/utils/vbsp/nodraw.cpp b/utils/vbsp/nodraw.cpp new file mode 100644 index 0000000..954ea67 --- /dev/null +++ b/utils/vbsp/nodraw.cpp @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" + +Vector draw_mins, draw_maxs; + +void Draw_ClearWindow (void) +{ +} + +//============================================================ + +#define GLSERV_PORT 25001 + + +void GLS_BeginScene (void) +{ +} + +void GLS_Winding (winding_t *w, int code) +{ +} + +void GLS_EndScene (void) +{ +} diff --git a/utils/vbsp/normals.cpp b/utils/vbsp/normals.cpp new file mode 100644 index 0000000..3a910cb --- /dev/null +++ b/utils/vbsp/normals.cpp @@ -0,0 +1,50 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "bsplib.h" +#include "vbsp.h" + + +void SaveVertexNormals( void ) +{ + int i, j; + dface_t *f; + texinfo_t *tex; + + + g_numvertnormalindices = 0; + g_numvertnormals = 0; + + for( i = 0 ;i<numfaces ; i++ ) + { + f = &dfaces[i]; + tex = &texinfo[f->texinfo]; + + for( j = 0; j < f->numedges; j++ ) + { + if( g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES ) + { + Error( "g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES (%d)", MAX_MAP_VERTNORMALINDICES ); + } + + g_vertnormalindices[g_numvertnormalindices] = g_numvertnormals; + g_numvertnormalindices++; + } + + // Add this face plane's normal. + // Note: this doesn't do an exhaustive vertex normal match because the vrad does it. + // The result is that a little extra memory is wasted coming out of vbsp, but it + // goes away after vrad. + if( g_numvertnormals == MAX_MAP_VERTNORMALS ) + { + Error( "g_numvertnormals == MAX_MAP_VERTNORMALS (%d)", MAX_MAP_VERTNORMALS ); + } + + g_vertnormals[g_numvertnormals] = dplanes[f->planenum].normal; + g_numvertnormals++; + } +} diff --git a/utils/vbsp/notes.txt b/utils/vbsp/notes.txt new file mode 100644 index 0000000..5034c4b --- /dev/null +++ b/utils/vbsp/notes.txt @@ -0,0 +1,66 @@ +// ----------------------------------------------------------------- +// JAY: +// +DONE: Fix tools for HL2/TF2 coordinate space +DONE: Load textures from Half-Life .WADs +DONE: Write out Q2 texture format (not miptex) +DONE: Write test map viewer +DONE: Test detail brushes +DONE: view portals to test +NOT DOING:Write out HL style collision trees +DONE: new engine loader +DONE: new vis in HL2 engine - looks simple now +DONE: Do QRAD backwards? i.e. use Valve QRAD, and merge in the Q2 file formats? probably +DONE: Integrate Ken's qrad code into qrad3 +DONE: add area portal visibility to HL2 engine +DONE: write area portal entities for HL2/TF2 +DONE: test area portal code +Split clusters for outdoor vis + +// ----------------------------------------------------------------- + +QBSP3 Chop is based on recursive subdivision. + - Force natural alignment of some sort to eliminate slivers where brushes meet ? + + +Use Q2 style ladder indicator? yes +Use Q2 style friction indicator? probably or not if physics based + + +// ----------------------------------------------------------------- +// CHARLIE: +// + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +NOTE: set DISP_PROTO to compile with displacement map info -- not on by default until + the prototype is done, the checked in .exe does not have displacement map functionality +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +DONE: put DISP_PROTO defines around all of the displacement code until prototyping is done +DONE: add displacement map structure +DONE: add displacement face structure +DONE: change face/side structures to accept displacement texinfo and displacement face indices +DONE: change .map loader to parse displacment map info +DONE: don't allow merge or subdivision of displacement faces +DONE: when splitting brushes, the new generated side get initialized to -1 +DONE: add find displacement face functionality, then create it if not found +DONE: add find displacement map functionality, then create it if not found +DONE: initialize the displacement data before loading the .map file +initialize the face data with dispface and dispmap = -1, is this necessary???? +DONE: copy from bsp tool face to bsp file face -- the displacement info +DONE: add/copy lumps +DONE: swap data for writing to bsp -- not really necessary, but to keep in sync with the rest +DONE: write .bsp data out +DONE: add displacement data to .bsp statistics -- print file + +Test maps: +DONE: map where disp face gets split by block node +DONE: map where disp face attempts merge/split +DONE: map with texture disp face +DONE: map with lots of disp faces +DONE: map with multiple disp faces referencing one map +DONE: map with multiple disp faces on one brush +DONE: map with multiple disp faces on one brush referencing one map +DONE: map with funky texture split encased on one portal + +//------------------------------------------------------------------
\ No newline at end of file diff --git a/utils/vbsp/overlay.cpp b/utils/vbsp/overlay.cpp new file mode 100644 index 0000000..edceba9 --- /dev/null +++ b/utils/vbsp/overlay.cpp @@ -0,0 +1,487 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "disp_vbsp.h" +#include "builddisp.h" +#include "mathlib/vmatrix.h" + +void Overlay_BuildBasisOrigin( doverlay_t *pOverlay ); + +// Overlay list. +CUtlVector<mapoverlay_t> g_aMapOverlays; +CUtlVector<mapoverlay_t> g_aMapWaterOverlays; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int Overlay_GetFromEntity( entity_t *pMapEnt ) +{ + int iAccessorID = -1; + + // Allocate the new overlay. + int iOverlay = g_aMapOverlays.AddToTail(); + mapoverlay_t *pMapOverlay = &g_aMapOverlays[iOverlay]; + + // Get the overlay data. + pMapOverlay->nId = g_aMapOverlays.Count() - 1; + + if ( ValueForKey( pMapEnt, "targetname" )[ 0 ] != '\0' ) + { + // Overlay has a name, remember it's ID for accessing + iAccessorID = pMapOverlay->nId; + } + + pMapOverlay->flU[0] = FloatForKey( pMapEnt, "StartU" ); + pMapOverlay->flU[1] = FloatForKey( pMapEnt, "EndU" ); + pMapOverlay->flV[0] = FloatForKey( pMapEnt, "StartV" ); + pMapOverlay->flV[1] = FloatForKey( pMapEnt, "EndV" ); + + pMapOverlay->flFadeDistMinSq = FloatForKey( pMapEnt, "fademindist" ); + if ( pMapOverlay->flFadeDistMinSq > 0 ) + { + pMapOverlay->flFadeDistMinSq *= pMapOverlay->flFadeDistMinSq; + } + + pMapOverlay->flFadeDistMaxSq = FloatForKey( pMapEnt, "fademaxdist" ); + if ( pMapOverlay->flFadeDistMaxSq > 0 ) + { + pMapOverlay->flFadeDistMaxSq *= pMapOverlay->flFadeDistMaxSq; + } + + GetVectorForKey( pMapEnt, "BasisOrigin", pMapOverlay->vecOrigin ); + + pMapOverlay->m_nRenderOrder = IntForKey( pMapEnt, "RenderOrder" ); + if ( pMapOverlay->m_nRenderOrder < 0 || pMapOverlay->m_nRenderOrder >= OVERLAY_NUM_RENDER_ORDERS ) + Error( "Overlay (%s) at %f %f %f has invalid render order (%d).\n", ValueForKey( pMapEnt, "material" ), + pMapOverlay->vecOrigin.x, pMapOverlay->vecOrigin.y, pMapOverlay->vecOrigin.z, + pMapOverlay->m_nRenderOrder ); + + GetVectorForKey( pMapEnt, "uv0", pMapOverlay->vecUVPoints[0] ); + GetVectorForKey( pMapEnt, "uv1", pMapOverlay->vecUVPoints[1] ); + GetVectorForKey( pMapEnt, "uv2", pMapOverlay->vecUVPoints[2] ); + GetVectorForKey( pMapEnt, "uv3", pMapOverlay->vecUVPoints[3] ); + + GetVectorForKey( pMapEnt, "BasisU", pMapOverlay->vecBasis[0] ); + GetVectorForKey( pMapEnt, "BasisV", pMapOverlay->vecBasis[1] ); + GetVectorForKey( pMapEnt, "BasisNormal", pMapOverlay->vecBasis[2] ); + + const char *pMaterialName = ValueForKey( pMapEnt, "material" ); + Assert( strlen( pMaterialName ) < OVERLAY_MAP_STRLEN ); + if ( strlen( pMaterialName ) >= OVERLAY_MAP_STRLEN ) + { + Error( "Overlay Material Name (%s) too long! > OVERLAY_MAP_STRLEN (%d)", pMaterialName, OVERLAY_MAP_STRLEN ); + return -1; + } + strcpy( pMapOverlay->szMaterialName, pMaterialName ); + + // Convert the sidelist to side id(s). + const char *pSideList = ValueForKey( pMapEnt, "sides" ); + char *pTmpList = ( char* )_alloca( strlen( pSideList ) + 1 ); + strcpy( pTmpList, pSideList ); + const char *pScan = strtok( pTmpList, " " ); + if ( !pScan ) + return iAccessorID; + + pMapOverlay->aSideList.Purge(); + pMapOverlay->aFaceList.Purge(); + + do + { + int nSideId; + if ( sscanf( pScan, "%d", &nSideId ) == 1 ) + { + pMapOverlay->aSideList.AddToTail( nSideId ); + } + } while ( ( pScan = strtok( NULL, " " ) ) ); + + return iAccessorID; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +side_t *GetSide( int nSideId ) +{ + for( int iSide = 0; iSide < g_LoadingMap->nummapbrushsides; ++iSide ) + { + if ( g_LoadingMap->brushsides[iSide].id == nSideId ) + return &g_LoadingMap->brushsides[iSide]; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void Overlay_UpdateSideLists( int StartIndex ) +{ + int nMapOverlayCount = g_aMapOverlays.Count(); + for( int iMapOverlay = StartIndex; iMapOverlay < nMapOverlayCount; ++iMapOverlay ) + { + mapoverlay_t *pMapOverlay = &g_aMapOverlays.Element( iMapOverlay ); + if ( pMapOverlay ) + { + int nSideCount = pMapOverlay->aSideList.Count(); + for( int iSide = 0; iSide < nSideCount; ++iSide ) + { + side_t *pSide = GetSide( pMapOverlay->aSideList[iSide] ); + if ( pSide ) + { + if ( pSide->aOverlayIds.Find( pMapOverlay->nId ) == -1 ) + { + pSide->aOverlayIds.AddToTail( pMapOverlay->nId ); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void OverlayTransition_UpdateSideLists( int StartIndex ) +{ + int nOverlayCount = g_aMapWaterOverlays.Count(); + for( int iOverlay = StartIndex; iOverlay < nOverlayCount; ++iOverlay ) + { + mapoverlay_t *pOverlay = &g_aMapWaterOverlays.Element( iOverlay ); + if ( pOverlay ) + { + int nSideCount = pOverlay->aSideList.Count(); + for( int iSide = 0; iSide < nSideCount; ++iSide ) + { + side_t *pSide = GetSide( pOverlay->aSideList[iSide] ); + if ( pSide ) + { + if ( pSide->aWaterOverlayIds.Find( pOverlay->nId ) == -1 ) + { + pSide->aWaterOverlayIds.AddToTail( pOverlay->nId ); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void Overlay_AddFaceToLists( int iFace, side_t *pSide ) +{ + int nOverlayIdCount = pSide->aOverlayIds.Count(); + for( int iOverlayId = 0; iOverlayId < nOverlayIdCount; ++iOverlayId ) + { + mapoverlay_t *pMapOverlay = &g_aMapOverlays.Element( pSide->aOverlayIds[iOverlayId] ); + if ( pMapOverlay ) + { + if( pMapOverlay->aFaceList.Find( iFace ) == -1 ) + { + pMapOverlay->aFaceList.AddToTail( iFace ); + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void OverlayTransition_AddFaceToLists( int iFace, side_t *pSide ) +{ + int nOverlayIdCount = pSide->aWaterOverlayIds.Count(); + for( int iOverlayId = 0; iOverlayId < nOverlayIdCount; ++iOverlayId ) + { + mapoverlay_t *pMapOverlay = &g_aMapWaterOverlays.Element( pSide->aWaterOverlayIds[iOverlayId] - ( MAX_MAP_OVERLAYS + 1 ) ); + if ( pMapOverlay ) + { + if( pMapOverlay->aFaceList.Find( iFace ) == -1 ) + { + pMapOverlay->aFaceList.AddToTail( iFace ); + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void Overlay_EmitOverlayFace( mapoverlay_t *pMapOverlay ) +{ + Assert( g_nOverlayCount < MAX_MAP_OVERLAYS ); + if ( g_nOverlayCount >= MAX_MAP_OVERLAYS ) + { + Error ( "Too Many Overlays!\nMAX_MAP_OVERLAYS = %d", MAX_MAP_OVERLAYS ); + return; + } + + doverlay_t *pOverlay = &g_Overlays[g_nOverlayCount]; + doverlayfade_t *pOverlayFade = &g_OverlayFades[g_nOverlayCount]; + + g_nOverlayCount++; + + // Conver the map overlay into a .bsp overlay (doverlay_t). + if ( pOverlay ) + { + pOverlay->nId = pMapOverlay->nId; + + pOverlay->flU[0] = pMapOverlay->flU[0]; + pOverlay->flU[1] = pMapOverlay->flU[1]; + pOverlay->flV[0] = pMapOverlay->flV[0]; + pOverlay->flV[1] = pMapOverlay->flV[1]; + + VectorCopy( pMapOverlay->vecUVPoints[0], pOverlay->vecUVPoints[0] ); + VectorCopy( pMapOverlay->vecUVPoints[1], pOverlay->vecUVPoints[1] ); + VectorCopy( pMapOverlay->vecUVPoints[2], pOverlay->vecUVPoints[2] ); + VectorCopy( pMapOverlay->vecUVPoints[3], pOverlay->vecUVPoints[3] ); + + VectorCopy( pMapOverlay->vecOrigin, pOverlay->vecOrigin ); + + VectorCopy( pMapOverlay->vecBasis[2], pOverlay->vecBasisNormal ); + + pOverlay->SetRenderOrder( pMapOverlay->m_nRenderOrder ); + + // Encode the BasisU into the unused z component of the vecUVPoints 0, 1, 2 + pOverlay->vecUVPoints[0].z = pMapOverlay->vecBasis[0].x; + pOverlay->vecUVPoints[1].z = pMapOverlay->vecBasis[0].y; + pOverlay->vecUVPoints[2].z = pMapOverlay->vecBasis[0].z; + + // Encode whether or not the v axis should be flipped. + Vector vecCross = pMapOverlay->vecBasis[2].Cross( pMapOverlay->vecBasis[0] ); + if ( vecCross.Dot( pMapOverlay->vecBasis[1] ) < 0.0f ) + { + pOverlay->vecUVPoints[3].z = 1.0f; + } + + // Texinfo. + texinfo_t texInfo; + texInfo.flags = 0; + texInfo.texdata = FindOrCreateTexData( pMapOverlay->szMaterialName ); + for( int iVec = 0; iVec < 2; ++iVec ) + { + for( int iAxis = 0; iAxis < 3; ++iAxis ) + { + texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][iAxis] = 0.0f; + texInfo.textureVecsTexelsPerWorldUnits[iVec][iAxis] = 0.0f; + } + + texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][3] = -99999.0f; + texInfo.textureVecsTexelsPerWorldUnits[iVec][3] = -99999.0f; + } + pOverlay->nTexInfo = FindOrCreateTexInfo( texInfo ); + + // Face List + int nFaceCount = pMapOverlay->aFaceList.Count(); + Assert( nFaceCount < OVERLAY_BSP_FACE_COUNT ); + if ( nFaceCount >= OVERLAY_BSP_FACE_COUNT ) + { + Error( "Overlay touching too many faces (touching %d, max %d)\nOverlay %s at %.1f %.1f %.1f", nFaceCount, OVERLAY_BSP_FACE_COUNT, pMapOverlay->szMaterialName, pMapOverlay->vecOrigin.x, pMapOverlay->vecOrigin.y, pMapOverlay->vecOrigin.z ); + return; + } + + pOverlay->SetFaceCount( nFaceCount ); + for( int iFace = 0; iFace < nFaceCount; ++iFace ) + { + pOverlay->aFaces[iFace] = pMapOverlay->aFaceList.Element( iFace ); + } + } + + // Convert the map overlay fade data into a .bsp overlay fade (doverlayfade_t). + if ( pOverlayFade ) + { + pOverlayFade->flFadeDistMinSq = pMapOverlay->flFadeDistMinSq; + pOverlayFade->flFadeDistMaxSq = pMapOverlay->flFadeDistMaxSq; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void OverlayTransition_EmitOverlayFace( mapoverlay_t *pMapOverlay ) +{ + Assert( g_nWaterOverlayCount < MAX_MAP_WATEROVERLAYS ); + if ( g_nWaterOverlayCount >= MAX_MAP_WATEROVERLAYS ) + { + Error ( "Too many water overlays!\nMAX_MAP_WATEROVERLAYS = %d", MAX_MAP_WATEROVERLAYS ); + return; + } + + dwateroverlay_t *pOverlay = &g_WaterOverlays[g_nWaterOverlayCount]; + g_nWaterOverlayCount++; + + // Conver the map overlay into a .bsp overlay (doverlay_t). + if ( pOverlay ) + { + pOverlay->nId = pMapOverlay->nId; + + pOverlay->flU[0] = pMapOverlay->flU[0]; + pOverlay->flU[1] = pMapOverlay->flU[1]; + pOverlay->flV[0] = pMapOverlay->flV[0]; + pOverlay->flV[1] = pMapOverlay->flV[1]; + + VectorCopy( pMapOverlay->vecUVPoints[0], pOverlay->vecUVPoints[0] ); + VectorCopy( pMapOverlay->vecUVPoints[1], pOverlay->vecUVPoints[1] ); + VectorCopy( pMapOverlay->vecUVPoints[2], pOverlay->vecUVPoints[2] ); + VectorCopy( pMapOverlay->vecUVPoints[3], pOverlay->vecUVPoints[3] ); + + VectorCopy( pMapOverlay->vecOrigin, pOverlay->vecOrigin ); + + VectorCopy( pMapOverlay->vecBasis[2], pOverlay->vecBasisNormal ); + + pOverlay->SetRenderOrder( pMapOverlay->m_nRenderOrder ); + + // Encode the BasisU into the unused z component of the vecUVPoints 0, 1, 2 + pOverlay->vecUVPoints[0].z = pMapOverlay->vecBasis[0].x; + pOverlay->vecUVPoints[1].z = pMapOverlay->vecBasis[0].y; + pOverlay->vecUVPoints[2].z = pMapOverlay->vecBasis[0].z; + + // Encode whether or not the v axis should be flipped. + Vector vecCross = pMapOverlay->vecBasis[2].Cross( pMapOverlay->vecBasis[0] ); + if ( vecCross.Dot( pMapOverlay->vecBasis[1] ) < 0.0f ) + { + pOverlay->vecUVPoints[3].z = 1.0f; + } + + // Texinfo. + texinfo_t texInfo; + texInfo.flags = 0; + texInfo.texdata = FindOrCreateTexData( pMapOverlay->szMaterialName ); + for( int iVec = 0; iVec < 2; ++iVec ) + { + for( int iAxis = 0; iAxis < 3; ++iAxis ) + { + texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][iAxis] = 0.0f; + texInfo.textureVecsTexelsPerWorldUnits[iVec][iAxis] = 0.0f; + } + + texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][3] = -99999.0f; + texInfo.textureVecsTexelsPerWorldUnits[iVec][3] = -99999.0f; + } + pOverlay->nTexInfo = FindOrCreateTexInfo( texInfo ); + + // Face List + int nFaceCount = pMapOverlay->aFaceList.Count(); + Assert( nFaceCount < WATEROVERLAY_BSP_FACE_COUNT ); + if ( nFaceCount >= WATEROVERLAY_BSP_FACE_COUNT ) + { + Error( "Water Overlay touching too many faces (touching %d, max %d)\nOverlay %s at %.1f %.1f %.1f", nFaceCount, OVERLAY_BSP_FACE_COUNT, pMapOverlay->szMaterialName, pMapOverlay->vecOrigin.x, pMapOverlay->vecOrigin.y, pMapOverlay->vecOrigin.z ); + return; + } + + pOverlay->SetFaceCount( nFaceCount ); + for( int iFace = 0; iFace < nFaceCount; ++iFace ) + { + pOverlay->aFaces[iFace] = pMapOverlay->aFaceList.Element( iFace ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Overlay_EmitOverlayFaces( void ) +{ + int nMapOverlayCount = g_aMapOverlays.Count(); + for( int iMapOverlay = 0; iMapOverlay < nMapOverlayCount; ++iMapOverlay ) + { + Overlay_EmitOverlayFace( &g_aMapOverlays.Element( iMapOverlay ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void OverlayTransition_EmitOverlayFaces( void ) +{ + int nMapOverlayCount = g_aMapWaterOverlays.Count(); + for( int iMapOverlay = 0; iMapOverlay < nMapOverlayCount; ++iMapOverlay ) + { + OverlayTransition_EmitOverlayFace( &g_aMapWaterOverlays.Element( iMapOverlay ) ); + } +} + + +//----------------------------------------------------------------------------- +// These routines were mostly stolen from MapOverlay.cpp in Hammer +//----------------------------------------------------------------------------- +#define OVERLAY_BASIS_U 0 +#define OVERLAY_BASIS_V 1 +#define OVERLAY_BASIS_NORMAL 2 +#define OVERLAY_HANDLES_COUNT 4 + + +inline void TransformPoint( const VMatrix& matrix, Vector &point ) +{ + Vector orgVector = point; + matrix.V3Mul( orgVector, point ); +} + + +inline bool fequal( float value, float target, float delta) { return ( (value<(target+delta))&&(value>(target-delta)) ); } + + +//----------------------------------------------------------------------------- +// Purpose: this function translate / rotate an overlay. +// Input : pOverlay - the overlay to be translated +// OriginOffset - the translation +// AngleOffset - the rotation +// Matrix - the translation / rotation matrix +// Output : none +//----------------------------------------------------------------------------- +void Overlay_Translate( mapoverlay_t *pOverlay, Vector &OriginOffset, QAngle &AngleOffset, matrix3x4_t &Matrix ) +{ + VMatrix tmpMatrix( Matrix ); + + Vector temp = pOverlay->vecOrigin; + VectorTransform( temp, Matrix, pOverlay->vecOrigin ); + + // erase move component + tmpMatrix.SetTranslation( vec3_origin ); + + // check if matrix would still change something + if ( !tmpMatrix.IsIdentity() ) + { + // make sure axes are normalized (they should be anyways) + pOverlay->vecBasis[OVERLAY_BASIS_U].NormalizeInPlace(); + pOverlay->vecBasis[OVERLAY_BASIS_V].NormalizeInPlace(); + + Vector vecU = pOverlay->vecBasis[OVERLAY_BASIS_U]; + Vector vecV = pOverlay->vecBasis[OVERLAY_BASIS_V]; + Vector vecNormal = pOverlay->vecBasis[OVERLAY_BASIS_NORMAL]; + + TransformPoint( tmpMatrix, vecU ); + TransformPoint( tmpMatrix, vecV ); + TransformPoint( tmpMatrix, vecNormal ); + + float fScaleU = vecU.Length(); + float fScaleV = vecV.Length(); + float flScaleNormal = vecNormal.Length(); + + bool bIsUnit = ( fequal( fScaleU, 1.0f, 0.0001 ) && fequal( fScaleV, 1.0f, 0.0001 ) && fequal( flScaleNormal, 1.0f, 0.0001 ) ); + bool bIsPerp = ( fequal( DotProduct( vecU, vecV ), 0.0f, 0.0025 ) && fequal( DotProduct( vecU, vecNormal ), 0.0f, 0.0025 ) && fequal( DotProduct( vecV, vecNormal ), 0.0f, 0.0025 ) ); + + // if ( fequal(fScaleU,1,0.0001) && fequal(fScaleV,1,0.0001) && fequal(DotProduct( vecU, vecV ),0,0.0025) ) + if ( bIsUnit && bIsPerp ) + { + // transformation doesnt scale or shear anything, so just update base axes + pOverlay->vecBasis[OVERLAY_BASIS_U] = vecU; + pOverlay->vecBasis[OVERLAY_BASIS_V] = vecV; + pOverlay->vecBasis[OVERLAY_BASIS_NORMAL] = vecNormal; + } + else + { + // more complex transformation, move UV coordinates, but leave base axes + for ( int iHandle=0; iHandle<OVERLAY_HANDLES_COUNT;iHandle++) + { + Vector vecUV = pOverlay->vecUVPoints[iHandle]; + Vector vecPos = ( vecUV.x * pOverlay->vecBasis[OVERLAY_BASIS_U] + vecUV.y * pOverlay->vecBasis[OVERLAY_BASIS_V] ); + + // to transform in world space + TransformPoint( tmpMatrix, vecPos ); + + vecUV.x = pOverlay->vecBasis[OVERLAY_BASIS_U].Dot( vecPos ); + vecUV.y = pOverlay->vecBasis[OVERLAY_BASIS_V].Dot( vecPos ); + + pOverlay->vecUVPoints[iHandle] = vecUV; + } + } + } + +} diff --git a/utils/vbsp/portals.cpp b/utils/vbsp/portals.cpp new file mode 100644 index 0000000..6c59cff --- /dev/null +++ b/utils/vbsp/portals.cpp @@ -0,0 +1,1684 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" +#include "utlvector.h" +#include "mathlib/vmatrix.h" +#include "iscratchpad3d.h" +#include "csg.h" +#include "fmtstr.h" + +int c_active_portals; +int c_peak_portals; +int c_boundary; +int c_boundary_sides; + +/* +=========== +AllocPortal +=========== +*/ +portal_t *AllocPortal (void) +{ + static int s_PortalCount = 0; + + portal_t *p; + + if (numthreads == 1) + c_active_portals++; + if (c_active_portals > c_peak_portals) + c_peak_portals = c_active_portals; + + p = (portal_t*)malloc (sizeof(portal_t)); + memset (p, 0, sizeof(portal_t)); + p->id = s_PortalCount; + ++s_PortalCount; + + return p; +} + +void FreePortal (portal_t *p) +{ + if (p->winding) + FreeWinding (p->winding); + if (numthreads == 1) + c_active_portals--; + free (p); +} + +//============================================================== + +/* +============== +VisibleContents + +Returns the single content bit of the +strongest visible content present +============== +*/ +int VisibleContents (int contents) +{ + int i; + + for (i=1 ; i<=LAST_VISIBLE_CONTENTS ; i<<=1) + { + if (contents & i ) + { + return i; + } + } + + return 0; +} + + +/* +=============== +ClusterContents +=============== +*/ +int ClusterContents (node_t *node) +{ + int c1, c2, c; + + if (node->planenum == PLANENUM_LEAF) + return node->contents; + + c1 = ClusterContents(node->children[0]); + c2 = ClusterContents(node->children[1]); + c = c1|c2; + + // a cluster may include some solid detail areas, but + // still be seen into + if ( ! (c1&CONTENTS_SOLID) || ! (c2&CONTENTS_SOLID) ) + c &= ~CONTENTS_SOLID; + return c; +} + +/* +============= +Portal_VisFlood + +Returns true if the portal is empty or translucent, allowing +the PVS calculation to see through it. +The nodes on either side of the portal may actually be clusters, +not leafs, so all contents should be ored together +============= +*/ +qboolean Portal_VisFlood (portal_t *p) +{ + int c1, c2; + + if (!p->onnode) + return false; // to global outsideleaf + + c1 = ClusterContents(p->nodes[0]); + c2 = ClusterContents(p->nodes[1]); + + if (!VisibleContents (c1^c2)) + return true; + + if (c1 & (CONTENTS_TRANSLUCENT|CONTENTS_DETAIL)) + c1 = 0; + if (c2 & (CONTENTS_TRANSLUCENT|CONTENTS_DETAIL)) + c2 = 0; + + if ( (c1|c2) & CONTENTS_SOLID ) + return false; // can't see through solid + + if (! (c1 ^ c2)) + return true; // identical on both sides + + if (!VisibleContents (c1^c2)) + return true; + return false; +} + + +/* +=============== +Portal_EntityFlood + +The entity flood determines which areas are +"outside" on the map, which are then filled in. +Flowing from side s to side !s +=============== +*/ +qboolean Portal_EntityFlood (portal_t *p, int s) +{ + if (p->nodes[0]->planenum != PLANENUM_LEAF + || p->nodes[1]->planenum != PLANENUM_LEAF) + Error ("Portal_EntityFlood: not a leaf"); + + // can never cross to a solid + if ( (p->nodes[0]->contents & CONTENTS_SOLID) + || (p->nodes[1]->contents & CONTENTS_SOLID) ) + return false; + + // can flood through everything else + return true; +} + +qboolean Portal_AreaLeakFlood (portal_t *p, int s) +{ + if ( !Portal_EntityFlood( p, s ) ) + return false; + + // can never cross through areaportal + if ( (p->nodes[0]->contents & CONTENTS_AREAPORTAL) + || (p->nodes[1]->contents & CONTENTS_AREAPORTAL) ) + return false; + + // can flood through everything else + return true; +} + + +//============================================================================= + +int c_tinyportals; + +/* +============= +AddPortalToNodes +============= +*/ +void AddPortalToNodes (portal_t *p, node_t *front, node_t *back) +{ + if (p->nodes[0] || p->nodes[1]) + Error ("AddPortalToNode: allready included"); + + p->nodes[0] = front; + p->next[0] = front->portals; + front->portals = p; + + p->nodes[1] = back; + p->next[1] = back->portals; + back->portals = p; +} + + +/* +============= +RemovePortalFromNode +============= +*/ +void RemovePortalFromNode (portal_t *portal, node_t *l) +{ + portal_t **pp, *t; + +// remove reference to the current portal + pp = &l->portals; + while (1) + { + t = *pp; + if (!t) + Error ("RemovePortalFromNode: portal not in leaf"); + + if ( t == portal ) + break; + + if (t->nodes[0] == l) + pp = &t->next[0]; + else if (t->nodes[1] == l) + pp = &t->next[1]; + else + Error ("RemovePortalFromNode: portal not bounding leaf"); + } + + if (portal->nodes[0] == l) + { + *pp = portal->next[0]; + portal->nodes[0] = NULL; + } + else if (portal->nodes[1] == l) + { + *pp = portal->next[1]; + portal->nodes[1] = NULL; + } +} + +//============================================================================ + +void PrintPortal (portal_t *p) +{ + int i; + winding_t *w; + + w = p->winding; + for (i=0 ; i<w->numpoints ; i++) + Msg ("(%5.0f,%5.0f,%5.0f)\n",w->p[i][0] + , w->p[i][1], w->p[i][2]); +} + +// because of water areaportals support, the areaportal may not be the only brush on this node +bspbrush_t *AreaportalBrushForNode( node_t *node ) +{ + bspbrush_t *b = node->brushlist; + while ( b && !(b->original->contents & CONTENTS_AREAPORTAL) ) + { + b = b->next; + } + Assert( b->original->entitynum != 0 ); + return b; +} + +/* +================ +MakeHeadnodePortals + +The created portals will face the global outside_node +================ +*/ +// buffer space around sides of nodes +#define SIDESPACE 8 +void MakeHeadnodePortals (tree_t *tree) +{ + Vector bounds[2]; + int i, j, n; + portal_t *p, *portals[6]; + plane_t bplanes[6], *pl; + node_t *node; + + node = tree->headnode; + +// pad with some space so there will never be null volume leafs + for (i=0 ; i<3 ; i++) + { + bounds[0][i] = tree->mins[i] - SIDESPACE; + bounds[1][i] = tree->maxs[i] + SIDESPACE; + } + + tree->outside_node.planenum = PLANENUM_LEAF; + tree->outside_node.brushlist = NULL; + tree->outside_node.portals = NULL; + tree->outside_node.contents = 0; + + for (i=0 ; i<3 ; i++) + for (j=0 ; j<2 ; j++) + { + n = j*3 + i; + + p = AllocPortal (); + portals[n] = p; + + pl = &bplanes[n]; + memset (pl, 0, sizeof(*pl)); + if (j) + { + pl->normal[i] = -1; + pl->dist = -bounds[j][i]; + } + else + { + pl->normal[i] = 1; + pl->dist = bounds[j][i]; + } + p->plane = *pl; + p->winding = BaseWindingForPlane (pl->normal, pl->dist); + AddPortalToNodes (p, node, &tree->outside_node); + } + +// clip the basewindings by all the other planes + for (i=0 ; i<6 ; i++) + { + for (j=0 ; j<6 ; j++) + { + if (j == i) + continue; + ChopWindingInPlace (&portals[i]->winding, bplanes[j].normal, bplanes[j].dist, ON_EPSILON); + } + } +} + +//=================================================== + + +/* +================ +BaseWindingForNode +================ +*/ +#define BASE_WINDING_EPSILON 0.001 +#define SPLIT_WINDING_EPSILON 0.001 + +winding_t *BaseWindingForNode (node_t *node) +{ + winding_t *w; + node_t *n; + plane_t *plane; + Vector normal; + vec_t dist; + + w = BaseWindingForPlane (g_MainMap->mapplanes[node->planenum].normal, g_MainMap->mapplanes[node->planenum].dist); + + // clip by all the parents + for (n=node->parent ; n && w ; ) + { + plane = &g_MainMap->mapplanes[n->planenum]; + + if (n->children[0] == node) + { // take front + ChopWindingInPlace (&w, plane->normal, plane->dist, BASE_WINDING_EPSILON); + } + else + { // take back + VectorSubtract (vec3_origin, plane->normal, normal); + dist = -plane->dist; + ChopWindingInPlace (&w, normal, dist, BASE_WINDING_EPSILON); + } + node = n; + n = n->parent; + } + + return w; +} + +//============================================================ + +/* +================== +MakeNodePortal + +create the new portal by taking the full plane winding for the cutting plane +and clipping it by all of parents of this node +================== +*/ +void MakeNodePortal (node_t *node) +{ + portal_t *new_portal, *p; + winding_t *w; + Vector normal; + float dist = 0.0f; + int side = 0; + + w = BaseWindingForNode (node); + + // clip the portal by all the other portals in the node + for (p = node->portals ; p && w; p = p->next[side]) + { + if (p->nodes[0] == node) + { + side = 0; + VectorCopy (p->plane.normal, normal); + dist = p->plane.dist; + } + else if (p->nodes[1] == node) + { + side = 1; + VectorSubtract (vec3_origin, p->plane.normal, normal); + dist = -p->plane.dist; + } + else + { + Error ("CutNodePortals_r: mislinked portal"); + } + + ChopWindingInPlace (&w, normal, dist, 0.1); + } + + if (!w) + { + return; + } + + if (WindingIsTiny (w)) + { + c_tinyportals++; + FreeWinding (w); + return; + } + + + new_portal = AllocPortal (); + new_portal->plane = g_MainMap->mapplanes[node->planenum]; + new_portal->onnode = node; + 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 = 0; + plane_t *plane; + winding_t *frontwinding, *backwinding; + + plane = &g_MainMap->mapplanes[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 +// + ClipWindingEpsilon (p->winding, plane->normal, plane->dist, + SPLIT_WINDING_EPSILON, &frontwinding, &backwinding); + + if (frontwinding && WindingIsTiny(frontwinding)) + { + FreeWinding (frontwinding); + frontwinding = NULL; + c_tinyportals++; + } + + if (backwinding && WindingIsTiny(backwinding)) + { + FreeWinding (backwinding); + backwinding = NULL; + c_tinyportals++; + } + + if (!frontwinding && !backwinding) + { // tiny windings on both sides + continue; + } + + if (!frontwinding) + { + FreeWinding (backwinding); + if (side == 0) + AddPortalToNodes (p, b, other_node); + else + AddPortalToNodes (p, other_node, b); + continue; + } + if (!backwinding) + { + FreeWinding (frontwinding); + 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 +================ +*/ +void CalcNodeBounds (node_t *node) +{ + portal_t *p; + int s; + int i; + + // calc mins/maxs for both leafs and nodes + ClearBounds (node->mins, node->maxs); + for (p = node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + for (i=0 ; i<p->winding->numpoints ; i++) + AddPointToBounds (p->winding->p[i], node->mins, node->maxs); + } +} + + +/* +================== +MakeTreePortals_r +================== +*/ +void MakeTreePortals_r (node_t *node) +{ + int i; + + CalcNodeBounds (node); + if (node->mins[0] >= node->maxs[0]) + { + Warning("WARNING: node without a volume\n"); + } + + for (i=0 ; i<3 ; i++) + { + if (node->mins[i] < (MIN_COORD_INTEGER-SIDESPACE) || node->maxs[i] > (MAX_COORD_INTEGER+SIDESPACE)) + { + const char *pMatName = "<NO BRUSH>"; + // split by brush side + if ( node->side ) + { + texinfo_t *pTexInfo = &texinfo[node->side->texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + pMatName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + } + Vector point = node->portals->winding->p[0]; + Warning("WARNING: BSP node with unbounded volume (material: %s, near %s)\n", pMatName, VecToString(point) ); + break; + } + } + if (node->planenum == PLANENUM_LEAF) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + MakeTreePortals_r (node->children[0]); + MakeTreePortals_r (node->children[1]); +} + +/* +================== +MakeTreePortals +================== +*/ +void MakeTreePortals (tree_t *tree) +{ + MakeHeadnodePortals (tree); + MakeTreePortals_r (tree->headnode); +} + +/* +========================================================= + +FLOOD ENTITIES + +========================================================= +*/ + +//----------------------------------------------------------------------------- +// Purpose: Floods outward from the given node, marking visited nodes with +// the number of hops from a node with an entity. If we ever mark +// the outside_node for this tree, we've leaked. +// Input : node - +// dist - +//----------------------------------------------------------------------------- +void FloodPortals_r (node_t *node, int dist) +{ + portal_t *p; + int s; + + node->occupied = dist; + + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + + // Skip nodes that have already been marked. + if (p->nodes[!s]->occupied) + continue; + + // Skip portals that lead to or from nodes with solid contents. + if (!Portal_EntityFlood (p, s)) + continue; + + FloodPortals_r (p->nodes[!s], dist+1); + } +} + +void FloodAreaLeak_r( node_t *node, int dist ) +{ + portal_t *p; + int s; + + node->occupied = dist; + + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + + if (p->nodes[!s]->occupied) + continue; + + if (!Portal_AreaLeakFlood (p, s)) + continue; + + FloodAreaLeak_r( p->nodes[!s], dist+1 ); + } +} + +void ClearOccupied_r( node_t *headnode ) +{ + if ( !headnode ) + return; + + headnode->occupied = 0; + ClearOccupied_r( headnode->children[0] ); + ClearOccupied_r( headnode->children[1] ); +} + +void FloodAreaLeak( node_t *headnode, node_t *pFirstSide ) +{ + ClearOccupied_r( headnode ); + FloodAreaLeak_r( pFirstSide, 2 ); +} + +//----------------------------------------------------------------------------- +// Purpose: For the given entity at the given origin, finds the leaf node in the +// BSP tree that the entity occupies. +// +// We then flood outward from that leaf to see if the entity leaks. +// Input : headnode - +// origin - +// occupant - +// Output : Returns false if the entity is in solid, true if it is not. +//----------------------------------------------------------------------------- +qboolean PlaceOccupant (node_t *headnode, Vector& origin, entity_t *occupant) +{ + node_t *node; + vec_t d; + plane_t *plane; + + // find the leaf to start in + node = headnode; + while (node->planenum != PLANENUM_LEAF) + { + plane = &g_MainMap->mapplanes[node->planenum]; + d = DotProduct (origin, plane->normal) - plane->dist; + if (d >= 0) + node = node->children[0]; + else + node = node->children[1]; + } + + if (node->contents == CONTENTS_SOLID) + return false; + + node->occupant = occupant; + + // Flood outward from here to see if this entity leaks. + FloodPortals_r (node, 1); + + return true; +} + +/* +============= +FloodEntities + +Marks all nodes that can be reached by entites +============= +*/ +qboolean FloodEntities (tree_t *tree) +{ + int i; + Vector origin; + char *cl; + qboolean inside; + node_t *headnode; + + headnode = tree->headnode; + qprintf ("--- FloodEntities ---\n"); + inside = false; + tree->outside_node.occupied = 0; + + for (i=1 ; i<num_entities ; i++) + { + GetVectorForKey (&entities[i], "origin", origin); + if (VectorCompare(origin, vec3_origin)) + continue; + + cl = ValueForKey (&entities[i], "classname"); + + origin[2] += 1; // so objects on floor are ok + + // nudge playerstart around if needed so clipping hulls allways + // have a valid point + if (!strcmp (cl, "info_player_start")) + { + int x, y; + + for (x=-16 ; x<=16 ; x += 16) + { + for (y=-16 ; y<=16 ; y += 16) + { + origin[0] += x; + origin[1] += y; + if (PlaceOccupant (headnode, origin, &entities[i])) + { + inside = true; + goto gotit; + } + origin[0] -= x; + origin[1] -= y; + } + } +gotit: ; + } + else + { + if (PlaceOccupant (headnode, origin, &entities[i])) + inside = true; + } + } + + if (!inside) + { + qprintf ("no entities in open -- no filling\n"); + } + + if (tree->outside_node.occupied) + { + qprintf ("entity reached from outside -- no filling\n" ); + } + + return (qboolean)(inside && !tree->outside_node.occupied); +} + + +/* +========================================================= + +FLOOD AREAS + +========================================================= +*/ + +int c_areas; + +bool IsAreaportalNode( node_t *node ) +{ + return ( node->contents & CONTENTS_AREAPORTAL ) ? true : false; +} +/* +============= +FloodAreas_r +============= +*/ + +void FloodAreas_r (node_t *node, portal_t *pSeeThrough) +{ + portal_t *p; + int s; + bspbrush_t *b; + entity_t *e; + + if ( IsAreaportalNode(node) ) + { + // this node is part of an area portal + b = AreaportalBrushForNode( node ); + e = &entities[b->original->entitynum]; + + // if the current area has allready touched this + // portal, we are done + if (e->portalareas[0] == c_areas || e->portalareas[1] == c_areas) + return; + + // note the current area as bounding the portal + if (e->portalareas[1]) + { + Warning("WARNING: areaportal entity %i (brush %i) touches > 2 areas\n", b->original->entitynum, b->original->id ); + return; + } + + if (e->portalareas[0]) + { + e->portalareas[1] = c_areas; + e->m_pPortalsLeadingIntoAreas[1] = pSeeThrough; + } + else + { + e->portalareas[0] = c_areas; + e->m_pPortalsLeadingIntoAreas[0] = pSeeThrough; + } + + return; + } + + if (node->area) + return; // allready got it + node->area = c_areas; + + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); +#if 0 + if (p->nodes[!s]->occupied) + continue; +#endif + if (!Portal_EntityFlood (p, s)) + continue; + + FloodAreas_r (p->nodes[!s], p); + } +} + +/* +============= +FindAreas_r + +Just decend the tree, and for each node that hasn't had an +area set, flood fill out from there +============= +*/ +void FindAreas_r (node_t *node) +{ + if (node->planenum != PLANENUM_LEAF) + { + FindAreas_r (node->children[0]); + FindAreas_r (node->children[1]); + return; + } + + if (node->area) + return; // allready got it + + if (node->contents & CONTENTS_SOLID) + return; + + if (!node->occupied) + return; // not reachable by entities + + // area portals are allways only flooded into, never + // out of + if (IsAreaportalNode(node)) + return; + + c_areas++; + FloodAreas_r (node, NULL); +} + + +void ReportAreaportalLeak( tree_t *tree, node_t *node ) +{ + portal_t *p, *pStart = NULL; + int s; + + // Find a portal out of this areaportal into empty space + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + if ( !Portal_EntityFlood( p, !s ) ) + continue; + if ( p->nodes[!s]->contents & CONTENTS_AREAPORTAL ) + continue; + + pStart = p; + break; + } + + if ( pStart ) + { + s = pStart->nodes[0] == node; + Assert(!(pStart->nodes[s]->contents & CONTENTS_AREAPORTAL) ); + // flood fill the area outside this areaportal brush + FloodAreaLeak( tree->headnode, pStart->nodes[s] ); + + // find the portal into the longest path around the portal + portal_t *pBest = NULL; + int bestDist = 0; + for (p=node->portals ; p ; p = p->next[s]) + { + if ( p == pStart ) + continue; + + s = (p->nodes[1] == node); + if ( p->nodes[!s]->occupied > bestDist ) + { + pBest = p; + bestDist = p->nodes[!s]->occupied; + } + } + if ( pBest ) + { + s = (pBest->nodes[0] == node); + // write the linefile that goes from pBest to pStart + AreaportalLeakFile( tree, pStart, pBest, pBest->nodes[s] ); + } + } +} + + +/* +============= +SetAreaPortalAreas_r + +Just decend the tree, and for each node that hasn't had an +area set, flood fill out from there +============= +*/ +void SetAreaPortalAreas_r (tree_t *tree, node_t *node) +{ + bspbrush_t *b; + entity_t *e; + + if (node->planenum != PLANENUM_LEAF) + { + SetAreaPortalAreas_r (tree, node->children[0]); + SetAreaPortalAreas_r (tree, node->children[1]); + return; + } + + if (IsAreaportalNode(node)) + { + if (node->area) + return; // already set + + b = AreaportalBrushForNode( node ); + e = &entities[b->original->entitynum]; + node->area = e->portalareas[0]; + if (!e->portalareas[1]) + { + ReportAreaportalLeak( tree, node ); + Warning("\nBrush %i: areaportal brush doesn't touch two areas\n", b->original->id); + return; + } + } +} + + +// Return a positive value between 0 and 2*PI telling the angle distance +// from flBaseAngle to flTestAngle. +float AngleOffset( float flBaseAngle, float flTestAngle ) +{ + while( flTestAngle > flBaseAngle ) + flTestAngle -= 2 * M_PI; + + return fmod( flBaseAngle - flTestAngle, (float) (2 * M_PI) ); +} + + +int FindUniquePoints( const Vector2D *pPoints, int nPoints, int *indexMap, int nMaxIndexMapPoints, float flTolerance ) +{ + float flToleranceSqr = flTolerance * flTolerance; + + // This could be slightly more efficient. + int nUniquePoints = 0; + for ( int i=0; i < nPoints; i++ ) + { + int j; + for ( j=0; j < nUniquePoints; j++ ) + { + if ( pPoints[i].DistToSqr( pPoints[indexMap[j]] ) < flToleranceSqr ) + break; + } + if ( j == nUniquePoints ) + { + if ( nUniquePoints >= nMaxIndexMapPoints ) + Error( "FindUniquePoints: overflowed unique point list (size %d).", nMaxIndexMapPoints ); + + indexMap[nUniquePoints++] = i; + } + } + + return nUniquePoints; +} + +// Build a 2D convex hull of the set of points. +// This essentially giftwraps the points as it walks around the perimeter. +int Convex2D( Vector2D const *pPoints, int nPoints, int *indices, int nMaxIndices ) +{ + int nIndices = 0; + bool touched[512]; + int indexMap[512]; + + if( nPoints == 0 ) + return 0; + + + // If we don't collapse the points into a unique set, we can loop around forever + // and max out nMaxIndices. + nPoints = FindUniquePoints( pPoints, nPoints, indexMap, ARRAYSIZE( indexMap ), 0.1f ); + memset( touched, 0, nPoints*sizeof(touched[0]) ); + + // Find the (lower) left side. + int i; + int iBest = 0; + for( i=1; i < nPoints; i++ ) + { + if( pPoints[indexMap[i]].x < pPoints[indexMap[iBest]].x || + (pPoints[indexMap[i]].x == pPoints[indexMap[iBest]].x && pPoints[indexMap[i]].y < pPoints[indexMap[iBest]].y) ) + { + iBest = i; + } + } + + touched[iBest] = true; + indices[0] = indexMap[iBest]; + nIndices = 1; + + Vector2D curEdge( 0, 1 ); + + // Wind around clockwise. + while( 1 ) + { + Vector2D const *pStartPoint = &pPoints[ indices[nIndices-1] ]; + + float flEdgeAngle = atan2( curEdge.y, curEdge.x ); + + int iMinAngle = -1; + float flMinAngle = 5000; + + for( i=0; i < nPoints; i++ ) + { + Vector2D vTo = pPoints[indexMap[i]] - *pStartPoint; + float flDistToSqr = vTo.LengthSqr(); + if ( flDistToSqr <= 0.1f ) + continue; + + // Get the angle from the edge to this point. + float flAngle = atan2( vTo.y, vTo.x ); + flAngle = AngleOffset( flEdgeAngle, flAngle ); + + if( fabs( flAngle - flMinAngle ) < 0.00001f ) + { + float flDistToTestSqr = pStartPoint->DistToSqr( pPoints[iMinAngle] ); + + // If the angle is the same, pick the point farthest away. + // unless the current one is closing the face loop + if ( iMinAngle != indices[0] && flDistToSqr > flDistToTestSqr ) + { + flMinAngle = flAngle; + iMinAngle = indexMap[i]; + } + } + else if( flAngle < flMinAngle ) + { + flMinAngle = flAngle; + iMinAngle = indexMap[i]; + } + } + + if( iMinAngle == -1 ) + { + // Couldn't find a point? + Assert( false ); + break; + } + else if( iMinAngle == indices[0] ) + { + // Finished. + break; + } + else + { + // Add this point. + if( nIndices >= nMaxIndices ) + break; + + for ( int jj = 0; jj < nIndices; jj++ ) + { + // if this assert hits, this routine is broken and is generating a spiral + // rather than a closed polygon - basically an edge overlap of some kind + Assert(indices[jj] != iMinAngle ); + } + + indices[nIndices] = iMinAngle; + ++nIndices; + } + + curEdge = pPoints[indices[nIndices-1]] - pPoints[indices[nIndices-2]]; + } + + return nIndices; +} + +void FindPortalsLeadingToArea_R( + node_t *pHeadNode, + int iSrcArea, + int iDestArea, + plane_t *pPlane, + CUtlVector<portal_t*> &portals ) +{ + if (pHeadNode->planenum != PLANENUM_LEAF) + { + FindPortalsLeadingToArea_R( pHeadNode->children[0], iSrcArea, iDestArea, pPlane, portals ); + FindPortalsLeadingToArea_R( pHeadNode->children[1], iSrcArea, iDestArea, pPlane, portals ); + return; + } + + // Ok.. this is a leaf, check its portals. + int s; + for( portal_t *p = pHeadNode->portals; p ;p = p->next[!s] ) + { + s = (p->nodes[0] == pHeadNode); + + if( !p->nodes[0]->occupied || !p->nodes[1]->occupied ) + continue; + + if( p->nodes[1]->area == iDestArea && p->nodes[0]->area == iSrcArea || + p->nodes[0]->area == iDestArea && p->nodes[1]->area == iSrcArea ) + { + // Make sure the plane normals point the same way. + plane_t *pMapPlane = &g_MainMap->mapplanes[p->onnode->planenum]; + float flDot = fabs( pMapPlane->normal.Dot( pPlane->normal ) ); + if( fabs( 1 - flDot ) < 0.01f ) + { + Vector vPlanePt1 = pPlane->normal * pPlane->dist; + Vector vPlanePt2 = pMapPlane->normal * pMapPlane->dist; + + if( vPlanePt1.DistToSqr( vPlanePt2 ) < 0.01f ) + { + portals.AddToTail( p ); + } + } + } + } +} + + +void EmitClipPortalGeometry( node_t *pHeadNode, portal_t *pPortal, int iSrcArea, dareaportal_t *dp ) +{ + // Build a list of all the points in portals from the same original face. + CUtlVector<portal_t*> portals; + FindPortalsLeadingToArea_R( + pHeadNode, + iSrcArea, + dp->otherarea, + &pPortal->plane, + portals ); + + CUtlVector<Vector> points; + for( int iPortal=0; iPortal < portals.Size(); iPortal++ ) + { + portal_t *pPointPortal = portals[iPortal]; + winding_t *pWinding = pPointPortal->winding; + for( int i=0; i < pWinding->numpoints; i++ ) + { + points.AddToTail( pWinding->p[i] ); + } + } + + // Get the 2D convex hull. + + //// First transform them into a plane. + QAngle vAngles; + Vector vecs[3]; + + VectorAngles( pPortal->plane.normal, vAngles ); + AngleVectors( vAngles, &vecs[0], &vecs[1], &vecs[2] ); + VMatrix mTransform; + mTransform.Identity(); + mTransform.SetBasisVectors( vecs[0], vecs[1], vecs[2] ); + VMatrix mInvTransform = mTransform.Transpose(); + + int i; + CUtlVector<Vector2D> points2D; + for( i=0; i < points.Size(); i++ ) + { + Vector vTest = mTransform * points[i]; + points2D.AddToTail( Vector2D( vTest.y, vTest.z ) ); + } + + // Build the hull. + int indices[512]; + int nIndices = Convex2D( points2D.Base(), points2D.Size(), indices, 512 ); + + // Output the hull. + dp->m_FirstClipPortalVert = g_nClipPortalVerts; + dp->m_nClipPortalVerts = nIndices; + + if ( nIndices >= 32 ) + { + Warning( "Warning: area portal has %d verts. Could be a vbsp bug.\n", nIndices ); + } + + if( dp->m_FirstClipPortalVert + dp->m_nClipPortalVerts >= MAX_MAP_PORTALVERTS ) + { + Vector *p = pPortal->winding->p; + Error( "MAX_MAP_PORTALVERTS (probably a broken areaportal near %.1f %.1f %.1f ", p->x, p->y, p->z ); + } + + for( i=0; i < nIndices; i++ ) + { + g_ClipPortalVerts[g_nClipPortalVerts] = points[ indices[i] ]; + ++g_nClipPortalVerts; + } +} + + +// Sets node_t::area for non-leaf nodes (this allows an optimization in the renderer). +void SetNodeAreaIndices_R( node_t *node ) +{ + // All leaf area indices should already be set. + if( node->planenum == PLANENUM_LEAF ) + return; + + // Have the children set their area indices. + SetNodeAreaIndices_R( node->children[0] ); + SetNodeAreaIndices_R( node->children[1] ); + + // If all children (leaves or nodes) are in the same area, then set our area + // to this area as well. Otherwise, set it to -1. + if( node->children[0]->area == node->children[1]->area ) + node->area = node->children[0]->area; + else + node->area = -1; +} + + +/* +============= +EmitAreaPortals + +============= +*/ +void EmitAreaPortals (node_t *headnode) +{ + entity_t *e; + dareaportal_t *dp; + + if (c_areas > MAX_MAP_AREAS) + Error ("Map is split into too many unique areas (max = %d)\nProbably too many areaportals", MAX_MAP_AREAS); + numareas = c_areas+1; + numareaportals = 1; // leave 0 as an error + + // Reset the clip portal vert info. + g_nClipPortalVerts = 0; + + for (int iSrcArea=1 ; iSrcArea<=c_areas ; iSrcArea++) + { + dareas[iSrcArea].firstareaportal = numareaportals; + for (int j=0 ; j<num_entities ; j++) + { + e = &entities[j]; + if (!e->areaportalnum) + continue; + + if (e->portalareas[0] == iSrcArea || e->portalareas[1] == iSrcArea) + { + int iSide = (e->portalareas[0] == iSrcArea); + + // We're only interested in the portal that divides the two areas. + // One of the portals that leads into the CONTENTS_AREAPORTAL just bounds + // the same two areas but the other bounds two different ones. + portal_t *pLeadingPortal = e->m_pPortalsLeadingIntoAreas[0]; + if( pLeadingPortal->nodes[0]->area == pLeadingPortal->nodes[1]->area ) + pLeadingPortal = e->m_pPortalsLeadingIntoAreas[1]; + + if( pLeadingPortal ) + { + Assert( pLeadingPortal->nodes[0]->area != pLeadingPortal->nodes[1]->area ); + + dp = &dareaportals[numareaportals]; + numareaportals++; + + dp->m_PortalKey = e->areaportalnum; + dp->otherarea = e->portalareas[iSide]; + dp->planenum = pLeadingPortal->onnode->planenum; + + Assert( pLeadingPortal->nodes[0]->planenum == PLANENUM_LEAF ); + Assert( pLeadingPortal->nodes[1]->planenum == PLANENUM_LEAF ); + + if( pLeadingPortal->nodes[0]->area == dp->otherarea ) + { + // Use the flipped version of the plane. + dp->planenum = (dp->planenum & ~1) | (~dp->planenum & 1); + } + + EmitClipPortalGeometry( headnode, pLeadingPortal, iSrcArea, dp ); + } + } + } + + dareas[iSrcArea].numareaportals = numareaportals - dareas[iSrcArea].firstareaportal; + } + + SetNodeAreaIndices_R( headnode ); + + qprintf ("%5i numareas\n", numareas); + qprintf ("%5i numareaportals\n", numareaportals); +} + +/* +============= +FloodAreas + +Mark each leaf with an area, bounded by CONTENTS_AREAPORTAL +============= +*/ +void FloodAreas (tree_t *tree) +{ + int start = Plat_FloatTime(); + qprintf ("--- FloodAreas ---\n"); + Msg("Processing areas..."); + FindAreas_r (tree->headnode); + SetAreaPortalAreas_r (tree, tree->headnode); + qprintf ("%5i areas\n", c_areas); + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); +} + +//====================================================== + +int c_outside; +int c_inside; +int c_solid; + +void FillOutside_r (node_t *node) +{ + if (node->planenum != PLANENUM_LEAF) + { + FillOutside_r (node->children[0]); + FillOutside_r (node->children[1]); + return; + } + + // anything not reachable by an entity + // can be filled away + if (!node->occupied) + { + if (node->contents != CONTENTS_SOLID) + { + c_outside++; + node->contents = CONTENTS_SOLID; + } + else + c_solid++; + } + else + c_inside++; + +} + +/* +============= +FillOutside + +Fill all nodes that can't be reached by entities +============= +*/ +void FillOutside (node_t *headnode) +{ + c_outside = 0; + c_inside = 0; + c_solid = 0; + qprintf ("--- FillOutside ---\n"); + FillOutside_r (headnode); + qprintf ("%5i solid leafs\n", c_solid); + qprintf ("%5i leafs filled\n", c_outside); + qprintf ("%5i inside leafs\n", c_inside); +} + + +static float ComputeDistFromPlane( winding_t *pWinding, plane_t *pPlane, float maxdist ) +{ + float totaldist = 0.0f; + for (int i = 0; i < pWinding->numpoints; ++i) + { + totaldist += fabs(DotProduct( pPlane->normal, pWinding->p[i] ) - pPlane->dist); + if (totaldist > maxdist) + return totaldist; + } + return totaldist; +} + + +//----------------------------------------------------------------------------- +// Display portal error +//----------------------------------------------------------------------------- +static void DisplayPortalError( portal_t *p, int viscontents ) +{ + char contents[3][1024]; + PrintBrushContentsToString( p->nodes[0]->contents, contents[0], sizeof( contents[0] ) ); + PrintBrushContentsToString( p->nodes[1]->contents, contents[1], sizeof( contents[1] ) ); + PrintBrushContentsToString( viscontents, contents[2], sizeof( contents[2] ) ); + + Vector center; + WindingCenter( p->winding, center ); + Warning( "\nFindPortalSide: Couldn't find a good match for which brush to assign to a portal near (%.1f %.1f %.1f)\n", center.x, center.y, center.z); + Warning( "Leaf 0 contents: %s\n", contents[0] ); + Warning( "Leaf 1 contents: %s\n", contents[1] ); + Warning( "viscontents (node 0 contents ^ node 1 contents): %s\n", contents[2] ); + Warning( "This means that none of the brushes in leaf 0 or 1 that touches the portal has %s\n", contents[2] ); + Warning( "Check for a huge brush enclosing the coordinates above that has contents %s\n", contents[2] ); + Warning( "Candidate brush IDs: " ); + + CUtlVector<int> listed; + for (int j=0 ; j<2 ; j++) + { + node_t *n = p->nodes[j]; + for (bspbrush_t *bb=n->brushlist ; bb ; bb=bb->next) + { + mapbrush_t *brush = bb->original; + if ( brush->contents & viscontents ) + { + if ( listed.Find( brush->brushnum ) == -1 ) + { + listed.AddToTail( brush->brushnum ); + Warning( "Brush %d: ", brush->id ); + } + } + } + } + Warning( "\n\n" ); +} + + +//============================================================== + +/* +============ +FindPortalSide + +Finds a brush side to use for texturing the given portal +============ +*/ +void FindPortalSide (portal_t *p) +{ + int viscontents; + bspbrush_t *bb; + mapbrush_t *brush; + node_t *n; + int i,j; + int planenum; + side_t *side, *bestside; + float bestdist; + plane_t *p1, *p2; + + // decide which content change is strongest + // solid > lava > water, etc + viscontents = VisibleContents (p->nodes[0]->contents ^ p->nodes[1]->contents); + if (!viscontents) + { + return; + } + + planenum = p->onnode->planenum; + bestside = NULL; + bestdist = 1000000; + + for (j=0 ; j<2 ; j++) + { + n = p->nodes[j]; + p1 = &g_MainMap->mapplanes[p->onnode->planenum]; + + for (bb=n->brushlist ; bb ; bb=bb->next) + { + brush = bb->original; + if ( !(brush->contents & viscontents) ) + continue; + + for (i=0 ; i<brush->numsides ; i++) + { + side = &brush->original_sides[i]; + if (side->bevel) + continue; + if (side->texinfo == TEXINFO_NODE) + continue; // non-visible + + if ((side->planenum&~1) == planenum) + { // exact match + bestside = &brush->original_sides[i]; + bestdist = 0.0f; + goto gotit; + } + + p2 = &g_MainMap->mapplanes[side->planenum&~1]; + + float dist = ComputeDistFromPlane( p->winding, p2, bestdist ); + if (dist < bestdist) + { + bestside = side; + bestdist = dist; + } + } + } + } + +gotit: + if (!bestside) + qprintf ("WARNING: side not found for portal\n"); + + // Compute average dist, check for problems... + if ((bestdist / p->winding->numpoints) > 2) + { + static int nWarnCount = 0; + if ( nWarnCount < 8 ) + { + DisplayPortalError( p, viscontents ); + if ( ++nWarnCount == 8 ) + { + Warning("*** Suppressing further FindPortalSide errors.... ***\n" ); + } + } + } + + p->sidefound = true; + p->side = bestside; +} + + +/* +=============== +MarkVisibleSides_r + +=============== +*/ +void MarkVisibleSides_r (node_t *node) +{ + portal_t *p; + int s; + + if (node->planenum != PLANENUM_LEAF) + { + MarkVisibleSides_r (node->children[0]); + MarkVisibleSides_r (node->children[1]); + return; + } + + // empty leafs are never boundary leafs + if (!node->contents) + return; + + // see if there is a visible face + for (p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (!p->onnode) + continue; // edge of world + if (!p->sidefound) + FindPortalSide (p); + if (p->side) + p->side->visible = true; + } + +} + +/* +============= +MarkVisibleSides + +============= +*/ +// UNDONE: Put detail brushes in a separate list (not mapbrushes) ? +void MarkVisibleSides (tree_t *tree, int startbrush, int endbrush, int detailScreen) +{ + int i, j; + mapbrush_t *mb; + int numsides; + qboolean detail; + + qprintf ("--- MarkVisibleSides ---\n"); + + // clear all the visible flags + for (i=startbrush ; i<endbrush ; i++) + { + mb = &g_MainMap->mapbrushes[i]; + + if ( detailScreen != FULL_DETAIL ) + { + qboolean onlyDetail = (detailScreen==ONLY_DETAIL)?true:false; + // true for detail brushes + detail = (mb->contents & CONTENTS_DETAIL) ? true : false; + if ( onlyDetail ^ detail ) + { + // both of these must have the same value or we're not interested in this brush + continue; + } + } + + numsides = mb->numsides; + for (j=0 ; j<numsides ; j++) + mb->original_sides[j].visible = false; + } + + // set visible flags on the sides that are used by portals + MarkVisibleSides_r (tree->headnode); +} + + +//----------------------------------------------------------------------------- +// Used to determine which sides are visible +//----------------------------------------------------------------------------- +void MarkVisibleSides (tree_t *tree, mapbrush_t **ppBrushes, int nCount ) +{ + qprintf ("--- MarkVisibleSides ---\n"); + + // clear all the visible flags + int i, j; + for ( i=0; i < nCount; ++i ) + { + mapbrush_t *mb = ppBrushes[i]; + int numsides = mb->numsides; + for (j=0 ; j<numsides ; j++) + { + mb->original_sides[j].visible = false; + } + } + + // set visible flags on the sides that are used by portals + MarkVisibleSides_r( tree->headnode ); +} + diff --git a/utils/vbsp/portals.h b/utils/vbsp/portals.h new file mode 100644 index 0000000..cf65ac7 --- /dev/null +++ b/utils/vbsp/portals.h @@ -0,0 +1,19 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef PORTALS_H +#define PORTALS_H +#ifdef _WIN32 +#pragma once +#endif + + +// Sets up the g_ClipPortalIndices array. +void TranslateClipPortalIndices(); + + +#endif // PORTALS_H diff --git a/utils/vbsp/prtfile.cpp b/utils/vbsp/prtfile.cpp new file mode 100644 index 0000000..71d7e5c --- /dev/null +++ b/utils/vbsp/prtfile.cpp @@ -0,0 +1,374 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" +#include "collisionutils.h" +/* +============================================================================== + +PORTAL FILE GENERATION + +Save out name.prt for qvis to read +============================================================================== +*/ + + +#define PORTALFILE "PRT1" + +struct cluster_portals_t +{ + CUtlVector<portal_t *> portals; +}; + +int num_visclusters; // clusters the player can be in +int num_visportals; + +int g_SkyCluster = -1; + +void WriteFloat (FILE *f, vec_t v) +{ + if ( fabs(v - RoundInt(v)) < 0.001 ) + fprintf (f,"%i ",(int)RoundInt(v)); + else + fprintf (f,"%f ",v); +} + + +/* +================= +WritePortalFile_r +================= +*/ +void WritePortalFile(FILE *pFile, const CUtlVector<cluster_portals_t> &list) +{ + portal_t *p; + winding_t *w; + Vector normal; + vec_t dist; + + for ( int clusterIndex = 0; clusterIndex < list.Count(); clusterIndex++ ) + { + for ( int j = 0; j < list[clusterIndex].portals.Count(); j++ ) + { + p = list[clusterIndex].portals[j]; + w = p->winding; + // write out to the file + + // sometimes planes get turned around when they are very near + // the changeover point between different axis. interpret the + // plane the same way vis will, and flip the side orders if needed + // FIXME: is this still relevent? + WindingPlane (w, normal, &dist); + if ( DotProduct (p->plane.normal, normal) < 0.99 ) + { // backwards... + fprintf (pFile,"%i %i %i ",w->numpoints, p->nodes[1]->cluster, p->nodes[0]->cluster); + } + else + { + fprintf (pFile,"%i %i %i ",w->numpoints, p->nodes[0]->cluster, p->nodes[1]->cluster); + } + + for (int i=0 ; i<w->numpoints ; i++) + { + fprintf (pFile,"("); + WriteFloat (pFile, w->p[i][0]); + WriteFloat (pFile, w->p[i][1]); + WriteFloat (pFile, w->p[i][2]); + fprintf (pFile,") "); + } + fprintf (pFile,"\n"); + } + } +} + +struct viscluster_t +{ + bspbrush_t *pBrushes; + int clusterIndex; + int leafCount; + int leafStart; +}; + +static CUtlVector<viscluster_t> g_VisClusters; + +// add to the list of brushes the merge leaves into single vis clusters +void AddVisCluster( entity_t *pFuncVisCluster ) +{ + viscluster_t tmp; + Vector clipMins, clipMaxs; + clipMins[0] = clipMins[1] = clipMins[2] = MIN_COORD_INTEGER; + clipMaxs[0] = clipMaxs[1] = clipMaxs[2] = MAX_COORD_INTEGER; + + // build the map brushes out into the minimum non-overlapping set of brushes + bspbrush_t *pBSPBrush = MakeBspBrushList( pFuncVisCluster->firstbrush, pFuncVisCluster->firstbrush + pFuncVisCluster->numbrushes, + clipMins, clipMaxs, NO_DETAIL); + tmp.pBrushes = ChopBrushes( pBSPBrush ); + + // store the entry in the list + tmp.clusterIndex = -1; + tmp.leafCount = 0; + tmp.leafStart = 0; + +#if DEBUG_VISUALIZE_CLUSTERS + int debug = atoi(ValueForKey(pFuncVisCluster,"debug")); + if ( debug ) + { + Msg("Debug vis cluster %d\n", g_VisClusters.Count() ); + } +#endif + + g_VisClusters.AddToTail( tmp ); + + // clear out this entity so it won't get written to the bsp + pFuncVisCluster->epairs = NULL; + pFuncVisCluster->numbrushes = 0; +} + +// returns the total overlapping volume of intersection between the node and the brush list +float VolumeOfIntersection( bspbrush_t *pBrushList, node_t *pNode ) +{ + float volume = 0.0f; + for ( bspbrush_t *pBrush = pBrushList; pBrush; pBrush = pBrush->next ) + { + if ( IsBoxIntersectingBox( pNode->mins, pNode->maxs, pBrush->mins, pBrush->maxs ) ) + { + bspbrush_t *pIntersect = IntersectBrush( pNode->volume, pBrush ); + if ( pIntersect ) + { + volume += BrushVolume( pIntersect ); + FreeBrush( pIntersect ); + } + } + } + + return volume; +} + +// Search for a forced cluster that this node is within +// NOTE: Returns the first one found, these won't merge themselves together +int GetVisCluster( node_t *pNode ) +{ + float maxVolume = BrushVolume(pNode->volume) * 0.10f; // needs to cover at least 10% of the volume to overlap + int maxIndex = -1; + // UNDONE: This could get slow + for ( int i = g_VisClusters.Count(); --i >= 0; ) + { + float volume = VolumeOfIntersection( g_VisClusters[i].pBrushes, pNode ); + if ( volume > maxVolume ) + { + volume = maxVolume; + maxIndex = i; + } + } + return maxIndex; +} +/* +================ +NumberLeafs_r +================ +*/ +void BuildVisLeafList_r (node_t *node, CUtlVector<node_t *> &leaves) +{ + if (node->planenum != PLANENUM_LEAF) + { // decision node + node->cluster = -99; + BuildVisLeafList_r (node->children[0], leaves); + BuildVisLeafList_r (node->children[1], leaves); + return; + } + + if ( node->contents & CONTENTS_SOLID ) + { // solid block, viewpoint never inside + node->cluster = -1; + return; + } + leaves.AddToTail(node); +} + +// Give each leaf in the list of empty leaves a vis cluster number +// some are within func_viscluster volumes and will be merged together +// every other leaf gets its own unique number +void NumberLeafs( const CUtlVector<node_t *> &leaves ) +{ + for ( int i = 0; i < leaves.Count(); i++ ) + { + node_t *node = leaves[i]; + int visCluster = GetVisCluster( node ); + if ( visCluster >= 0 ) + { + if ( g_VisClusters[visCluster].clusterIndex < 0 ) + { + g_VisClusters[visCluster].clusterIndex = num_visclusters; + num_visclusters++; + } + g_VisClusters[visCluster].leafCount++; + node->cluster = g_VisClusters[visCluster].clusterIndex; + } + else + { + if ( !g_bSkyVis && Is3DSkyboxArea( node->area ) ) + { + if ( g_SkyCluster < 0 ) + { + // allocate a cluster for the sky + g_SkyCluster = num_visclusters; + num_visclusters++; + } + node->cluster = g_SkyCluster; + } + else + { + node->cluster = num_visclusters; + num_visclusters++; + } + } + } + +#if DEBUG_VISUALIZE_CLUSTERS + for ( int i = 0; i < g_VisClusters.Count(); i++ ) + { + char name[256]; + sprintf(name, "u:\\main\\game\\ep2\\maps\\vis_%02d.gl", i ); + FileHandle_t fp = g_pFileSystem->Open( name, "w" ); + Msg("Writing %s\n", name ); + for ( bspbrush_t *pBrush = g_VisClusters[i].pBrushes; pBrush; pBrush = pBrush->next ) + { + for (int i = 0; i < pBrush->numsides; i++ ) + OutputWindingColor( pBrush->sides[i].winding, fp, 0, 255, 0 ); + } + for ( int j = 0; j < leaves.Count(); j++ ) + { + if ( leaves[j]->cluster == g_VisClusters[i].clusterIndex ) + { + bspbrush_t *pBrush = leaves[j]->volume; + for (int k = 0; k < pBrush->numsides; k++ ) + OutputWindingColor( pBrush->sides[k].winding, fp, 64 + (j&31), 64, 64 - (j&31) ); + } + } + g_pFileSystem->Close(fp); + } +#endif +} + +// build a list of all vis portals that connect clusters +int BuildPortalList( CUtlVector<cluster_portals_t> &portalList, const CUtlVector<node_t *> &leaves ) +{ + int portalCount = 0; + for ( int i = 0; i < leaves.Count(); i++ ) + { + node_t *node = leaves[i]; + // count the portals + for (portal_t *p = node->portals ; p ; ) + { + if (p->nodes[0] == node) // only write out from first leaf + { + if ( p->nodes[0]->cluster != p->nodes[1]->cluster ) + { + if (Portal_VisFlood (p)) + { + portalCount++; + portalList[node->cluster].portals.AddToTail(p); + } + } + p = p->next[0]; + } + else + p = p->next[1]; + } + } + return portalCount; +} + + +/* +================ +CreateVisPortals_r +================ +*/ +void CreateVisPortals_r (node_t *node) +{ + // stop as soon as we get to a leaf + if (node->planenum == PLANENUM_LEAF ) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + CreateVisPortals_r (node->children[0]); + CreateVisPortals_r (node->children[1]); +} + +int clusterleaf; +void SaveClusters_r (node_t *node) +{ + if (node->planenum == PLANENUM_LEAF) + { + dleafs[clusterleaf++].cluster = node->cluster; + return; + } + SaveClusters_r (node->children[0]); + SaveClusters_r (node->children[1]); +} + +/* +================ +WritePortalFile +================ +*/ +void WritePortalFile (tree_t *tree) +{ + char filename[1024]; + node_t *headnode; + int start = Plat_FloatTime(); + + qprintf ("--- WritePortalFile ---\n"); + + sprintf (filename, "%s.prt", source); + Msg ("writing %s...", filename); + + headnode = tree->headnode; + + FreeTreePortals_r (headnode); + MakeHeadnodePortals (tree); + + CreateVisPortals_r (headnode); + +// set the cluster field in every leaf and count the total number of portals + num_visclusters = 0; + Msg("Building visibility clusters...\n"); + CUtlVector<node_t *> leaves; + BuildVisLeafList_r( headnode, leaves ); + + NumberLeafs (leaves); + CUtlVector<cluster_portals_t> portalList; + portalList.SetCount( num_visclusters ); + num_visportals = BuildPortalList( portalList, leaves ); +// write the file + FILE *pf = fopen (filename, "w"); + if (!pf) + Error ("Error opening %s", filename); + + fprintf (pf, "%s\n", PORTALFILE); + fprintf (pf, "%i\n", num_visclusters); + fprintf (pf, "%i\n", num_visportals); + + qprintf ("%5i visclusters\n", num_visclusters); + qprintf ("%5i visportals\n", num_visportals); + + WritePortalFile(pf, portalList); + + fclose (pf); + + // we need to store the clusters out now because ordering + // issues made us do this after writebsp... + clusterleaf = 1; + SaveClusters_r (headnode); + + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); +} + diff --git a/utils/vbsp/staticprop.cpp b/utils/vbsp/staticprop.cpp new file mode 100644 index 0000000..3ba6b95 --- /dev/null +++ b/utils/vbsp/staticprop.cpp @@ -0,0 +1,759 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Places "detail" objects which are client-only renderable things +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "bsplib.h" +#include "utlvector.h" +#include "bspfile.h" +#include "gamebspfile.h" +#include "VPhysics_Interface.h" +#include "Studio.h" +#include "byteswap.h" +#include "UtlBuffer.h" +#include "CollisionUtils.h" +#include <float.h> +#include "CModel.h" +#include "PhysDll.h" +#include "utlsymbol.h" +#include "tier1/strtools.h" +#include "KeyValues.h" + +static void SetCurrentModel( studiohdr_t *pStudioHdr ); +static void FreeCurrentModelVertexes(); + +IPhysicsCollision *s_pPhysCollision = NULL; + +//----------------------------------------------------------------------------- +// These puppies are used to construct the game lumps +//----------------------------------------------------------------------------- +static CUtlVector<StaticPropDictLump_t> s_StaticPropDictLump; +static CUtlVector<StaticPropLump_t> s_StaticPropLump; +static CUtlVector<StaticPropLeafLump_t> s_StaticPropLeafLump; + + +//----------------------------------------------------------------------------- +// Used to build the static prop +//----------------------------------------------------------------------------- +struct StaticPropBuild_t +{ + char const* m_pModelName; + char const* m_pLightingOrigin; + Vector m_Origin; + QAngle m_Angles; + int m_Solid; + int m_Skin; + int m_Flags; + float m_FadeMinDist; + float m_FadeMaxDist; + bool m_FadesOut; + float m_flForcedFadeScale; + unsigned short m_nMinDXLevel; + unsigned short m_nMaxDXLevel; + int m_LightmapResolutionX; + int m_LightmapResolutionY; +}; + + +//----------------------------------------------------------------------------- +// Used to cache collision model generation +//----------------------------------------------------------------------------- +struct ModelCollisionLookup_t +{ + CUtlSymbol m_Name; + CPhysCollide* m_pCollide; +}; + +static bool ModelLess( ModelCollisionLookup_t const& src1, ModelCollisionLookup_t const& src2 ) +{ + return src1.m_Name < src2.m_Name; +} + +static CUtlRBTree<ModelCollisionLookup_t, unsigned short> s_ModelCollisionCache( 0, 32, ModelLess ); +static CUtlVector<int> s_LightingInfo; + + +//----------------------------------------------------------------------------- +// Gets the keyvalues from a studiohdr +//----------------------------------------------------------------------------- +bool StudioKeyValues( studiohdr_t* pStudioHdr, KeyValues *pValue ) +{ + if ( !pStudioHdr ) + return false; + + return pValue->LoadFromBuffer( pStudioHdr->pszName(), pStudioHdr->KeyValueText() ); +} + + +//----------------------------------------------------------------------------- +// Makes sure the studio model is a static prop +//----------------------------------------------------------------------------- +enum isstaticprop_ret +{ + RET_VALID, + RET_FAIL_NOT_MARKED_STATIC_PROP, + RET_FAIL_DYNAMIC, +}; + +isstaticprop_ret IsStaticProp( studiohdr_t* pHdr ) +{ + if (!(pHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP)) + return RET_FAIL_NOT_MARKED_STATIC_PROP; + + // If it's got a propdata section in the model's keyvalues, it's not allowed to be a prop_static + KeyValues *modelKeyValues = new KeyValues(pHdr->pszName()); + if ( StudioKeyValues( pHdr, modelKeyValues ) ) + { + KeyValues *sub = modelKeyValues->FindKey("prop_data"); + if ( sub ) + { + if ( !(sub->GetInt( "allowstatic", 0 )) ) + { + modelKeyValues->deleteThis(); + return RET_FAIL_DYNAMIC; + } + } + } + modelKeyValues->deleteThis(); + + return RET_VALID; +} + + +//----------------------------------------------------------------------------- +// Add static prop model to the list of models +//----------------------------------------------------------------------------- + +static int AddStaticPropDictLump( char const* pModelName ) +{ + StaticPropDictLump_t dictLump; + strncpy( dictLump.m_Name, pModelName, DETAIL_NAME_LENGTH ); + + for (int i = s_StaticPropDictLump.Size(); --i >= 0; ) + { + if (!memcmp(&s_StaticPropDictLump[i], &dictLump, sizeof(dictLump) )) + return i; + } + + return s_StaticPropDictLump.AddToTail( dictLump ); +} + + +//----------------------------------------------------------------------------- +// Load studio model vertex data from a file... +//----------------------------------------------------------------------------- +bool LoadStudioModel( char const* pModelName, char const* pEntityType, CUtlBuffer& buf ) +{ + if ( !g_pFullFileSystem->ReadFile( pModelName, NULL, buf ) ) + return false; + + // Check that it's valid + if (strncmp ((const char *) buf.PeekGet(), "IDST", 4) && + strncmp ((const char *) buf.PeekGet(), "IDAG", 4)) + { + return false; + } + + studiohdr_t* pHdr = (studiohdr_t*)buf.PeekGet(); + + Studio_ConvertStudioHdrToNewVersion( pHdr ); + + if (pHdr->version != STUDIO_VERSION) + { + return false; + } + + isstaticprop_ret isStaticProp = IsStaticProp(pHdr); + if ( isStaticProp != RET_VALID ) + { + if ( isStaticProp == RET_FAIL_NOT_MARKED_STATIC_PROP ) + { + Warning("Error! To use model \"%s\"\n" + " with %s, it must be compiled with $staticprop!\n", pModelName, pEntityType ); + } + else if ( isStaticProp == RET_FAIL_DYNAMIC ) + { + Warning("Error! %s using model \"%s\", which must be used on a dynamic entity (i.e. prop_physics). Deleted.\n", pEntityType, pModelName ); + } + return false; + } + + // ensure reset + pHdr->pVertexBase = NULL; + pHdr->pIndexBase = NULL; + + return true; +} + + +//----------------------------------------------------------------------------- +// Computes a convex hull from a studio mesh +//----------------------------------------------------------------------------- +static CPhysConvex* ComputeConvexHull( mstudiomesh_t* pMesh ) +{ + // Generate a list of all verts in the mesh + Vector** ppVerts = (Vector**)stackalloc(pMesh->numvertices * sizeof(Vector*) ); + const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData(); + Assert( vertData ); // This can only return NULL on X360 for now + for (int i = 0; i < pMesh->numvertices; ++i) + { + ppVerts[i] = vertData->Position(i); + } + + // Generate a convex hull from the verts + return s_pPhysCollision->ConvexFromVerts( ppVerts, pMesh->numvertices ); +} + + +//----------------------------------------------------------------------------- +// Computes a convex hull from the studio model +//----------------------------------------------------------------------------- +CPhysCollide* ComputeConvexHull( studiohdr_t* pStudioHdr ) +{ + CUtlVector<CPhysConvex*> convexHulls; + + for (int body = 0; body < pStudioHdr->numbodyparts; ++body ) + { + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( body ); + for( int model = 0; model < pBodyPart->nummodels; ++model ) + { + mstudiomodel_t *pStudioModel = pBodyPart->pModel( model ); + for( int mesh = 0; mesh < pStudioModel->nummeshes; ++mesh ) + { + // Make a convex hull for each mesh + // NOTE: This won't work unless the model has been compiled + // with $staticprop + mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( mesh ); + convexHulls.AddToTail( ComputeConvexHull( pStudioMesh ) ); + } + } + } + + // Convert an array of convex elements to a compiled collision model + // (this deletes the convex elements) + return s_pPhysCollision->ConvertConvexToCollide( convexHulls.Base(), convexHulls.Size() ); +} + + +//----------------------------------------------------------------------------- +// Add, find collision model in cache +//----------------------------------------------------------------------------- +static CPhysCollide* GetCollisionModel( char const* pModelName ) +{ + // Convert to a common string + char* pTemp = (char*)_alloca(strlen(pModelName) + 1); + strcpy( pTemp, pModelName ); + _strlwr( pTemp ); + + char* pSlash = strchr( pTemp, '\\' ); + while( pSlash ) + { + *pSlash = '/'; + pSlash = strchr( pTemp, '\\' ); + } + + // Find it in the cache + ModelCollisionLookup_t lookup; + lookup.m_Name = pTemp; + int i = s_ModelCollisionCache.Find( lookup ); + if (i != s_ModelCollisionCache.InvalidIndex()) + return s_ModelCollisionCache[i].m_pCollide; + + // Load the studio model file + CUtlBuffer buf; + if (!LoadStudioModel(pModelName, "prop_static", buf)) + { + Warning("Error loading studio model \"%s\"!\n", pModelName ); + + // This way we don't try to load it multiple times + lookup.m_pCollide = 0; + s_ModelCollisionCache.Insert( lookup ); + + return 0; + } + + // Compute the convex hull of the model... + studiohdr_t* pStudioHdr = (studiohdr_t*)buf.PeekGet(); + + // necessary for vertex access + SetCurrentModel( pStudioHdr ); + + lookup.m_pCollide = ComputeConvexHull( pStudioHdr ); + s_ModelCollisionCache.Insert( lookup ); + + if ( !lookup.m_pCollide ) + { + Warning("Bad geometry on \"%s\"!\n", pModelName ); + } + + // Debugging + if (g_DumpStaticProps) + { + static int propNum = 0; + char tmp[128]; + sprintf( tmp, "staticprop%03d.txt", propNum ); + DumpCollideToGlView( lookup.m_pCollide, tmp ); + ++propNum; + } + + FreeCurrentModelVertexes(); + + // Insert into cache... + return lookup.m_pCollide; +} + + +//----------------------------------------------------------------------------- +// Tests a single leaf against the static prop +//----------------------------------------------------------------------------- + +static bool TestLeafAgainstCollide( int depth, int* pNodeList, + Vector const& origin, QAngle const& angles, CPhysCollide* pCollide ) +{ + // Copy the planes in the node list into a list of planes + float* pPlanes = (float*)_alloca(depth * 4 * sizeof(float) ); + int idx = 0; + for (int i = depth; --i >= 0; ++idx ) + { + int sign = (pNodeList[i] < 0) ? -1 : 1; + int node = (sign < 0) ? - pNodeList[i] - 1 : pNodeList[i]; + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + pPlanes[idx*4] = sign * pPlane->normal[0]; + pPlanes[idx*4+1] = sign * pPlane->normal[1]; + pPlanes[idx*4+2] = sign * pPlane->normal[2]; + pPlanes[idx*4+3] = sign * pPlane->dist; + } + + // Make a convex solid out of the planes + CPhysConvex* pPhysConvex = s_pPhysCollision->ConvexFromPlanes( pPlanes, depth, 0.0f ); + + // This should never happen, but if it does, return no collision + Assert( pPhysConvex ); + if (!pPhysConvex) + return false; + + CPhysCollide* pLeafCollide = s_pPhysCollision->ConvertConvexToCollide( &pPhysConvex, 1 ); + + // Collide the leaf solid with the static prop solid + trace_t tr; + s_pPhysCollision->TraceCollide( vec3_origin, vec3_origin, pLeafCollide, vec3_angle, + pCollide, origin, angles, &tr ); + + s_pPhysCollision->DestroyCollide( pLeafCollide ); + + return (tr.startsolid != 0); +} + +//----------------------------------------------------------------------------- +// Find all leaves that intersect with this bbox + test against the static prop.. +//----------------------------------------------------------------------------- + +static void ComputeConvexHullLeaves_R( int node, int depth, int* pNodeList, + Vector const& mins, Vector const& maxs, + Vector const& origin, QAngle const& angles, CPhysCollide* pCollide, + CUtlVector<unsigned short>& leafList ) +{ + Assert( pNodeList && pCollide ); + Vector cornermin, cornermax; + + while( node >= 0 ) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + // Arbitrary split plane here + for (int i = 0; i < 3; ++i) + { + if (pPlane->normal[i] >= 0) + { + cornermin[i] = mins[i]; + cornermax[i] = maxs[i]; + } + else + { + cornermin[i] = maxs[i]; + cornermax[i] = mins[i]; + } + } + + if (DotProduct( pPlane->normal, cornermax ) <= pPlane->dist) + { + // Add the node to the list of nodes + pNodeList[depth] = node; + ++depth; + + node = pNode->children[1]; + } + else if (DotProduct( pPlane->normal, cornermin ) >= pPlane->dist) + { + // In this case, we are going in front of the plane. That means that + // this plane must have an outward normal facing in the oppisite direction + // We indicate this be storing a negative node index in the node list + pNodeList[depth] = - node - 1; + ++depth; + + node = pNode->children[0]; + } + else + { + // Here the box is split by the node. First, we'll add the plane as if its + // outward facing normal is in the direction of the node plane, then + // we'll have to reverse it for the other child... + pNodeList[depth] = node; + ++depth; + + ComputeConvexHullLeaves_R( pNode->children[1], + depth, pNodeList, mins, maxs, origin, angles, pCollide, leafList ); + + pNodeList[depth - 1] = - node - 1; + ComputeConvexHullLeaves_R( pNode->children[0], + depth, pNodeList, mins, maxs, origin, angles, pCollide, leafList ); + return; + } + } + + Assert( pNodeList && pCollide ); + + // Never add static props to solid leaves + if ( (dleafs[-node-1].contents & CONTENTS_SOLID) == 0 ) + { + if (TestLeafAgainstCollide( depth, pNodeList, origin, angles, pCollide )) + { + leafList.AddToTail( -node - 1 ); + } + } +} + +//----------------------------------------------------------------------------- +// Places Static Props in the level +//----------------------------------------------------------------------------- + +static void ComputeStaticPropLeaves( CPhysCollide* pCollide, Vector const& origin, + QAngle const& angles, CUtlVector<unsigned short>& leafList ) +{ + // Compute an axis-aligned bounding box for the collide + Vector mins, maxs; + s_pPhysCollision->CollideGetAABB( &mins, &maxs, pCollide, origin, angles ); + + // Find all leaves that intersect with the bounds + int tempNodeList[1024]; + ComputeConvexHullLeaves_R( 0, 0, tempNodeList, mins, maxs, + origin, angles, pCollide, leafList ); +} + + +//----------------------------------------------------------------------------- +// Computes the lighting origin +//----------------------------------------------------------------------------- +static bool ComputeLightingOrigin( StaticPropBuild_t const& build, Vector& lightingOrigin ) +{ + for (int i = s_LightingInfo.Count(); --i >= 0; ) + { + int entIndex = s_LightingInfo[i]; + + // Check against all lighting info entities + char const* pTargetName = ValueForKey( &entities[entIndex], "targetname" ); + if (!Q_strcmp(pTargetName, build.m_pLightingOrigin)) + { + GetVectorForKey( &entities[entIndex], "origin", lightingOrigin ); + return true; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Places Static Props in the level +//----------------------------------------------------------------------------- +static void AddStaticPropToLump( StaticPropBuild_t const& build ) +{ + // Get the collision model + CPhysCollide* pConvexHull = GetCollisionModel( build.m_pModelName ); + if (!pConvexHull) + return; + + // Compute the leaves the static prop's convex hull hits + CUtlVector< unsigned short > leafList; + ComputeStaticPropLeaves( pConvexHull, build.m_Origin, build.m_Angles, leafList ); + + if ( !leafList.Count() ) + { + Warning( "Static prop %s outside the map (%.2f, %.2f, %.2f)\n", build.m_pModelName, build.m_Origin.x, build.m_Origin.y, build.m_Origin.z ); + return; + } + // Insert an element into the lump data... + int i = s_StaticPropLump.AddToTail( ); + StaticPropLump_t& propLump = s_StaticPropLump[i]; + propLump.m_PropType = AddStaticPropDictLump( build.m_pModelName ); + VectorCopy( build.m_Origin, propLump.m_Origin ); + VectorCopy( build.m_Angles, propLump.m_Angles ); + propLump.m_FirstLeaf = s_StaticPropLeafLump.Count(); + propLump.m_LeafCount = leafList.Count(); + propLump.m_Solid = build.m_Solid; + propLump.m_Skin = build.m_Skin; + propLump.m_Flags = build.m_Flags; + if (build.m_FadesOut) + { + propLump.m_Flags |= STATIC_PROP_FLAG_FADES; + } + propLump.m_FadeMinDist = build.m_FadeMinDist; + propLump.m_FadeMaxDist = build.m_FadeMaxDist; + propLump.m_flForcedFadeScale = build.m_flForcedFadeScale; + propLump.m_nMinDXLevel = build.m_nMinDXLevel; + propLump.m_nMaxDXLevel = build.m_nMaxDXLevel; + + if (build.m_pLightingOrigin && *build.m_pLightingOrigin) + { + if (ComputeLightingOrigin( build, propLump.m_LightingOrigin )) + { + propLump.m_Flags |= STATIC_PROP_USE_LIGHTING_ORIGIN; + } + } + + propLump.m_nLightmapResolutionX = build.m_LightmapResolutionX; + propLump.m_nLightmapResolutionY = build.m_LightmapResolutionY; + + // Add the leaves to the leaf lump + for (int j = 0; j < leafList.Size(); ++j) + { + StaticPropLeafLump_t insert; + insert.m_Leaf = leafList[j]; + s_StaticPropLeafLump.AddToTail( insert ); + } + +} + + +//----------------------------------------------------------------------------- +// Places static props in the lump +//----------------------------------------------------------------------------- + +static void SetLumpData( ) +{ + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(GAMELUMP_STATIC_PROPS); + if (handle != g_GameLumps.InvalidGameLump()) + g_GameLumps.DestroyGameLump(handle); + + int dictsize = s_StaticPropDictLump.Size() * sizeof(StaticPropDictLump_t); + int objsize = s_StaticPropLump.Size() * sizeof(StaticPropLump_t); + int leafsize = s_StaticPropLeafLump.Size() * sizeof(StaticPropLeafLump_t); + int size = dictsize + objsize + leafsize + 3 * sizeof(int); + + handle = g_GameLumps.CreateGameLump( GAMELUMP_STATIC_PROPS, size, 0, GAMELUMP_STATIC_PROPS_VERSION ); + + // Serialize the data + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), size ); + buf.PutInt( s_StaticPropDictLump.Size() ); + if (dictsize) + buf.Put( s_StaticPropDictLump.Base(), dictsize ); + buf.PutInt( s_StaticPropLeafLump.Size() ); + if (leafsize) + buf.Put( s_StaticPropLeafLump.Base(), leafsize ); + buf.PutInt( s_StaticPropLump.Size() ); + if (objsize) + buf.Put( s_StaticPropLump.Base(), objsize ); +} + + +//----------------------------------------------------------------------------- +// Places Static Props in the level +//----------------------------------------------------------------------------- + +void EmitStaticProps() +{ + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); + if ( physicsFactory ) + { + s_pPhysCollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); + if( !s_pPhysCollision ) + return; + } + + // Generate a list of lighting origins, and strip them out + int i; + for ( i = 0; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!Q_strcmp(pEntity, "info_lighting")) + { + s_LightingInfo.AddToTail(i); + } + } + + // Emit specifically specified static props + for ( i = 0; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!strcmp(pEntity, "static_prop") || !strcmp(pEntity, "prop_static")) + { + StaticPropBuild_t build; + + GetVectorForKey( &entities[i], "origin", build.m_Origin ); + GetAnglesForKey( &entities[i], "angles", build.m_Angles ); + build.m_pModelName = ValueForKey( &entities[i], "model" ); + build.m_Solid = IntForKey( &entities[i], "solid" ); + build.m_Skin = IntForKey( &entities[i], "skin" ); + build.m_FadeMaxDist = FloatForKey( &entities[i], "fademaxdist" ); + build.m_Flags = 0;//IntForKey( &entities[i], "spawnflags" ) & STATIC_PROP_WC_MASK; + if (IntForKey( &entities[i], "ignorenormals" ) == 1) + { + build.m_Flags |= STATIC_PROP_IGNORE_NORMALS; + } + if (IntForKey( &entities[i], "disableshadows" ) == 1) + { + build.m_Flags |= STATIC_PROP_NO_SHADOW; + } + if (IntForKey( &entities[i], "disablevertexlighting" ) == 1) + { + build.m_Flags |= STATIC_PROP_NO_PER_VERTEX_LIGHTING; + } + if (IntForKey( &entities[i], "disableselfshadowing" ) == 1) + { + build.m_Flags |= STATIC_PROP_NO_SELF_SHADOWING; + } + + if (IntForKey( &entities[i], "screenspacefade" ) == 1) + { + build.m_Flags |= STATIC_PROP_SCREEN_SPACE_FADE; + } + + if (IntForKey( &entities[i], "generatelightmaps") == 0) + { + build.m_Flags |= STATIC_PROP_NO_PER_TEXEL_LIGHTING; + build.m_LightmapResolutionX = 0; + build.m_LightmapResolutionY = 0; + } + else + { + build.m_LightmapResolutionX = IntForKey( &entities[i], "lightmapresolutionx" ); + build.m_LightmapResolutionY = IntForKey( &entities[i], "lightmapresolutiony" ); + } + + const char *pKey = ValueForKey( &entities[i], "fadescale" ); + if ( pKey && pKey[0] ) + { + build.m_flForcedFadeScale = FloatForKey( &entities[i], "fadescale" ); + } + else + { + build.m_flForcedFadeScale = 1; + } + build.m_FadesOut = (build.m_FadeMaxDist > 0); + build.m_pLightingOrigin = ValueForKey( &entities[i], "lightingorigin" ); + if (build.m_FadesOut) + { + build.m_FadeMinDist = FloatForKey( &entities[i], "fademindist" ); + if (build.m_FadeMinDist < 0) + { + build.m_FadeMinDist = build.m_FadeMaxDist; + } + } + else + { + build.m_FadeMinDist = 0; + } + build.m_nMinDXLevel = (unsigned short)IntForKey( &entities[i], "mindxlevel" ); + build.m_nMaxDXLevel = (unsigned short)IntForKey( &entities[i], "maxdxlevel" ); + AddStaticPropToLump( build ); + + // strip this ent from the .bsp file + entities[i].epairs = 0; + } + } + + // Strip out lighting origins; has to be done here because they are used when + // static props are made + for ( i = s_LightingInfo.Count(); --i >= 0; ) + { + // strip this ent from the .bsp file + entities[s_LightingInfo[i]].epairs = 0; + } + + + SetLumpData( ); +} + +static studiohdr_t *g_pActiveStudioHdr; +static void SetCurrentModel( studiohdr_t *pStudioHdr ) +{ + // track the correct model + g_pActiveStudioHdr = pStudioHdr; +} + +static void FreeCurrentModelVertexes() +{ + Assert( g_pActiveStudioHdr ); + + if ( g_pActiveStudioHdr->pVertexBase ) + { + free( g_pActiveStudioHdr->pVertexBase ); + g_pActiveStudioHdr->pVertexBase = NULL; + } +} + +const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void * pModelData ) +{ + char fileName[260]; + FileHandle_t fileHandle; + vertexFileHeader_t *pVvdHdr; + + Assert( pModelData == NULL ); + Assert( g_pActiveStudioHdr ); + + if ( g_pActiveStudioHdr->pVertexBase ) + { + return (vertexFileHeader_t *)g_pActiveStudioHdr->pVertexBase; + } + + // mandatory callback to make requested data resident + // load and persist the vertex file + strcpy( fileName, "models/" ); + strcat( fileName, g_pActiveStudioHdr->pszName() ); + Q_StripExtension( fileName, fileName, sizeof( fileName ) ); + strcat( fileName, ".vvd" ); + + // load the model + fileHandle = g_pFileSystem->Open( fileName, "rb" ); + if ( !fileHandle ) + { + Error( "Unable to load vertex data \"%s\"\n", fileName ); + } + + // Get the file size + int size = g_pFileSystem->Size( fileHandle ); + if (size == 0) + { + g_pFileSystem->Close( fileHandle ); + Error( "Bad size for vertex data \"%s\"\n", fileName ); + } + + pVvdHdr = (vertexFileHeader_t *)malloc(size); + g_pFileSystem->Read( pVvdHdr, size, fileHandle ); + g_pFileSystem->Close( fileHandle ); + + // check header + if (pVvdHdr->id != MODEL_VERTEX_FILE_ID) + { + Error("Error Vertex File %s id %d should be %d\n", fileName, pVvdHdr->id, MODEL_VERTEX_FILE_ID); + } + if (pVvdHdr->version != MODEL_VERTEX_FILE_VERSION) + { + Error("Error Vertex File %s version %d should be %d\n", fileName, pVvdHdr->version, MODEL_VERTEX_FILE_VERSION); + } + if (pVvdHdr->checksum != g_pActiveStudioHdr->checksum) + { + Error("Error Vertex File %s checksum %d should be %d\n", fileName, pVvdHdr->checksum, g_pActiveStudioHdr->checksum); + } + + g_pActiveStudioHdr->pVertexBase = (void*)pVvdHdr; + return pVvdHdr; +} + diff --git a/utils/vbsp/textures.cpp b/utils/vbsp/textures.cpp new file mode 100644 index 0000000..4f49c5d --- /dev/null +++ b/utils/vbsp/textures.cpp @@ -0,0 +1,737 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" +#include "utilmatlib.h" +#include "physdll.h" +#include <assert.h> +#include <malloc.h> +#include "tier1/strtools.h" +#include "materialpatch.h" +#include "KeyValues.h" + +void LoadSurfaceProperties( void ); + +IPhysicsSurfaceProps *physprops = NULL; + +int nummiptex; +textureref_t textureref[MAX_MAP_TEXTURES]; + +bool g_bHasWater = false; + +extern qboolean onlyents; + +dtexdata_t *GetTexData( int index ) +{ + if ( index < 0 ) + return NULL; + Assert( !onlyents ); + return &dtexdata[ index ]; +} + +static qboolean StringIsTrue( const char *str ) +{ + if( Q_strcasecmp( str, "true" ) == 0 ) + { + return true; + } + if( Q_strcasecmp( str, "1" ) == 0 ) + { + return true; + } + return false; +} + +int FindMiptex (const char *name) +{ + int i; + MaterialSystemMaterial_t matID; + const char *propVal, *propVal2; + int opacity; + bool found; + + for (i=0 ; i<nummiptex ; i++) + { + if (!strcmp (name, textureref[i].name)) + { + return i; + } + } + if (nummiptex == MAX_MAP_TEXTURES) + Error ("Too many unique textures, max %d", MAX_MAP_TEXTURES); + strcpy (textureref[i].name, name); + + textureref[i].lightmapWorldUnitsPerLuxel = 0.0f; + textureref[i].flags = 0; + textureref[i].contents = 0; + + matID = FindOriginalMaterial( name, &found ); + if( matID == MATERIAL_NOT_FOUND ) + { + return 0; + } + + if (!found) + Warning("Material not found!: %s\n", name ); + + // HANDLE ALL OF THE STUFF THAT ISN'T RENDERED WITH THE MATERIAL THAT IS ONE IT. + + // handle sky + if( ( propVal = GetMaterialVar( matID, "%compileSky" ) ) && + StringIsTrue( propVal ) ) + { + textureref[i].flags |= SURF_SKY | SURF_NOLIGHT; + } + else if( ( propVal = GetMaterialVar( matID, "%compile2DSky" ) ) && + StringIsTrue( propVal ) ) + { + textureref[i].flags |= SURF_SKY | SURF_SKY2D | SURF_NOLIGHT; + } + // handle hint brushes + else if ( ( propVal = GetMaterialVar( matID, "%compileHint" ) ) && + StringIsTrue( propVal ) ) + { + textureref[i].flags |= SURF_NODRAW | SURF_NOLIGHT | SURF_HINT; + } + // handle skip faces + else if ( ( propVal = GetMaterialVar( matID, "%compileSkip" ) ) && + StringIsTrue( propVal ) ) + { + textureref[i].flags |= SURF_NODRAW | SURF_NOLIGHT | SURF_SKIP; + } + // handle origin brushes + else if ( ( propVal = GetMaterialVar( matID, "%compileOrigin" ) ) && + StringIsTrue( propVal ) ) + { + textureref[i].contents |= CONTENTS_ORIGIN | CONTENTS_DETAIL; + textureref[i].flags |= SURF_NODRAW | SURF_NOLIGHT; + } + // handle clip brushes + else if ( ( propVal = GetMaterialVar( matID, "%compileClip" ) ) && + StringIsTrue( propVal ) ) + { + textureref[i].contents |= CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP; + textureref[i].flags |= SURF_NODRAW | SURF_NOLIGHT; + } + else if ( ( propVal = GetMaterialVar( matID, "%playerClip" ) ) && + StringIsTrue( propVal ) ) + { + textureref[i].contents |= CONTENTS_PLAYERCLIP; + textureref[i].flags |= SURF_NODRAW | SURF_NOLIGHT; + } + // handle npc clip brushes + else if ( ( propVal = GetMaterialVar( matID, "%compileNpcClip" ) ) && + StringIsTrue( propVal ) ) + { + textureref[i].contents |= CONTENTS_MONSTERCLIP; + textureref[i].flags |= SURF_NODRAW | SURF_NOLIGHT; + } + // handle surface lights which are meant to + else if ( ( propVal = GetMaterialVar( matID, "%compileNoChop" ) ) && + StringIsTrue( propVal ) ) + { + textureref[i].flags |= SURF_NOCHOP; + } + // handle triggers + else if ( ( propVal = GetMaterialVar( matID, "%compileTrigger" ) ) && + StringIsTrue( propVal ) ) + { + textureref[i].flags |= ( SURF_NOLIGHT | SURF_TRIGGER ); + if ( g_NodrawTriggers ) + { + textureref[i].flags |= SURF_NODRAW; + } + } + // handle nolight surfs (except water) + else if ( (( propVal = GetMaterialVar( matID, "%compileNoLight" ) ) && StringIsTrue( propVal )) && + !(( propVal2 = GetMaterialVar( matID, "%compileWater" ) ) && StringIsTrue( propVal2 ) ) ) + { + textureref[i].flags |= SURF_NOLIGHT; + } + else + { + // HANDLE ALL OF THE STUFF THAT IS RENDERED WITH THE MATERIAL THAT IS ON IT. + + // Handle ladders. + if ( ( propVal = GetMaterialVar( matID, "%compileLadder" ) ) && StringIsTrue( propVal ) ) + { + textureref[i].contents |= CONTENTS_LADDER; + } + + // handle wet materials + if ( ( propVal = GetMaterialVar( matID, "%noPortal" ) ) && + StringIsTrue( propVal ) ) + { + textureref[i].flags |= SURF_NOPORTAL; + } + + if ( ( propVal = GetMaterialVar( matID, "%compilePassBullets" ) ) && StringIsTrue( propVal ) ) + { + // change contents to grate, so bullets pass through + // NOTE: This has effects on visibility too! + textureref[i].contents &= ~CONTENTS_SOLID; + textureref[i].contents |= CONTENTS_GRATE; + } + + if( g_BumpAll || GetMaterialShaderPropertyBool( matID, UTILMATLIB_NEEDS_BUMPED_LIGHTMAPS ) ) + { + textureref[i].flags |= SURF_BUMPLIGHT; + } + + if( GetMaterialShaderPropertyBool( matID, UTILMATLIB_NEEDS_LIGHTMAP ) ) + { + textureref[i].flags &= ~SURF_NOLIGHT; + } + else if( !g_bLightIfMissing ) + { + textureref[i].flags |= SURF_NOLIGHT; + } + // handle nodraw faces/brushes + if ( ( propVal = GetMaterialVar( matID, "%compileNoDraw" ) ) && StringIsTrue( propVal ) ) + { + // textureref[i].contents |= CONTENTS_DETAIL; + textureref[i].flags |= SURF_NODRAW | SURF_NOLIGHT; + } + + // Just a combination of nodraw + pass bullets, makes things easier + if ( ( propVal = GetMaterialVar( matID, "%compileInvisible" ) ) && StringIsTrue( propVal ) ) + { + // change contents to grate, so bullets pass through + // NOTE: This has effects on visibility too! + textureref[i].contents &= ~CONTENTS_SOLID; + textureref[i].contents |= CONTENTS_GRATE; + textureref[i].flags |= SURF_NODRAW | SURF_NOLIGHT; + } + + bool checkWindow = true; + // handle non solid + if ( ( propVal = GetMaterialVar( matID, "%compileNonsolid" ) ) && StringIsTrue( propVal ) ) + { + textureref[i].contents = CONTENTS_OPAQUE; + // Non-Solid can't be a window either! + checkWindow = false; + } + // handle block LOS + if ( ( propVal = GetMaterialVar( matID, "%compileBlockLOS" ) ) && StringIsTrue( propVal ) ) + { + textureref[i].contents = CONTENTS_BLOCKLOS; + + // BlockLOS can't be a window either! + checkWindow = false; + } + + if ( ( propVal = GetMaterialVar( matID, "%compileDetail" ) ) && + StringIsTrue( propVal ) ) + { + textureref[i].contents |= CONTENTS_DETAIL; + } + + bool bKeepLighting = ( ( propVal = GetMaterialVar( matID, "%compileKeepLight" ) ) && + StringIsTrue( propVal ) ); + + // handle materials that want to be treated as water. + if ( ( propVal = GetMaterialVar( matID, "%compileWater" ) ) && + StringIsTrue( propVal ) ) + { + textureref[i].contents &= ~(CONTENTS_SOLID|CONTENTS_DETAIL); + textureref[i].contents |= CONTENTS_WATER; + textureref[i].flags |= SURF_WARP | SURF_NOSHADOWS | SURF_NODECALS; + + if ( g_DisableWaterLighting && !bKeepLighting ) + { + textureref[i].flags |= SURF_NOLIGHT; + } + + // Set this so that we can check at the end of the process the presence of a a WaterLODControl entity. + g_bHasWater = true; + } + const char *pShaderName = GetMaterialShaderName(matID); + if ( !bKeepLighting && !Q_strncasecmp( pShaderName, "water", 5 ) || !Q_strncasecmp( pShaderName, "UnlitGeneric", 12 ) ) + { + //if ( !(textureref[i].flags & SURF_NOLIGHT) ) + // Warning("Forcing lit materal %s to nolight\n", name ); + textureref[i].flags |= SURF_NOLIGHT; + } + + if ( ( propVal = GetMaterialVar( matID, "%compileSlime" ) ) && + StringIsTrue( propVal ) ) + { + textureref[i].contents &= ~(CONTENTS_SOLID|CONTENTS_DETAIL); + textureref[i].contents |= CONTENTS_SLIME; + textureref[i].flags |= SURF_NODECALS; + // Set this so that we can check at the end of the process the presence of a a WaterLODControl entity. + g_bHasWater = true; + } + + opacity = GetMaterialShaderPropertyInt( matID, UTILMATLIB_OPACITY ); + + if ( checkWindow && opacity != UTILMATLIB_OPAQUE ) + { + // transparent *and solid* brushes that aren't grates or water must be windows + if ( !(textureref[i].contents & (CONTENTS_GRATE|CONTENTS_WATER)) ) + { + textureref[i].contents |= CONTENTS_WINDOW; + } + + textureref[i].contents &= ~CONTENTS_SOLID; + + // this affects engine primitive sorting, SURF_TRANS means sort as a translucent primitive + if ( opacity == UTILMATLIB_TRANSLUCENT ) + { + textureref[i].flags |= SURF_TRANS; + } + + } + if ( textureref[i].flags & SURF_NOLIGHT ) + { + textureref[i].flags &= ~SURF_BUMPLIGHT; + } + } + + nummiptex++; + + return i; +} + +/* +================== +textureAxisFromPlane +================== +*/ +Vector baseaxis[18] = +{ + Vector(0,0,1), Vector(1,0,0), Vector(0,-1,0), // floor + Vector(0,0,-1), Vector(1,0,0), Vector(0,-1,0), // ceiling + Vector(1,0,0), Vector(0,1,0), Vector(0,0,-1), // west wall + Vector(-1,0,0), Vector(0,1,0), Vector(0,0,-1), // east wall + Vector(0,1,0), Vector(1,0,0), Vector(0,0,-1), // south wall + Vector(0,-1,0), Vector(1,0,0), Vector(0,0,-1) // north wall +}; + +void TextureAxisFromPlane(plane_t *pln, Vector& xv, Vector& yv) +{ + int bestaxis; + vec_t dot,best; + int i; + + best = 0; + bestaxis = 0; + + for (i=0 ; i<6 ; i++) + { + dot = DotProduct (pln->normal, baseaxis[i*3]); + if (dot > best) + { + best = dot; + bestaxis = i; + } + } + + VectorCopy (baseaxis[bestaxis*3+1], xv); + VectorCopy (baseaxis[bestaxis*3+2], yv); +} + + + +int g_SurfaceProperties[MAX_MAP_TEXDATA]; + + +int GetSurfaceProperties( MaterialSystemMaterial_t matID, const char *pMatName ) +{ + const char *pPropString = NULL; + int surfaceIndex = -1; + + if ( physprops ) + { + pPropString = GetMaterialVar( matID, "$surfaceprop" ); + if ( pPropString ) + { + surfaceIndex = physprops->GetSurfaceIndex( pPropString ); + if ( surfaceIndex < 0 ) + { + Msg("Can't find surfaceprop %s for material %s, using default\n", pPropString, pMatName ); + surfaceIndex = physprops->GetSurfaceIndex( pPropString ); + surfaceIndex = physprops->GetSurfaceIndex( "default" ); + } + } + } + + return surfaceIndex; +} + +int GetSurfaceProperties2( MaterialSystemMaterial_t matID, const char *pMatName ) +{ + const char *pPropString = NULL; + int surfaceIndex = -1; + + if ( physprops ) + { + pPropString = GetMaterialVar( matID, "$surfaceprop2" ); + if ( pPropString ) + { + surfaceIndex = physprops->GetSurfaceIndex( pPropString ); + if ( surfaceIndex < 0 ) + { + Msg("Can't find surfacepropblend %s for material %s, using default\n", pPropString, pMatName ); + surfaceIndex = physprops->GetSurfaceIndex( "default" ); + } + } + else + { + // No surface property 2. + return -1; + } + } + + return surfaceIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds or adds a texdata for the specified name ( same as below except +// instead of finding the named texture, copies the settings from the passed +// in sourceTexture. ) +// Used for creation of one off .vmt files for water surface textures +// Input : *pName - texture name +// Output : int index into dtexdata array +//----------------------------------------------------------------------------- +int FindAliasedTexData( const char *pName_, dtexdata_t *sourceTexture ) +{ + char *pName = ( char * )_alloca( strlen( pName_ ) + 1 ); + strcpy( pName, pName_ ); + strlwr( pName ); + int i, output; + bool found; + dtexdata_t *pTexData; + MaterialSystemMaterial_t matID; + + for ( i = 0; i < numtexdata; i++ ) + { + if ( !strcmp( pName, TexDataStringTable_GetString( GetTexData( i )->nameStringTableID ) ) ) + return i; + } + + + output = numtexdata; + if ( numtexdata >= MAX_MAP_TEXDATA ) + { + Error( "Too many unique texture mappings, max = %d\n", MAX_MAP_TEXDATA ); + } + pTexData = GetTexData( output ); + numtexdata++; + + // Save the name of the material. + pTexData->nameStringTableID = TexDataStringTable_AddOrFindString( pName ); + + // Get the width, height, view_width, view_height, and reflectivity from the material system. + matID = FindOriginalMaterial( TexDataStringTable_GetString( sourceTexture->nameStringTableID ), &found, false ); + if( matID == MATERIAL_NOT_FOUND || (!found) ) + { + qprintf( "WARNING: material not found: \"%s\"\n", pName ); + return -1; + } + + GetMaterialDimensions( matID, &pTexData->width, &pTexData->height ); + pTexData->view_width = pTexData->width; // undone: what is this? + pTexData->view_height = pTexData->height; // undone: what is this? + + GetMaterialReflectivity( matID, pTexData->reflectivity.Base() ); + g_SurfaceProperties[output] = GetSurfaceProperties( matID, pName ); + + return output; +} + + +//----------------------------------------------------------------------------- +// Finds a texdata for the specified name, returns -1 if not found +//----------------------------------------------------------------------------- +int FindTexData( const char *pName ) +{ + // Make sure the texdata doesn't already exist. + for( int i = 0; i < numtexdata; i++ ) + { + char const *pTexDataName = TexDataStringTable_GetString( GetTexData( i )->nameStringTableID ); + if ( !Q_stricmp( pTexDataName, pName ) ) + return i; + } + return -1; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Finds or adds a texdata for the specified name +// Input : *pName - texture name +// Output : int index into dtexdata array +//----------------------------------------------------------------------------- +int FindOrCreateTexData( const char *pName_ ) +{ + char *pName = ( char * )_alloca( strlen( pName_ ) + 1 ); + strcpy( pName, pName_ ); + + int nOutput = FindTexData( pName ); + if ( nOutput >= 0 ) + return nOutput; + + // Didn't find it, add a new one + nOutput = numtexdata; + if ( numtexdata >= MAX_MAP_TEXDATA ) + { + Error( "Too many unique texture mappings, max = %d\n", MAX_MAP_TEXDATA ); + } + dtexdata_t *pTexData = GetTexData( nOutput ); + numtexdata++; + + // Save the name of the material. + pTexData->nameStringTableID = TexDataStringTable_AddOrFindString( pName ); + + // Get the width, height, view_width, view_height, and reflectivity from the material system. + bool bFound; + MaterialSystemMaterial_t matID = FindOriginalMaterial( pName, &bFound ); + if ( matID == MATERIAL_NOT_FOUND || (!bFound) ) + { + qprintf( "WARNING: material not found: \"%s\"\n", pName ); + return nOutput; + } + + GetMaterialDimensions( matID, &pTexData->width, &pTexData->height ); + pTexData->view_width = pTexData->width; // undone: what is this? + pTexData->view_height = pTexData->height; // undone: what is this? + + GetMaterialReflectivity( matID, pTexData->reflectivity.Base() ); + g_SurfaceProperties[nOutput] = GetSurfaceProperties( matID, pName ); + +#if 0 + Msg( "reflectivity: %f %f %f\n", + pTexData->reflectivity[0], + pTexData->reflectivity[1], + pTexData->reflectivity[2] ); +#endif + + return nOutput; +} + +int AddCloneTexData( dtexdata_t *pExistingTexData, char const *cloneTexDataName ) +{ + int existingIndex = pExistingTexData - GetTexData( 0 ); + dtexdata_t *pNewTexData = GetTexData( numtexdata ); + int newIndex = numtexdata; + numtexdata++; + + *pNewTexData = *pExistingTexData; + pNewTexData->nameStringTableID = TexDataStringTable_AddOrFindString( cloneTexDataName ); + g_SurfaceProperties[newIndex] = g_SurfaceProperties[existingIndex]; + + return newIndex; +} + + +//----------------------------------------------------------------------------- +// Finds a texinfo that exactly matches the passed in texinfo +//----------------------------------------------------------------------------- +int FindTexInfo( const texinfo_t &searchTexInfo ) +{ + for( int i = 0; i < texinfo.Count(); i++ ) + { + // Just an early-out for performance + if ( texinfo[i].texdata != searchTexInfo.texdata ) + continue; + + if ( !memcmp( &texinfo[i], &searchTexInfo, sizeof( texinfo_t ) ) ) + return i; + } + + return -1; +} + + +//----------------------------------------------------------------------------- +// Finds or creates a texinfo that exactly matches the passed in texinfo +//----------------------------------------------------------------------------- +int FindOrCreateTexInfo( const texinfo_t &searchTexInfo ) +{ + int i = FindTexInfo( searchTexInfo ); + if ( i >= 0 ) + return i; + + i = texinfo.AddToTail( searchTexInfo ); + + if ( onlyents ) + { + Error( "FindOrCreateTexInfo: Tried to create new texinfo during -onlyents compile!\nMust compile without -onlyents" ); + } + + return i; +} + +int TexinfoForBrushTexture (plane_t *plane, brush_texture_t *bt, const Vector& origin) +{ + Vector vecs[2]; + int sv, tv; + vec_t ang, sinv, cosv; + vec_t ns, nt; + texinfo_t tx; + int i, j; + + if (!bt->name[0]) + return 0; + + memset (&tx, 0, sizeof(tx)); + + // HLTOOLS - add support for texture vectors stored in the map file + if (g_nMapFileVersion < 220) + { + TextureAxisFromPlane(plane, vecs[0], vecs[1]); + } + + if (!bt->textureWorldUnitsPerTexel[0]) + bt->textureWorldUnitsPerTexel[0] = 1; + if (!bt->textureWorldUnitsPerTexel[1]) + bt->textureWorldUnitsPerTexel[1] = 1; + + + float shiftScaleU = 1.0f / 16.0f; + float shiftScaleV = 1.0f / 16.0f; + + if (g_nMapFileVersion < 220) + { + // rotate axis + if (bt->rotate == 0) + { sinv = 0 ; cosv = 1; } + else if (bt->rotate == 90) + { sinv = 1 ; cosv = 0; } + else if (bt->rotate == 180) + { sinv = 0 ; cosv = -1; } + else if (bt->rotate == 270) + { sinv = -1 ; cosv = 0; } + else + { + ang = bt->rotate / 180 * M_PI; + sinv = sin(ang); + cosv = cos(ang); + } + + if (vecs[0][0]) + sv = 0; + else if (vecs[0][1]) + sv = 1; + else + sv = 2; + + if (vecs[1][0]) + tv = 0; + else if (vecs[1][1]) + tv = 1; + else + tv = 2; + + for (i=0 ; i<2 ; i++) + { + ns = cosv * vecs[i][sv] - sinv * vecs[i][tv]; + nt = sinv * vecs[i][sv] + cosv * vecs[i][tv]; + vecs[i][sv] = ns; + vecs[i][tv] = nt; + } + + for (i=0 ; i<2 ; i++) + { + for (j=0 ; j<3 ; j++) + { + tx.textureVecsTexelsPerWorldUnits[i][j] = vecs[i][j] / bt->textureWorldUnitsPerTexel[i]; + tx.lightmapVecsLuxelsPerWorldUnits[i][j] = tx.textureVecsTexelsPerWorldUnits[i][j] / 16.0f; + } + } + } + else + { + tx.textureVecsTexelsPerWorldUnits[0][0] = bt->UAxis[0] / bt->textureWorldUnitsPerTexel[0]; + tx.textureVecsTexelsPerWorldUnits[0][1] = bt->UAxis[1] / bt->textureWorldUnitsPerTexel[0]; + tx.textureVecsTexelsPerWorldUnits[0][2] = bt->UAxis[2] / bt->textureWorldUnitsPerTexel[0]; + + tx.textureVecsTexelsPerWorldUnits[1][0] = bt->VAxis[0] / bt->textureWorldUnitsPerTexel[1]; + tx.textureVecsTexelsPerWorldUnits[1][1] = bt->VAxis[1] / bt->textureWorldUnitsPerTexel[1]; + tx.textureVecsTexelsPerWorldUnits[1][2] = bt->VAxis[2] / bt->textureWorldUnitsPerTexel[1]; + + tx.lightmapVecsLuxelsPerWorldUnits[0][0] = bt->UAxis[0] / bt->lightmapWorldUnitsPerLuxel; + tx.lightmapVecsLuxelsPerWorldUnits[0][1] = bt->UAxis[1] / bt->lightmapWorldUnitsPerLuxel; + tx.lightmapVecsLuxelsPerWorldUnits[0][2] = bt->UAxis[2] / bt->lightmapWorldUnitsPerLuxel; + + tx.lightmapVecsLuxelsPerWorldUnits[1][0] = bt->VAxis[0] / bt->lightmapWorldUnitsPerLuxel; + tx.lightmapVecsLuxelsPerWorldUnits[1][1] = bt->VAxis[1] / bt->lightmapWorldUnitsPerLuxel; + tx.lightmapVecsLuxelsPerWorldUnits[1][2] = bt->VAxis[2] / bt->lightmapWorldUnitsPerLuxel; + + shiftScaleU = bt->textureWorldUnitsPerTexel[0] / bt->lightmapWorldUnitsPerLuxel; + shiftScaleV = bt->textureWorldUnitsPerTexel[1] / bt->lightmapWorldUnitsPerLuxel; + } + + tx.textureVecsTexelsPerWorldUnits[0][3] = bt->shift[0] + + DOT_PRODUCT( origin, tx.textureVecsTexelsPerWorldUnits[0] ); + tx.textureVecsTexelsPerWorldUnits[1][3] = bt->shift[1] + + DOT_PRODUCT( origin, tx.textureVecsTexelsPerWorldUnits[1] ); + + tx.lightmapVecsLuxelsPerWorldUnits[0][3] = shiftScaleU * bt->shift[0] + + DOT_PRODUCT( origin, tx.lightmapVecsLuxelsPerWorldUnits[0] ); + tx.lightmapVecsLuxelsPerWorldUnits[1][3] = shiftScaleV * bt->shift[1] + + DOT_PRODUCT( origin, tx.lightmapVecsLuxelsPerWorldUnits[1] ); + + tx.flags = bt->flags; + tx.texdata = FindOrCreateTexData( bt->name ); + + // find the texinfo + return FindOrCreateTexInfo( tx ); +} + + +void LoadSurfacePropFile( const char *pMaterialFilename ) +{ + FileHandle_t fp = g_pFileSystem->Open( pMaterialFilename, "rb" ); + + if ( fp == FILESYSTEM_INVALID_HANDLE ) + { + return; + } + + int len = g_pFileSystem->Size( fp ); + + char *pText = new char[len]; + g_pFileSystem->Read( pText, len, fp ); + g_pFileSystem->Close( fp ); + + physprops->ParseSurfaceData( pMaterialFilename, pText ); + + delete[] pText; +} +//----------------------------------------------------------------------------- +// Purpose: Loads the surface properties database into the physics DLL +//----------------------------------------------------------------------------- +void LoadSurfaceProperties( void ) +{ + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); + if ( !physicsFactory ) + return; + + physprops = (IPhysicsSurfaceProps *)physicsFactory( VPHYSICS_SURFACEPROPS_INTERFACE_VERSION, NULL ); + + const char *SURFACEPROP_MANIFEST_FILE = "scripts/surfaceproperties_manifest.txt"; + KeyValues *manifest = new KeyValues( SURFACEPROP_MANIFEST_FILE ); + if ( manifest->LoadFromFile( g_pFileSystem, SURFACEPROP_MANIFEST_FILE, "GAME" ) ) + { + for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() ) + { + if ( !Q_stricmp( sub->GetName(), "file" ) ) + { + // Add + LoadSurfacePropFile( sub->GetString() ); + continue; + } + } + } + + manifest->deleteThis(); +} + + diff --git a/utils/vbsp/tree.cpp b/utils/vbsp/tree.cpp new file mode 100644 index 0000000..529e983 --- /dev/null +++ b/utils/vbsp/tree.cpp @@ -0,0 +1,207 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "vbsp.h" + +extern int c_nodes; + +void RemovePortalFromNode (portal_t *portal, node_t *l); + +node_t *NodeForPoint (node_t *node, Vector& origin) +{ + plane_t *plane; + vec_t d; + + while (node->planenum != PLANENUM_LEAF) + { + plane = &g_MainMap->mapplanes[node->planenum]; + d = DotProduct (origin, plane->normal) - plane->dist; + if (d >= 0) + node = node->children[0]; + else + node = node->children[1]; + } + + return node; +} + + + +/* +============= +FreeTreePortals_r +============= +*/ +void FreeTreePortals_r (node_t *node) +{ + portal_t *p, *nextp; + int s; + + // free children + if (node->planenum != PLANENUM_LEAF) + { + FreeTreePortals_r (node->children[0]); + FreeTreePortals_r (node->children[1]); + } + + // free portals + for (p=node->portals ; p ; p=nextp) + { + s = (p->nodes[1] == node); + nextp = p->next[s]; + + RemovePortalFromNode (p, p->nodes[!s]); + FreePortal (p); + } + node->portals = NULL; +} + +/* +============= +FreeTree_r +============= +*/ +void FreeTree_r (node_t *node) +{ + face_t *f, *nextf; + + // free children + if (node->planenum != PLANENUM_LEAF) + { + FreeTree_r (node->children[0]); + FreeTree_r (node->children[1]); + } + + // free bspbrushes + FreeBrushList (node->brushlist); + + // free faces + for (f=node->faces ; f ; f=nextf) + { + nextf = f->next; + FreeFace (f); + } + + // free the node + if (node->volume) + FreeBrush (node->volume); + + if (numthreads == 1) + c_nodes--; + free (node); +} + + +/* +============= +FreeTree +============= +*/ +void FreeTree (tree_t *tree) +{ + if ( !tree ) + return; + + FreeTreePortals_r (tree->headnode); + FreeTree_r (tree->headnode); + free (tree); +} + +//=============================================================== + +void PrintTree_r (node_t *node, int depth) +{ + int i; + plane_t *plane; + bspbrush_t *bb; + + for (i=0 ; i<depth ; i++) + Msg (" "); + if (node->planenum == PLANENUM_LEAF) + { + if (!node->brushlist) + Msg ("NULL\n"); + else + { + for (bb=node->brushlist ; bb ; bb=bb->next) + Msg ("%i ", bb->original->brushnum); + Msg ("\n"); + } + return; + } + + plane = &g_MainMap->mapplanes[node->planenum]; + Msg ("#%i (%5.2f %5.2f %5.2f):%5.2f\n", node->planenum, + plane->normal[0], plane->normal[1], plane->normal[2], + plane->dist); + PrintTree_r (node->children[0], depth+1); + PrintTree_r (node->children[1], depth+1); +} + +/* +========================================================= + +NODES THAT DON'T SEPERATE DIFFERENT CONTENTS CAN BE PRUNED + +========================================================= +*/ + +int c_pruned; + +/* +============ +PruneNodes_r +============ +*/ +void PruneNodes_r (node_t *node) +{ + bspbrush_t *b, *next; + + if (node->planenum == PLANENUM_LEAF) + return; + PruneNodes_r (node->children[0]); + PruneNodes_r (node->children[1]); + + if ( (node->children[0]->contents & CONTENTS_SOLID) + && (node->children[1]->contents & CONTENTS_SOLID) ) + { + if (node->faces) + Error ("node->faces seperating CONTENTS_SOLID"); + if (node->children[0]->faces || node->children[1]->faces) + Error ("!node->faces with children"); + + // FIXME: free stuff + node->planenum = PLANENUM_LEAF; + node->contents = CONTENTS_SOLID; + + if (node->brushlist) + Error ("PruneNodes: node->brushlist"); + + // combine brush lists + node->brushlist = node->children[1]->brushlist; + + for (b=node->children[0]->brushlist ; b ; b=next) + { + next = b->next; + b->next = node->brushlist; + node->brushlist = b; + } + + c_pruned++; + } +} + + +void PruneNodes (node_t *node) +{ + qprintf ("--- PruneNodes ---\n"); + c_pruned = 0; + PruneNodes_r (node); + qprintf ("%5i pruned nodes\n", c_pruned); +} + +//=========================================================== diff --git a/utils/vbsp/vbsp.cpp b/utils/vbsp/vbsp.cpp new file mode 100644 index 0000000..7b8c059 --- /dev/null +++ b/utils/vbsp/vbsp.cpp @@ -0,0 +1,1443 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: BSP Building tool +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "detail.h" +#include "physdll.h" +#include "utilmatlib.h" +#include "disp_vbsp.h" +#include "writebsp.h" +#include "tier0/icommandline.h" +#include "materialsystem/imaterialsystem.h" +#include "map.h" +#include "tools_minidump.h" +#include "materialsub.h" +#include "loadcmdline.h" +#include "byteswap.h" +#include "worldvertextransitionfixup.h" + +extern float g_maxLightmapDimension; + +char source[1024]; +char mapbase[ 64 ]; +char name[1024]; +char materialPath[1024]; + +vec_t microvolume = 1.0; +qboolean noprune; +qboolean glview; +qboolean nodetail; +qboolean fulldetail; +qboolean onlyents; +bool onlyprops; +qboolean nomerge; +qboolean nomergewater = false; +qboolean nowater; +qboolean nocsg; +qboolean noweld; +qboolean noshare; +qboolean nosubdiv; +qboolean notjunc; +qboolean noopt; +qboolean leaktest; +qboolean verboseentities; +qboolean dumpcollide = false; +qboolean g_bLowPriority = false; +qboolean g_DumpStaticProps = false; +qboolean g_bSkyVis = false; // skybox vis is off by default, toggle this to enable it +bool g_bLightIfMissing = false; +bool g_snapAxialPlanes = false; +bool g_bKeepStaleZip = false; +bool g_NodrawTriggers = false; +bool g_DisableWaterLighting = false; +bool g_bAllowDetailCracks = false; +bool g_bNoVirtualMesh = false; + +float g_defaultLuxelSize = DEFAULT_LUXEL_SIZE; +float g_luxelScale = 1.0f; +float g_minLuxelScale = 1.0f; +bool g_BumpAll = false; + +int g_nDXLevel = 0; // default dxlevel if you don't specify it on the command-line. +CUtlVector<int> g_SkyAreas; +char outbase[32]; + +char g_szEmbedDir[MAX_PATH] = { 0 }; + +// HLTOOLS: Introduce these calcs to make the block algorithm proportional to the proper +// world coordinate extents. Assumes square spatial constraints. +#define BLOCKS_SIZE 1024 +#define BLOCKS_SPACE (COORD_EXTENT/BLOCKS_SIZE) +#define BLOCKX_OFFSET ((BLOCKS_SPACE/2)+1) +#define BLOCKY_OFFSET ((BLOCKS_SPACE/2)+1) +#define BLOCKS_MIN (-(BLOCKS_SPACE/2)) +#define BLOCKS_MAX ((BLOCKS_SPACE/2)-1) + +int block_xl = BLOCKS_MIN, block_xh = BLOCKS_MAX, block_yl = BLOCKS_MIN, block_yh = BLOCKS_MAX; + +int entity_num; + + +node_t *block_nodes[BLOCKS_SPACE+2][BLOCKS_SPACE+2]; + +//----------------------------------------------------------------------------- +// Assign occluder areas (must happen *after* the world model is processed) +//----------------------------------------------------------------------------- +void AssignOccluderAreas( tree_t *pTree ); +static void Compute3DSkyboxAreas( node_t *headnode, CUtlVector<int>& areas ); + + +/* +============ +BlockTree + +============ +*/ +node_t *BlockTree (int xl, int yl, int xh, int yh) +{ + node_t *node; + Vector normal; + float dist; + int mid; + + if (xl == xh && yl == yh) + { + node = block_nodes[xl+BLOCKX_OFFSET][yl+BLOCKY_OFFSET]; + if (!node) + { // return an empty leaf + node = AllocNode (); + node->planenum = PLANENUM_LEAF; + node->contents = 0; //CONTENTS_SOLID; + return node; + } + return node; + } + + // create a seperator along the largest axis + node = AllocNode (); + + if (xh - xl > yh - yl) + { // split x axis + mid = xl + (xh-xl)/2 + 1; + normal[0] = 1; + normal[1] = 0; + normal[2] = 0; + dist = mid*BLOCKS_SIZE; + node->planenum = g_MainMap->FindFloatPlane (normal, dist); + node->children[0] = BlockTree ( mid, yl, xh, yh); + node->children[1] = BlockTree ( xl, yl, mid-1, yh); + } + else + { + mid = yl + (yh-yl)/2 + 1; + normal[0] = 0; + normal[1] = 1; + normal[2] = 0; + dist = mid*BLOCKS_SIZE; + node->planenum = g_MainMap->FindFloatPlane (normal, dist); + node->children[0] = BlockTree ( xl, mid, xh, yh); + node->children[1] = BlockTree ( xl, yl, xh, mid-1); + } + + return node; +} + +/* +============ +ProcessBlock_Thread + +============ +*/ +int brush_start, brush_end; +void ProcessBlock_Thread (int threadnum, int blocknum) +{ + int xblock, yblock; + Vector mins, maxs; + bspbrush_t *brushes; + tree_t *tree; + node_t *node; + + yblock = block_yl + blocknum / (block_xh-block_xl+1); + xblock = block_xl + blocknum % (block_xh-block_xl+1); + + qprintf ("############### block %2i,%2i ###############\n", xblock, yblock); + + mins[0] = xblock*BLOCKS_SIZE; + mins[1] = yblock*BLOCKS_SIZE; + mins[2] = MIN_COORD_INTEGER; + maxs[0] = (xblock+1)*BLOCKS_SIZE; + maxs[1] = (yblock+1)*BLOCKS_SIZE; + maxs[2] = MAX_COORD_INTEGER; + + // the makelist and chopbrushes could be cached between the passes... + brushes = MakeBspBrushList (brush_start, brush_end, mins, maxs, NO_DETAIL); + if (!brushes) + { + node = AllocNode (); + node->planenum = PLANENUM_LEAF; + node->contents = CONTENTS_SOLID; + block_nodes[xblock+BLOCKX_OFFSET][yblock+BLOCKY_OFFSET] = node; + return; + } + + FixupAreaportalWaterBrushes( brushes ); + if (!nocsg) + brushes = ChopBrushes (brushes); + + tree = BrushBSP (brushes, mins, maxs); + + block_nodes[xblock+BLOCKX_OFFSET][yblock+BLOCKY_OFFSET] = tree->headnode; +} + + +/* +============ +ProcessWorldModel + +============ +*/ +void SplitSubdividedFaces( node_t *headnode ); // garymcthack +void ProcessWorldModel (void) +{ + entity_t *e; + tree_t *tree = NULL; + qboolean leaked; + int optimize; + int start; + + e = &entities[entity_num]; + + brush_start = e->firstbrush; + brush_end = brush_start + e->numbrushes; + leaked = false; + + // + // perform per-block operations + // + if (block_xh * BLOCKS_SIZE > g_MainMap->map_maxs[0]) + { + block_xh = floor(g_MainMap->map_maxs[0]/BLOCKS_SIZE); + } + if ( (block_xl+1) * BLOCKS_SIZE < g_MainMap->map_mins[0]) + { + block_xl = floor(g_MainMap->map_mins[0]/BLOCKS_SIZE); + } + if (block_yh * BLOCKS_SIZE > g_MainMap->map_maxs[1]) + { + block_yh = floor(g_MainMap->map_maxs[1]/BLOCKS_SIZE); + } + if ( (block_yl+1) * BLOCKS_SIZE < g_MainMap->map_mins[1]) + { + block_yl = floor(g_MainMap->map_mins[1]/BLOCKS_SIZE); + } + + // HLTOOLS: updated to +/- MAX_COORD_INTEGER ( new world size limits / worldsize.h ) + if (block_xl < BLOCKS_MIN) + { + block_xl = BLOCKS_MIN; + } + if (block_yl < BLOCKS_MIN) + { + block_yl = BLOCKS_MIN; + } + if (block_xh > BLOCKS_MAX) + { + block_xh = BLOCKS_MAX; + } + if (block_yh > BLOCKS_MAX) + { + block_yh = BLOCKS_MAX; + } + + for (optimize = 0 ; optimize <= 1 ; optimize++) + { + qprintf ("--------------------------------------------\n"); + + RunThreadsOnIndividual ((block_xh-block_xl+1)*(block_yh-block_yl+1), + !verbose, ProcessBlock_Thread); + + // + // build the division tree + // oversizing the blocks guarantees that all the boundaries + // will also get nodes. + // + + qprintf ("--------------------------------------------\n"); + + tree = AllocTree (); + tree->headnode = BlockTree (block_xl-1, block_yl-1, block_xh+1, block_yh+1); + + tree->mins[0] = (block_xl)*BLOCKS_SIZE; + tree->mins[1] = (block_yl)*BLOCKS_SIZE; + tree->mins[2] = g_MainMap->map_mins[2] - 8; + + tree->maxs[0] = (block_xh+1)*BLOCKS_SIZE; + tree->maxs[1] = (block_yh+1)*BLOCKS_SIZE; + tree->maxs[2] = g_MainMap->map_maxs[2] + 8; + + // + // perform the global operations + // + + // make the portals/faces by traversing down to each empty leaf + MakeTreePortals (tree); + + if (FloodEntities (tree)) + { + // turns everthing outside into solid + FillOutside (tree->headnode); + } + else + { + Warning( ("**** leaked ****\n") ); + leaked = true; + LeakFile (tree); + if (leaktest) + { + Warning( ("--- MAP LEAKED ---\n") ); + exit (0); + } + } + + // mark the brush sides that actually turned into faces + MarkVisibleSides (tree, brush_start, brush_end, NO_DETAIL); + if (noopt || leaked) + break; + if (!optimize) + { + // If we are optimizing, free the tree. Next time we will construct it again, but + // we'll use the information in MarkVisibleSides() so we'll only split with planes that + // actually contribute renderable geometry + FreeTree (tree); + } + } + + FloodAreas (tree); + + RemoveAreaPortalBrushes_R( tree->headnode ); + + start = Plat_FloatTime(); + Msg("Building Faces..."); + // this turns portals with one solid side into faces + // it also subdivides each face if necessary to fit max lightmap dimensions + MakeFaces (tree->headnode); + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); + + if (glview) + { + WriteGLView (tree, source); + } + + AssignOccluderAreas( tree ); + Compute3DSkyboxAreas( tree->headnode, g_SkyAreas ); + face_t *pLeafFaceList = NULL; + if ( !nodetail ) + { + pLeafFaceList = MergeDetailTree( tree, brush_start, brush_end ); + } + + start = Plat_FloatTime(); + + Msg("FixTjuncs...\n"); + + // This unifies the vertex list for all edges (splits collinear edges to remove t-junctions) + // It also welds the list of vertices out of each winding/portal and rounds nearly integer verts to integer + pLeafFaceList = FixTjuncs (tree->headnode, pLeafFaceList); + + // this merges all of the solid nodes that have separating planes + if (!noprune) + { + Msg("PruneNodes...\n"); + PruneNodes (tree->headnode); + } + +// Msg( "SplitSubdividedFaces...\n" ); +// SplitSubdividedFaces( tree->headnode ); + + Msg("WriteBSP...\n"); + WriteBSP (tree->headnode, pLeafFaceList); + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); + + if (!leaked) + { + WritePortalFile (tree); + } + + FreeTree( tree ); + FreeLeafFaces( pLeafFaceList ); +} + +/* +============ +ProcessSubModel + +============ +*/ +void ProcessSubModel( ) +{ + entity_t *e; + int start, end; + tree_t *tree; + bspbrush_t *list; + Vector mins, maxs; + + e = &entities[entity_num]; + + start = e->firstbrush; + end = start + e->numbrushes; + + mins[0] = mins[1] = mins[2] = MIN_COORD_INTEGER; + maxs[0] = maxs[1] = maxs[2] = MAX_COORD_INTEGER; + list = MakeBspBrushList (start, end, mins, maxs, FULL_DETAIL); + + if (!nocsg) + list = ChopBrushes (list); + tree = BrushBSP (list, mins, maxs); + + // This would wind up crashing the engine because we'd have a negative leaf index in dmodel_t::headnode. + if ( tree->headnode->planenum == PLANENUM_LEAF ) + { + const char *pClassName = ValueForKey( e, "classname" ); + const char *pTargetName = ValueForKey( e, "targetname" ); + Error( "bmodel %d has no head node (class '%s', targetname '%s')", entity_num, pClassName, pTargetName ); + } + + MakeTreePortals (tree); + +#if DEBUG_BRUSHMODEL + if ( entity_num == DEBUG_BRUSHMODEL ) + WriteGLView( tree, "tree_all" ); +#endif + + MarkVisibleSides (tree, start, end, FULL_DETAIL); + MakeFaces (tree->headnode); + + FixTjuncs( tree->headnode, NULL ); + WriteBSP( tree->headnode, NULL ); + +#if DEBUG_BRUSHMODEL + if ( entity_num == DEBUG_BRUSHMODEL ) + { + WriteGLView( tree, "tree_vis" ); + WriteGLViewFaces( tree, "tree_faces" ); + } +#endif + + FreeTree (tree); +} + + +//----------------------------------------------------------------------------- +// Returns true if the entity is a func_occluder +//----------------------------------------------------------------------------- +bool IsFuncOccluder( int entity_num ) +{ + entity_t *mapent = &entities[entity_num]; + const char *pClassName = ValueForKey( mapent, "classname" ); + return (strcmp("func_occluder", pClassName) == 0); +} + + +//----------------------------------------------------------------------------- +// Computes the area of a brush's occluders +//----------------------------------------------------------------------------- +float ComputeOccluderBrushArea( mapbrush_t *pBrush ) +{ + float flArea = 0.0f; + for ( int j = 0; j < pBrush->numsides; ++j ) + { + side_t *pSide = &(pBrush->original_sides[j]); + + // Skip nodraw surfaces + if ( texinfo[pSide->texinfo].flags & SURF_NODRAW ) + continue; + + if ( !pSide->winding ) + continue; + + flArea += WindingArea( pSide->winding ); + } + + return flArea; +} + + +//----------------------------------------------------------------------------- +// Clips all occluder brushes against each other +//----------------------------------------------------------------------------- +static tree_t *ClipOccluderBrushes( ) +{ + // Create a list of all occluder brushes in the level + CUtlVector< mapbrush_t * > mapBrushes( 1024, 1024 ); + for ( entity_num=0; entity_num < g_MainMap->num_entities; ++entity_num ) + { + if (!IsFuncOccluder(entity_num)) + continue; + + entity_t *e = &entities[entity_num]; + int end = e->firstbrush + e->numbrushes; + int i; + for ( i = e->firstbrush; i < end; ++i ) + { + mapBrushes.AddToTail( &g_MainMap->mapbrushes[i] ); + } + } + + int nBrushCount = mapBrushes.Count(); + if ( nBrushCount == 0 ) + return NULL; + + Vector mins, maxs; + mins[0] = mins[1] = mins[2] = MIN_COORD_INTEGER; + maxs[0] = maxs[1] = maxs[2] = MAX_COORD_INTEGER; + + bspbrush_t *list = MakeBspBrushList( mapBrushes.Base(), nBrushCount, mins, maxs ); + + if (!nocsg) + list = ChopBrushes (list); + tree_t *tree = BrushBSP (list, mins, maxs); + MakeTreePortals (tree); + MarkVisibleSides (tree, mapBrushes.Base(), nBrushCount); + MakeFaces( tree->headnode ); + + // NOTE: This will output the occluder face vertices + planes + FixTjuncs( tree->headnode, NULL ); + + return tree; +} + + +//----------------------------------------------------------------------------- +// Generate a list of unique sides in the occluder tree +//----------------------------------------------------------------------------- +static void GenerateOccluderSideList( int nEntity, CUtlVector<side_t*> &occluderSides ) +{ + entity_t *e = &entities[nEntity]; + int end = e->firstbrush + e->numbrushes; + int i, j; + for ( i = e->firstbrush; i < end; ++i ) + { + mapbrush_t *mb = &g_MainMap->mapbrushes[i]; + for ( j = 0; j < mb->numsides; ++j ) + { + occluderSides.AddToTail( &(mb->original_sides[j]) ); + } + } +} + + +//----------------------------------------------------------------------------- +// Generate a list of unique faces in the occluder tree +//----------------------------------------------------------------------------- +static void GenerateOccluderFaceList( node_t *pOccluderNode, CUtlVector<face_t*> &occluderFaces ) +{ + if (pOccluderNode->planenum == PLANENUM_LEAF) + return; + + for ( face_t *f=pOccluderNode->faces ; f ; f = f->next ) + { + occluderFaces.AddToTail( f ); + } + + GenerateOccluderFaceList( pOccluderNode->children[0], occluderFaces ); + GenerateOccluderFaceList( pOccluderNode->children[1], occluderFaces ); +} + + +//----------------------------------------------------------------------------- +// For occluder area assignment +//----------------------------------------------------------------------------- +struct OccluderInfo_t +{ + int m_nOccluderEntityIndex; +}; + +static CUtlVector< OccluderInfo_t > g_OccluderInfo; + + +//----------------------------------------------------------------------------- +// Emits occluder brushes +//----------------------------------------------------------------------------- +static void EmitOccluderBrushes() +{ + char str[64]; + + g_OccluderData.RemoveAll(); + g_OccluderPolyData.RemoveAll(); + g_OccluderVertexIndices.RemoveAll(); + + tree_t *pOccluderTree = ClipOccluderBrushes(); + if (!pOccluderTree) + return; + + CUtlVector<face_t*> faceList( 1024, 1024 ); + CUtlVector<side_t*> sideList( 1024, 1024 ); + GenerateOccluderFaceList( pOccluderTree->headnode, faceList ); + +#ifdef _DEBUG + int *pEmitted = (int*)stackalloc( faceList.Count() * sizeof(int) ); + memset( pEmitted, 0, faceList.Count() * sizeof(int) ); +#endif + + for ( entity_num=1; entity_num < num_entities; ++entity_num ) + { + if (!IsFuncOccluder(entity_num)) + continue; + + // Output only those parts of the occluder tree which are a part of the brush + int nOccluder = g_OccluderData.AddToTail(); + doccluderdata_t &occluderData = g_OccluderData[ nOccluder ]; + occluderData.firstpoly = g_OccluderPolyData.Count(); + occluderData.mins.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + occluderData.maxs.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX ); + occluderData.flags = 0; + occluderData.area = -1; + + // NOTE: If you change the algorithm by which occluder numbers are allocated, + // then you must also change FixupOnlyEntsOccluderEntities() below + sprintf (str, "%i", nOccluder); + SetKeyValue (&entities[entity_num], "occludernumber", str); + + int nIndex = g_OccluderInfo.AddToTail(); + g_OccluderInfo[nIndex].m_nOccluderEntityIndex = entity_num; + + sideList.RemoveAll(); + GenerateOccluderSideList( entity_num, sideList ); + for ( int i = faceList.Count(); --i >= 0; ) + { + // Skip nodraw surfaces, but not triggers that have been marked as nodraw + face_t *f = faceList[i]; + if ( ( texinfo[f->texinfo].flags & SURF_NODRAW ) && + (( texinfo[f->texinfo].flags & SURF_TRIGGER ) == 0 ) ) + continue; + + // Only emit faces that appear in the side list of the occluder + for ( int j = sideList.Count(); --j >= 0; ) + { + if ( sideList[j] != f->originalface ) + continue; + + if ( f->numpoints < 3 ) + continue; + + // not a final face + Assert ( !f->merged && !f->split[0] && !f->split[1] ); + +#ifdef _DEBUG + Assert( !pEmitted[i] ); + pEmitted[i] = entity_num; +#endif + + int k = g_OccluderPolyData.AddToTail(); + doccluderpolydata_t *pOccluderPoly = &g_OccluderPolyData[k]; + + pOccluderPoly->planenum = f->planenum; + pOccluderPoly->vertexcount = f->numpoints; + pOccluderPoly->firstvertexindex = g_OccluderVertexIndices.Count(); + for( k = 0; k < f->numpoints; ++k ) + { + g_OccluderVertexIndices.AddToTail( f->vertexnums[k] ); + + const Vector &p = dvertexes[f->vertexnums[k]].point; + VectorMin( occluderData.mins, p, occluderData.mins ); + VectorMax( occluderData.maxs, p, occluderData.maxs ); + } + + break; + } + } + + occluderData.polycount = g_OccluderPolyData.Count() - occluderData.firstpoly; + + // Mark this brush as not having brush geometry so it won't be re-emitted with a brush model + entities[entity_num].numbrushes = 0; + } + + FreeTree( pOccluderTree ); +} + + +//----------------------------------------------------------------------------- +// Set occluder area +//----------------------------------------------------------------------------- +void SetOccluderArea( int nOccluder, int nArea, int nEntityNum ) +{ + if ( g_OccluderData[nOccluder].area <= 0 ) + { + g_OccluderData[nOccluder].area = nArea; + } + else if ( (nArea != 0) && (g_OccluderData[nOccluder].area != nArea) ) + { + const char *pTargetName = ValueForKey( &entities[nEntityNum], "targetname" ); + if (!pTargetName) + { + pTargetName = "<no name>"; + } + Warning("Occluder \"%s\" straddles multiple areas. This is invalid!\n", pTargetName ); + } +} + + +//----------------------------------------------------------------------------- +// Assign occluder areas (must happen *after* the world model is processed) +//----------------------------------------------------------------------------- +void AssignAreaToOccluder( int nOccluder, tree_t *pTree, bool bCrossAreaPortals ) +{ + int nFirstPoly = g_OccluderData[nOccluder].firstpoly; + int nEntityNum = g_OccluderInfo[nOccluder].m_nOccluderEntityIndex; + for ( int j = 0; j < g_OccluderData[nOccluder].polycount; ++j ) + { + doccluderpolydata_t *pOccluderPoly = &g_OccluderPolyData[nFirstPoly + j]; + int nFirstVertex = pOccluderPoly->firstvertexindex; + for ( int k = 0; k < pOccluderPoly->vertexcount; ++k ) + { + int nVertexIndex = g_OccluderVertexIndices[nFirstVertex + k]; + node_t *pNode = NodeForPoint( pTree->headnode, dvertexes[ nVertexIndex ].point ); + + SetOccluderArea( nOccluder, pNode->area, nEntityNum ); + + int nOtherSideIndex; + portal_t *pPortal; + for ( pPortal = pNode->portals; pPortal; pPortal = pPortal->next[!nOtherSideIndex] ) + { + nOtherSideIndex = (pPortal->nodes[0] == pNode) ? 1 : 0; + if (!pPortal->onnode) + continue; // edge of world + + // Don't cross over area portals for the area check + if ((!bCrossAreaPortals) && pPortal->nodes[nOtherSideIndex]->contents & CONTENTS_AREAPORTAL) + continue; + + int nAdjacentArea = pPortal->nodes[nOtherSideIndex] ? pPortal->nodes[nOtherSideIndex]->area : 0; + SetOccluderArea( nOccluder, nAdjacentArea, nEntityNum ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Assign occluder areas (must happen *after* the world model is processed) +//----------------------------------------------------------------------------- +void AssignOccluderAreas( tree_t *pTree ) +{ + for ( int i = 0; i < g_OccluderData.Count(); ++i ) + { + AssignAreaToOccluder( i, pTree, false ); + + // This can only have happened if the only valid portal out leads into an areaportal + if ( g_OccluderData[i].area <= 0 ) + { + AssignAreaToOccluder( i, pTree, true ); + } + } +} + + + +//----------------------------------------------------------------------------- +// Make sure the func_occluders have the appropriate data set +//----------------------------------------------------------------------------- +void FixupOnlyEntsOccluderEntities() +{ + char str[64]; + int nOccluder = 0; + for ( entity_num=1; entity_num < num_entities; ++entity_num ) + { + if (!IsFuncOccluder(entity_num)) + continue; + + // NOTE: If you change the algorithm by which occluder numbers are allocated above, + // then you must also change this + sprintf (str, "%i", nOccluder); + SetKeyValue (&entities[entity_num], "occludernumber", str); + ++nOccluder; + } +} + + +void MarkNoDynamicShadowSides() +{ + for ( int iSide=0; iSide < g_MainMap->nummapbrushsides; iSide++ ) + { + g_MainMap->brushsides[iSide].m_bDynamicShadowsEnabled = true; + } + + for ( int i=0; i < g_NoDynamicShadowSides.Count(); i++ ) + { + int brushSideID = g_NoDynamicShadowSides[i]; + + // Find the side with this ID. + for ( int iSide=0; iSide < g_MainMap->nummapbrushsides; iSide++ ) + { + if ( g_MainMap->brushsides[iSide].id == brushSideID ) + g_MainMap->brushsides[iSide].m_bDynamicShadowsEnabled = false; + } + } +} + +//----------------------------------------------------------------------------- +// Compute the 3D skybox areas +//----------------------------------------------------------------------------- +static void Compute3DSkyboxAreas( node_t *headnode, CUtlVector<int>& areas ) +{ + for (int i = 0; i < g_MainMap->num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!strcmp(pEntity, "sky_camera")) + { + // Found a 3D skybox camera, get a leaf that lies in it + node_t *pLeaf = PointInLeaf( headnode, entities[i].origin ); + if (pLeaf->contents & CONTENTS_SOLID) + { + Error ("Error! Entity sky_camera in solid volume! at %.1f %.1f %.1f\n", entities[i].origin.x, entities[i].origin.y, entities[i].origin.z); + } + areas.AddToTail( pLeaf->area ); + } + } +} + +bool Is3DSkyboxArea( int area ) +{ + for ( int i = g_SkyAreas.Count(); --i >=0; ) + { + if ( g_SkyAreas[i] == area ) + return true; + } + return false; +} + + +/* +============ +ProcessModels +============ +*/ +void ProcessModels (void) +{ + BeginBSPFile (); + + // Mark sides that have no dynamic shadows. + MarkNoDynamicShadowSides(); + + // emit the displacement surfaces + EmitInitialDispInfos(); + + // Clip occluder brushes against each other, + // Remove them from the list of models to process below + EmitOccluderBrushes( ); + + for ( entity_num=0; entity_num < num_entities; ++entity_num ) + { + entity_t *pEntity = &entities[entity_num]; + if ( !pEntity->numbrushes ) + continue; + + qprintf ("############### model %i ###############\n", nummodels); + + BeginModel (); + + if (entity_num == 0) + { + ProcessWorldModel(); + } + else + { + ProcessSubModel( ); + } + + EndModel (); + + if (!verboseentities) + { + verbose = false; // don't bother printing submodels + } + } + + // Turn the skybox into a cubemap in case we don't build env_cubemap textures. + Cubemap_CreateDefaultCubemaps(); + EndBSPFile (); +} + + +void LoadPhysicsDLL( void ) +{ + PhysicsDLLPath( "vphysics.dll" ); +} + + +void PrintCommandLine( int argc, char **argv ) +{ + Warning( "Command line: " ); + for ( int z=0; z < argc; z++ ) + { + Warning( "\"%s\" ", argv[z] ); + } + Warning( "\n\n" ); +} + + +int RunVBSP( int argc, char **argv ) +{ + int i; + double start, end; + char path[1024]; + + CommandLine()->CreateCmdLine( argc, argv ); + MathLib_Init( 2.2f, 2.2f, 0.0f, OVERBRIGHT, false, false, false, false ); + InstallSpewFunction(); + SpewActivate( "developer", 1 ); + + CmdLib_InitFileSystem( argv[ argc-1 ] ); + + Q_StripExtension( ExpandArg( argv[ argc-1 ] ), source, sizeof( source ) ); + Q_FileBase( source, mapbase, sizeof( mapbase ) ); + strlwr( mapbase ); + + // Maintaining legacy behavior here to avoid breaking tools: regardless of the extension we are passed, we strip it + // to get the "source" name, and append extensions as desired... + char mapFile[1024]; + V_strncpy( mapFile, source, sizeof( mapFile ) ); + V_strncat( mapFile, ".bsp", sizeof( mapFile ) ); + + LoadCmdLineFromFile( argc, argv, mapbase, "vbsp" ); + + Msg( "Valve Software - vbsp.exe (%s)\n", __DATE__ ); + + for (i=1 ; i<argc ; i++) + { + if (!stricmp(argv[i],"-threads")) + { + numthreads = atoi (argv[i+1]); + i++; + } + else if (!Q_stricmp(argv[i],"-glview")) + { + glview = true; + } + else if ( !Q_stricmp(argv[i], "-v") || !Q_stricmp(argv[i], "-verbose") ) + { + Msg("verbose = true\n"); + verbose = true; + } + else if (!Q_stricmp(argv[i], "-noweld")) + { + Msg ("noweld = true\n"); + noweld = true; + } + else if (!Q_stricmp(argv[i], "-nocsg")) + { + Msg ("nocsg = true\n"); + nocsg = true; + } + else if (!Q_stricmp(argv[i], "-noshare")) + { + Msg ("noshare = true\n"); + noshare = true; + } + else if (!Q_stricmp(argv[i], "-notjunc")) + { + Msg ("notjunc = true\n"); + notjunc = true; + } + else if (!Q_stricmp(argv[i], "-nowater")) + { + Msg ("nowater = true\n"); + nowater = true; + } + else if (!Q_stricmp(argv[i], "-noopt")) + { + Msg ("noopt = true\n"); + noopt = true; + } + else if (!Q_stricmp(argv[i], "-noprune")) + { + Msg ("noprune = true\n"); + noprune = true; + } + else if (!Q_stricmp(argv[i], "-nomerge")) + { + Msg ("nomerge = true\n"); + nomerge = true; + } + else if (!Q_stricmp(argv[i], "-nomergewater")) + { + Msg ("nomergewater = true\n"); + nomergewater = true; + } + else if (!Q_stricmp(argv[i], "-nosubdiv")) + { + Msg ("nosubdiv = true\n"); + nosubdiv = true; + } + else if (!Q_stricmp(argv[i], "-nodetail")) + { + Msg ("nodetail = true\n"); + nodetail = true; + } + else if (!Q_stricmp(argv[i], "-fulldetail")) + { + Msg ("fulldetail = true\n"); + fulldetail = true; + } + else if (!Q_stricmp(argv[i], "-onlyents")) + { + Msg ("onlyents = true\n"); + onlyents = true; + } + else if (!Q_stricmp(argv[i], "-onlyprops")) + { + Msg ("onlyprops = true\n"); + onlyprops = true; + } + else if (!Q_stricmp(argv[i], "-micro")) + { + microvolume = atof(argv[i+1]); + Msg ("microvolume = %f\n", microvolume); + i++; + } + else if (!Q_stricmp(argv[i], "-leaktest")) + { + Msg ("leaktest = true\n"); + leaktest = true; + } + else if (!Q_stricmp(argv[i], "-verboseentities")) + { + Msg ("verboseentities = true\n"); + verboseentities = true; + } + else if (!Q_stricmp(argv[i], "-snapaxial")) + { + Msg ("snap axial = true\n"); + g_snapAxialPlanes = true; + } +#if 0 + else if (!Q_stricmp(argv[i], "-maxlightmapdim")) + { + g_maxLightmapDimension = atof(argv[i+1]); + Msg ("g_maxLightmapDimension = %f\n", g_maxLightmapDimension); + i++; + } +#endif + else if (!Q_stricmp(argv[i], "-block")) + { + block_xl = block_xh = atoi(argv[i+1]); + block_yl = block_yh = atoi(argv[i+2]); + Msg ("block: %i,%i\n", block_xl, block_yl); + i+=2; + } + else if (!Q_stricmp(argv[i], "-blocks")) + { + block_xl = atoi(argv[i+1]); + block_yl = atoi(argv[i+2]); + block_xh = atoi(argv[i+3]); + block_yh = atoi(argv[i+4]); + Msg ("blocks: %i,%i to %i,%i\n", + block_xl, block_yl, block_xh, block_yh); + i+=4; + } + else if ( !Q_stricmp( argv[i], "-dumpcollide" ) ) + { + Msg("Dumping collision models to collideXXX.txt\n" ); + dumpcollide = true; + } + else if ( !Q_stricmp( argv[i], "-dumpstaticprop" ) ) + { + Msg("Dumping static props to staticpropXXX.txt\n" ); + g_DumpStaticProps = true; + } + else if ( !Q_stricmp( argv[i], "-forceskyvis" ) ) + { + Msg("Enabled vis in 3d skybox\n" ); + g_bSkyVis = true; + } + else if (!Q_stricmp (argv[i],"-tmpout")) + { + strcpy (outbase, "/tmp"); + } +#if 0 + else if( !Q_stricmp( argv[i], "-defaultluxelsize" ) ) + { + g_defaultLuxelSize = atof( argv[i+1] ); + i++; + } +#endif + else if( !Q_stricmp( argv[i], "-luxelscale" ) ) + { + g_luxelScale = atof( argv[i+1] ); + i++; + } + else if( !strcmp( argv[i], "-minluxelscale" ) ) + { + g_minLuxelScale = atof( argv[i+1] ); + if (g_minLuxelScale < 1) + g_minLuxelScale = 1; + i++; + } + else if( !Q_stricmp( argv[i], "-dxlevel" ) ) + { + g_nDXLevel = atoi( argv[i+1] ); + Msg( "DXLevel = %d\n", g_nDXLevel ); + i++; + } + else if( !Q_stricmp( argv[i], "-bumpall" ) ) + { + g_BumpAll = true; + } + else if( !Q_stricmp( argv[i], "-low" ) ) + { + g_bLowPriority = true; + } + else if( !Q_stricmp( argv[i], "-lightifmissing" ) ) + { + g_bLightIfMissing = true; + } + else if ( !Q_stricmp( argv[i], CMDLINEOPTION_NOVCONFIG ) ) + { + } + else if ( !Q_stricmp( argv[i], "-allowdebug" ) || !Q_stricmp( argv[i], "-steam" ) ) + { + // nothing to do here, but don't bail on this option + } + else if ( !Q_stricmp( argv[i], "-vproject" ) || !Q_stricmp( argv[i], "-game" ) || !Q_stricmp( argv[i], "-insert_search_path" ) ) + { + ++i; + } + else if ( !Q_stricmp( argv[i], "-keepstalezip" ) ) + { + g_bKeepStaleZip = true; + } + else if ( !Q_stricmp( argv[i], "-xbox" ) ) + { + // enable mandatory xbox extensions + g_NodrawTriggers = true; + g_DisableWaterLighting = true; + } + else if ( !Q_stricmp( argv[i], "-allowdetailcracks")) + { + g_bAllowDetailCracks = true; + } + else if ( !Q_stricmp( argv[i], "-novirtualmesh")) + { + g_bNoVirtualMesh = true; + } + else if ( !Q_stricmp( argv[i], "-replacematerials" ) ) + { + g_ReplaceMaterials = true; + } + else if ( !Q_stricmp(argv[i], "-nodrawtriggers") ) + { + g_NodrawTriggers = true; + } + else if ( !Q_stricmp( argv[i], "-FullMinidumps" ) ) + { + EnableFullMinidumps( true ); + } + else if ( !Q_stricmp( argv[i], "-embed" ) && i < argc - 1 ) + { + V_MakeAbsolutePath( g_szEmbedDir, sizeof( g_szEmbedDir ), argv[++i], "." ); + V_FixSlashes( g_szEmbedDir ); + if ( !V_RemoveDotSlashes( g_szEmbedDir ) ) + { + Error( "Bad -embed - Can't resolve pathname for '%s'", g_szEmbedDir ); + break; + } + V_StripTrailingSlash( g_szEmbedDir ); + g_pFullFileSystem->AddSearchPath( g_szEmbedDir, "GAME", PATH_ADD_TO_TAIL ); + g_pFullFileSystem->AddSearchPath( g_szEmbedDir, "MOD", PATH_ADD_TO_TAIL ); + } + else if (argv[i][0] == '-') + { + Warning("VBSP: Unknown option \"%s\"\n\n", argv[i]); + i = 100000; // force it to print the usage + break; + } + else + break; + } + + if (i != argc - 1) + { + PrintCommandLine( argc, argv ); + + Warning( + "usage : vbsp [options...] mapfile\n" + "example: vbsp -onlyents c:\\hl2\\hl2\\maps\\test\n" + "\n" + "Common options (use -v to see all options):\n" + "\n" + " -v (or -verbose): Turn on verbose output (also shows more command\n" + " line options).\n" + "\n" + " -onlyents : This option causes vbsp only import the entities from the .vmf\n" + " file. -onlyents won't reimport brush models.\n" + " -onlyprops : Only update the static props and detail props.\n" + " -glview : Writes .gl files in the current directory that can be viewed\n" + " with glview.exe. If you use -tmpout, it will write the files\n" + " into the \\tmp folder.\n" + " -nodetail : Get rid of all detail geometry. The geometry left over is\n" + " what affects visibility.\n" + " -nowater : Get rid of water brushes.\n" + " -low : Run as an idle-priority process.\n" + " -embed <directory> : Use <directory> as an additional search path for assets\n" + " and embed all assets in this directory into the compiled\n" + " map\n" + "\n" + " -vproject <directory> : Override the VPROJECT environment variable.\n" + " -game <directory> : Same as -vproject.\n" + "\n" ); + + if ( verbose ) + { + Warning( + "Other options :\n" + " -novconfig : Don't bring up graphical UI on vproject errors.\n" + " -threads : Control the number of threads vbsp uses (defaults to the # of\n" + " processors on your machine).\n" + " -verboseentities: If -v is on, this disables verbose output for submodels.\n" + " -noweld : Don't join face vertices together.\n" + " -nocsg : Don't chop out intersecting brush areas.\n" + " -noshare : Emit unique face edges instead of sharing them.\n" + " -notjunc : Don't fixup t-junctions.\n" + " -noopt : By default, vbsp removes the 'outer shell' of the map, which\n" + " are all the faces you can't see because you can never get\n" + " outside the map. -noopt disables this behaviour.\n" + " -noprune : Don't prune neighboring solid nodes.\n" + " -nomerge : Don't merge together chopped faces on nodes.\n" + " -nomergewater: Don't merge together chopped faces on water.\n" + " -nosubdiv : Don't subdivide faces for lightmapping.\n" + " -micro <#> : vbsp will warn when brushes are output with a volume less\n" + " than this number (default: 1.0).\n" + " -fulldetail : Mark all detail geometry as normal geometry (so all detail\n" + " geometry will affect visibility).\n" + " -leaktest : Stop processing the map if a leak is detected. Whether or not\n" + " this flag is set, a leak file will be written out at\n" + " <vmf filename>.lin, and it can be imported into Hammer.\n" + " -bumpall : Force all surfaces to be bump mapped.\n" + " -snapaxial : Snap axial planes to integer coordinates.\n" + " -block # # : Control the grid size mins that vbsp chops the level on.\n" + " -blocks # # # # : Enter the mins and maxs for the grid size vbsp uses.\n" + " -dumpstaticprops: Dump static props to staticprop*.txt\n" + " -dumpcollide : Write files with collision info.\n" + " -forceskyvis : Enable vis calculations in 3d skybox leaves\n" + " -luxelscale # : Scale all lightmaps by this amount (default: 1.0).\n" + " -minluxelscale #: No luxel scale will be lower than this amount (default: 1.0).\n" + " -lightifmissing : Force lightmaps to be generated for all surfaces even if\n" + " they don't need lightmaps.\n" + " -keepstalezip : Keep the BSP's zip files intact but regenerate everything\n" + " else.\n" + " -virtualdispphysics : Use virtual (not precomputed) displacement collision models\n" + " -xbox : Enable mandatory xbox options\n" + " -x360 : Generate Xbox360 version of vsp\n" + " -nox360 : Disable generation Xbox360 version of vsp (default)\n" + " -replacematerials : Substitute materials according to materialsub.txt in content\\maps\n" + " -FullMinidumps : Write large minidumps on crash.\n" + ); + } + + DeleteCmdLine( argc, argv ); + CmdLib_Cleanup(); + CmdLib_Exit( 1 ); + } + + // Sanity check + if ( *g_szEmbedDir && ( onlyents || onlyprops ) ) + { + Warning( "-embed only makes sense alongside full BSP compiles.\n" + "\n" + "Use the bspzip utility to update embedded files.\n" ); + DeleteCmdLine( argc, argv ); + CmdLib_Cleanup(); + CmdLib_Exit( 1 ); + } + + start = Plat_FloatTime(); + + // Run in the background? + if( g_bLowPriority ) + { + SetLowPriority(); + } + + if( ( g_nDXLevel != 0 ) && ( g_nDXLevel < 80 ) ) + { + g_BumpAll = false; + } + + if( g_luxelScale == 1.0f ) + { + if ( g_nDXLevel == 70 ) + { + g_luxelScale = 4.0f; + } + } + + ThreadSetDefault (); + numthreads = 1; // multiple threads aren't helping... + + // Setup the logfile. + char logFile[512]; + _snprintf( logFile, sizeof(logFile), "%s.log", source ); + SetSpewFunctionLogFile( logFile ); + + LoadPhysicsDLL(); + LoadSurfaceProperties(); + +#if 0 + Msg( "qdir: %s This is the the path of the initial source file \n", qdir ); + Msg( "gamedir: %s This is the base engine + mod-specific game dir (e.g. d:/tf2/mytfmod/) \n", gamedir ); + Msg( "basegamedir: %s This is the base engine + base game directory (e.g. e:/hl2/hl2/, or d:/tf2/tf2/ )\n", basegamedir ); +#endif + + sprintf( materialPath, "%smaterials", gamedir ); + InitMaterialSystem( materialPath, CmdLib_GetFileSystemFactory() ); + Msg( "materialPath: %s\n", materialPath ); + + // delete portal and line files + sprintf (path, "%s.prt", source); + remove (path); + sprintf (path, "%s.lin", source); + remove (path); + + strcpy (name, ExpandArg (argv[i])); + + const char *pszExtension = V_GetFileExtension( name ); + if ( !pszExtension ) + { + V_SetExtension( name, ".vmm", sizeof( name ) ); + if ( !FileExists( name ) ) + { + V_SetExtension( name, ".vmf", sizeof( name ) ); + } + } + + // if we're combining materials, load the script file + if ( g_ReplaceMaterials ) + { + LoadMaterialReplacementKeys( gamedir, mapbase ); + } + + // + // if onlyents, just grab the entites and resave + // + if (onlyents) + { + LoadBSPFile (mapFile); + num_entities = 0; + // Clear out the cubemap samples since they will be reparsed even with -onlyents + g_nCubemapSamples = 0; + + // Mark as stale since the lighting could be screwed with new ents. + AddBufferToPak( GetPakFile(), "stale.txt", "stale", strlen( "stale" ) + 1, false ); + + LoadMapFile (name); + SetModelNumbers (); + SetLightStyles (); + + // NOTE: If we ever precompute lighting for static props in + // vrad, EmitStaticProps should be removed here + + // Emit static props found in the .vmf file + EmitStaticProps(); + + // NOTE: Don't deal with detail props here, it blows away lighting + + // Recompute the skybox + ComputeBoundsNoSkybox(); + + // Make sure that we have a water lod control eneity if we have water in the map. + EnsurePresenceOfWaterLODControlEntity(); + + // Make sure the func_occluders have the appropriate data set + FixupOnlyEntsOccluderEntities(); + + // Doing this here because stuff abov may filter out entities + UnparseEntities (); + + WriteBSPFile (mapFile); + } + else if (onlyprops) + { + // In the only props case, deal with static + detail props only + LoadBSPFile (mapFile); + + LoadMapFile(name); + SetModelNumbers(); + SetLightStyles(); + + // Emit static props found in the .vmf file + EmitStaticProps(); + + // Place detail props found in .vmf and based on material properties + LoadEmitDetailObjectDictionary( gamedir ); + EmitDetailObjects(); + + WriteBSPFile (mapFile); + } + else + { + // + // start from scratch + // + + // Load just the file system from the bsp + if( g_bKeepStaleZip && FileExists( mapFile ) ) + { + LoadBSPFile_FileSystemOnly (mapFile); + // Mark as stale since the lighting could be screwed with new ents. + AddBufferToPak( GetPakFile(), "stale.txt", "stale", strlen( "stale" ) + 1, false ); + } + + LoadMapFile (name); + WorldVertexTransitionFixup(); + if( ( g_nDXLevel == 0 ) || ( g_nDXLevel >= 70 ) ) + { + Cubemap_FixupBrushSidesMaterials(); + Cubemap_AttachDefaultCubemapToSpecularSides(); + Cubemap_AddUnreferencedCubemaps(); + } + SetModelNumbers (); + SetLightStyles (); + LoadEmitDetailObjectDictionary( gamedir ); + ProcessModels (); + + // Add embed dir if provided + if ( *g_szEmbedDir ) + { + AddDirToPak( GetPakFile(), g_szEmbedDir ); + WriteBSPFile( mapFile ); + } + } + + end = Plat_FloatTime(); + + char str[512]; + GetHourMinuteSecondsString( (int)( end - start ), str, sizeof( str ) ); + Msg( "%s elapsed\n", str ); + + DeleteCmdLine( argc, argv ); + ReleasePakFileLumps(); + DeleteMaterialReplacementKeys(); + ShutdownMaterialSystem(); + CmdLib_Cleanup(); + return 0; +} + + +/* +============= +main +============ +*/ +int main (int argc, char **argv) +{ + // Install an exception handler. + SetupDefaultToolsMinidumpHandler(); + return RunVBSP( argc, argv ); +} + + diff --git a/utils/vbsp/vbsp.h b/utils/vbsp/vbsp.h new file mode 100644 index 0000000..689bbaa --- /dev/null +++ b/utils/vbsp/vbsp.h @@ -0,0 +1,657 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#if !defined( VBSP_H ) +#define VBSP_H + + +#include "cmdlib.h" +#include "mathlib/vector.h" +#include "scriplib.h" +#include "polylib.h" +#include "threads.h" +#include "bsplib.h" +#include "qfiles.h" +#include "utilmatlib.h" +#include "ChunkFile.h" + +#ifdef WIN32 +#pragma warning( disable: 4706 ) +#endif + +class CUtlBuffer; + +#define MAX_BRUSH_SIDES 128 +#define CLIP_EPSILON 0.1 + +#define TEXINFO_NODE -1 // side is allready on a node + +// this will output glview files for the given brushmodel. Brushmodel 1 is the world, 2 is the first brush entity, etc. +#define DEBUG_BRUSHMODEL 0 + +struct portal_t; +struct node_t; + +struct plane_t : public dplane_t +{ + plane_t *hash_chain; + + plane_t() { normal.Init(); } +}; + + +struct brush_texture_t +{ + Vector UAxis; + Vector VAxis; + vec_t shift[2]; + vec_t rotate; + vec_t textureWorldUnitsPerTexel[2]; + vec_t lightmapWorldUnitsPerLuxel; + char name[TEXTURE_NAME_LENGTH]; + int flags; + + brush_texture_t() : UAxis(0,0,0), VAxis(0,0,0) {} +}; + +struct mapdispinfo_t; + +struct side_t +{ + int planenum; + int texinfo; + mapdispinfo_t *pMapDisp; + + winding_t *winding; + side_t *original; // bspbrush_t sides will reference the mapbrush_t sides + int contents; // from miptex + int surf; // from miptex + qboolean visible; // choose visble planes first + qboolean tested; // this plane allready checked as a split + qboolean bevel; // don't ever use for bsp splitting + + side_t *next; + int origIndex; + int id; // This is the unique id generated by worldcraft for this side. + unsigned int smoothingGroups; + CUtlVector<int> aOverlayIds; // List of overlays that reside on this side. + CUtlVector<int> aWaterOverlayIds; // List of water overlays that reside on this side. + bool m_bDynamicShadowsEnabled; // Goes into dface_t::SetDynamicShadowsEnabled(). +}; + +struct mapbrush_t +{ + int entitynum; + int brushnum; + int id; // The unique ID of this brush in the editor, used for reporting errors. + int contents; + Vector mins, maxs; + int numsides; + side_t *original_sides; +}; + +#define PLANENUM_LEAF -1 + +#define MAXEDGES 32 + +struct face_t +{ + int id; + + face_t *next; // on node + + // the chain of faces off of a node can be merged or split, + // but each face_t along the way will remain in the chain + // until the entire tree is freed + face_t *merged; // if set, this face isn't valid anymore + face_t *split[2]; // if set, this face isn't valid anymore + + portal_t *portal; + int texinfo; + int dispinfo; + // This is only for surfaces that are the boundaries of fog volumes + // (ie. water surfaces) + // All of the rest of the surfaces can look at their leaf to find out + // what fog volume they are in. + node_t *fogVolumeLeaf; + + int planenum; + int contents; // faces in different contents can't merge + int outputnumber; + winding_t *w; + int numpoints; + qboolean badstartvert; // tjunctions cannot be fixed without a midpoint vertex + int vertexnums[MAXEDGES]; + side_t *originalface; // save the "side" this face came from + int firstPrimID; + int numPrims; + unsigned int smoothingGroups; +}; + +void EmitFace( face_t *f, qboolean onNode ); + +struct mapdispinfo_t +{ + face_t face; + int entitynum; + int power; + int minTess; + float smoothingAngle; + Vector uAxis; + Vector vAxis; + Vector startPosition; + float alphaValues[MAX_DISPVERTS]; + float maxDispDist; + float dispDists[MAX_DISPVERTS]; + Vector vectorDisps[MAX_DISPVERTS]; + Vector vectorOffsets[MAX_DISPVERTS]; + int contents; + int brushSideID; + unsigned short triTags[MAX_DISPTRIS]; + int flags; + +#ifdef VSVMFIO + float m_elevation; // "elevation" + Vector m_offsetNormals[ MAX_DISPTRIS ]; // "offset_normals" +#endif // VSVMFIO + +}; + +extern int nummapdispinfo; +extern mapdispinfo_t mapdispinfo[MAX_MAP_DISPINFO]; + +extern float g_defaultLuxelSize; +extern float g_luxelScale; +extern float g_minLuxelScale; +extern bool g_BumpAll; +extern int g_nDXLevel; + +int GetDispInfoEntityNum( mapdispinfo_t *pDisp ); +void ComputeBoundsNoSkybox( ); + +struct bspbrush_t +{ + int id; + bspbrush_t *next; + Vector mins, maxs; + int side, testside; // side of node during construction + mapbrush_t *original; + int numsides; + side_t sides[6]; // variably sized +}; + + +#define MAX_NODE_BRUSHES 8 + +struct leafface_t +{ + face_t *pFace; + leafface_t *pNext; +}; + +struct node_t +{ + int id; + + // both leafs and nodes + int planenum; // -1 = leaf node + node_t *parent; + Vector mins, maxs; // valid after portalization + bspbrush_t *volume; // one for each leaf/node + + // nodes only + side_t *side; // the side that created the node + node_t *children[2]; + face_t *faces; // these are the cutup ones that live in the plane of "side". + + // leafs only + bspbrush_t *brushlist; // fragments of all brushes in this leaf + leafface_t *leaffacelist; + int contents; // OR of all brush contents + int occupied; // 1 or greater can reach entity + entity_t *occupant; // for leak file testing + int cluster; // for portalfile writing + int area; // for areaportals + portal_t *portals; // also on nodes during construction + int diskId; // dnodes or dleafs index after this has been emitted +}; + + +struct portal_t +{ + int id; + plane_t plane; + node_t *onnode; // NULL = outside box + node_t *nodes[2]; // [0] = front side of plane + portal_t *next[2]; + winding_t *winding; + qboolean sidefound; // false if ->side hasn't been checked + side_t *side; // NULL = non-visible + face_t *face[2]; // output face in bsp file +}; + + +struct tree_t +{ + node_t *headnode; + node_t outside_node; + Vector mins, maxs; + bool leaked; +}; + + +extern int entity_num; + +struct LoadSide_t; +struct LoadEntity_t; +class CManifest; + +class CMapFile +{ +public: + CMapFile( void ) { Init(); } + + void Init( void ); + + void AddPlaneToHash (plane_t *p); + int CreateNewFloatPlane (Vector& normal, vec_t dist); + int FindFloatPlane (Vector& normal, vec_t dist); + int PlaneFromPoints(const Vector &p0, const Vector &p1, const Vector &p2); + void AddBrushBevels (mapbrush_t *b); + qboolean MakeBrushWindings (mapbrush_t *ob); + void MoveBrushesToWorld( entity_t *mapent ); + void MoveBrushesToWorldGeneral( entity_t *mapent ); + void RemoveContentsDetailFromEntity( entity_t *mapent ); + int SideIDToIndex( int brushSideID ); + void AddLadderKeys( entity_t *mapent ); + ChunkFileResult_t LoadEntityCallback(CChunkFile *pFile, int nParam); + void ForceFuncAreaPortalWindowContents(); + ChunkFileResult_t LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo); + ChunkFileResult_t LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity); + ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); + void TestExpandBrushes(void); + + static char m_InstancePath[ MAX_PATH ]; + static void SetInstancePath( const char *pszInstancePath ); + static const char *GetInstancePath( void ) { return m_InstancePath; } + static bool DeterminePath( const char *pszBaseFileName, const char *pszInstanceFileName, char *pszOutFileName ); + + void CheckForInstances( const char *pszFileName ); + void MergeInstance( entity_t *pInstanceEntity, CMapFile *Instance ); + void MergePlanes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); + void MergeBrushes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); + void MergeBrushSides( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); + void ReplaceInstancePair( epair_t *pPair, entity_t *pInstanceEntity ); + void MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); + void MergeOverlays( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); + + static int m_InstanceCount; + static int c_areaportals; + + plane_t mapplanes[MAX_MAP_PLANES]; + int nummapplanes; + + #define PLANE_HASHES 1024 + plane_t *planehash[PLANE_HASHES]; + + int nummapbrushes; + mapbrush_t mapbrushes[MAX_MAP_BRUSHES]; + + Vector map_mins, map_maxs; + + int nummapbrushsides; + side_t brushsides[MAX_MAP_BRUSHSIDES]; + + brush_texture_t side_brushtextures[MAX_MAP_BRUSHSIDES]; + + int num_entities; + entity_t entities[MAX_MAP_ENTITIES]; + + int c_boxbevels; + int c_edgebevels; + int c_clipbrushes; + int g_ClipTexinfo; + + class CConnectionPairs + { + public: + CConnectionPairs( epair_t *pair, CConnectionPairs *next ) + { + m_Pair = pair; + m_Next = next; + } + + epair_t *m_Pair; + CConnectionPairs *m_Next; + }; + + CConnectionPairs *m_ConnectionPairs; + + int m_StartMapOverlays; + int m_StartMapWaterOverlays; +}; + +extern CMapFile *g_MainMap; +extern CMapFile *g_LoadingMap; + +extern CUtlVector< CMapFile * > g_Maps; + +extern int g_nMapFileVersion; + +extern qboolean noprune; +extern qboolean nodetail; +extern qboolean fulldetail; +extern qboolean nomerge; +extern qboolean nomergewater; +extern qboolean nosubdiv; +extern qboolean nowater; +extern qboolean noweld; +extern qboolean noshare; +extern qboolean notjunc; +extern qboolean nocsg; +extern qboolean noopt; +extern qboolean dumpcollide; +extern qboolean nodetailcuts; +extern qboolean g_DumpStaticProps; +extern qboolean g_bSkyVis; +extern vec_t microvolume; +extern bool g_snapAxialPlanes; +extern bool g_NodrawTriggers; +extern bool g_DisableWaterLighting; +extern bool g_bAllowDetailCracks; +extern bool g_bNoVirtualMesh; +extern char outbase[32]; + +extern char source[1024]; +extern char mapbase[ 64 ]; +extern CUtlVector<int> g_SkyAreas; + +bool LoadMapFile( const char *pszFileName ); +int GetVertexnum( Vector& v ); +bool Is3DSkyboxArea( int area ); + +//============================================================================= + +// textures.c + +struct textureref_t +{ + char name[TEXTURE_NAME_LENGTH]; + int flags; + float lightmapWorldUnitsPerLuxel; + int contents; +}; + +extern textureref_t textureref[MAX_MAP_TEXTURES]; + +int FindMiptex (const char *name); + +int TexinfoForBrushTexture (plane_t *plane, brush_texture_t *bt, const Vector& origin); +int GetSurfaceProperties2( MaterialSystemMaterial_t matID, const char *pMatName ); + +extern int g_SurfaceProperties[MAX_MAP_TEXDATA]; +void LoadSurfaceProperties( void ); + +int PointLeafnum ( dmodel_t* pModel, const Vector& p ); + +//============================================================================= + +void FindGCD (int *v); + +mapbrush_t *Brush_LoadEntity (entity_t *ent); +int PlaneTypeForNormal (Vector& normal); +qboolean MakeBrushPlanes (mapbrush_t *b); +int FindIntPlane (int *inormal, int *iorigin); +void CreateBrush (int brushnum); + + +//============================================================================= +// detail objects +//============================================================================= + +void LoadEmitDetailObjectDictionary( char const* pGameDir ); +void EmitDetailObjects(); + +//============================================================================= +// static props +//============================================================================= + +void EmitStaticProps(); +bool LoadStudioModel( char const* pFileName, char const* pEntityType, CUtlBuffer& buf ); + +//============================================================================= +//============================================================================= +// procedurally created .vmt files +//============================================================================= + +void EmitStaticProps(); + +// draw.c + +extern Vector draw_mins, draw_maxs; +extern bool g_bLightIfMissing; + +void Draw_ClearWindow (void); +void DrawWinding (winding_t *w); + +void GLS_BeginScene (void); +void GLS_Winding (winding_t *w, int code); +void GLS_EndScene (void); + +//============================================================================= + +// csg + +enum detailscreen_e +{ + FULL_DETAIL = 0, + ONLY_DETAIL = 1, + NO_DETAIL = 2, +}; + +#define TRANSPARENT_CONTENTS (CONTENTS_GRATE|CONTENTS_WINDOW) + +#include "csg.h" + +//============================================================================= + +// brushbsp + +void WriteBrushList (char *name, bspbrush_t *brush, qboolean onlyvis); + +bspbrush_t *CopyBrush (bspbrush_t *brush); + +void SplitBrush (bspbrush_t *brush, int planenum, + bspbrush_t **front, bspbrush_t **back); + +tree_t *AllocTree (void); +node_t *AllocNode (void); +bspbrush_t *AllocBrush (int numsides); +int CountBrushList (bspbrush_t *brushes); +void FreeBrush (bspbrush_t *brushes); +vec_t BrushVolume (bspbrush_t *brush); +node_t *NodeForPoint (node_t *node, Vector& origin); + +void BoundBrush (bspbrush_t *brush); +void FreeBrushList (bspbrush_t *brushes); +node_t *PointInLeaf (node_t *node, Vector& point); + +tree_t *BrushBSP (bspbrush_t *brushlist, Vector& mins, Vector& maxs); + +#define PSIDE_FRONT 1 +#define PSIDE_BACK 2 +#define PSIDE_BOTH (PSIDE_FRONT|PSIDE_BACK) +#define PSIDE_FACING 4 +int BrushBspBoxOnPlaneSide (const Vector& mins, const Vector& maxs, dplane_t *plane); +extern qboolean WindingIsTiny (winding_t *w); + +//============================================================================= + +// portals.c + +int VisibleContents (int contents); + +void MakeHeadnodePortals (tree_t *tree); +void MakeNodePortal (node_t *node); +void SplitNodePortals (node_t *node); + +qboolean Portal_VisFlood (portal_t *p); + +qboolean FloodEntities (tree_t *tree); +void FillOutside (node_t *headnode); +void FloodAreas (tree_t *tree); +void MarkVisibleSides (tree_t *tree, int start, int end, int detailScreen); +void MarkVisibleSides (tree_t *tree, mapbrush_t **ppBrushes, int nCount ); +void FreePortal (portal_t *p); +void EmitAreaPortals (node_t *headnode); + +void MakeTreePortals (tree_t *tree); + +//============================================================================= + +// glfile.c + +void OutputWinding (winding_t *w, FileHandle_t glview); +void OutputWindingColor (winding_t *w, FileHandle_t glview, int r, int g, int b); +void WriteGLView (tree_t *tree, char *source); +void WriteGLViewFaces (tree_t *tree, const char *source); +void WriteGLViewBrushList( bspbrush_t *pList, const char *pName ); +//============================================================================= + +// leakfile.c + +void LeakFile (tree_t *tree); +void AreaportalLeakFile( tree_t *tree, portal_t *pStartPortal, portal_t *pEndPortal, node_t *pStart ); + +//============================================================================= + +// prtfile.c + +void AddVisCluster( entity_t *pFuncVisCluster ); +void WritePortalFile (tree_t *tree); + +//============================================================================= + +// writebsp.c + +void SetModelNumbers (void); +void SetLightStyles (void); + +void BeginBSPFile (void); +void WriteBSP (node_t *headnode, face_t *pLeafFaceList); +void EndBSPFile (void); +void BeginModel (void); +void EndModel (void); + +extern int firstmodeledge; +extern int firstmodelface; + +//============================================================================= + +// faces.c + +void MakeFaces (node_t *headnode); +void MakeDetailFaces (node_t *headnode); +face_t *FixTjuncs( node_t *headnode, face_t *pLeafFaceList ); + +face_t *AllocFace (void); +void FreeFace (face_t *f); +void FreeFaceList( face_t *pFaces ); + +void MergeFaceList(face_t **pFaceList); +void SubdivideFaceList(face_t **pFaceList); + +extern face_t *edgefaces[MAX_MAP_EDGES][2]; + + +//============================================================================= + +// tree.c + +void FreeTree (tree_t *tree); +void FreeTree_r (node_t *node); +void PrintTree_r (node_t *node, int depth); +void FreeTreePortals_r (node_t *node); +void PruneNodes_r (node_t *node); +void PruneNodes (node_t *node); + +// Returns true if the entity is a func_occluder +bool IsFuncOccluder( int entity_num ); + + +//============================================================================= +// ivp.cpp +class CPhysCollide; +void EmitPhysCollision(); +void DumpCollideToGlView( CPhysCollide *pCollide, const char *pFilename ); +void EmitWaterVolumesForBSP( dmodel_t *pModel, node_t *headnode ); + +//============================================================================= +// find + find or create the texdata +int FindTexData( const char *pName ); +int FindOrCreateTexData( const char *pName ); +// Add a clone of an existing texdata with a new name +int AddCloneTexData( dtexdata_t *pExistingTexData, char const *cloneTexDataName ); +int FindOrCreateTexInfo( const texinfo_t &searchTexInfo ); +int FindAliasedTexData( const char *pName, dtexdata_t *sourceTexture ); +int FindTexInfo( const texinfo_t &searchTexInfo ); + +//============================================================================= +// normals.c +void SaveVertexNormals( void ); + +//============================================================================= +// cubemap.cpp +void Cubemap_InsertSample( const Vector& origin, int size ); +void Cubemap_CreateDefaultCubemaps( void ); +void Cubemap_SaveBrushSides( const char *pSideListStr ); +void Cubemap_FixupBrushSidesMaterials( void ); +void Cubemap_AttachDefaultCubemapToSpecularSides( void ); +// Add skipped cubemaps that are referenced by the engine +void Cubemap_AddUnreferencedCubemaps( void ); + +//============================================================================= +// overlay.cpp +#define OVERLAY_MAP_STRLEN 256 + +struct mapoverlay_t +{ + int nId; + unsigned short m_nRenderOrder; + char szMaterialName[OVERLAY_MAP_STRLEN]; + float flU[2]; + float flV[2]; + float flFadeDistMinSq; + float flFadeDistMaxSq; + Vector vecUVPoints[4]; + Vector vecOrigin; + Vector vecBasis[3]; + CUtlVector<int> aSideList; + CUtlVector<int> aFaceList; +}; + +extern CUtlVector<mapoverlay_t> g_aMapOverlays; +extern CUtlVector<mapoverlay_t> g_aMapWaterOverlays; + +int Overlay_GetFromEntity( entity_t *pMapEnt ); +void Overlay_UpdateSideLists( int StartIndex ); +void Overlay_AddFaceToLists( int iFace, side_t *pSide ); +void Overlay_EmitOverlayFaces( void ); +void OverlayTransition_UpdateSideLists( int StartIndex ); +void OverlayTransition_AddFaceToLists( int iFace, side_t *pSide ); +void OverlayTransition_EmitOverlayFaces( void ); +void Overlay_Translate( mapoverlay_t *pOverlay, Vector &OriginOffset, QAngle &AngleOffset, matrix3x4_t &Matrix ); + +//============================================================================= + +void RemoveAreaPortalBrushes_R( node_t *node ); + +dtexdata_t *GetTexData( int index ); + +#endif + diff --git a/utils/vbsp/vbsp.vpc b/utils/vbsp/vbsp.vpc new file mode 100644 index 0000000..2f2a224 --- /dev/null +++ b/utils/vbsp/vbsp.vpc @@ -0,0 +1,186 @@ +//----------------------------------------------------------------------------- +// VBSP.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\common,..\vmpi" + $PreprocessorDefinitions "$BASE;MACRO_MATHLIB;PROTECTED_THINGS_DISABLE" + } + + $Linker + { + $AdditionalDependencies "$BASE ws2_32.lib odbc32.lib odbccp32.lib winmm.lib" + $EnableLargeAddresses "Support Addresses Larger Than 2 Gigabytes (/LARGEADDRESSAWARE)" [$WIN32] + } +} + +$Project "Vbsp" +{ + $Folder "Source Files" + { + $File "boundbox.cpp" + $File "brushbsp.cpp" + $File "$SRCDIR\public\CollisionUtils.cpp" + $File "csg.cpp" + $File "cubemap.cpp" + $File "detail.cpp" + $File "detailObjects.cpp" + $File "$SRCDIR\public\disp_common.cpp" + $File "disp_ivp.cpp" + $File "$SRCDIR\public\disp_powerinfo.cpp" + $File "disp_vbsp.cpp" + $File "faces.cpp" + $File "glfile.cpp" + $File "ivp.cpp" + $File "leakfile.cpp" + $File "$SRCDIR\public\loadcmdline.cpp" + $File "$SRCDIR\public\lumpfiles.cpp" + $File "map.cpp" + $File "manifest.cpp" + $File "materialpatch.cpp" + $File "materialsub.cpp" + $File "..\common\mstristrip.cpp" + $File "nodraw.cpp" + $File "normals.cpp" + $File "overlay.cpp" + $File "..\common\physdll.cpp" + $File "portals.cpp" + $File "prtfile.cpp" + $File "$SRCDIR\public\ScratchPad3D.cpp" + $File "..\common\scratchpad_helpers.cpp" + $File "StaticProp.cpp" + $File "textures.cpp" + $File "tree.cpp" + $File "..\common\utilmatlib.cpp" + $File "vbsp.cpp" + $File "worldvertextransitionfixup.cpp" + $File "writebsp.cpp" + $File "$SRCDIR\public\zip_utils.cpp" + + $Folder "Common Files" + { + $File "..\common\bsplib.cpp" + $File "$SRCDIR\public\builddisp.cpp" + $File "$SRCDIR\public\ChunkFile.cpp" + $File "..\common\cmdlib.cpp" + $File "$SRCDIR\public\filesystem_helpers.cpp" + $File "$SRCDIR\public\filesystem_init.cpp" + $File "..\common\filesystem_tools.cpp" + $File "..\common\map_shared.cpp" + $File "..\common\pacifier.cpp" + $File "..\common\polylib.cpp" + $File "..\common\scriplib.cpp" + $File "..\common\threads.cpp" + $File "..\common\tools_minidump.cpp" + $File "..\common\tools_minidump.h" + } + } + + $Folder "Header Files" + { + $File "boundbox.h" + $File "csg.h" + $File "detail.h" + $File "$SRCDIR\public\disp_powerinfo.h" + $File "disp_vbsp.h" + $File "$SRCDIR\public\disp_vertindex.h" + $File "faces.h" + $File "map.h" + $File "manifest.h" + $File "materialpatch.h" + $File "materialsub.h" + $File "..\common\scratchpad_helpers.h" + $File "vbsp.h" + $File "worldvertextransitionfixup.h" + $File "writebsp.h" + + $Folder "Common header files" + { + $File "..\common\bsplib.h" + $File "$SRCDIR\public\builddisp.h" + $File "$SRCDIR\public\ChunkFile.h" + $File "..\common\cmdlib.h" + $File "disp_ivp.h" + $File "$SRCDIR\public\filesystem.h" + $File "$SRCDIR\public\filesystem_helpers.h" + $File "..\common\FileSystem_Tools.h" + $File "$SRCDIR\public\GameBSPFile.h" + $File "$SRCDIR\public\tier1\interface.h" + $File "ivp.h" + $File "..\common\map_shared.h" + $File "..\common\pacifier.h" + $File "..\common\polylib.h" + $File "$SRCDIR\public\tier1\tokenreader.h" + $File "..\common\utilmatlib.h" + $File "..\vmpi\vmpi.h" + $File "$SRCDIR\public\zip_uncompressed.h" + } + } + + $Folder "Public Headers" + { + $File "$SRCDIR\public\mathlib\amd3dx.h" + $File "$SRCDIR\public\arraystack.h" + $File "$SRCDIR\public\tier0\basetypes.h" + $File "$SRCDIR\public\BSPFILE.H" + $File "$SRCDIR\public\bspflags.h" + $File "$SRCDIR\public\BSPTreeData.h" + $File "$SRCDIR\public\mathlib\bumpvects.h" + $File "$SRCDIR\public\tier1\byteswap.h" + $File "$SRCDIR\public\cmodel.h" + $File "$SRCDIR\public\CollisionUtils.h" + $File "$SRCDIR\public\tier0\commonmacros.h" + $File "$SRCDIR\public\tier0\dbg.h" + $File "$SRCDIR\public\disp_common.h" + $File "$SRCDIR\public\IScratchPad3D.h" + $File "$SRCDIR\public\mathlib\mathlib.h" + $File "..\common\mstristrip.h" + $File "$SRCDIR\public\nmatrix.h" + $File "$SRCDIR\public\NTree.h" + $File "$SRCDIR\public\nvector.h" + $File "$SRCDIR\public\phyfile.h" + $File "..\common\physdll.h" + $File "..\common\qfiles.h" + $File "$SRCDIR\public\ScratchPad3D.h" + $File "..\common\scriplib.h" + $File "$SRCDIR\public\studio.h" + $File "..\common\threads.h" + $File "$SRCDIR\public\tier1\utlbuffer.h" + $File "$SRCDIR\public\tier1\utllinkedlist.h" + $File "$SRCDIR\public\tier1\utlmemory.h" + $File "$SRCDIR\public\tier1\utlrbtree.h" + $File "$SRCDIR\public\tier1\utlsymbol.h" + $File "$SRCDIR\public\tier1\utlvector.h" + $File "$SRCDIR\public\vcollide.h" + $File "$SRCDIR\public\mathlib\vector.h" + $File "$SRCDIR\public\mathlib\vector2d.h" + $File "$SRCDIR\public\mathlib\vector4d.h" + $File "$SRCDIR\public\mathlib\vmatrix.h" + $File "$SRCDIR\public\vphysics_interface.h" + $File "$SRCDIR\public\mathlib\vplane.h" + $File "$SRCDIR\public\wadtypes.h" + $File "$SRCDIR\public\worldsize.h" + } + + $Folder "Link Libraries" + { + $Lib bitmap + $Lib fgdlib + $Lib mathlib + $Lib tier2 + $Lib vtf + $Lib "$LIBCOMMON/lzma" + } + + $File "notes.txt" +} diff --git a/utils/vbsp/worldvertextransitionfixup.cpp b/utils/vbsp/worldvertextransitionfixup.cpp new file mode 100644 index 0000000..f97f393 --- /dev/null +++ b/utils/vbsp/worldvertextransitionfixup.cpp @@ -0,0 +1,212 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "bsplib.h" +#include "vbsp.h" +#include "tier1/UtlBuffer.h" +#include "tier1/utlvector.h" +#include "KeyValues.h" +#include "materialpatch.h" + +struct entitySideList_t +{ + int firstBrushSide; + int brushSideCount; +}; + +static bool SideIsNotDispAndHasDispMaterial( int iSide ) +{ + side_t *pSide = &g_MainMap->brushsides[iSide]; + + // If it's a displacement, then it's fine to have a displacement-only material. + if ( pSide->pMapDisp ) + { + return false; + } + + pSide->texinfo; + + return true; +} + +static void BackSlashToForwardSlash( char *pname ) +{ + while ( *pname ) { + if ( *pname == '\\' ) + *pname = '/'; + pname++; + } +} + +//----------------------------------------------------------------------------- +// Generate patched material name +//----------------------------------------------------------------------------- +static void GeneratePatchedMaterialName( const char *pMaterialName, char *pBuffer, int nMaxLen ) +{ + int nLen = Q_snprintf( pBuffer, nMaxLen, "maps/%s/%s_wvt_patch", mapbase, pMaterialName ); + + Assert( nLen < TEXTURE_NAME_LENGTH - 1 ); + if ( nLen >= TEXTURE_NAME_LENGTH - 1 ) + { + Error( "Generated worldvertextransition patch name : %s too long! (max = %d)\n", pBuffer, TEXTURE_NAME_LENGTH ); + } + + BackSlashToForwardSlash( pBuffer ); + Q_strlower( pBuffer ); +} + +static void RemoveKey( KeyValues *kv, const char *pSubKeyName ) +{ + KeyValues *pSubKey = kv->FindKey( pSubKeyName ); + if( pSubKey ) + { + kv->RemoveSubKey( pSubKey ); + pSubKey->deleteThis(); + } +} + +void CreateWorldVertexTransitionPatchedMaterial( const char *pOriginalMaterialName, const char *pPatchedMaterialName ) +{ + KeyValues *kv = LoadMaterialKeyValues( pOriginalMaterialName, 0 ); + if( kv ) + { + // change shader to Lightmappedgeneric (from worldvertextransition*) + kv->SetName( "LightmappedGeneric" ); + // don't need no stinking $basetexture2 or any other second texture vars + RemoveKey( kv, "$basetexture2" ); + RemoveKey( kv, "$bumpmap2" ); + RemoveKey( kv, "$bumpframe2" ); + RemoveKey( kv, "$basetexture2noenvmap" ); + RemoveKey( kv, "$blendmodulatetexture" ); + RemoveKey( kv, "$maskedblending" ); + RemoveKey( kv, "$surfaceprop2" ); + // If we didn't want a basetexture on the first texture in the blend, we don't want an envmap at all. + KeyValues *basetexturenoenvmap = kv->FindKey( "$BASETEXTURENOENVMAP" ); + if( basetexturenoenvmap->GetInt() ) + { + RemoveKey( kv, "$envmap" ); + } + + Warning( "Patching WVT material: %s\n", pPatchedMaterialName ); + WriteMaterialKeyValuesToPak( pPatchedMaterialName, kv ); + } +} + +int CreateBrushVersionOfWorldVertexTransitionMaterial( int originalTexInfo ) +{ + // Don't make cubemap tex infos for nodes + if ( originalTexInfo == TEXINFO_NODE ) + return originalTexInfo; + + texinfo_t *pTexInfo = &texinfo[originalTexInfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + const char *pOriginalMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + + // Get out of here if the originalTexInfo is already a patched wvt material + if ( Q_stristr( pOriginalMaterialName, "_wvt_patch" ) ) + return originalTexInfo; + + char patchedMaterialName[1024]; + GeneratePatchedMaterialName( pOriginalMaterialName, patchedMaterialName, 1024 ); +// Warning( "GeneratePatchedMaterialName: %s %s\n", pMaterialName, patchedMaterialName ); + + // Make sure the texdata doesn't already exist. + int nTexDataID = FindTexData( patchedMaterialName ); + bool bHasTexData = (nTexDataID != -1); + if( !bHasTexData ) + { + // Create the new vmt material file + CreateWorldVertexTransitionPatchedMaterial( pOriginalMaterialName, patchedMaterialName ); + + // Make a new texdata + nTexDataID = AddCloneTexData( pTexData, patchedMaterialName ); + } + + Assert( nTexDataID != -1 ); + + texinfo_t newTexInfo; + newTexInfo = *pTexInfo; + newTexInfo.texdata = nTexDataID; + + int nTexInfoID = -1; + + // See if we need to make a new texinfo + bool bHasTexInfo = false; + if( bHasTexData ) + { + nTexInfoID = FindTexInfo( newTexInfo ); + bHasTexInfo = (nTexInfoID != -1); + } + + // Make a new texinfo if we need to. + if( !bHasTexInfo ) + { + nTexInfoID = texinfo.AddToTail( newTexInfo ); + } + + Assert( nTexInfoID != -1 ); + return nTexInfoID; +} + +const char *GetShaderNameForTexInfo( int iTexInfo ) +{ + texinfo_t *pTexInfo = &texinfo[iTexInfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + MaterialSystemMaterial_t hMaterial = FindMaterial( pMaterialName, NULL, false ); + const char *pShaderName = GetMaterialShaderName( hMaterial ); + return pShaderName; +} + +void WorldVertexTransitionFixup( void ) +{ + CUtlVector<entitySideList_t> sideList; + sideList.SetCount( g_MainMap->num_entities ); + int i; + for ( i = 0; i < g_MainMap->num_entities; i++ ) + { + sideList[i].firstBrushSide = 0; + sideList[i].brushSideCount = 0; + } + + for ( i = 0; i < g_MainMap->nummapbrushes; i++ ) + { + sideList[g_MainMap->mapbrushes[i].entitynum].brushSideCount += g_MainMap->mapbrushes[i].numsides; + } + int curSide = 0; + for ( i = 0; i < g_MainMap->num_entities; i++ ) + { + sideList[i].firstBrushSide = curSide; + curSide += sideList[i].brushSideCount; + } + + int currentEntity = 0; + for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide ) + { + side_t *pSide = &g_MainMap->brushsides[iSide]; + + // skip displacments + if ( pSide->pMapDisp ) + continue; + + if( pSide->texinfo < 0 ) + continue; + + const char *pShaderName = GetShaderNameForTexInfo( pSide->texinfo ); + if ( !pShaderName || !Q_stristr( pShaderName, "worldvertextransition" ) ) + { + continue; + } + + while ( currentEntity < g_MainMap->num_entities-1 && + iSide > sideList[currentEntity].firstBrushSide + sideList[currentEntity].brushSideCount ) + { + currentEntity++; + } + + pSide->texinfo = CreateBrushVersionOfWorldVertexTransitionMaterial( pSide->texinfo ); + } +}
\ No newline at end of file diff --git a/utils/vbsp/worldvertextransitionfixup.h b/utils/vbsp/worldvertextransitionfixup.h new file mode 100644 index 0000000..9a1f0d3 --- /dev/null +++ b/utils/vbsp/worldvertextransitionfixup.h @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef WORLDVERTEXTRANSITIONFIXUP_H +#define WORLDVERTEXTRANSITIONFIXUP_H +#ifdef _WIN32 +#pragma once +#endif + +void WorldVertexTransitionFixup( void ); + +#endif // WORLDVERTEXTRANSITIONFIXUP_H diff --git a/utils/vbsp/writebsp.cpp b/utils/vbsp/writebsp.cpp new file mode 100644 index 0000000..005990d --- /dev/null +++ b/utils/vbsp/writebsp.cpp @@ -0,0 +1,1553 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" +#include "disp_vbsp.h" +#include "utlvector.h" +#include "faces.h" +#include "builddisp.h" +#include "tier1/strtools.h" +#include "utilmatlib.h" +#include "utldict.h" +#include "map.h" + +int c_nofaces; +int c_facenodes; + +// NOTE: This is a global used to link faces back to the tree node/portals they came from +// it's used when filling water volumes +node_t *dfacenodes[MAX_MAP_FACES]; + + +/* +========================================================= + +ONLY SAVE OUT PLANES THAT ARE ACTUALLY USED AS NODES + +========================================================= +*/ + +void EmitFaceVertexes (face_t **list, face_t *f); +void AssignOccluderAreas(); + +/* +============ +EmitPlanes + +There is no oportunity to discard planes, because all of the original +brushes will be saved in the map. +============ +*/ +void EmitPlanes (void) +{ + int i; + dplane_t *dp; + plane_t *mp; + int planetranslate[MAX_MAP_PLANES]; + + mp = g_MainMap->mapplanes; + for (i=0 ; i<g_MainMap->nummapplanes ; i++, mp++) + { + dp = &dplanes[numplanes]; + planetranslate[i] = numplanes; + VectorCopy ( mp->normal, dp->normal); + dp->dist = mp->dist; + dp->type = mp->type; + numplanes++; + } +} + + +//======================================================== + +void EmitMarkFace (dleaf_t *leaf_p, face_t *f) +{ + int i; + int facenum; + + while (f->merged) + f = f->merged; + + if (f->split[0]) + { + EmitMarkFace (leaf_p, f->split[0]); + EmitMarkFace (leaf_p, f->split[1]); + return; + } + + facenum = f->outputnumber; + if (facenum == -1) + return; // degenerate face + + if (facenum < 0 || facenum >= numfaces) + Error ("Bad leafface"); + for (i=leaf_p->firstleafface ; i<numleaffaces ; i++) + if (dleaffaces[i] == facenum) + break; // merged out face + if (i == numleaffaces) + { + if (numleaffaces >= MAX_MAP_LEAFFACES) + Error ("Too many detail brush faces, max = %d\n", MAX_MAP_LEAFFACES); + + dleaffaces[numleaffaces] = facenum; + numleaffaces++; + } + +} + + +/* +================== +EmitLeaf +================== +*/ +void EmitLeaf (node_t *node) +{ + dleaf_t *leaf_p; + portal_t *p; + int s; + face_t *f; + bspbrush_t *b; + int i; + int brushnum; + leafface_t *pList; + + // emit a leaf + if (numleafs >= MAX_MAP_LEAFS) + Error ("Too many BSP leaves, max = %d", MAX_MAP_LEAFS); + + node->diskId = numleafs; + leaf_p = &dleafs[numleafs]; + numleafs++; + + if( nummodels == 0 ) + { + leaf_p->cluster = node->cluster; + } + else + { + // Submodels don't have clusters. If this isn't set to -1 here, then there + // will be multiple leaves (albeit from different models) that reference + // the same cluster and parts of the code like ivp.cpp's ConvertWaterModelToPhysCollide + // won't work. + leaf_p->cluster = -1; + } + + leaf_p->contents = node->contents; + leaf_p->area = node->area; + + // By default, assume the leaf can see the skybox. + // VRAD will do the actual computation to see if it really can see the skybox + leaf_p->flags = LEAF_FLAGS_SKY; + + // + // write bounding box info + // + VECTOR_COPY (node->mins, leaf_p->mins); + VECTOR_COPY (node->maxs, leaf_p->maxs); + + // + // write the leafbrushes + // + leaf_p->firstleafbrush = numleafbrushes; + for (b=node->brushlist ; b ; b=b->next) + { + if (numleafbrushes >= MAX_MAP_LEAFBRUSHES) + Error ("Too many brushes in one leaf, max = %d", MAX_MAP_LEAFBRUSHES); + + brushnum = b->original - g_MainMap->mapbrushes; + for (i=leaf_p->firstleafbrush ; i<numleafbrushes ; i++) + { + if (dleafbrushes[i] == brushnum) + break; + } + + if (i == numleafbrushes) + { + dleafbrushes[numleafbrushes] = brushnum; + numleafbrushes++; + } + } + leaf_p->numleafbrushes = numleafbrushes - leaf_p->firstleafbrush; + + // + // write the leaffaces + // + if (leaf_p->contents & CONTENTS_SOLID) + return; // no leaffaces in solids + + leaf_p->firstleafface = numleaffaces; + + for (p = node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + f = p->face[s]; + if (!f) + continue; // not a visible portal + + EmitMarkFace (leaf_p, f); + } + + // emit the detail faces + for ( pList = node->leaffacelist; pList; pList = pList->pNext ) + { + EmitMarkFace( leaf_p, pList->pFace ); + } + + + leaf_p->numleaffaces = numleaffaces - leaf_p->firstleafface; +} + +// per face plane - original face "side" list +side_t *pOrigFaceSideList[MAX_MAP_PLANES]; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CreateOrigFace( face_t *f ) +{ + int i, j; + dface_t *of; + side_t *side; + int vIndices[128]; + int eIndex[2]; + winding_t *pWinding; + + // not a real face! + if( !f->w ) + return -1; + + // get the original face -- the "side" + side = f->originalface; + + // get the original face winding + if( !side->winding ) + { + return -1; + } + + // + // get the next original face + // + if( numorigfaces >= MAX_MAP_FACES ) + Error( "Too many faces in map, max = %d", MAX_MAP_FACES ); + of = &dorigfaces[numorigfaces]; + numorigfaces++; + + // set original face to -1 -- it is an origianl face! + of->origFace = -1; + + // + // add side to plane list + // + side->next = pOrigFaceSideList[f->planenum]; + pOrigFaceSideList[f->planenum] = side; + side->origIndex = numorigfaces - 1; + + pWinding = CopyWinding( side->winding ); + + // + // plane info + // + of->planenum = side->planenum; + if ( side->contents & CONTENTS_DETAIL ) + of->onNode = 0; + else + of->onNode = 1; + of->side = side->planenum & 1; + + // + // edge info + // + of->firstedge = numsurfedges; + of->numedges = side->winding->numpoints; + + // + // material info + // + of->texinfo = side->texinfo; + of->dispinfo = f->dispinfo; + + // + // save the vertices + // + for( i = 0; i < pWinding->numpoints; i++ ) + { + // + // compare vertices + // + vIndices[i] = GetVertexnum( pWinding->p[i] ); + } + + // + // save off points -- as edges + // + for( i = 0; i < pWinding->numpoints; i++ ) + { + // + // look for matching edges first + // + eIndex[0] = vIndices[i]; + eIndex[1] = vIndices[(i+1)%pWinding->numpoints]; + + for( j = firstmodeledge; j < numedges; j++ ) + { + if( ( eIndex[0] == dedges[j].v[1] ) && + ( eIndex[1] == dedges[j].v[0] ) && + ( edgefaces[j][0]->contents == f->contents ) ) + { + // check for multiple backward edges!! -- shouldn't have + if( edgefaces[j][1] ) + continue; + + // set back edge + edgefaces[j][1] = f; + + // + // get next surface edge + // + if( numsurfedges >= MAX_MAP_SURFEDGES ) + Error( "Too much brush geometry in bsp, numsurfedges == MAX_MAP_SURFEDGES" ); + dsurfedges[numsurfedges] = -j; + numsurfedges++; + break; + } + } + + if( j == numedges ) + { + // + // get next edge + // + AddEdge( eIndex[0], eIndex[1], f ); + + // + // get next surface edge + // + if( numsurfedges >= MAX_MAP_SURFEDGES ) + Error( "Too much brush geometry in bsp, numsurfedges == MAX_MAP_SURFEDGES" ); + dsurfedges[numsurfedges] = ( numedges - 1 ); + numsurfedges++; + } + } + + // return the index + return ( numorigfaces - 1 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: search for a face within the origface list and return the index if +// found +// Input: f - the face to compare +// Output: the index of the face it found, -1 if not found +//----------------------------------------------------------------------------- +int FindOrigFace( face_t *f ) +{ + int i; + static int bClear = 0; + side_t *pSide; + + // + // initially clear the face side lists (per face plane) + // + if( !bClear ) + { + for( i = 0; i < MAX_MAP_PLANES; i++ ) + { + pOrigFaceSideList[i] = NULL; + } + bClear = 1; + } + + // + // compare the sides + // + for( pSide = pOrigFaceSideList[f->planenum]; pSide; pSide = pSide->next ) + { + if( pSide == f->originalface ) + return pSide->origIndex; + } + + // original face not found in list + return -1; +} + + +//----------------------------------------------------------------------------- +// Purpose: to find an the original face within the list of original faces, if +// a match is not found then create a new origFace -- either way pass +// back the index of the origface in the list +// Input: f - face containing the original face information +// Output: the index of the origface in the origface list +//----------------------------------------------------------------------------- +int FindOrCreateOrigFace( face_t *f ) +{ + int index; + + // check for an original face + if( !f->originalface ) + return -1; + + // + // find or create a orig face and return the index + // + index = FindOrigFace( f ); + + if( index == -1 ) + return CreateOrigFace( f ); + else if( index == -2 ) + return -1; + + return index; +} + +/* +================== +EmitFace +================== +*/ +void EmitFace( face_t *f, qboolean onNode ) +{ + dface_t *df; + int i; + int e; + +// void SubdivideFaceBySubdivSize( face_t *f ); // garymcthack +// SubdivideFaceBySubdivSize( f ); + + // set initial output number + f->outputnumber = -1; + + // degenerated + if( f->numpoints < 3 ) + return; + + // not a final face + if( f->merged || f->split[0] || f->split[1] ) + return; + + // don't emit NODRAW faces for runtime + if ( texinfo[f->texinfo].flags & SURF_NODRAW ) + { + // keep NODRAW terrain surfaces though + if ( f->dispinfo == -1 ) + return; + Warning("NODRAW on terrain surface!\n"); + } + + // save output number so leaffaces can use + f->outputnumber = numfaces; + + // + // get the next available .bsp face slot + // + if (numfaces >= MAX_MAP_FACES) + Error( "Too many faces in map, max = %d", MAX_MAP_FACES ); + df = &dfaces[numfaces]; + + // Save the correlation between dfaces and faces -- since dfaces doesnt have worldcraft face id + dfaceids.AddToTail(); + dfaceids[numfaces].hammerfaceid = f->originalface->id; + + numfaces++; + + // + // plane info - planenum is used by qlight, but not quake + // + df->planenum = f->planenum; + df->onNode = onNode; + df->side = f->planenum & 1; + + // + // material info + // + df->texinfo = f->texinfo; + df->dispinfo = f->dispinfo; + df->smoothingGroups = f->smoothingGroups; + + // save the original "side"/face data + df->origFace = FindOrCreateOrigFace( f ); + df->surfaceFogVolumeID = -1; + dfacenodes[numfaces-1] = f->fogVolumeLeaf; + if ( f->fogVolumeLeaf ) + { + Assert( f->fogVolumeLeaf->planenum == PLANENUM_LEAF ); + } + + // + // edge info + // + df->firstedge = numsurfedges; + df->numedges = f->numpoints; + + // UNDONE: Nodraw faces have no winding - revisit to see if this is necessary + if ( f->w ) + { + df->area = WindingArea( f->w ); + } + else + { + df->area = 0; + } + + df->firstPrimID = f->firstPrimID; + df->SetNumPrims( f->numPrims ); + df->SetDynamicShadowsEnabled( f->originalface->m_bDynamicShadowsEnabled ); + + // + // save off points -- as edges + // + for( i = 0; i < f->numpoints; i++ ) + { + //e = GetEdge (f->pts[i], f->pts[(i+1)%f->numpoints], f); + e = GetEdge2 (f->vertexnums[i], f->vertexnums[(i+1)%f->numpoints], f); + + if (numsurfedges >= MAX_MAP_SURFEDGES) + Error( "Too much brush geometry in bsp, numsurfedges == MAX_MAP_SURFEDGES" ); + dsurfedges[numsurfedges] = e; + numsurfedges++; + } + + // Create overlay face lists. + side_t *pSide = f->originalface; + if ( pSide ) + { + int nOverlayCount = pSide->aOverlayIds.Count(); + if ( nOverlayCount > 0 ) + { + Overlay_AddFaceToLists( ( numfaces - 1 ), pSide ); + } + + nOverlayCount = pSide->aWaterOverlayIds.Count(); + if ( nOverlayCount > 0 ) + { + OverlayTransition_AddFaceToLists( ( numfaces - 1 ), pSide ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Emit all of the faces stored at the leaves (faces from detail brushes) +//----------------------------------------------------------------------------- +void EmitLeafFaces( face_t *pLeafFaceList ) +{ + face_t *f = pLeafFaceList; + while ( f ) + { + EmitFace( f, false ); + f = f->next; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Free the list of faces stored at the leaves +//----------------------------------------------------------------------------- +void FreeLeafFaces( face_t *pLeafFaceList ) +{ + int count = 0; + face_t *f, *next; + + f = pLeafFaceList; + + while ( f ) + { + next = f->next; + FreeFace( f ); + f = next; + count++; + } +} + +/* +============ +EmitDrawingNode_r +============ +*/ +int EmitDrawNode_r (node_t *node) +{ + dnode_t *n; + face_t *f; + int i; + + if (node->planenum == PLANENUM_LEAF) + { + EmitLeaf (node); + return -numleafs; + } + + // emit a node + if (numnodes == MAX_MAP_NODES) + Error ("MAX_MAP_NODES"); + node->diskId = numnodes; + + n = &dnodes[numnodes]; + numnodes++; + + VECTOR_COPY (node->mins, n->mins); + VECTOR_COPY (node->maxs, n->maxs); + + if (node->planenum & 1) + Error ("WriteDrawNodes_r: odd planenum"); + n->planenum = node->planenum; + n->firstface = numfaces; + n->area = node->area; + + if (!node->faces) + c_nofaces++; + else + c_facenodes++; + + for (f=node->faces ; f ; f=f->next) + EmitFace (f, true); + + n->numfaces = numfaces - n->firstface; + + + // + // recursively output the other nodes + // + for (i=0 ; i<2 ; i++) + { + if (node->children[i]->planenum == PLANENUM_LEAF) + { + n->children[i] = -(numleafs + 1); + EmitLeaf (node->children[i]); + } + else + { + n->children[i] = numnodes; + EmitDrawNode_r (node->children[i]); + } + } + + return n - dnodes; +} + + +//========================================================= + +// This will generate a scratchpad file with the level's geometry in it and the noshadow faces drawn red. +// #define SCRATCHPAD_NO_SHADOW_FACES +#if defined( SCRATCHPAD_NO_SHADOW_FACES ) + #include "scratchpad_helpers.h" + IScratchPad3D *g_pPad; +#endif + + +void MarkNoShadowFaces() +{ +#if defined( SCRATCHPAD_NO_SHADOW_FACES ) + g_pPad = ScratchPad3D_Create(); + ScratchPad_DrawWorld( g_pPad, false, CSPColor(1,1,1,0.3) ); + + for ( int iFace=0; iFace < numfaces; iFace++ ) + { + dface_t *pFace = &dfaces[iFace]; + + if ( !pFace->AreDynamicShadowsEnabled() ) + { + ScratchPad_DrawFace( g_pPad, pFace, iFace, CSPColor(1,0,0,1), Vector(1,0,0) ); + ScratchPad_DrawFace( g_pPad, pFace, iFace, CSPColor(1,0,0,1), Vector(-1,0,0) ); + ScratchPad_DrawFace( g_pPad, pFace, iFace, CSPColor(1,0,0,1), Vector(0,1,0) ); + } + } + g_pPad->Release(); +#endif +} + +struct texinfomap_t +{ + int refCount; + int outputIndex; +}; +struct texdatamap_t +{ + int refCount; + int outputIndex; +}; + +// Find the best used texinfo to remap this brush side +int FindMatchingBrushSideTexinfo( int sideIndex, const texinfomap_t *pMap ) +{ + dbrushside_t &side = dbrushsides[sideIndex]; + // find one with the same flags & surfaceprops (even if the texture name is different) + int sideTexFlags = texinfo[side.texinfo].flags; + int sideTexData = texinfo[side.texinfo].texdata; + int sideSurfaceProp = g_SurfaceProperties[sideTexData]; + for ( int j = 0; j < texinfo.Count(); j++ ) + { + if ( pMap[j].refCount > 0 && + texinfo[j].flags == sideTexFlags && + g_SurfaceProperties[texinfo[j].texdata] == sideSurfaceProp ) + { + // found one + return j; + } + } + + // can't find a better match + return side.texinfo; +} + +// Remove all unused texinfos and rebuild array +void ComapctTexinfoArray( texinfomap_t *pMap ) +{ + CUtlVector<texinfo_t> old; + old.CopyArray( texinfo.Base(), texinfo.Count() ); + texinfo.RemoveAll(); + int firstSky = -1; + int first2DSky = -1; + for ( int i = 0; i < old.Count(); i++ ) + { + if ( !pMap[i].refCount ) + { + pMap[i].outputIndex = -1; + continue; + } + // only add one sky texinfo + one 2D sky texinfo + if ( old[i].flags & SURF_SKY2D ) + { + if ( first2DSky < 0 ) + { + first2DSky = texinfo.AddToTail( old[i] ); + } + pMap[i].outputIndex = first2DSky; + continue; + } + if ( old[i].flags & SURF_SKY ) + { + if ( firstSky < 0 ) + { + firstSky = texinfo.AddToTail( old[i] ); + } + pMap[i].outputIndex = firstSky; + continue; + } + pMap[i].outputIndex = texinfo.AddToTail( old[i] ); + } +} + +void CompactTexdataArray( texdatamap_t *pMap ) +{ + CUtlVector<char> oldStringData; + oldStringData.CopyArray( g_TexDataStringData.Base(), g_TexDataStringData.Count() ); + g_TexDataStringData.RemoveAll(); + CUtlVector<int> oldStringTable; + oldStringTable.CopyArray( g_TexDataStringTable.Base(), g_TexDataStringTable.Count() ); + g_TexDataStringTable.RemoveAll(); + CUtlVector<dtexdata_t> oldTexData; + oldTexData.CopyArray( dtexdata, numtexdata ); + // clear current table and rebuild + numtexdata = 0; + for ( int i = 0; i < oldTexData.Count(); i++ ) + { + // unreferenced, note in map and skip + if ( !pMap[i].refCount ) + { + pMap[i].outputIndex = -1; + continue; + } + pMap[i].outputIndex = numtexdata; + + // get old string and re-add to table + const char *pString = &oldStringData[oldStringTable[oldTexData[i].nameStringTableID]]; + int nameIndex = TexDataStringTable_AddOrFindString( pString ); + // copy old texdata and fixup with new name in compacted table + dtexdata[numtexdata] = oldTexData[i]; + dtexdata[numtexdata].nameStringTableID = nameIndex; + numtexdata++; + } +} + +void CompactTexinfos() +{ + Msg("Compacting texture/material tables...\n"); + texinfomap_t *texinfoMap = new texinfomap_t[texinfo.Count()]; + texdatamap_t *texdataMap = new texdatamap_t[numtexdata]; + memset( texinfoMap, 0, sizeof(texinfoMap[0])*texinfo.Count() ); + memset( texdataMap, 0, sizeof(texdataMap[0])*numtexdata ); + int i; + // get texinfos referenced by faces + for ( i = 0; i < numfaces; i++ ) + { + texinfoMap[dfaces[i].texinfo].refCount++; + } + // get texinfos referenced by brush sides + for ( i = 0; i < numbrushsides; i++ ) + { + // not referenced by any visible geometry + Assert( dbrushsides[i].texinfo >= 0 ); + if ( !texinfoMap[dbrushsides[i].texinfo].refCount ) + { + dbrushsides[i].texinfo = FindMatchingBrushSideTexinfo( i, texinfoMap ); + // didn't find anything suitable, go ahead and reference it + if ( !texinfoMap[dbrushsides[i].texinfo].refCount ) + { + texinfoMap[dbrushsides[i].texinfo].refCount++; + } + } + } + // get texinfos referenced by overlays + for ( i = 0; i < g_nOverlayCount; i++ ) + { + texinfoMap[g_Overlays[i].nTexInfo].refCount++; + } + for ( i = 0; i < numleafwaterdata; i++ ) + { + if ( dleafwaterdata[i].surfaceTexInfoID >= 0 ) + { + texinfoMap[dleafwaterdata[i].surfaceTexInfoID].refCount++; + } + } + for ( i = 0; i < *pNumworldlights; i++ ) + { + if ( dworldlights[i].texinfo >= 0 ) + { + texinfoMap[dworldlights[i].texinfo].refCount++; + } + } + for ( i = 0; i < g_nWaterOverlayCount; i++ ) + { + if ( g_WaterOverlays[i].nTexInfo >= 0 ) + { + texinfoMap[g_WaterOverlays[i].nTexInfo].refCount++; + } + } + // reference all used texdatas + for ( i = 0; i < texinfo.Count(); i++ ) + { + if ( texinfoMap[i].refCount > 0 ) + { + texdataMap[texinfo[i].texdata].refCount++; + } + } + + int oldCount = texinfo.Count(); + int oldTexdataCount = numtexdata; + int oldTexdataString = g_TexDataStringData.Count(); + ComapctTexinfoArray( texinfoMap ); + CompactTexdataArray( texdataMap ); + for ( i = 0; i < texinfo.Count(); i++ ) + { + int mapIndex = texdataMap[texinfo[i].texdata].outputIndex; + Assert( mapIndex >= 0 ); + texinfo[i].texdata = mapIndex; + //const char *pName = TexDataStringTable_GetString( dtexdata[texinfo[i].texdata].nameStringTableID ); + } + // remap texinfos on faces + for ( i = 0; i < numfaces; i++ ) + { + Assert( texinfoMap[dfaces[i].texinfo].outputIndex >= 0 ); + dfaces[i].texinfo = texinfoMap[dfaces[i].texinfo].outputIndex; + } + // remap texinfos on brushsides + for ( i = 0; i < numbrushsides; i++ ) + { + Assert( texinfoMap[dbrushsides[i].texinfo].outputIndex >= 0 ); + dbrushsides[i].texinfo = texinfoMap[dbrushsides[i].texinfo].outputIndex; + } + // remap texinfos on overlays + for ( i = 0; i < g_nOverlayCount; i++ ) + { + g_Overlays[i].nTexInfo = texinfoMap[g_Overlays[i].nTexInfo].outputIndex; + } + // remap leaf water data + for ( i = 0; i < numleafwaterdata; i++ ) + { + if ( dleafwaterdata[i].surfaceTexInfoID >= 0 ) + { + dleafwaterdata[i].surfaceTexInfoID = texinfoMap[dleafwaterdata[i].surfaceTexInfoID].outputIndex; + } + } + // remap world lights + for ( i = 0; i < *pNumworldlights; i++ ) + { + if ( dworldlights[i].texinfo >= 0 ) + { + dworldlights[i].texinfo = texinfoMap[dworldlights[i].texinfo].outputIndex; + } + } + // remap water overlays + for ( i = 0; i < g_nWaterOverlayCount; i++ ) + { + if ( g_WaterOverlays[i].nTexInfo >= 0 ) + { + g_WaterOverlays[i].nTexInfo = texinfoMap[g_WaterOverlays[i].nTexInfo].outputIndex; + } + } + + Msg("Reduced %d texinfos to %d\n", oldCount, texinfo.Count() ); + Msg("Reduced %d texdatas to %d (%d bytes to %d)\n", oldTexdataCount, numtexdata, oldTexdataString, g_TexDataStringData.Count() ); + + delete[] texinfoMap; + delete[] texdataMap; +} + +/* +============ +WriteBSP +============ +*/ +void WriteBSP (node_t *headnode, face_t *pLeafFaceList ) +{ + int i; + int oldfaces; + int oldorigfaces; + + c_nofaces = 0; + c_facenodes = 0; + + qprintf ("--- WriteBSP ---\n"); + + oldfaces = numfaces; + oldorigfaces = numorigfaces; + + GetEdge2_InitOptimizedList(); + EmitLeafFaces( pLeafFaceList ); + dmodels[nummodels].headnode = EmitDrawNode_r (headnode); + + // Only emit area portals for the main world. + if( nummodels == 0 ) + { + EmitAreaPortals (headnode); + } + + // + // add all displacement faces for the particular model + // + for( i = 0; i < nummapdispinfo; i++ ) + { + int entityIndex = GetDispInfoEntityNum( &mapdispinfo[i] ); + if( entityIndex == entity_num ) + { + EmitFaceVertexes( NULL, &mapdispinfo[i].face ); + EmitFace( &mapdispinfo[i].face, FALSE ); + } + } + + EmitWaterVolumesForBSP( &dmodels[nummodels], headnode ); + qprintf ("%5i nodes with faces\n", c_facenodes); + qprintf ("%5i nodes without faces\n", c_nofaces); + qprintf ("%5i faces\n", numfaces-oldfaces); + qprintf( "%5i original faces\n", numorigfaces-oldorigfaces ); +} + + + +//=========================================================== + +/* +============ +SetModelNumbers +============ +*/ +void SetModelNumbers (void) +{ + int i; + int models; + char value[10]; + + models = 1; + for (i=1 ; i<num_entities ; i++) + { + if (!entities[i].numbrushes) + continue; + + if ( !IsFuncOccluder(i) ) + { + sprintf (value, "*%i", models); + models++; + } + else + { + sprintf (value, ""); + } + SetKeyValue (&entities[i], "model", value); + } +} + + +/* +============ +SetLightStyles +============ +*/ +#define MAX_SWITCHED_LIGHTS 32 +void SetLightStyles (void) +{ + int stylenum; + char *t; + entity_t *e; + int i, j; + char value[10]; + char lighttargets[MAX_SWITCHED_LIGHTS][64]; + + + // any light that is controlled (has a targetname) + // must have a unique style number generated for it + + stylenum = 0; + for (i=1 ; i<num_entities ; i++) + { + e = &entities[i]; + + t = ValueForKey (e, "classname"); + if (Q_strncasecmp (t, "light", 5)) + continue; + + // This is not true for dynamic lights + if (!Q_strcasecmp (t, "light_dynamic")) + continue; + + 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) + { + if (stylenum == MAX_SWITCHED_LIGHTS) + Error ("Too many switched lights (error at light %s), max = %d", t, MAX_SWITCHED_LIGHTS); + strcpy (lighttargets[j], t); + stylenum++; + } + sprintf (value, "%i", 32 + j); + char *pCurrentStyle = ValueForKey( e, "style" ); + // the designer has set a default lightstyle as well as making the light switchable + if ( pCurrentStyle ) + { + int oldStyle = atoi(pCurrentStyle); + if ( oldStyle != 0 ) + { + // save off the default style so the game code can make a switchable copy of it + SetKeyValue( e, "defaultstyle", pCurrentStyle ); + } + } + SetKeyValue (e, "style", value); + } + +} + +/* +============ +EmitBrushes +============ +*/ +void EmitBrushes (void) +{ + int i, j, bnum, s, x; + dbrush_t *db; + mapbrush_t *b; + dbrushside_t *cp; + Vector normal; + vec_t dist; + int planenum; + + numbrushsides = 0; + numbrushes = g_MainMap->nummapbrushes; + + for (bnum=0 ; bnum<g_MainMap->nummapbrushes ; bnum++) + { + b = &g_MainMap->mapbrushes[bnum]; + db = &dbrushes[bnum]; + + db->contents = b->contents; + db->firstside = numbrushsides; + db->numsides = b->numsides; + for (j=0 ; j<b->numsides ; j++) + { + if (numbrushsides == MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + cp = &dbrushsides[numbrushsides]; + numbrushsides++; + cp->planenum = b->original_sides[j].planenum; + cp->texinfo = b->original_sides[j].texinfo; + if ( cp->texinfo == -1 ) + { + cp->texinfo = g_MainMap->g_ClipTexinfo; + } + cp->bevel = b->original_sides[j].bevel; + } + + // add any axis planes not contained in the brush to bevel off corners + for (x=0 ; x<3 ; x++) + for (s=-1 ; s<=1 ; s+=2) + { + // add the plane + VectorCopy (vec3_origin, normal); + normal[x] = s; + if (s == -1) + dist = -b->mins[x]; + else + dist = b->maxs[x]; + planenum = g_MainMap->FindFloatPlane (normal, dist); + for (i=0 ; i<b->numsides ; i++) + if (b->original_sides[i].planenum == planenum) + break; + if (i == b->numsides) + { + if (numbrushsides >= MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + + dbrushsides[numbrushsides].planenum = planenum; + dbrushsides[numbrushsides].texinfo = + dbrushsides[numbrushsides-1].texinfo; + numbrushsides++; + db->numsides++; + } + } + } +} + + + +/* +================== +BeginBSPFile +================== +*/ +void BeginBSPFile (void) +{ + // these values may actually be initialized + // if the file existed when loaded, so clear them explicitly + nummodels = 0; + numfaces = 0; + numnodes = 0; + numbrushsides = 0; + numvertexes = 0; + numleaffaces = 0; + numleafbrushes = 0; + numsurfedges = 0; + + // edge 0 is not used, because 0 can't be negated + numedges = 1; + + // leave vertex 0 as an error + numvertexes = 1; + + // leave leaf 0 as an error + numleafs = 1; + dleafs[0].contents = CONTENTS_SOLID; + + // BUGBUG: This doesn't work! +#if 0 + // make a default empty leaf for the tracing code + memset( &dleafs[1], 0, sizeof(dleafs[1]) ); + dleafs[1].contents = CONTENTS_EMPTY; +#endif +} + +// We can't calculate this properly until vvis (since we need vis to do this), so we set +// to zero everywhere by default. +static void ClearDistToClosestWater( void ) +{ + int i; + for( i = 0; i < numleafs; i++ ) + { + g_LeafMinDistToWater[i] = 0; + } +} + + +void DiscoverMacroTextures() +{ + CUtlDict<int,int> tempDict; + + g_FaceMacroTextureInfos.SetSize( numfaces ); + for ( int iFace=0; iFace < numfaces; iFace++ ) + { + texinfo_t *pTexInfo = &texinfo[dfaces[iFace].texinfo]; + if ( pTexInfo->texdata < 0 ) + continue; + + dtexdata_t *pTexData = &dtexdata[pTexInfo->texdata]; + const char *pMaterialName = &g_TexDataStringData[ g_TexDataStringTable[pTexData->nameStringTableID] ]; + + MaterialSystemMaterial_t hMaterial = FindMaterial( pMaterialName, NULL, false ); + + const char *pMacroTextureName = GetMaterialVar( hMaterial, "$macro_texture" ); + if ( pMacroTextureName ) + { + if ( tempDict.Find( pMacroTextureName ) == tempDict.InvalidIndex() ) + { + Msg( "-- DiscoverMacroTextures: %s\n", pMacroTextureName ); + tempDict.Insert( pMacroTextureName, 0 ); + } + + int stringID = TexDataStringTable_AddOrFindString( pMacroTextureName ); + g_FaceMacroTextureInfos[iFace].m_MacroTextureNameID = (unsigned short)stringID; + } + else + { + g_FaceMacroTextureInfos[iFace].m_MacroTextureNameID = 0xFFFF; + } + } +} + + +// Make sure that we have a water lod control entity if we have water in the map. +void EnsurePresenceOfWaterLODControlEntity( void ) +{ + extern bool g_bHasWater; + if( !g_bHasWater ) + { + // Don't bother if there isn't any water in the map. + return; + } + for( int i=0; i < num_entities; i++ ) + { + entity_t *e = &entities[i]; + + const char *pClassName = ValueForKey( e, "classname" ); + if( !Q_stricmp( pClassName, "water_lod_control" ) ) + { + // Found one!!!! + return; + } + } + + // None found, add one. + Warning( "Water found with no water_lod_control entity, creating a default one.\n" ); + + entity_t *mapent = &entities[num_entities]; + num_entities++; + memset(mapent, 0, sizeof(*mapent)); + mapent->firstbrush = g_MainMap->nummapbrushes; + mapent->numbrushes = 0; + + SetKeyValue( mapent, "classname", "water_lod_control" ); + SetKeyValue( mapent, "cheapwaterstartdistance", "1000" ); + SetKeyValue( mapent, "cheapwaterenddistance", "2000" ); +} + + +/* +============ +EndBSPFile +============ +*/ +void EndBSPFile (void) +{ + // Mark noshadow faces. + MarkNoShadowFaces(); + + EmitBrushes (); + EmitPlanes (); + + // stick flat normals at the verts + SaveVertexNormals(); + + // Figure out lightmap extents for all faces. + UpdateAllFaceLightmapExtents(); + + // Generate geometry and lightmap alpha for displacements. + EmitDispLMAlphaAndNeighbors(); + + // Emit overlay data. + Overlay_EmitOverlayFaces(); + OverlayTransition_EmitOverlayFaces(); + + // phys collision needs dispinfo to operate (needs to generate phys collision for displacement surfs) + EmitPhysCollision(); + + // We can't calculate this properly until vvis (since we need vis to do this), so we set + // to zero everywhere by default. + ClearDistToClosestWater(); + + // Emit static props found in the .vmf file + EmitStaticProps(); + + // Place detail props found in .vmf and based on material properties + EmitDetailObjects(); + + // Compute bounds after creating disp info because we need to reference it + ComputeBoundsNoSkybox(); + + // Make sure that we have a water lod control eneity if we have water in the map. + EnsurePresenceOfWaterLODControlEntity(); + + // Doing this here because stuff about may filter out entities + UnparseEntities (); + + // remove unused texinfos + CompactTexinfos(); + + // Figure out which faces want macro textures. + DiscoverMacroTextures(); + + char fileName[1024]; + V_strncpy( fileName, source, sizeof( fileName ) ); + V_DefaultExtension( fileName, ".bsp", sizeof( fileName ) ); + Msg ("Writing %s\n", fileName); + WriteBSPFile (fileName); +} + + +/* +================== +BeginModel +================== +*/ +int firstmodleaf; +void BeginModel (void) +{ + dmodel_t *mod; + int start, end; + mapbrush_t *b; + int j; + entity_t *e; + Vector mins, maxs; + + if (nummodels == MAX_MAP_MODELS) + Error ("Too many brush models in map, max = %d", MAX_MAP_MODELS); + mod = &dmodels[nummodels]; + + mod->firstface = numfaces; + + firstmodleaf = numleafs; + firstmodeledge = numedges; + firstmodelface = numfaces; + + // + // bound the brushes + // + e = &entities[entity_num]; + + start = e->firstbrush; + end = start + e->numbrushes; + ClearBounds (mins, maxs); + + for (j=start ; j<end ; j++) + { + b = &g_MainMap->mapbrushes[j]; + if (!b->numsides) + continue; // not a real brush (origin brush) + AddPointToBounds (b->mins, mins, maxs); + AddPointToBounds (b->maxs, mins, maxs); + } + + VectorCopy (mins, mod->mins); + VectorCopy (maxs, mod->maxs); +} + + +/* +================== +EndModel +================== +*/ +void EndModel (void) +{ + dmodel_t *mod; + + mod = &dmodels[nummodels]; + + mod->numfaces = numfaces - mod->firstface; + + nummodels++; +} + + + +//----------------------------------------------------------------------------- +// figure out which leaf a point is in +//----------------------------------------------------------------------------- +static int PointLeafnum_r (const Vector& p, int num) +{ + float d; + while (num >= 0) + { + dnode_t* node = dnodes + num; + dplane_t* plane = dplanes + node->planenum; + + if (plane->type < 3) + d = p[plane->type] - plane->dist; + else + d = DotProduct (plane->normal, p) - plane->dist; + if (d < 0) + num = node->children[1]; + else + num = node->children[0]; + } + + return -1 - num; +} + +int PointLeafnum ( dmodel_t* pModel, const Vector& p ) +{ + return PointLeafnum_r (p, pModel->headnode); +} + + +//----------------------------------------------------------------------------- +// Adds a noew to the bounding box +//----------------------------------------------------------------------------- +static void AddNodeToBounds(int node, CUtlVector<int>& skipAreas, Vector& mins, Vector& maxs) +{ + // not a leaf + if (node >= 0) + { + AddNodeToBounds( dnodes[node].children[0], skipAreas, mins, maxs ); + AddNodeToBounds( dnodes[node].children[1], skipAreas, mins, maxs ); + } + else + { + int leaf = - 1 - node; + + // Don't bother with solid leaves + if (dleafs[leaf].contents & CONTENTS_SOLID) + return; + + // Skip 3D skybox + int i; + for ( i = skipAreas.Count(); --i >= 0; ) + { + if (dleafs[leaf].area == skipAreas[i]) + return; + } + + unsigned int firstface = dleafs[leaf].firstleafface; + for ( i = 0; i < dleafs[leaf].numleaffaces; ++i ) + { + unsigned int face = dleaffaces[ firstface + i ]; + + // Skip skyboxes + nodraw + texinfo_t& tex = texinfo[dfaces[face].texinfo]; + if (tex.flags & (SURF_SKY | SURF_NODRAW)) + continue; + + unsigned int firstedge = dfaces[face].firstedge; + Assert( firstedge >= 0 ); + + for (int j = 0; j < dfaces[face].numedges; ++j) + { + Assert( firstedge+j < numsurfedges ); + int edge = abs(dsurfedges[firstedge+j]); + dedge_t* pEdge = &dedges[edge]; + Assert( pEdge->v[0] >= 0 ); + Assert( pEdge->v[1] >= 0 ); + AddPointToBounds (dvertexes[pEdge->v[0]].point, mins, maxs); + AddPointToBounds (dvertexes[pEdge->v[1]].point, mins, maxs); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Check to see if a displacement lives in any leaves that are not +// in the 3d skybox +//----------------------------------------------------------------------------- +bool IsBoxInsideWorld( int node, CUtlVector<int> &skipAreas, const Vector &vecMins, const Vector &vecMaxs ) +{ + while( 1 ) + { + // leaf + if (node < 0) + { + // get the leaf + int leaf = - 1 - node; + + // Don't bother with solid leaves + if (dleafs[leaf].contents & CONTENTS_SOLID) + return false; + + // Skip 3D skybox + int i; + for ( i = skipAreas.Count(); --i >= 0; ) + { + if ( dleafs[leaf].area == skipAreas[i] ) + return false; + } + + return true; + } + + // + // get displacement bounding box position relative to the node plane + // + dnode_t *pNode = &dnodes[ node ]; + dplane_t *pPlane = &dplanes[ pNode->planenum ]; + + int sideResult = BrushBspBoxOnPlaneSide( vecMins, vecMaxs, pPlane ); + + // front side + if( sideResult == 1 ) + { + node = pNode->children[0]; + } + // back side + else if( sideResult == 2 ) + { + node = pNode->children[1]; + } + //split + else + { + if ( IsBoxInsideWorld( pNode->children[0], skipAreas, vecMins, vecMaxs ) ) + return true; + + node = pNode->children[1]; + } + } +} + + +//----------------------------------------------------------------------------- +// Adds the displacement surfaces in the world to the bounds +//----------------------------------------------------------------------------- +void AddDispsToBounds( int nHeadNode, CUtlVector<int>& skipAreas, Vector &vecMins, Vector &vecMaxs ) +{ + Vector vecDispMins, vecDispMaxs; + + // first determine how many displacement surfaces there will be per leaf + int i; + for ( i = 0; i < g_dispinfo.Count(); ++i ) + { + ComputeDispInfoBounds( i, vecDispMins, vecDispMaxs ); + if ( IsBoxInsideWorld( nHeadNode, skipAreas, vecDispMins, vecDispMaxs ) ) + { + AddPointToBounds( vecDispMins, vecMins, vecMaxs ); + AddPointToBounds( vecDispMaxs, vecMins, vecMaxs ); + } + } +} + + +//----------------------------------------------------------------------------- +// Compute the bounding box, excluding 3D skybox + skybox, add it to keyvalues +//----------------------------------------------------------------------------- +void ComputeBoundsNoSkybox( ) +{ + // Iterate over all world leaves, skip those which are part of skybox + Vector mins, maxs; + ClearBounds (mins, maxs); + AddNodeToBounds( dmodels[0].headnode, g_SkyAreas, mins, maxs ); + AddDispsToBounds( dmodels[0].headnode, g_SkyAreas, mins, maxs ); + + // Add the bounds to the worldspawn data + for (int i = 0; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!strcmp(pEntity, "worldspawn")) + { + char string[32]; + sprintf (string, "%i %i %i", (int)mins[0], (int)mins[1], (int)mins[2]); + SetKeyValue (&entities[i], "world_mins", string); + sprintf (string, "%i %i %i", (int)maxs[0], (int)maxs[1], (int)maxs[2]); + SetKeyValue (&entities[i], "world_maxs", string); + break; + } + } +} + + diff --git a/utils/vbsp/writebsp.h b/utils/vbsp/writebsp.h new file mode 100644 index 0000000..5dfce09 --- /dev/null +++ b/utils/vbsp/writebsp.h @@ -0,0 +1,34 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef WRITEBSP_H +#define WRITEBSP_H +#ifdef _WIN32 +#pragma once +#endif + +#include "bspfile.h" +#include "utlmap.h" + +struct node_t; + +//----------------------------------------------------------------------------- +// Emits occluder faces +//----------------------------------------------------------------------------- +void EmitOccluderFaces (node_t *node); + + +//----------------------------------------------------------------------------- +// Purpose: Free the list of faces stored at the leaves +//----------------------------------------------------------------------------- +void FreeLeafFaces( face_t *pLeafFaceList ); + +//----------------------------------------------------------------------------- +// Purpose: Make sure that we have a water lod control entity if we have water in the map. +//----------------------------------------------------------------------------- +void EnsurePresenceOfWaterLODControlEntity( void ); + +#endif // WRITEBSP_H |