diff options
| author | Narendra Umate <[email protected]> | 2013-12-02 23:36:05 -0800 |
|---|---|---|
| committer | Narendra Umate <[email protected]> | 2013-12-02 23:36:05 -0800 |
| commit | 8737f191f3b59f001a77bf6c08091109211c1c9f (patch) | |
| tree | dbbf05c004d9b026f2c1f23f06600fe0add82c36 /mp/src/utils/vbsp | |
| parent | Update .gitignore. (diff) | |
| parent | Make .xcconfigs text files too. (diff) | |
| download | source-sdk-2013-8737f191f3b59f001a77bf6c08091109211c1c9f.tar.xz source-sdk-2013-8737f191f3b59f001a77bf6c08091109211c1c9f.zip | |
Merge remote-tracking branch 'upstream/master'
Diffstat (limited to 'mp/src/utils/vbsp')
43 files changed, 23361 insertions, 23361 deletions
diff --git a/mp/src/utils/vbsp/boundbox.cpp b/mp/src/utils/vbsp/boundbox.cpp index d0366cfc..ecb8dfa0 100644 --- a/mp/src/utils/vbsp/boundbox.cpp +++ b/mp/src/utils/vbsp/boundbox.cpp @@ -1,285 +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 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)(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;
-}
-
+//========= 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 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)(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/mp/src/utils/vbsp/boundbox.h b/mp/src/utils/vbsp/boundbox.h index c9838fe6..4720a40e 100644 --- a/mp/src/utils/vbsp/boundbox.h +++ b/mp/src/utils/vbsp/boundbox.h @@ -1,79 +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
+//========= 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/mp/src/utils/vbsp/brushbsp.cpp b/mp/src/utils/vbsp/brushbsp.cpp index 17323c58..d455f30b 100644 --- a/mp/src/utils/vbsp/brushbsp.cpp +++ b/mp/src/utils/vbsp/brushbsp.cpp @@ -1,1469 +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;
-}
-
+//========= 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/mp/src/utils/vbsp/csg.cpp b/mp/src/utils/vbsp/csg.cpp index c4b89bd9..5de1d680 100644 --- a/mp/src/utils/vbsp/csg.cpp +++ b/mp/src/utils/vbsp/csg.cpp @@ -1,784 +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;
-}
-
-
+//========= 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/mp/src/utils/vbsp/csg.h b/mp/src/utils/vbsp/csg.h index 30436dd9..158dcf43 100644 --- a/mp/src/utils/vbsp/csg.h +++ b/mp/src/utils/vbsp/csg.h @@ -1,32 +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
+//========= 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/mp/src/utils/vbsp/cubemap.cpp b/mp/src/utils/vbsp/cubemap.cpp index 2415115b..aeff12f1 100644 --- a/mp/src/utils/vbsp/cubemap.cpp +++ b/mp/src/utils/vbsp/cubemap.cpp @@ -1,996 +1,996 @@ -//========= 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();
-
- if( !pSkyboxBaseName )
- {
- 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;
- }
-
- char skyboxMaterialName[MAX_PATH];
- Q_snprintf( skyboxMaterialName, MAX_PATH, "skybox/%s", pSkyboxBaseName );
-
- IVTFTexture *pSrcVTFTextures[6];
-
- 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 );
- }
- }
-}
+//========= 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(); + + if( !pSkyboxBaseName ) + { + 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; + } + + char skyboxMaterialName[MAX_PATH]; + Q_snprintf( skyboxMaterialName, MAX_PATH, "skybox/%s", pSkyboxBaseName ); + + IVTFTexture *pSrcVTFTextures[6]; + + 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/mp/src/utils/vbsp/detail.cpp b/mp/src/utils/vbsp/detail.cpp index 88071dd3..840068de 100644 --- a/mp/src/utils/vbsp/detail.cpp +++ b/mp/src/utils/vbsp/detail.cpp @@ -1,693 +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;
-}
+//========= 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/mp/src/utils/vbsp/detail.h b/mp/src/utils/vbsp/detail.h index aa0147d1..64f02b33 100644 --- a/mp/src/utils/vbsp/detail.h +++ b/mp/src/utils/vbsp/detail.h @@ -1,18 +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
+//========= 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/mp/src/utils/vbsp/detailobjects.cpp b/mp/src/utils/vbsp/detailobjects.cpp index b110534c..e7475d94 100644 --- a/mp/src/utils/vbsp/detailobjects.cpp +++ b/mp/src/utils/vbsp/detailobjects.cpp @@ -1,966 +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;
- Q_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 );
- }
-}
+//========= 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; + Q_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/mp/src/utils/vbsp/disp_ivp.cpp b/mp/src/utils/vbsp/disp_ivp.cpp index 3fe50799..1ed16518 100644 --- a/mp/src/utils/vbsp/disp_ivp.cpp +++ b/mp/src/utils/vbsp/disp_ivp.cpp @@ -1,359 +1,359 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-#include "vbsp.h"
-#include "disp_vbsp.h"
-#include "builddisp.h"
-#include "disp_common.h"
-#include "ivp.h"
-#include "disp_ivp.h"
-#include "vphysics_interface.h"
-#include "vphysics/virtualmesh.h"
-#include "utlrbtree.h"
-#include "tier1/utlbuffer.h"
-#include "materialpatch.h"
-
-struct disp_grid_t
-{
- int gridIndex;
- CUtlVector<int> dispList;
-};
-
-static CUtlVector<disp_grid_t> gDispGridList;
-
-
-
-disp_grid_t &FindOrInsertGrid( int gridIndex )
-{
- // linear search is slow, but only a few grids will be present
- for ( int i = gDispGridList.Count()-1; i >= 0; i-- )
- {
- if ( gDispGridList[i].gridIndex == gridIndex )
- {
- return gDispGridList[i];
- }
- }
- int index = gDispGridList.AddToTail();
- gDispGridList[index].gridIndex = gridIndex;
-
- // must be empty
- Assert( gDispGridList[index].dispList.Count() == 0 );
-
- return gDispGridList[index];
-}
-
-// UNDONE: Tune these or adapt them to map size or triangle count?
-#define DISP_GRID_SIZEX 4096
-#define DISP_GRID_SIZEY 4096
-#define DISP_GRID_SIZEZ 8192
-
-int Disp_GridIndex( CCoreDispInfo *pDispInfo )
-{
- // quick hash the center into the grid and put the whole terrain in that grid
- Vector mins, maxs;
- pDispInfo->GetNode(0)->GetBoundingBox( mins, maxs );
- Vector center;
- center = 0.5 * (mins + maxs);
- // make sure it's positive
- center += Vector(MAX_COORD_INTEGER,MAX_COORD_INTEGER,MAX_COORD_INTEGER);
- int gridX = center.x / DISP_GRID_SIZEX;
- int gridY = center.y / DISP_GRID_SIZEY;
- int gridZ = center.z / DISP_GRID_SIZEZ;
-
- gridX &= 0xFF;
- gridY &= 0xFF;
- gridZ &= 0xFF;
- return MAKEID( gridX, gridY, gridZ, 0 );
-}
-
-void AddToGrid( int gridIndex, int dispIndex )
-{
- disp_grid_t &grid = FindOrInsertGrid( gridIndex );
- grid.dispList.AddToTail( dispIndex );
-}
-
-MaterialSystemMaterial_t GetMatIDFromDisp( mapdispinfo_t *pMapDisp )
-{
- texinfo_t *pTexInfo = &texinfo[pMapDisp->face.texinfo];
- dtexdata_t *pTexData = GetTexData( pTexInfo->texdata );
- MaterialSystemMaterial_t matID = FindOriginalMaterial( TexDataStringTable_GetString( pTexData->nameStringTableID ), NULL, true );
- return matID;
-}
-
-// check this and disable virtual mesh if in use
-bool Disp_HasPower4Displacements()
-{
- for ( int i = 0; i < g_CoreDispInfos.Count(); i++ )
- {
- if ( g_CoreDispInfos[i]->GetPower() > 3 )
- {
- return true;
- }
- }
- return false;
-}
-
-// adds all displacement faces as a series of convex objects
-// UNDONE: Only add the displacements for this model?
-void Disp_AddCollisionModels( CUtlVector<CPhysCollisionEntry *> &collisionList, dmodel_t *pModel, int contentsMask)
-{
- int dispIndex;
-
- // Add each displacement to the grid hash
- for ( dispIndex = 0; dispIndex < g_CoreDispInfos.Count(); dispIndex++ )
- {
- CCoreDispInfo *pDispInfo = g_CoreDispInfos[ dispIndex ];
- mapdispinfo_t *pMapDisp = &mapdispinfo[ dispIndex ];
-
- // not solid for this pass
- if ( !(pMapDisp->contents & contentsMask) )
- continue;
-
- int gridIndex = Disp_GridIndex( pDispInfo );
- AddToGrid( gridIndex, dispIndex );
- }
-
- // now make a polysoup for the terrain in each grid
- for ( int grid = 0; grid < gDispGridList.Count(); grid++ )
- {
- int triCount = 0;
- CPhysPolysoup *pTerrainPhysics = physcollision->PolysoupCreate();
-
- // iterate the displacements in this grid
- for ( int listIndex = 0; listIndex < gDispGridList[grid].dispList.Count(); listIndex++ )
- {
- dispIndex = gDispGridList[grid].dispList[listIndex];
- CCoreDispInfo *pDispInfo = g_CoreDispInfos[ dispIndex ];
- mapdispinfo_t *pMapDisp = &mapdispinfo[ dispIndex ];
-
- // Get the material id.
- MaterialSystemMaterial_t matID = GetMatIDFromDisp( pMapDisp );
-
- // Build a triangle list. This shares the tesselation code with the engine.
- CUtlVector<unsigned short> indices;
- CVBSPTesselateHelper helper;
- helper.m_pIndices = &indices;
- helper.m_pActiveVerts = pDispInfo->GetAllowedVerts().Base();
- helper.m_pPowerInfo = pDispInfo->GetPowerInfo();
-
- ::TesselateDisplacement( &helper );
-
- Assert( indices.Count() > 0 );
- Assert( indices.Count() % 3 == 0 ); // Make sure indices are a multiple of 3.
- int nTriCount = indices.Count() / 3;
- triCount += nTriCount;
- if ( triCount >= 65536 )
- {
- // don't put more than 64K tris in any single collision model
- CPhysCollide *pCollide = physcollision->ConvertPolysoupToCollide( pTerrainPhysics, false );
- if ( pCollide )
- {
- collisionList.AddToTail( new CPhysCollisionEntryStaticMesh( pCollide, NULL ) );
- }
- // Throw this polysoup away and start over for the remaining triangles
- physcollision->PolysoupDestroy( pTerrainPhysics );
- pTerrainPhysics = physcollision->PolysoupCreate();
- triCount = nTriCount;
- }
- Vector tmpVerts[3];
- for ( int iTri = 0; iTri < nTriCount; ++iTri )
- {
- float flAlphaTotal = 0.0f;
- for ( int iTriVert = 0; iTriVert < 3; ++iTriVert )
- {
- pDispInfo->GetVert( indices[iTri*3+iTriVert], tmpVerts[iTriVert] );
- flAlphaTotal += pDispInfo->GetAlpha( indices[iTri*3+iTriVert] );
- }
-
- int nProp = g_SurfaceProperties[texinfo[pMapDisp->face.texinfo].texdata];
- if ( flAlphaTotal > DISP_ALPHA_PROP_DELTA )
- {
- int nProp2 = GetSurfaceProperties2( matID, "surfaceprop2" );
- if ( nProp2 != -1 )
- {
- nProp = nProp2;
- }
- }
- int nMaterialIndex = RemapWorldMaterial( nProp );
- physcollision->PolysoupAddTriangle( pTerrainPhysics, tmpVerts[0], tmpVerts[1], tmpVerts[2], nMaterialIndex );
- }
- }
-
- // convert the whole grid's polysoup to a collide and store in the collision list
- CPhysCollide *pCollide = physcollision->ConvertPolysoupToCollide( pTerrainPhysics, false );
- if ( pCollide )
- {
- collisionList.AddToTail( new CPhysCollisionEntryStaticMesh( pCollide, NULL ) );
- }
- // now that we have the collide, we're done with the soup
- physcollision->PolysoupDestroy( pTerrainPhysics );
- }
-}
-
-
-class CDispMeshEvent : public IVirtualMeshEvent
-{
-public:
- CDispMeshEvent( unsigned short *pIndices, int indexCount, CCoreDispInfo *pDispInfo );
- virtual void GetVirtualMesh( void *userData, virtualmeshlist_t *pList );
- virtual void GetWorldspaceBounds( void *userData, Vector *pMins, Vector *pMaxs );
- virtual void GetTrianglesInSphere( void *userData, const Vector ¢er, float radius, virtualmeshtrianglelist_t *pList );
-
- CUtlVector<Vector> m_verts;
- unsigned short *m_pIndices;
- int m_indexCount;
-};
-
-CDispMeshEvent::CDispMeshEvent( unsigned short *pIndices, int indexCount, CCoreDispInfo *pDispInfo )
-{
- m_pIndices = pIndices;
- m_indexCount = indexCount;
- int maxIndex = 0;
- for ( int i = 0; i < indexCount; i++ )
- {
- if ( pIndices[i] > maxIndex )
- {
- maxIndex = pIndices[i];
- }
- }
- for ( int i = 0; i < indexCount/2; i++ )
- {
- V_swap( pIndices[i], pIndices[(indexCount-i)-1] );
- }
- int count = maxIndex + 1;
- m_verts.SetCount( count );
- for ( int i = 0; i < count; i++ )
- {
- m_verts[i] = pDispInfo->GetVert(i);
- }
-}
-
-void CDispMeshEvent::GetVirtualMesh( void *userData, virtualmeshlist_t *pList )
-{
- Assert(userData==((void *)this));
- pList->pVerts = m_verts.Base();
- pList->indexCount = m_indexCount;
- pList->triangleCount = m_indexCount/3;
- pList->vertexCount = m_verts.Count();
- pList->surfacePropsIndex = 0; // doesn't matter here, reset at runtime
- pList->pHull = NULL;
- int indexMax = ARRAYSIZE(pList->indices);
- int indexCount = min(m_indexCount, indexMax);
- Assert(m_indexCount < indexMax);
- Q_memcpy( pList->indices, m_pIndices, sizeof(*m_pIndices) * indexCount );
-}
-
-void CDispMeshEvent::GetWorldspaceBounds( void *userData, Vector *pMins, Vector *pMaxs )
-{
- Assert(userData==((void *)this));
- ClearBounds( *pMins, *pMaxs );
- for ( int i = 0; i < m_verts.Count(); i++ )
- {
- AddPointToBounds( m_verts[i], *pMins, *pMaxs );
- }
-}
-
-void CDispMeshEvent::GetTrianglesInSphere( void *userData, const Vector ¢er, float radius, virtualmeshtrianglelist_t *pList )
-{
- Assert(userData==((void *)this));
- pList->triangleCount = m_indexCount/3;
- int indexMax = ARRAYSIZE(pList->triangleIndices);
- int indexCount = min(m_indexCount, indexMax);
- Assert(m_indexCount < MAX_VIRTUAL_TRIANGLES*3);
- Q_memcpy( pList->triangleIndices, m_pIndices, sizeof(*m_pIndices) * indexCount );
-}
-
-void Disp_BuildVirtualMesh( int contentsMask )
-{
- CUtlVector<CPhysCollide *> virtualMeshes;
- virtualMeshes.EnsureCount( g_CoreDispInfos.Count() );
- for ( int i = 0; i < g_CoreDispInfos.Count(); i++ )
- {
- CCoreDispInfo *pDispInfo = g_CoreDispInfos[ i ];
- mapdispinfo_t *pMapDisp = &mapdispinfo[ i ];
-
- virtualMeshes[i] = NULL;
- // not solid for this pass
- if ( !(pMapDisp->contents & contentsMask) )
- continue;
-
- // Build a triangle list. This shares the tesselation code with the engine.
- CUtlVector<unsigned short> indices;
- CVBSPTesselateHelper helper;
- helper.m_pIndices = &indices;
- helper.m_pActiveVerts = pDispInfo->GetAllowedVerts().Base();
- helper.m_pPowerInfo = pDispInfo->GetPowerInfo();
-
- ::TesselateDisplacement( &helper );
-
- // validate the collision data
- if ( 1 )
- {
- int triCount = indices.Count() / 3;
- for ( int j = 0; j < triCount; j++ )
- {
- int index = j * 3;
- Vector v0 = pDispInfo->GetVert( indices[index+0] );
- Vector v1 = pDispInfo->GetVert( indices[index+1] );
- Vector v2 = pDispInfo->GetVert( indices[index+2] );
- if ( v0 == v1 || v1 == v2 || v2 == v0 )
- {
- Warning( "Displacement %d has bad geometry near %.2f %.2f %.2f\n", i, v0.x, v0.y, v0.z );
- texinfo_t *pTexInfo = &texinfo[pMapDisp->face.texinfo];
- dtexdata_t *pTexData = GetTexData( pTexInfo->texdata );
- const char *pMatName = TexDataStringTable_GetString( pTexData->nameStringTableID );
-
- Error( "Can't compile displacement physics, exiting. Texture is %s\n", pMatName );
- }
- }
-
- }
- CDispMeshEvent meshHandler( indices.Base(), indices.Count(), pDispInfo );
- virtualmeshparams_t params;
- params.buildOuterHull = true;
- params.pMeshEventHandler = &meshHandler;
- params.userData = &meshHandler;
- virtualMeshes[i] = physcollision->CreateVirtualMesh( params );
- }
- unsigned int totalSize = 0;
- CUtlBuffer buf;
- dphysdisp_t header;
- header.numDisplacements = g_CoreDispInfos.Count();
- buf.PutObjects( &header );
-
- CUtlVector<char> dispBuf;
- for ( int i = 0; i < header.numDisplacements; i++ )
- {
- if ( virtualMeshes[i] )
- {
- unsigned int testSize = physcollision->CollideSize( virtualMeshes[i] );
- totalSize += testSize;
- buf.PutShort( testSize );
- }
- else
- {
- buf.PutShort( -1 );
- }
- }
- for ( int i = 0; i < header.numDisplacements; i++ )
- {
- if ( virtualMeshes[i] )
- {
- unsigned int testSize = physcollision->CollideSize( virtualMeshes[i] );
- dispBuf.RemoveAll();
- dispBuf.EnsureCount(testSize);
-
- unsigned int outSize = physcollision->CollideWrite( dispBuf.Base(), virtualMeshes[i], false );
- Assert( outSize == testSize );
- buf.Put( dispBuf.Base(), outSize );
- }
- }
- g_PhysDispSize = totalSize + sizeof(dphysdisp_t) + (sizeof(unsigned short) * header.numDisplacements);
- Assert( buf.TellMaxPut() == g_PhysDispSize );
- g_PhysDispSize = buf.TellMaxPut();
- g_pPhysDisp = new byte[g_PhysDispSize];
- Q_memcpy( g_pPhysDisp, buf.Base(), g_PhysDispSize );
-}
-
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "vbsp.h" +#include "disp_vbsp.h" +#include "builddisp.h" +#include "disp_common.h" +#include "ivp.h" +#include "disp_ivp.h" +#include "vphysics_interface.h" +#include "vphysics/virtualmesh.h" +#include "utlrbtree.h" +#include "tier1/utlbuffer.h" +#include "materialpatch.h" + +struct disp_grid_t +{ + int gridIndex; + CUtlVector<int> dispList; +}; + +static CUtlVector<disp_grid_t> gDispGridList; + + + +disp_grid_t &FindOrInsertGrid( int gridIndex ) +{ + // linear search is slow, but only a few grids will be present + for ( int i = gDispGridList.Count()-1; i >= 0; i-- ) + { + if ( gDispGridList[i].gridIndex == gridIndex ) + { + return gDispGridList[i]; + } + } + int index = gDispGridList.AddToTail(); + gDispGridList[index].gridIndex = gridIndex; + + // must be empty + Assert( gDispGridList[index].dispList.Count() == 0 ); + + return gDispGridList[index]; +} + +// UNDONE: Tune these or adapt them to map size or triangle count? +#define DISP_GRID_SIZEX 4096 +#define DISP_GRID_SIZEY 4096 +#define DISP_GRID_SIZEZ 8192 + +int Disp_GridIndex( CCoreDispInfo *pDispInfo ) +{ + // quick hash the center into the grid and put the whole terrain in that grid + Vector mins, maxs; + pDispInfo->GetNode(0)->GetBoundingBox( mins, maxs ); + Vector center; + center = 0.5 * (mins + maxs); + // make sure it's positive + center += Vector(MAX_COORD_INTEGER,MAX_COORD_INTEGER,MAX_COORD_INTEGER); + int gridX = center.x / DISP_GRID_SIZEX; + int gridY = center.y / DISP_GRID_SIZEY; + int gridZ = center.z / DISP_GRID_SIZEZ; + + gridX &= 0xFF; + gridY &= 0xFF; + gridZ &= 0xFF; + return MAKEID( gridX, gridY, gridZ, 0 ); +} + +void AddToGrid( int gridIndex, int dispIndex ) +{ + disp_grid_t &grid = FindOrInsertGrid( gridIndex ); + grid.dispList.AddToTail( dispIndex ); +} + +MaterialSystemMaterial_t GetMatIDFromDisp( mapdispinfo_t *pMapDisp ) +{ + texinfo_t *pTexInfo = &texinfo[pMapDisp->face.texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + MaterialSystemMaterial_t matID = FindOriginalMaterial( TexDataStringTable_GetString( pTexData->nameStringTableID ), NULL, true ); + return matID; +} + +// check this and disable virtual mesh if in use +bool Disp_HasPower4Displacements() +{ + for ( int i = 0; i < g_CoreDispInfos.Count(); i++ ) + { + if ( g_CoreDispInfos[i]->GetPower() > 3 ) + { + return true; + } + } + return false; +} + +// adds all displacement faces as a series of convex objects +// UNDONE: Only add the displacements for this model? +void Disp_AddCollisionModels( CUtlVector<CPhysCollisionEntry *> &collisionList, dmodel_t *pModel, int contentsMask) +{ + int dispIndex; + + // Add each displacement to the grid hash + for ( dispIndex = 0; dispIndex < g_CoreDispInfos.Count(); dispIndex++ ) + { + CCoreDispInfo *pDispInfo = g_CoreDispInfos[ dispIndex ]; + mapdispinfo_t *pMapDisp = &mapdispinfo[ dispIndex ]; + + // not solid for this pass + if ( !(pMapDisp->contents & contentsMask) ) + continue; + + int gridIndex = Disp_GridIndex( pDispInfo ); + AddToGrid( gridIndex, dispIndex ); + } + + // now make a polysoup for the terrain in each grid + for ( int grid = 0; grid < gDispGridList.Count(); grid++ ) + { + int triCount = 0; + CPhysPolysoup *pTerrainPhysics = physcollision->PolysoupCreate(); + + // iterate the displacements in this grid + for ( int listIndex = 0; listIndex < gDispGridList[grid].dispList.Count(); listIndex++ ) + { + dispIndex = gDispGridList[grid].dispList[listIndex]; + CCoreDispInfo *pDispInfo = g_CoreDispInfos[ dispIndex ]; + mapdispinfo_t *pMapDisp = &mapdispinfo[ dispIndex ]; + + // Get the material id. + MaterialSystemMaterial_t matID = GetMatIDFromDisp( pMapDisp ); + + // Build a triangle list. This shares the tesselation code with the engine. + CUtlVector<unsigned short> indices; + CVBSPTesselateHelper helper; + helper.m_pIndices = &indices; + helper.m_pActiveVerts = pDispInfo->GetAllowedVerts().Base(); + helper.m_pPowerInfo = pDispInfo->GetPowerInfo(); + + ::TesselateDisplacement( &helper ); + + Assert( indices.Count() > 0 ); + Assert( indices.Count() % 3 == 0 ); // Make sure indices are a multiple of 3. + int nTriCount = indices.Count() / 3; + triCount += nTriCount; + if ( triCount >= 65536 ) + { + // don't put more than 64K tris in any single collision model + CPhysCollide *pCollide = physcollision->ConvertPolysoupToCollide( pTerrainPhysics, false ); + if ( pCollide ) + { + collisionList.AddToTail( new CPhysCollisionEntryStaticMesh( pCollide, NULL ) ); + } + // Throw this polysoup away and start over for the remaining triangles + physcollision->PolysoupDestroy( pTerrainPhysics ); + pTerrainPhysics = physcollision->PolysoupCreate(); + triCount = nTriCount; + } + Vector tmpVerts[3]; + for ( int iTri = 0; iTri < nTriCount; ++iTri ) + { + float flAlphaTotal = 0.0f; + for ( int iTriVert = 0; iTriVert < 3; ++iTriVert ) + { + pDispInfo->GetVert( indices[iTri*3+iTriVert], tmpVerts[iTriVert] ); + flAlphaTotal += pDispInfo->GetAlpha( indices[iTri*3+iTriVert] ); + } + + int nProp = g_SurfaceProperties[texinfo[pMapDisp->face.texinfo].texdata]; + if ( flAlphaTotal > DISP_ALPHA_PROP_DELTA ) + { + int nProp2 = GetSurfaceProperties2( matID, "surfaceprop2" ); + if ( nProp2 != -1 ) + { + nProp = nProp2; + } + } + int nMaterialIndex = RemapWorldMaterial( nProp ); + physcollision->PolysoupAddTriangle( pTerrainPhysics, tmpVerts[0], tmpVerts[1], tmpVerts[2], nMaterialIndex ); + } + } + + // convert the whole grid's polysoup to a collide and store in the collision list + CPhysCollide *pCollide = physcollision->ConvertPolysoupToCollide( pTerrainPhysics, false ); + if ( pCollide ) + { + collisionList.AddToTail( new CPhysCollisionEntryStaticMesh( pCollide, NULL ) ); + } + // now that we have the collide, we're done with the soup + physcollision->PolysoupDestroy( pTerrainPhysics ); + } +} + + +class CDispMeshEvent : public IVirtualMeshEvent +{ +public: + CDispMeshEvent( unsigned short *pIndices, int indexCount, CCoreDispInfo *pDispInfo ); + virtual void GetVirtualMesh( void *userData, virtualmeshlist_t *pList ); + virtual void GetWorldspaceBounds( void *userData, Vector *pMins, Vector *pMaxs ); + virtual void GetTrianglesInSphere( void *userData, const Vector ¢er, float radius, virtualmeshtrianglelist_t *pList ); + + CUtlVector<Vector> m_verts; + unsigned short *m_pIndices; + int m_indexCount; +}; + +CDispMeshEvent::CDispMeshEvent( unsigned short *pIndices, int indexCount, CCoreDispInfo *pDispInfo ) +{ + m_pIndices = pIndices; + m_indexCount = indexCount; + int maxIndex = 0; + for ( int i = 0; i < indexCount; i++ ) + { + if ( pIndices[i] > maxIndex ) + { + maxIndex = pIndices[i]; + } + } + for ( int i = 0; i < indexCount/2; i++ ) + { + V_swap( pIndices[i], pIndices[(indexCount-i)-1] ); + } + int count = maxIndex + 1; + m_verts.SetCount( count ); + for ( int i = 0; i < count; i++ ) + { + m_verts[i] = pDispInfo->GetVert(i); + } +} + +void CDispMeshEvent::GetVirtualMesh( void *userData, virtualmeshlist_t *pList ) +{ + Assert(userData==((void *)this)); + pList->pVerts = m_verts.Base(); + pList->indexCount = m_indexCount; + pList->triangleCount = m_indexCount/3; + pList->vertexCount = m_verts.Count(); + pList->surfacePropsIndex = 0; // doesn't matter here, reset at runtime + pList->pHull = NULL; + int indexMax = ARRAYSIZE(pList->indices); + int indexCount = min(m_indexCount, indexMax); + Assert(m_indexCount < indexMax); + Q_memcpy( pList->indices, m_pIndices, sizeof(*m_pIndices) * indexCount ); +} + +void CDispMeshEvent::GetWorldspaceBounds( void *userData, Vector *pMins, Vector *pMaxs ) +{ + Assert(userData==((void *)this)); + ClearBounds( *pMins, *pMaxs ); + for ( int i = 0; i < m_verts.Count(); i++ ) + { + AddPointToBounds( m_verts[i], *pMins, *pMaxs ); + } +} + +void CDispMeshEvent::GetTrianglesInSphere( void *userData, const Vector ¢er, float radius, virtualmeshtrianglelist_t *pList ) +{ + Assert(userData==((void *)this)); + pList->triangleCount = m_indexCount/3; + int indexMax = ARRAYSIZE(pList->triangleIndices); + int indexCount = min(m_indexCount, indexMax); + Assert(m_indexCount < MAX_VIRTUAL_TRIANGLES*3); + Q_memcpy( pList->triangleIndices, m_pIndices, sizeof(*m_pIndices) * indexCount ); +} + +void Disp_BuildVirtualMesh( int contentsMask ) +{ + CUtlVector<CPhysCollide *> virtualMeshes; + virtualMeshes.EnsureCount( g_CoreDispInfos.Count() ); + for ( int i = 0; i < g_CoreDispInfos.Count(); i++ ) + { + CCoreDispInfo *pDispInfo = g_CoreDispInfos[ i ]; + mapdispinfo_t *pMapDisp = &mapdispinfo[ i ]; + + virtualMeshes[i] = NULL; + // not solid for this pass + if ( !(pMapDisp->contents & contentsMask) ) + continue; + + // Build a triangle list. This shares the tesselation code with the engine. + CUtlVector<unsigned short> indices; + CVBSPTesselateHelper helper; + helper.m_pIndices = &indices; + helper.m_pActiveVerts = pDispInfo->GetAllowedVerts().Base(); + helper.m_pPowerInfo = pDispInfo->GetPowerInfo(); + + ::TesselateDisplacement( &helper ); + + // validate the collision data + if ( 1 ) + { + int triCount = indices.Count() / 3; + for ( int j = 0; j < triCount; j++ ) + { + int index = j * 3; + Vector v0 = pDispInfo->GetVert( indices[index+0] ); + Vector v1 = pDispInfo->GetVert( indices[index+1] ); + Vector v2 = pDispInfo->GetVert( indices[index+2] ); + if ( v0 == v1 || v1 == v2 || v2 == v0 ) + { + Warning( "Displacement %d has bad geometry near %.2f %.2f %.2f\n", i, v0.x, v0.y, v0.z ); + texinfo_t *pTexInfo = &texinfo[pMapDisp->face.texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + const char *pMatName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + + Error( "Can't compile displacement physics, exiting. Texture is %s\n", pMatName ); + } + } + + } + CDispMeshEvent meshHandler( indices.Base(), indices.Count(), pDispInfo ); + virtualmeshparams_t params; + params.buildOuterHull = true; + params.pMeshEventHandler = &meshHandler; + params.userData = &meshHandler; + virtualMeshes[i] = physcollision->CreateVirtualMesh( params ); + } + unsigned int totalSize = 0; + CUtlBuffer buf; + dphysdisp_t header; + header.numDisplacements = g_CoreDispInfos.Count(); + buf.PutObjects( &header ); + + CUtlVector<char> dispBuf; + for ( int i = 0; i < header.numDisplacements; i++ ) + { + if ( virtualMeshes[i] ) + { + unsigned int testSize = physcollision->CollideSize( virtualMeshes[i] ); + totalSize += testSize; + buf.PutShort( testSize ); + } + else + { + buf.PutShort( -1 ); + } + } + for ( int i = 0; i < header.numDisplacements; i++ ) + { + if ( virtualMeshes[i] ) + { + unsigned int testSize = physcollision->CollideSize( virtualMeshes[i] ); + dispBuf.RemoveAll(); + dispBuf.EnsureCount(testSize); + + unsigned int outSize = physcollision->CollideWrite( dispBuf.Base(), virtualMeshes[i], false ); + Assert( outSize == testSize ); + buf.Put( dispBuf.Base(), outSize ); + } + } + g_PhysDispSize = totalSize + sizeof(dphysdisp_t) + (sizeof(unsigned short) * header.numDisplacements); + Assert( buf.TellMaxPut() == g_PhysDispSize ); + g_PhysDispSize = buf.TellMaxPut(); + g_pPhysDisp = new byte[g_PhysDispSize]; + Q_memcpy( g_pPhysDisp, buf.Base(), g_PhysDispSize ); +} + diff --git a/mp/src/utils/vbsp/disp_ivp.h b/mp/src/utils/vbsp/disp_ivp.h index 2c473f9a..c89daceb 100644 --- a/mp/src/utils/vbsp/disp_ivp.h +++ b/mp/src/utils/vbsp/disp_ivp.h @@ -1,49 +1,49 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-
-#ifndef DISP_IVP_H
-#define DISP_IVP_H
-#ifdef _WIN32
-#pragma once
-#endif
-
-#include "utlvector.h"
-#include "../../public/disp_tesselate.h"
-
-
-class CPhysCollisionEntry;
-struct dmodel_t;
-
-
-// This provides the template functions that the engine's tesselation code needs
-// so we can share the code in VBSP.
-class CVBSPTesselateHelper : public CBaseTesselateHelper
-{
-public:
- void EndTriangle()
- {
- m_pIndices->AddToTail( m_TempIndices[0] );
- m_pIndices->AddToTail( m_TempIndices[1] );
- m_pIndices->AddToTail( m_TempIndices[2] );
- }
-
- DispNodeInfo_t& GetNodeInfo( int iNodeBit )
- {
- // VBSP doesn't care about these. Give it back something to play with.
- static DispNodeInfo_t dummy;
- return dummy;
- }
-
-public:
- CUtlVector<unsigned short> *m_pIndices;
-};
-
-
-extern void Disp_AddCollisionModels( CUtlVector<CPhysCollisionEntry *> &collisionList, dmodel_t *pModel, int contentsMask );
-extern void Disp_BuildVirtualMesh( int contentsMask );
-extern bool Disp_HasPower4Displacements();
-
-#endif // DISP_IVP_H
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef DISP_IVP_H +#define DISP_IVP_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utlvector.h" +#include "../../public/disp_tesselate.h" + + +class CPhysCollisionEntry; +struct dmodel_t; + + +// This provides the template functions that the engine's tesselation code needs +// so we can share the code in VBSP. +class CVBSPTesselateHelper : public CBaseTesselateHelper +{ +public: + void EndTriangle() + { + m_pIndices->AddToTail( m_TempIndices[0] ); + m_pIndices->AddToTail( m_TempIndices[1] ); + m_pIndices->AddToTail( m_TempIndices[2] ); + } + + DispNodeInfo_t& GetNodeInfo( int iNodeBit ) + { + // VBSP doesn't care about these. Give it back something to play with. + static DispNodeInfo_t dummy; + return dummy; + } + +public: + CUtlVector<unsigned short> *m_pIndices; +}; + + +extern void Disp_AddCollisionModels( CUtlVector<CPhysCollisionEntry *> &collisionList, dmodel_t *pModel, int contentsMask ); +extern void Disp_BuildVirtualMesh( int contentsMask ); +extern bool Disp_HasPower4Displacements(); + +#endif // DISP_IVP_H diff --git a/mp/src/utils/vbsp/disp_vbsp.cpp b/mp/src/utils/vbsp/disp_vbsp.cpp index 82934f67..f3688595 100644 --- a/mp/src/utils/vbsp/disp_vbsp.cpp +++ b/mp/src/utils/vbsp/disp_vbsp.cpp @@ -1,675 +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;
-}
+//========= 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/mp/src/utils/vbsp/disp_vbsp.h b/mp/src/utils/vbsp/disp_vbsp.h index 1c26775e..5af77601 100644 --- a/mp/src/utils/vbsp/disp_vbsp.h +++ b/mp/src/utils/vbsp/disp_vbsp.h @@ -1,46 +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
+//========= 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/mp/src/utils/vbsp/faces.cpp b/mp/src/utils/vbsp/faces.cpp index 13ed9d61..f6ec3ee3 100644 --- a/mp/src/utils/vbsp/faces.cpp +++ b/mp/src/utils/vbsp/faces.cpp @@ -1,1810 +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);
+//========= 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/mp/src/utils/vbsp/faces.h b/mp/src/utils/vbsp/faces.h index cfebc24f..79d78954 100644 --- a/mp/src/utils/vbsp/faces.h +++ b/mp/src/utils/vbsp/faces.h @@ -1,20 +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
+//========= 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/mp/src/utils/vbsp/glfile.cpp b/mp/src/utils/vbsp/glfile.cpp index 236845bc..361d40b0 100644 --- a/mp/src/utils/vbsp/glfile.cpp +++ b/mp/src/utils/vbsp/glfile.cpp @@ -1,219 +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 );
-}
+//========= 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/mp/src/utils/vbsp/ivp.cpp b/mp/src/utils/vbsp/ivp.cpp index 585f0904..3c234c2f 100644 --- a/mp/src/utils/vbsp/ivp.cpp +++ b/mp/src/utils/vbsp/ivp.cpp @@ -1,1656 +1,1656 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-// $NoKeywords: $
-//=============================================================================//
-
-#include <stdio.h>
-#include "mathlib/vector.h"
-#include "bspfile.h"
-#include "bsplib.h"
-#include "cmdlib.h"
-#include "physdll.h"
-#include "utlvector.h"
-#include "vbsp.h"
-#include "phyfile.h"
-#include <float.h>
-#include "KeyValues.h"
-#include "UtlBuffer.h"
-#include "utlsymbol.h"
-#include "utlrbtree.h"
-#include "ivp.h"
-#include "disp_ivp.h"
-#include "materialpatch.h"
-#include "bitvec.h"
-
-// bit per leaf
-typedef CBitVec<MAX_MAP_LEAFS> leafbitarray_t;
-
-// parameters for conversion to vphysics
-#define NO_SHRINK 0.0f
-// NOTE: vphysics maintains a minimum separation radius between objects
-// This radius is set to 0.25, but it's symmetric. So shrinking potentially moveable
-// brushes by 0.5 in every direction ensures that these brushes can be constructed
-// touching the world, and constrained in place without collisions or friction
-// UNDONE: Add a key to disable this shrinking if necessary
-#define VPHYSICS_SHRINK (0.5f) // shrink BSP brushes by this much for collision
-#define VPHYSICS_MERGE 0.01f // merge verts closer than this
-
-void EmitPhysCollision();
-
-IPhysicsCollision *physcollision = NULL;
-extern IPhysicsSurfaceProps *physprops;
-
-// a list of all of the materials in the world model
-static CUtlVector<int> s_WorldPropList;
-
-//-----------------------------------------------------------------------------
-// Purpose: Write key/value pairs out to a memory buffer
-//-----------------------------------------------------------------------------
-CTextBuffer::CTextBuffer( void )
-{
-}
-CTextBuffer::~CTextBuffer( void )
-{
-}
-
-void CTextBuffer::WriteText( const char *pText )
-{
- int len = strlen( pText );
- CopyData( pText, len );
-}
-
-void CTextBuffer::WriteIntKey( const char *pKeyName, int outputData )
-{
- char tmp[1024];
-
- // FAIL!
- if ( strlen(pKeyName) > 1000 )
- {
- Msg("Error writing collision data %s\n", pKeyName );
- return;
- }
- sprintf( tmp, "\"%s\" \"%d\"\n", pKeyName, outputData );
- CopyData( tmp, strlen(tmp) );
-}
-
-void CTextBuffer::WriteStringKey( const char *pKeyName, const char *outputData )
-{
- CopyStringQuotes( pKeyName );
- CopyData( " ", 1 );
- CopyStringQuotes( outputData );
- CopyData( "\n", 1 );
-}
-
-void CTextBuffer::WriteFloatKey( const char *pKeyName, float outputData )
-{
- char tmp[1024];
-
- // FAIL!
- if ( strlen(pKeyName) > 1000 )
- {
- Msg("Error writing collision data %s\n", pKeyName );
- return;
- }
- sprintf( tmp, "\"%s\" \"%f\"\n", pKeyName, outputData );
- CopyData( tmp, strlen(tmp) );
-}
-
-void CTextBuffer::WriteFloatArrayKey( const char *pKeyName, const float *outputData, int count )
-{
- char tmp[1024];
-
- // FAIL!
- if ( strlen(pKeyName) > 1000 )
- {
- Msg("Error writing collision data %s\n", pKeyName );
- return;
- }
- sprintf( tmp, "\"%s\" \"", pKeyName );
- for ( int i = 0; i < count; i++ )
- {
- char buf[80];
-
- sprintf( buf, "%f ", outputData[i] );
- strcat( tmp, buf );
- }
- strcat( tmp, "\"\n" );
-
- CopyData( tmp, strlen(tmp) );
-}
-
-void CTextBuffer::CopyStringQuotes( const char *pString )
-{
- CopyData( "\"", 1 );
- CopyData( pString, strlen(pString) );
- CopyData( "\"", 1 );
-}
-
-void CTextBuffer::Terminate( void )
-{
- CopyData( "\0", 1 );
-}
-
-void CTextBuffer::CopyData( const char *pData, int len )
-{
- int offset = m_buffer.AddMultipleToTail( len );
- memcpy( m_buffer.Base() + offset, pData, len );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Writes a glview text file containing the collision surface in question
-// Input : *pCollide -
-// *pFilename -
-//-----------------------------------------------------------------------------
-void DumpCollideToGlView( CPhysCollide *pCollide, const char *pFilename )
-{
- if ( !pCollide )
- return;
-
- Msg("Writing %s...\n", pFilename );
- Vector *outVerts;
- int vertCount = physcollision->CreateDebugMesh( pCollide, &outVerts );
- FILE *fp = fopen( pFilename, "w" );
- int triCount = vertCount / 3;
- int vert = 0;
- for ( int i = 0; i < triCount; i++ )
- {
- fprintf( fp, "3\n" );
- fprintf( fp, "%6.3f %6.3f %6.3f 1 0 0\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z );
- vert++;
- fprintf( fp, "%6.3f %6.3f %6.3f 0 1 0\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z );
- vert++;
- fprintf( fp, "%6.3f %6.3f %6.3f 0 0 1\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z );
- vert++;
- }
- fclose( fp );
- physcollision->DestroyDebugMesh( vertCount, outVerts );
-}
-
-
-void DumpCollideToPHY( CPhysCollide *pCollide, CTextBuffer *text, const char *pFilename )
-{
- Msg("Writing %s...\n", pFilename );
- FILE *fp = fopen( pFilename, "wb" );
- phyheader_t header;
- header.size = sizeof(header);
- header.id = 0;
- header.checkSum = 0;
- header.solidCount = 1;
- fwrite( &header, sizeof(header), 1, fp );
- int size = physcollision->CollideSize( pCollide );
- fwrite( &size, sizeof(int), 1, fp );
-
- char *buf = (char *)malloc( size );
- physcollision->CollideWrite( buf, pCollide );
- fwrite( buf, size, 1, fp );
-
- fwrite( text->GetData(), text->GetSize(), 1, fp );
- fclose( fp );
- free( buf );
-}
-
-CPhysCollisionEntry::CPhysCollisionEntry( CPhysCollide *pCollide )
-{
- m_pCollide = pCollide;
-}
-
-unsigned int CPhysCollisionEntry::GetCollisionBinarySize()
-{
- return physcollision->CollideSize( m_pCollide );
-}
-
-unsigned int CPhysCollisionEntry::WriteCollisionBinary( char *pDest )
-{
- return physcollision->CollideWrite( pDest, m_pCollide );
-}
-
-void CPhysCollisionEntry::DumpCollideFileName( const char *pName, int modelIndex, CTextBuffer *pTextBuffer )
-{
- char tmp[128];
- sprintf( tmp, "%s%03d.phy", pName, modelIndex );
- DumpCollideToPHY( m_pCollide, pTextBuffer, tmp );
- sprintf( tmp, "%s%03d.txt", pName, modelIndex );
- DumpCollideToGlView( m_pCollide, tmp );
-}
-
-
-class CPhysCollisionEntrySolid : public CPhysCollisionEntry
-{
-public:
- CPhysCollisionEntrySolid( CPhysCollide *pCollide, const char *pMaterialName, float mass );
-
- virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex );
- virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex );
-
-private:
- float m_volume;
- float m_mass;
- const char *m_pMaterial;
-};
-
-
-CPhysCollisionEntrySolid::CPhysCollisionEntrySolid( CPhysCollide *pCollide, const char *pMaterialName, float mass )
- : CPhysCollisionEntry( pCollide )
-{
- m_volume = physcollision->CollideVolume( m_pCollide );
- m_mass = mass;
- m_pMaterial = pMaterialName;
-}
-
-void CPhysCollisionEntrySolid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex )
-{
- DumpCollideFileName( "collide", modelIndex, pTextBuffer );
-}
-
-void CPhysCollisionEntrySolid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex )
-{
- pTextBuffer->WriteText( "solid {\n" );
- pTextBuffer->WriteIntKey( "index", collideIndex );
- pTextBuffer->WriteFloatKey( "mass", m_mass );
- if ( m_pMaterial )
- {
- pTextBuffer->WriteStringKey( "surfaceprop", m_pMaterial );
- }
- if ( m_volume != 0.f )
- {
- pTextBuffer->WriteFloatKey( "volume", m_volume );
- }
- pTextBuffer->WriteText( "}\n" );
-}
-
-
-class CPhysCollisionEntryStaticSolid : public CPhysCollisionEntry
-{
-public:
- CPhysCollisionEntryStaticSolid ( CPhysCollide *pCollide, int contentsMask );
-
- virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex );
- virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex );
-
-private:
- int m_contentsMask;
-};
-
-
-CPhysCollisionEntryStaticSolid ::CPhysCollisionEntryStaticSolid ( CPhysCollide *pCollide, int contentsMask )
- : CPhysCollisionEntry( pCollide ), m_contentsMask(contentsMask)
-{
-}
-
-void CPhysCollisionEntryStaticSolid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex )
-{
- char tmp[128];
- sprintf( tmp, "static%02d", modelIndex );
- DumpCollideFileName( tmp, collideIndex, pTextBuffer );
-}
-
-void CPhysCollisionEntryStaticSolid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex )
-{
- pTextBuffer->WriteText( "staticsolid {\n" );
- pTextBuffer->WriteIntKey( "index", collideIndex );
- pTextBuffer->WriteIntKey( "contents", m_contentsMask );
- pTextBuffer->WriteText( "}\n" );
-}
-
-CPhysCollisionEntryStaticMesh::CPhysCollisionEntryStaticMesh( CPhysCollide *pCollide, const char *pMaterialName )
- : CPhysCollisionEntry( pCollide )
-{
- m_pMaterial = pMaterialName;
-}
-
-void CPhysCollisionEntryStaticMesh::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex )
-{
- char tmp[128];
- sprintf( tmp, "mesh%02d", modelIndex );
- DumpCollideFileName( tmp, collideIndex, pTextBuffer );
-}
-
-void CPhysCollisionEntryStaticMesh::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex )
-{
- pTextBuffer->WriteText( "staticsolid {\n" );
- pTextBuffer->WriteIntKey( "index", collideIndex );
- pTextBuffer->WriteText( "}\n" );
-}
-
-class CPhysCollisionEntryFluid : public CPhysCollisionEntry
-{
-public:
- ~CPhysCollisionEntryFluid();
- CPhysCollisionEntryFluid( CPhysCollide *pCollide, const char *pSurfaceProp, float damping, const Vector &normal, float dist, int nContents );
-
- virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex );
- virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex );
-
-private:
- char *m_pSurfaceProp;
- float m_damping;
- Vector m_surfaceNormal;
- float m_surfaceDist;
- int m_contentsMask;
-};
-
-
-CPhysCollisionEntryFluid::CPhysCollisionEntryFluid( CPhysCollide *pCollide, const char *pSurfaceProp, float damping, const Vector &normal, float dist, int nContents )
- : CPhysCollisionEntry( pCollide )
-{
- m_surfaceNormal = normal;
- m_surfaceDist = dist;
- m_pSurfaceProp = new char[strlen(pSurfaceProp)+1];
- strcpy( m_pSurfaceProp, pSurfaceProp );
- m_damping = damping;
- m_contentsMask = nContents;
-}
-
-CPhysCollisionEntryFluid::~CPhysCollisionEntryFluid()
-{
- delete[] m_pSurfaceProp;
-}
-
-void CPhysCollisionEntryFluid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex )
-{
- char tmp[128];
- sprintf( tmp, "water%02d", modelIndex );
- DumpCollideFileName( tmp, collideIndex, pTextBuffer );
-}
-
-void CPhysCollisionEntryFluid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex )
-{
- pTextBuffer->WriteText( "fluid {\n" );
- pTextBuffer->WriteIntKey( "index", collideIndex );
- pTextBuffer->WriteStringKey( "surfaceprop", m_pSurfaceProp ); // write out water material
- pTextBuffer->WriteFloatKey( "damping", m_damping ); // write out water damping
- pTextBuffer->WriteIntKey( "contents", m_contentsMask ); // write out water contents
- float array[4];
- m_surfaceNormal.CopyToArray( array );
- array[3] = m_surfaceDist;
- pTextBuffer->WriteFloatArrayKey( "surfaceplane", array, 4 ); // write out water surface plane
- pTextBuffer->WriteFloatArrayKey( "currentvelocity", vec3_origin.Base(), 3 ); // write out water velocity
- pTextBuffer->WriteText( "}\n" );
-}
-
-// Get an index into the prop list of this prop (add it if necessary)
-static int PropIndex( CUtlVector<int> &propList, int propIndex )
-{
- for ( int i = 0; i < propList.Count(); i++ )
- {
- if ( propList[i] == propIndex )
- return i+1;
- }
-
- if ( propList.Count() < 126 )
- {
- return propList.AddToTail( propIndex )+1;
- }
-
- return 0;
-}
-
-int RemapWorldMaterial( int materialIndexIn )
-{
- return PropIndex( s_WorldPropList, materialIndexIn );
-}
-
-typedef struct
-{
- float normal[3];
- float dist;
-} listplane_t;
-
-static void AddListPlane( CUtlVector<listplane_t> *list, float x, float y, float z, float d )
-{
- listplane_t plane;
- plane.normal[0] = x;
- plane.normal[1] = y;
- plane.normal[2] = z;
- plane.dist = d;
-
- list->AddToTail( plane );
-}
-
-class CPlaneList
-{
-public:
-
- CPlaneList( float shrink, float merge );
- ~CPlaneList( void );
-
- void AddConvex( CPhysConvex *pConvex );
-
- // add the brushes to the model
- int AddBrushes( void );
-
- // Adds a single brush as a convex object
- void ReferenceBrush( int brushnumber );
- bool IsBrushReferenced( int brushnumber );
-
- void ReferenceLeaf( int leafIndex );
- bool IsLeafReferenced( int leafIndex );
- int GetFirstBrushSide();
-
-private:
-
- CPhysConvex *BuildConvexForBrush( int brushnumber, float shrink, CPhysCollide *pCollideTest, float shrinkMinimum );
-
-public:
- CUtlVector<CPhysConvex *> m_convex;
-
- CUtlVector<int> m_leafList;
- int m_contentsMask;
-
- float m_shrink;
- float m_merge;
- bool *m_brushAdded;
- float m_totalVolume;
-};
-
-CPlaneList::CPlaneList( float shrink, float merge )
-{
- m_shrink = shrink;
- m_merge = merge;
- m_contentsMask = MASK_SOLID;
- m_brushAdded = new bool[numbrushes];
- memset( m_brushAdded, 0, sizeof(bool) * numbrushes );
- m_totalVolume = 0;
- m_leafList.Purge();
-}
-
-
-CPlaneList::~CPlaneList( void )
-{
- delete[] m_brushAdded;
-}
-
-
-void CPlaneList::AddConvex( CPhysConvex *pConvex )
-{
- if ( pConvex )
- {
- m_totalVolume += physcollision->ConvexVolume( pConvex );
- m_convex.AddToTail( pConvex );
- }
-}
-
-// Adds a single brush as a convex object
-void CPlaneList::ReferenceBrush( int brushnumber )
-{
- if ( !(dbrushes[brushnumber].contents & m_contentsMask) )
- return;
-
- m_brushAdded[brushnumber] = true;
-
-}
-
-
-bool CPlaneList::IsBrushReferenced( int brushnumber )
-{
- return m_brushAdded[brushnumber];
-}
-
-CPhysConvex *CPlaneList::BuildConvexForBrush( int brushnumber, float shrink, CPhysCollide *pCollideTest, float shrinkMinimum )
-{
- CUtlVector<listplane_t> temp( 0, 32 );
-
- for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ )
- {
- dbrushside_t *pside = dbrushsides + i + dbrushes[brushnumber].firstside;
- if ( pside->bevel )
- continue;
-
- dplane_t *pplane = dplanes + pside->planenum;
- float shrinkThisPlane = shrink;
-
- if ( i < g_MainMap->mapbrushes[brushnumber].numsides )
- {
- if ( !g_MainMap->mapbrushes[brushnumber].original_sides[i].visible )
- {
- // don't shrink brush sides with no visible components.
- // this produces something closer to the ideal shrink than simply shrinking all planes
- shrinkThisPlane = 0;
- }
- }
- // Make sure shrinking won't swallow geometry along this axis.
- if ( pCollideTest && shrinkThisPlane != 0 )
- {
- Vector start = physcollision->CollideGetExtent( pCollideTest, vec3_origin, vec3_angle, pplane->normal );
- Vector end = physcollision->CollideGetExtent( pCollideTest, vec3_origin, vec3_angle, -pplane->normal );
- float thick = DotProduct( (end-start), pplane->normal );
- // NOTE: The object must be at least "shrinkMinimum" inches wide on each axis
- if ( fabs(thick) < shrinkMinimum )
- {
-#if _DEBUG
- Warning("Can't shrink brush %d, plane %d (%.2f, %.2f, %.2f)\n", brushnumber, pside->planenum, pplane->normal[0], pplane->normal[1], pplane->normal[2] );
-#endif
- shrinkThisPlane = 0;
- }
- }
- AddListPlane( &temp, pplane->normal[0], pplane->normal[1], pplane->normal[2], pplane->dist - shrinkThisPlane );
- }
- return physcollision->ConvexFromPlanes( (float *)temp.Base(), temp.Count(), m_merge );
-}
-
-int CPlaneList::AddBrushes( void )
-{
- int count = 0;
- for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ )
- {
- if ( IsBrushReferenced(brushnumber) )
- {
- CPhysConvex *pBrushConvex = NULL;
- if ( m_shrink != 0 )
- {
- // Make sure shrinking won't swallow this brush.
- CPhysConvex *pConvex = BuildConvexForBrush( brushnumber, 0, NULL, 0 );
- CPhysCollide *pUnshrunkCollide = physcollision->ConvertConvexToCollide( &pConvex, 1 );
- pBrushConvex = BuildConvexForBrush( brushnumber, m_shrink, pUnshrunkCollide, m_shrink * 3 );
- physcollision->DestroyCollide( pUnshrunkCollide );
- }
- else
- {
- pBrushConvex = BuildConvexForBrush( brushnumber, m_shrink, NULL, 1.0 );
- }
-
- if ( pBrushConvex )
- {
- count++;
- physcollision->SetConvexGameData( pBrushConvex, brushnumber );
- AddConvex( pBrushConvex );
- }
- }
- }
- return count;
-}
-
-
-int CPlaneList::GetFirstBrushSide()
-{
- for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ )
- {
- if ( IsBrushReferenced(brushnumber) )
- {
- for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ )
- {
- int sideIndex = i + dbrushes[brushnumber].firstside;
- dbrushside_t *pside = dbrushsides + sideIndex;
- if ( pside->bevel )
- continue;
- return sideIndex;
- }
- }
- }
- return 0;
-}
-
-// UNDONE: Try using this kind of algorithm if we run into precision problems.
-// NOTE: ConvexFromPlanes will be doing a bunch of matrix inversions that can suffer
-// if plane normals are too close to each other...
-#if 0
-void CPlaneList::AddBrushes( void )
-{
- CUtlVector<listplane_t> temp;
- for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ )
- {
- if ( IsBrushReferenced(brushnumber) )
- {
- CUtlVector<winding_t *> windings;
-
- for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ )
- {
- dbrushside_t *pside = dbrushsides + i + dbrushes[brushnumber].firstside;
- if (pside->bevel)
- continue;
- dplane_t *pplane = dplanes + pside->planenum;
- winding_t *w = BaseWindingForPlane( pplane->normal, pplane->dist - m_shrink );
- for ( int j = 0; j < dbrushes[brushnumber].numsides && w; j++ )
- {
- if (i == j)
- continue;
- dbrushside_t *pClipSide = dbrushsides + j + dbrushes[brushnumber].firstside;
- if (pClipSide->bevel)
- continue;
- dplane_t *pClipPlane = dplanes + pClipSide->planenum;
- ChopWindingInPlace (&w, -pClipPlane->normal, -pClipPlane->dist+m_shrink, 0); //CLIP_EPSILON);
- }
- if ( w )
- {
- windings.AddToTail( w );
- }
- }
-
- CUtlVector<Vector *> vertList;
- for ( int p = 0; p < windings.Count(); p++ )
- {
- for ( int v = 0; v < windings[p]->numpoints; v++ )
- {
- vertList.AddToTail( windings[p]->p + v );
- }
- }
- CPhysConvex *pConvex = physcollision->ConvexFromVerts( vertList.Base(), vertList.Count() );
- if ( pConvex )
- {
- physcollision->SetConvexGameData( pConvex, brushnumber );
- AddConvex( pConvex );
- }
- temp.RemoveAll();
- }
- }
-}
-#endif
-
-// If I have a list of leaves, make sure this leaf is in it.
-// Otherwise, process all leaves
-bool CPlaneList::IsLeafReferenced( int leafIndex )
-{
- if ( !m_leafList.Count() )
- return true;
-
- for ( int i = 0; i < m_leafList.Count(); i++ )
- {
- if ( m_leafList[i] == leafIndex )
- return true;
- }
-
- return false;
-}
-
-// Add a leaf to my list of interesting leaves
-void CPlaneList::ReferenceLeaf( int leafIndex )
-{
- m_leafList.AddToTail( leafIndex );
-}
-
-static void VisitLeaves_r( CPlaneList &planes, int node )
-{
- if ( node < 0 )
- {
- int leafIndex = -1 - node;
- if ( planes.IsLeafReferenced(leafIndex) )
- {
- int i;
-
- // Add the solids in the "empty" leaf
- for ( i = 0; i < dleafs[leafIndex].numleafbrushes; i++ )
- {
- int brushIndex = dleafbrushes[dleafs[leafIndex].firstleafbrush + i];
- planes.ReferenceBrush( brushIndex );
- }
- }
- }
- else
- {
- dnode_t *pnode = dnodes + node;
-
- VisitLeaves_r( planes, pnode->children[0] );
- VisitLeaves_r( planes, pnode->children[1] );
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-
-struct waterleaf_t
-{
- Vector surfaceNormal;
- float surfaceDist;
- float minZ;
- bool hasSurface;
- int waterLeafIndex;// this is the submerged leaf
- int planenum; //UNDONE: REMOVE
- int surfaceTexInfo; // if hasSurface == true, this is the texinfo index for the water material
- int outsideLeafIndex;// this is the leaf on the other side of the water surface
- node_t *pNode;
-};
-
-
-
-// returns true if newleaf should appear before currentleaf in the list
-static bool IsLowerLeaf( const waterleaf_t &newleaf, const waterleaf_t ¤tleaf )
-{
- if ( newleaf.hasSurface && currentleaf.hasSurface )
- {
- // the one with the upmost pointing z goes first
- if ( currentleaf.surfaceNormal.z > newleaf.surfaceNormal.z )
- return false;
-
- if ( fabs(currentleaf.surfaceNormal.z - newleaf.surfaceNormal.z) < 0.01 )
- {
- if ( newleaf.surfaceDist < currentleaf.surfaceDist )
- return true;
- }
- return true;
- }
- else if ( newleaf.hasSurface ) // the leaf with a surface always goes first
- return true;
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Water surfaces are stored in an RB tree and the tree is used to
-// create one-off .vmt files embedded in the .bsp for each surface so that the
-// water depth effect occurs on a per-water surface level.
-//-----------------------------------------------------------------------------
-struct WaterTexInfo
-{
- // The mangled new .vmt name ( materials/levelename/oldmaterial_depth_xxx ) where xxx is
- // the water depth (as an integer )
- CUtlSymbol m_FullName;
-
- // The original .vmt name
- CUtlSymbol m_MaterialName;
-
- // The depth of the water this texinfo refers to
- int m_nWaterDepth;
-
- // The texinfo id
- int m_nTexInfo;
-
- // The subdivision size for the water surface
-// float m_SubdivSize;
-};
-
-//-----------------------------------------------------------------------------
-// Purpose: Helper for RB tree operations ( we compare full mangled names )
-// Input : src1 -
-// src2 -
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool WaterLessFunc( WaterTexInfo const& src1, WaterTexInfo const& src2 )
-{
- return src1.m_FullName < src2.m_FullName;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: A growable RB tree of water surfaces
-//-----------------------------------------------------------------------------
-static CUtlRBTree< WaterTexInfo, int > g_WaterTexInfos( 0, 32, WaterLessFunc );
-
-#if 0
-float GetSubdivSizeForFogVolume( int fogVolumeID )
-{
- Assert( fogVolumeID >= 0 && fogVolumeID < g_WaterTexInfos.Count() );
- return g_WaterTexInfos[fogVolumeID].m_SubdivSize;
-}
-#endif
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *mapname -
-// *materialname -
-// waterdepth -
-// *fullname -
-//-----------------------------------------------------------------------------
-void GetWaterTextureName( char const *mapname, char const *materialname, int waterdepth, char *fullname )
-{
- char temp[ 512 ];
-
- // Construct the full name (prepend mapname to reduce name collisions)
- sprintf( temp, "maps/%s/%s_depth_%i", mapname, materialname, (int)waterdepth );
-
- // Make sure it's lower case
- strlwr( temp );
-
- strcpy( fullname, temp );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Called to write procedural materials in the rb tree to the embedded
-// pak file for this .bsp
-//-----------------------------------------------------------------------------
-void EmitWaterMaterialFile( WaterTexInfo *wti )
-{
- char waterTextureName[512];
- if ( !wti )
- {
- return;
- }
-
- GetWaterTextureName( mapbase, wti->m_MaterialName.String(), ( int )wti->m_nWaterDepth, waterTextureName );
-
- // Convert to string
- char szDepth[ 32 ];
- sprintf( szDepth, "%i", wti->m_nWaterDepth );
- CreateMaterialPatch( wti->m_MaterialName.String(), waterTextureName, "$waterdepth", szDepth, PATCH_INSERT );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Takes the texinfo_t referenced by the .vmt and the computed depth for the
-// surface and looks up or creates a texdata/texinfo for the mangled one-off water .vmt file
-// Input : *pBaseInfo -
-// depth -
-// Output : int
-//-----------------------------------------------------------------------------
-int FindOrCreateWaterTexInfo( texinfo_t *pBaseInfo, float depth )
-{
- char fullname[ 512 ];
- char materialname[ 512 ];
-
- // Get the base texture/material name
- char const *name = TexDataStringTable_GetString( GetTexData( pBaseInfo->texdata )->nameStringTableID );
-
- GetWaterTextureName( mapbase, name, (int)depth, fullname );
-
- // See if we already have an entry for this depth
- WaterTexInfo lookup;
- lookup.m_FullName = fullname;
- int idx = g_WaterTexInfos.Find( lookup );
-
- // If so, return the existing entry texinfo index
- if ( idx != g_WaterTexInfos.InvalidIndex() )
- {
- return g_WaterTexInfos[ idx ].m_nTexInfo;
- }
-
- // Otherwise, fill in the rest of the data
- lookup.m_nWaterDepth = (int)depth;
- // Remember the current material name
- sprintf( materialname, "%s", name );
- strlwr( materialname );
- lookup.m_MaterialName = materialname;
-
- texinfo_t ti;
- // Make a copy
- ti = *pBaseInfo;
- // Create a texdata that is based on the underlying existing entry
- ti.texdata = FindAliasedTexData( fullname, GetTexData( pBaseInfo->texdata ) );
-
- // Find or create a new index
- lookup.m_nTexInfo = FindOrCreateTexInfo( ti );
-
- // Add the new texinfo to the RB tree
- idx = g_WaterTexInfos.Insert( lookup );
-
- // Msg( "created texinfo for %s\n", lookup.m_FullName.String() );
-
- // Go ahead and create the new vmt file.
- EmitWaterMaterialFile( &g_WaterTexInfos[idx] );
-
- // Return the new texinfo
- return g_WaterTexInfos[ idx ].m_nTexInfo;
-}
-
-extern node_t *dfacenodes[MAX_MAP_FACES];
-static void WriteFogVolumeIDs( dmodel_t *pModel )
-{
- int i;
-
- // write fog volume ID to each face in this model
- for( i = pModel->firstface; i < pModel->firstface + pModel->numfaces; i++ )
- {
- dface_t *pFace = &dfaces[i];
- node_t *pFaceNode = dfacenodes[i];
- texinfo_t *pTexInfo = &texinfo[pFace->texinfo];
- pFace->surfaceFogVolumeID = -1;
- if ( pFaceNode )
- {
- if ( (pTexInfo->flags & SURF_WARP ) && pFaceNode->planenum == PLANENUM_LEAF && pFaceNode->diskId >= 0 )
- {
- pFace->surfaceFogVolumeID = dleafs[pFaceNode->diskId].leafWaterDataID;
- dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[pFace->surfaceFogVolumeID];
-
- // HACKHACK: Should probably mark these faces as water bottom or "bottommaterial" faces.
- // HACKHACK: Use a heuristic, if it points up, it's the water top.
- if ( dplanes[pFace->planenum].normal.z > 0 )
- {
- pFace->texinfo = pLeafWaterData->surfaceTexInfoID;
- }
- }
- else
- {
- // missed this face somehow?
- Assert( !(pTexInfo->flags & SURF_WARP ) );
- }
-
- }
- }
-}
-
-
-static bool PortalCrossesWater( waterleaf_t &baseleaf, portal_t *portal )
-{
- if ( baseleaf.hasSurface )
- {
- int side = WindingOnPlaneSide( portal->winding, baseleaf.surfaceNormal, baseleaf.surfaceDist );
- if ( side == SIDE_CROSS || side == SIDE_FRONT )
- return true;
- }
-
- return false;
-}
-
-
-static int FindOrCreateLeafWaterData( float surfaceZ, float minZ, int surfaceTexInfoID )
-{
- int i;
- for( i = 0; i < numleafwaterdata; i++ )
- {
- dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[i];
- if( pLeafWaterData->surfaceZ == surfaceZ &&
- pLeafWaterData->minZ == minZ &&
- pLeafWaterData->surfaceTexInfoID == surfaceTexInfoID )
- {
- return i;
- }
- }
- dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[numleafwaterdata];
- pLeafWaterData->surfaceZ = surfaceZ;
- pLeafWaterData->minZ = minZ;
- pLeafWaterData->surfaceTexInfoID = surfaceTexInfoID;
- numleafwaterdata++;
- return numleafwaterdata - 1;
-}
-
-
-// Enumerate all leaves under node with contents in contentsMask and add them to list
-void EnumLeaves_r( CUtlVector<node_t *> &list, node_t *node, int contentsMask )
-{
- if ( node->planenum != PLANENUM_LEAF )
- {
- EnumLeaves_r( list, node->children[0], contentsMask );
- EnumLeaves_r( list, node->children[1], contentsMask );
- return;
- }
-
- if ( !(node->contents & contentsMask) )
- return;
-
-
- // has the contents, put it in the list
- list.AddToTail( node );
-}
-
-
-// Builds a waterleaf_t for the given leaf
-static void BuildWaterLeaf( node_t *pLeafIn, waterleaf_t &waterLeafOut )
-{
- waterLeafOut.pNode = pLeafIn;
- waterLeafOut.waterLeafIndex = pLeafIn->diskId;
- waterLeafOut.outsideLeafIndex = -1;
- waterLeafOut.hasSurface = false;
- waterLeafOut.surfaceDist = MAX_COORD_INTEGER;
- waterLeafOut.surfaceNormal.Init( 0.f, 0.f, 1.f );
- waterLeafOut.planenum = -1;
- waterLeafOut.surfaceTexInfo = -1;
- waterLeafOut.minZ = MAX_COORD_INTEGER;
-
- // search the list of portals out of this leaf for one that leaves water
- // If you find one, this leaf has a surface, so fill out the surface data
- int oppositeNodeIndex = 0;
- for (portal_t *p = pLeafIn->portals ; p ; p = p->next[!oppositeNodeIndex])
- {
- oppositeNodeIndex = (p->nodes[0] == pLeafIn) ? 1 : 0;
-
- // not visible, can't be the portals we're looking for...
- if ( !p->side )
- continue;
-
- // See if this portal crosses into air
- node_t *pOpposite = p->nodes[oppositeNodeIndex];
- if ( !(pOpposite->contents & MASK_WATER) && !(pOpposite->contents & MASK_SOLID) )
- {
- // it does, there must be a surface here
- plane_t *plane = &g_MainMap->mapplanes[p->side->planenum];
- if ( waterLeafOut.hasSurface )
- {
- // Sort to find the most upward facing normal (skips sides)
- if ( waterLeafOut.surfaceNormal.z > plane->normal.z )
- continue;
- if ( (waterLeafOut.surfaceNormal.z == plane->normal.z) && waterLeafOut.surfaceDist >= plane->dist )
- continue;
- }
- // water surface needs to point at least somewhat up, this is
- // probably a map error
- if ( plane->normal.z <= 0 )
- continue;
- waterLeafOut.surfaceDist = plane->dist;
- waterLeafOut.surfaceNormal = plane->normal;
- waterLeafOut.hasSurface = true;
- waterLeafOut.outsideLeafIndex = p->nodes[oppositeNodeIndex]->diskId;
- waterLeafOut.surfaceTexInfo = p->side->texinfo;
- }
- }
-}
-
-
-static void InsertSortWaterLeaf( CUtlVector<waterleaf_t> &list, const waterleaf_t &leafInsert )
-{
- // insertion sort the leaf (lowest leaves go first)
- // leaves that aren't actually on the surface of the water will have leaf.hasSurface == false.
- for ( int i = 0; i < list.Count(); i++ )
- {
- if ( IsLowerLeaf( leafInsert, list[i] ) )
- {
- list.InsertBefore( i, leafInsert );
- return;
- }
- }
-
- // must the highest one, so stick it at the end.
- list.AddToTail( leafInsert );
-}
-
-
-// Flood fill the tree, finding neighboring water volumes and connecting them to this list
-// Cut groups that try to cross the surface.
-// Mark leaves that are in a group as "visited" so they won't be chosen by subsequent fills
-static void Flood_FindConnectedWaterVolumes_r( CUtlVector<node_t *> &list, node_t *pLeaf, waterleaf_t &baseleaf, leafbitarray_t &visited )
-{
- // already visited, or not the same water contents
- if ( pLeaf->diskId < 0 || visited.Get(pLeaf->diskId) || !(pLeaf->contents & (baseleaf.pNode->contents & MASK_WATER) ) )
- return;
-
- int oppositeNodeIndex = 0;
- for (portal_t *p = pLeaf->portals ; p ; p = p->next[!oppositeNodeIndex])
- {
- oppositeNodeIndex = (p->nodes[0] == pLeaf) ? 1 : 0;
-
- // If any portal crosses the water surface, don't flow through this leaf
- if ( PortalCrossesWater( baseleaf, p ) )
- return;
- }
-
- visited.Set( pLeaf->diskId );
- list.AddToTail( pLeaf );
-
- baseleaf.minZ = min( pLeaf->mins.z, baseleaf.minZ );
-
- for (portal_t *p = pLeaf->portals ; p ; p = p->next[!oppositeNodeIndex])
- {
- oppositeNodeIndex = (p->nodes[0] == pLeaf) ? 1 : 0;
-
- Flood_FindConnectedWaterVolumes_r( list, p->nodes[oppositeNodeIndex], baseleaf, visited );
- }
-}
-
-// UNDONE: This is a bit of a hack to avoid crashing when we can't find an
-// appropriate texinfo for a water model (to get physics properties)
-int FirstWaterTexinfo( bspbrush_t *brushlist, int contents )
-{
- while (brushlist)
- {
- if ( brushlist->original->contents & contents )
- {
- for ( int i = 0; i < brushlist->original->numsides; i++ )
- {
- if ( brushlist->original->original_sides[i].contents & contents )
- {
- return brushlist->original->original_sides[i].texinfo;
- }
- }
- }
- brushlist = brushlist->next;
- }
-
- Assert(0);
- return 0;
-}
-
-// This is a list of water data that will be turned into physics models
-struct watermodel_t
-{
- int modelIndex;
- int contents;
- waterleaf_t waterLeafData;
- int depthTexinfo;
- int firstWaterLeafIndex;
- int waterLeafCount;
- int fogVolumeIndex;
-};
-
-static CUtlVector<watermodel_t> g_WaterModels;
-static CUtlVector<int> g_WaterLeafList;
-
-// Creates a list of watermodel_t for later processing by EmitPhysCollision
-void EmitWaterVolumesForBSP( dmodel_t *pModel, node_t *node )
-{
- CUtlVector<node_t *> leafListAnyWater;
- // build the list of all leaves containing water
- EnumLeaves_r( leafListAnyWater, node, MASK_WATER );
-
- // make a sorted list to flood fill
- CUtlVector<waterleaf_t> list;
-
- int i;
- for ( i = 0; i < leafListAnyWater.Count(); i++ )
- {
- waterleaf_t waterLeaf;
- BuildWaterLeaf( leafListAnyWater[i], waterLeaf );
- InsertSortWaterLeaf( list, waterLeaf );
- }
-
- leafbitarray_t visited;
- CUtlVector<node_t *> waterAreaList;
- for ( i = 0; i < list.Count(); i++ )
- {
- Flood_FindConnectedWaterVolumes_r( waterAreaList, list[i].pNode, list[i], visited );
-
- // did we find a list of leaves connected to this one?
- // remember the list is sorted, so this one may have been attached to a previous
- // leaf. So it could have nothing hanging off of it.
- if ( waterAreaList.Count() )
- {
- // yes, emit a watermodel
- watermodel_t tmp;
- tmp.modelIndex = nummodels;
- tmp.contents = list[i].pNode->contents;
- tmp.waterLeafData = list[i];
- tmp.firstWaterLeafIndex = g_WaterLeafList.Count();
- tmp.waterLeafCount = waterAreaList.Count();
-
- float waterDepth = tmp.waterLeafData.surfaceDist - tmp.waterLeafData.minZ;
- if ( tmp.waterLeafData.surfaceTexInfo < 0 )
- {
- // the map has probably leaked in this case, but output something anyway.
- Assert(list[i].pNode->planenum == PLANENUM_LEAF);
- tmp.waterLeafData.surfaceTexInfo = FirstWaterTexinfo( list[i].pNode->brushlist, tmp.contents );
- }
- tmp.depthTexinfo = FindOrCreateWaterTexInfo( &texinfo[ tmp.waterLeafData.surfaceTexInfo ], waterDepth );
- tmp.fogVolumeIndex = FindOrCreateLeafWaterData( tmp.waterLeafData.surfaceDist, tmp.waterLeafData.minZ, tmp.waterLeafData.surfaceTexInfo );
-
- for ( int j = 0; j < waterAreaList.Count(); j++ )
- {
- g_WaterLeafList.AddToTail( waterAreaList[j]->diskId );
- }
- waterAreaList.RemoveAll();
- g_WaterModels.AddToTail( tmp );
- }
- }
-
- WriteFogVolumeIDs( pModel );
-}
-
-
-static void ConvertWaterModelToPhysCollide( CUtlVector<CPhysCollisionEntry *> &collisionList, int modelIndex,
- float shrinkSize, float mergeTolerance )
-{
- dmodel_t *pModel = dmodels + modelIndex;
-
- for ( int i = 0; i < g_WaterModels.Count(); i++ )
- {
- watermodel_t &waterModel = g_WaterModels[i];
- if ( waterModel.modelIndex != modelIndex )
- continue;
-
- CPlaneList planes( shrinkSize, mergeTolerance );
- int firstLeaf = waterModel.firstWaterLeafIndex;
- planes.m_contentsMask = waterModel.contents;
-
- // push all of the leaves into the collision list
- for ( int j = 0; j < waterModel.waterLeafCount; j++ )
- {
- int leafIndex = g_WaterLeafList[firstLeaf + j];
-
- dleaf_t *pLeaf = dleafs + leafIndex;
- // fixup waterdata
- pLeaf->leafWaterDataID = waterModel.fogVolumeIndex;
- planes.ReferenceLeaf( leafIndex );
- }
-
- // visit the referenced leaves that belong to this model
- VisitLeaves_r( planes, pModel->headnode );
-
- // Now add the brushes from those leaves as convex
-
- // BUGBUG: NOTE: If your map has a brush that crosses the surface, it will be added to two water
- // volumes. This only happens with connected water volumes with multiple surface heights
- // UNDONE: Right now map makers must cut such brushes. It could be automatically cut by adding the
- // surface plane to the list for each brush before calling ConvexFromPlanes()
- planes.AddBrushes();
-
- int count = planes.m_convex.Count();
- if ( !count )
- continue;
-
- // Save off the plane of the surface for this group as well as the collision model
- // for all convex objects in the group.
- CPhysCollide *pCollide = physcollision->ConvertConvexToCollide( planes.m_convex.Base(), count );
- if ( pCollide )
- {
- int waterSurfaceTexInfoID = -1;
- // use defaults
- const char *pSurfaceProp = "water";
- float damping = 0.01;
- if ( waterSurfaceTexInfoID >= 0 )
- {
- // material override
- int texdata = texinfo[waterSurfaceTexInfoID].texdata;
- int prop = g_SurfaceProperties[texdata];
- pSurfaceProp = physprops->GetPropName( prop );
- }
-
- if ( !waterModel.waterLeafData.hasSurface )
- {
- waterModel.waterLeafData.surfaceNormal.Init( 0,0,1 );
- Vector top = physcollision->CollideGetExtent( pCollide, vec3_origin, vec3_angle, waterModel.waterLeafData.surfaceNormal );
- waterModel.waterLeafData.surfaceDist = top.z;
- }
- CPhysCollisionEntryFluid *pCollisionEntryFuild = new CPhysCollisionEntryFluid( pCollide,
- pSurfaceProp, damping, waterModel.waterLeafData.surfaceNormal, waterModel.waterLeafData.surfaceDist, waterModel.contents );
- collisionList.AddToTail( pCollisionEntryFuild );
- }
- }
-}
-
-// compute a normal for a triangle of the given three points (points are clockwise, normal points out)
-static Vector TriangleNormal( const Vector &p0, const Vector &p1, const Vector &p2 )
-{
- Vector e0 = p1 - p0;
- Vector e1 = p2 - p0;
- Vector normal = CrossProduct( e1, e0 );
- VectorNormalize( normal );
-
- return normal;
-}
-
-
-// find the side of the brush with the normal closest to the given normal
-static dbrushside_t *FindBrushSide( int brushIndex, const Vector &normal )
-{
- dbrush_t *pbrush = &dbrushes[brushIndex];
- dbrushside_t *out = NULL;
- float best = -1.f;
-
- for ( int i = 0; i < pbrush->numsides; i++ )
- {
- dbrushside_t *pside = dbrushsides + i + pbrush->firstside;
- dplane_t *pplane = dplanes + pside->planenum;
- float dot = DotProduct( normal, pplane->normal );
- if ( dot > best )
- {
- best = dot;
- out = pside;
- }
- }
-
- return out;
-}
-
-
-
-static void ConvertWorldBrushesToPhysCollide( CUtlVector<CPhysCollisionEntry *> &collisionList, float shrinkSize, float mergeTolerance, int contentsMask )
-{
- CPlaneList planes( shrinkSize, mergeTolerance );
-
- planes.m_contentsMask = contentsMask;
-
- VisitLeaves_r( planes, dmodels[0].headnode );
- planes.AddBrushes();
-
- int count = planes.m_convex.Count();
- if ( count )
- {
- CPhysCollide *pCollide = physcollision->ConvertConvexToCollide( planes.m_convex.Base(), count );
-
- ICollisionQuery *pQuery = physcollision->CreateQueryModel( pCollide );
- int convex = pQuery->ConvexCount();
- for ( int i = 0; i < convex; i++ )
- {
- int triCount = pQuery->TriangleCount( i );
- int brushIndex = pQuery->GetGameData( i );
-
- Vector points[3];
- for ( int j = 0; j < triCount; j++ )
- {
- pQuery->GetTriangleVerts( i, j, points );
- Vector normal = TriangleNormal( points[0], points[1], points[2] );
- dbrushside_t *pside = FindBrushSide( brushIndex, normal );
- if ( pside->texinfo != TEXINFO_NODE )
- {
- int prop = g_SurfaceProperties[texinfo[pside->texinfo].texdata];
- pQuery->SetTriangleMaterialIndex( i, j, RemapWorldMaterial( prop ) );
- }
- }
- }
- physcollision->DestroyQueryModel( pQuery );
- pQuery = NULL;
-
- collisionList.AddToTail( new CPhysCollisionEntryStaticSolid( pCollide, contentsMask ) );
- }
-}
-
-// adds any world, terrain, and water collision models to the collision list
-static void BuildWorldPhysModel( CUtlVector<CPhysCollisionEntry *> &collisionList, float shrinkSize, float mergeTolerance )
-{
- ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, MASK_SOLID );
- ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, CONTENTS_PLAYERCLIP );
- ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, CONTENTS_MONSTERCLIP );
-
- if ( !g_bNoVirtualMesh && Disp_HasPower4Displacements() )
- {
- Warning("WARNING: Map using power 4 displacements, terrain physics cannot be compressed, map will need additional memory and CPU.\n");
- g_bNoVirtualMesh = true;
- }
-
- // if there's terrain, save it off as a static mesh/polysoup
- if ( g_bNoVirtualMesh || !physcollision->SupportsVirtualMesh() )
- {
- Disp_AddCollisionModels( collisionList, &dmodels[0], MASK_SOLID );
- }
- else
- {
- Disp_BuildVirtualMesh( MASK_SOLID );
- }
- ConvertWaterModelToPhysCollide( collisionList, 0, shrinkSize, mergeTolerance );
-}
-
-
-// adds a collision entry for this brush model
-static void ConvertModelToPhysCollide( CUtlVector<CPhysCollisionEntry *> &collisionList, int modelIndex, int contents, float shrinkSize, float mergeTolerance )
-{
- int i;
- CPlaneList planes( shrinkSize, mergeTolerance );
-
- planes.m_contentsMask = contents;
-
- dmodel_t *pModel = dmodels + modelIndex;
- VisitLeaves_r( planes, pModel->headnode );
- planes.AddBrushes();
- int count = planes.m_convex.Count();
- convertconvexparams_t params;
- params.Defaults();
- params.buildOuterConvexHull = count > 1 ? true : false;
- params.buildDragAxisAreas = true;
- Vector size = pModel->maxs - pModel->mins;
-
- float minSurfaceArea = -1.0f;
- for ( i = 0; i < 3; i++ )
- {
- int other = (i+1)%3;
- int cross = (i+2)%3;
- float surfaceArea = size[other] * size[cross];
- if ( minSurfaceArea < 0 || surfaceArea < minSurfaceArea )
- {
- minSurfaceArea = surfaceArea;
- }
- }
- // this can be really slow with super-large models and a low error tolerance
- // Basically you get a ray cast through each square of epsilon surface area on each OBB side
- // So compute it for 1% error (on the smallest side, less on larger sides)
- params.dragAreaEpsilon = clamp( minSurfaceArea * 1e-2f, 1.0f, 1024.0f );
- CPhysCollide *pCollide = physcollision->ConvertConvexToCollideParams( planes.m_convex.Base(), count, params );
-
- if ( !pCollide )
- return;
-
- struct
- {
- int prop;
- float area;
- } proplist[256];
- int numprops = 1;
-
- proplist[0].prop = -1;
- proplist[0].area = 1;
- // compute the array of props on the surface of this model
-
- // NODRAW brushes no longer have any faces
- if ( !dmodels[modelIndex].numfaces )
- {
- int sideIndex = planes.GetFirstBrushSide();
- int texdata = texinfo[dbrushsides[sideIndex].texinfo].texdata;
- int prop = g_SurfaceProperties[texdata];
- proplist[numprops].prop = prop;
- proplist[numprops].area = 2;
- numprops++;
- }
-
- for ( i = 0; i < dmodels[modelIndex].numfaces; i++ )
- {
- dface_t *face = dfaces + i + dmodels[modelIndex].firstface;
- int texdata = texinfo[face->texinfo].texdata;
- int prop = g_SurfaceProperties[texdata];
- int j;
- for ( j = 0; j < numprops; j++ )
- {
- if ( proplist[j].prop == prop )
- {
- proplist[j].area += face->area;
- break;
- }
- }
-
- if ( (!numprops || j >= numprops) && numprops < ARRAYSIZE(proplist) )
- {
- proplist[numprops].prop = prop;
- proplist[numprops].area = face->area;
- numprops++;
- }
- }
-
-
- // choose the prop with the most surface area
- int maxIndex = -1;
- float maxArea = 0;
- float totalArea = 0;
-
- for ( i = 0; i < numprops; i++ )
- {
- if ( proplist[i].area > maxArea )
- {
- maxIndex = i;
- maxArea = proplist[i].area;
- }
- // add up the total surface area
- totalArea += proplist[i].area;
- }
-
- float mass = 1.0f;
- const char *pMaterial = "default";
- if ( maxIndex >= 0 )
- {
- int prop = proplist[maxIndex].prop;
-
- // use default if this material has no prop
- if ( prop < 0 )
- prop = 0;
-
- pMaterial = physprops->GetPropName( prop );
- float density, thickness;
- physprops->GetPhysicsProperties( prop, &density, &thickness, NULL, NULL );
-
- // if this is a "shell" material (it is hollow and encloses some empty space)
- // compute the mass with a constant surface thickness
- if ( thickness != 0 )
- {
- mass = totalArea * thickness * density * CUBIC_METERS_PER_CUBIC_INCH;
- }
- else
- {
- // material is completely solid, compute total mass as if constant density throughout.
- mass = planes.m_totalVolume * density * CUBIC_METERS_PER_CUBIC_INCH;
- }
- }
-
- // Clamp mass to 100,000 kg
- if ( mass > VPHYSICS_MAX_MASS )
- {
- mass = VPHYSICS_MAX_MASS;
- }
-
- collisionList.AddToTail( new CPhysCollisionEntrySolid( pCollide, pMaterial, mass ) );
-}
-
-static void ClearLeafWaterData( void )
-{
- int i;
-
- for( i = 0; i < numleafs; i++ )
- {
- dleafs[i].leafWaterDataID = -1;
- dleafs[i].contents &= ~CONTENTS_TESTFOGVOLUME;
- }
-}
-
-
-// This is the only public entry to this file.
-// The global data touched in the file is:
-// from bsplib.h:
-// g_pPhysCollide : This is an output from this file.
-// g_PhysCollideSize : This is set in this file.
-// g_numdispinfo : This is an input to this file.
-// g_dispinfo : This is an input to this file.
-// numnodewaterdata : This is an output from this file.
-// dleafwaterdata : This is an output from this file.
-// from vbsp.h:
-// g_SurfaceProperties : This is an input to this file.
-void EmitPhysCollision()
-{
- ClearLeafWaterData();
-
- CreateInterfaceFn physicsFactory = GetPhysicsFactory();
- if ( physicsFactory )
- {
- physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL );
- }
-
- if ( !physcollision )
- {
- Warning("!!! WARNING: Can't build collision data!\n" );
- return;
- }
-
- CUtlVector<CPhysCollisionEntry *> collisionList[MAX_MAP_MODELS];
- CTextBuffer *pTextBuffer[MAX_MAP_MODELS];
-
- int physModelCount = 0, totalSize = 0;
-
- int start = Plat_FloatTime();
-
- Msg("Building Physics collision data...\n" );
-
- int i, j;
- for ( i = 0; i < nummodels; i++ )
- {
- // Build a list of collision models for this brush model section
- if ( i == 0 )
- {
- // world is the only model that processes water separately.
- // other brushes are assumed to be completely solid or completely liquid
- BuildWorldPhysModel( collisionList[i], NO_SHRINK, VPHYSICS_MERGE);
- }
- else
- {
- ConvertModelToPhysCollide( collisionList[i], i, MASK_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|MASK_WATER, VPHYSICS_SHRINK, VPHYSICS_MERGE );
- }
-
- pTextBuffer[i] = NULL;
- if ( !collisionList[i].Count() )
- continue;
-
- // if we've got collision models, write their script for processing in the game
- pTextBuffer[i] = new CTextBuffer;
- for ( j = 0; j < collisionList[i].Count(); j++ )
- {
- // dump a text file for visualization
- if ( dumpcollide )
- {
- collisionList[i][j]->DumpCollide( pTextBuffer[i], i, j );
- }
- // each model knows how to write its script
- collisionList[i][j]->WriteToTextBuffer( pTextBuffer[i], i, j );
- // total up the binary section's size
- totalSize += collisionList[i][j]->GetCollisionBinarySize() + sizeof(int);
- }
-
- // These sections only appear in the world's collision text
- if ( i == 0 )
- {
- if ( !g_bNoVirtualMesh && physcollision->SupportsVirtualMesh() )
- {
- pTextBuffer[i]->WriteText("virtualterrain {}\n");
- }
- if ( s_WorldPropList.Count() )
- {
- pTextBuffer[i]->WriteText( "materialtable {\n" );
- for ( j = 0; j < s_WorldPropList.Count(); j++ )
- {
- int propIndex = s_WorldPropList[j];
- if ( propIndex < 0 )
- {
- pTextBuffer[i]->WriteIntKey( "default", j+1 );
- }
- else
- {
- pTextBuffer[i]->WriteIntKey( physprops->GetPropName( propIndex ), j+1 );
- }
- }
- pTextBuffer[i]->WriteText( "}\n" );
- }
- }
-
- pTextBuffer[i]->Terminate();
-
- // total lump size includes the text buffers (scripts)
- totalSize += pTextBuffer[i]->GetSize();
-
- physModelCount++;
- }
-
- // add one for tail of list marker
- physModelCount++;
-
- // DWORD align the lump because AddLump assumes that it is DWORD aligned.
- byte *ptr ;
- g_PhysCollideSize = totalSize + (physModelCount * sizeof(dphysmodel_t));
- g_pPhysCollide = (byte *)malloc(( g_PhysCollideSize + 3 ) & ~3 );
- memset( g_pPhysCollide, 0, g_PhysCollideSize );
- ptr = g_pPhysCollide;
-
- for ( i = 0; i < nummodels; i++ )
- {
- if ( pTextBuffer[i] )
- {
- int j;
-
- dphysmodel_t model;
-
- model.modelIndex = i;
- model.solidCount = collisionList[i].Count();
- model.dataSize = sizeof(int) * model.solidCount;
-
- for ( j = 0; j < model.solidCount; j++ )
- {
- model.dataSize += collisionList[i][j]->GetCollisionBinarySize();
- }
- model.keydataSize = pTextBuffer[i]->GetSize();
-
- // store the header
- memcpy( ptr, &model, sizeof(model) );
- ptr += sizeof(model);
-
- for ( j = 0; j < model.solidCount; j++ )
- {
- int collideSize = collisionList[i][j]->GetCollisionBinarySize();
-
- // write size
- memcpy( ptr, &collideSize, sizeof(int) );
- ptr += sizeof(int);
-
- // now write the collision model
- collisionList[i][j]->WriteCollisionBinary( reinterpret_cast<char *>(ptr) );
- ptr += collideSize;
- }
-
- memcpy( ptr, pTextBuffer[i]->GetData(), pTextBuffer[i]->GetSize() );
- ptr += pTextBuffer[i]->GetSize();
- }
-
- delete pTextBuffer[i];
- }
-
- dphysmodel_t model;
-
- // Mark end of list
- model.modelIndex = -1;
- model.dataSize = -1;
- model.keydataSize = 0;
- model.solidCount = 0;
- memcpy( ptr, &model, sizeof(model) );
- ptr += sizeof(model);
- Assert( (ptr-g_pPhysCollide) == g_PhysCollideSize);
- Msg("done (%d) (%d bytes)\n", (int)(Plat_FloatTime() - start), g_PhysCollideSize );
-
- // UNDONE: Collision models (collisionList) memory leak!
-}
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <stdio.h> +#include "mathlib/vector.h" +#include "bspfile.h" +#include "bsplib.h" +#include "cmdlib.h" +#include "physdll.h" +#include "utlvector.h" +#include "vbsp.h" +#include "phyfile.h" +#include <float.h> +#include "KeyValues.h" +#include "UtlBuffer.h" +#include "utlsymbol.h" +#include "utlrbtree.h" +#include "ivp.h" +#include "disp_ivp.h" +#include "materialpatch.h" +#include "bitvec.h" + +// bit per leaf +typedef CBitVec<MAX_MAP_LEAFS> leafbitarray_t; + +// parameters for conversion to vphysics +#define NO_SHRINK 0.0f +// NOTE: vphysics maintains a minimum separation radius between objects +// This radius is set to 0.25, but it's symmetric. So shrinking potentially moveable +// brushes by 0.5 in every direction ensures that these brushes can be constructed +// touching the world, and constrained in place without collisions or friction +// UNDONE: Add a key to disable this shrinking if necessary +#define VPHYSICS_SHRINK (0.5f) // shrink BSP brushes by this much for collision +#define VPHYSICS_MERGE 0.01f // merge verts closer than this + +void EmitPhysCollision(); + +IPhysicsCollision *physcollision = NULL; +extern IPhysicsSurfaceProps *physprops; + +// a list of all of the materials in the world model +static CUtlVector<int> s_WorldPropList; + +//----------------------------------------------------------------------------- +// Purpose: Write key/value pairs out to a memory buffer +//----------------------------------------------------------------------------- +CTextBuffer::CTextBuffer( void ) +{ +} +CTextBuffer::~CTextBuffer( void ) +{ +} + +void CTextBuffer::WriteText( const char *pText ) +{ + int len = strlen( pText ); + CopyData( pText, len ); +} + +void CTextBuffer::WriteIntKey( const char *pKeyName, int outputData ) +{ + char tmp[1024]; + + // FAIL! + if ( strlen(pKeyName) > 1000 ) + { + Msg("Error writing collision data %s\n", pKeyName ); + return; + } + sprintf( tmp, "\"%s\" \"%d\"\n", pKeyName, outputData ); + CopyData( tmp, strlen(tmp) ); +} + +void CTextBuffer::WriteStringKey( const char *pKeyName, const char *outputData ) +{ + CopyStringQuotes( pKeyName ); + CopyData( " ", 1 ); + CopyStringQuotes( outputData ); + CopyData( "\n", 1 ); +} + +void CTextBuffer::WriteFloatKey( const char *pKeyName, float outputData ) +{ + char tmp[1024]; + + // FAIL! + if ( strlen(pKeyName) > 1000 ) + { + Msg("Error writing collision data %s\n", pKeyName ); + return; + } + sprintf( tmp, "\"%s\" \"%f\"\n", pKeyName, outputData ); + CopyData( tmp, strlen(tmp) ); +} + +void CTextBuffer::WriteFloatArrayKey( const char *pKeyName, const float *outputData, int count ) +{ + char tmp[1024]; + + // FAIL! + if ( strlen(pKeyName) > 1000 ) + { + Msg("Error writing collision data %s\n", pKeyName ); + return; + } + sprintf( tmp, "\"%s\" \"", pKeyName ); + for ( int i = 0; i < count; i++ ) + { + char buf[80]; + + sprintf( buf, "%f ", outputData[i] ); + strcat( tmp, buf ); + } + strcat( tmp, "\"\n" ); + + CopyData( tmp, strlen(tmp) ); +} + +void CTextBuffer::CopyStringQuotes( const char *pString ) +{ + CopyData( "\"", 1 ); + CopyData( pString, strlen(pString) ); + CopyData( "\"", 1 ); +} + +void CTextBuffer::Terminate( void ) +{ + CopyData( "\0", 1 ); +} + +void CTextBuffer::CopyData( const char *pData, int len ) +{ + int offset = m_buffer.AddMultipleToTail( len ); + memcpy( m_buffer.Base() + offset, pData, len ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Writes a glview text file containing the collision surface in question +// Input : *pCollide - +// *pFilename - +//----------------------------------------------------------------------------- +void DumpCollideToGlView( CPhysCollide *pCollide, const char *pFilename ) +{ + if ( !pCollide ) + return; + + Msg("Writing %s...\n", pFilename ); + Vector *outVerts; + int vertCount = physcollision->CreateDebugMesh( pCollide, &outVerts ); + FILE *fp = fopen( pFilename, "w" ); + int triCount = vertCount / 3; + int vert = 0; + for ( int i = 0; i < triCount; i++ ) + { + fprintf( fp, "3\n" ); + fprintf( fp, "%6.3f %6.3f %6.3f 1 0 0\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z ); + vert++; + fprintf( fp, "%6.3f %6.3f %6.3f 0 1 0\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z ); + vert++; + fprintf( fp, "%6.3f %6.3f %6.3f 0 0 1\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z ); + vert++; + } + fclose( fp ); + physcollision->DestroyDebugMesh( vertCount, outVerts ); +} + + +void DumpCollideToPHY( CPhysCollide *pCollide, CTextBuffer *text, const char *pFilename ) +{ + Msg("Writing %s...\n", pFilename ); + FILE *fp = fopen( pFilename, "wb" ); + phyheader_t header; + header.size = sizeof(header); + header.id = 0; + header.checkSum = 0; + header.solidCount = 1; + fwrite( &header, sizeof(header), 1, fp ); + int size = physcollision->CollideSize( pCollide ); + fwrite( &size, sizeof(int), 1, fp ); + + char *buf = (char *)malloc( size ); + physcollision->CollideWrite( buf, pCollide ); + fwrite( buf, size, 1, fp ); + + fwrite( text->GetData(), text->GetSize(), 1, fp ); + fclose( fp ); + free( buf ); +} + +CPhysCollisionEntry::CPhysCollisionEntry( CPhysCollide *pCollide ) +{ + m_pCollide = pCollide; +} + +unsigned int CPhysCollisionEntry::GetCollisionBinarySize() +{ + return physcollision->CollideSize( m_pCollide ); +} + +unsigned int CPhysCollisionEntry::WriteCollisionBinary( char *pDest ) +{ + return physcollision->CollideWrite( pDest, m_pCollide ); +} + +void CPhysCollisionEntry::DumpCollideFileName( const char *pName, int modelIndex, CTextBuffer *pTextBuffer ) +{ + char tmp[128]; + sprintf( tmp, "%s%03d.phy", pName, modelIndex ); + DumpCollideToPHY( m_pCollide, pTextBuffer, tmp ); + sprintf( tmp, "%s%03d.txt", pName, modelIndex ); + DumpCollideToGlView( m_pCollide, tmp ); +} + + +class CPhysCollisionEntrySolid : public CPhysCollisionEntry +{ +public: + CPhysCollisionEntrySolid( CPhysCollide *pCollide, const char *pMaterialName, float mass ); + + virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + +private: + float m_volume; + float m_mass; + const char *m_pMaterial; +}; + + +CPhysCollisionEntrySolid::CPhysCollisionEntrySolid( CPhysCollide *pCollide, const char *pMaterialName, float mass ) + : CPhysCollisionEntry( pCollide ) +{ + m_volume = physcollision->CollideVolume( m_pCollide ); + m_mass = mass; + m_pMaterial = pMaterialName; +} + +void CPhysCollisionEntrySolid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + DumpCollideFileName( "collide", modelIndex, pTextBuffer ); +} + +void CPhysCollisionEntrySolid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + pTextBuffer->WriteText( "solid {\n" ); + pTextBuffer->WriteIntKey( "index", collideIndex ); + pTextBuffer->WriteFloatKey( "mass", m_mass ); + if ( m_pMaterial ) + { + pTextBuffer->WriteStringKey( "surfaceprop", m_pMaterial ); + } + if ( m_volume != 0.f ) + { + pTextBuffer->WriteFloatKey( "volume", m_volume ); + } + pTextBuffer->WriteText( "}\n" ); +} + + +class CPhysCollisionEntryStaticSolid : public CPhysCollisionEntry +{ +public: + CPhysCollisionEntryStaticSolid ( CPhysCollide *pCollide, int contentsMask ); + + virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + +private: + int m_contentsMask; +}; + + +CPhysCollisionEntryStaticSolid ::CPhysCollisionEntryStaticSolid ( CPhysCollide *pCollide, int contentsMask ) + : CPhysCollisionEntry( pCollide ), m_contentsMask(contentsMask) +{ +} + +void CPhysCollisionEntryStaticSolid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + char tmp[128]; + sprintf( tmp, "static%02d", modelIndex ); + DumpCollideFileName( tmp, collideIndex, pTextBuffer ); +} + +void CPhysCollisionEntryStaticSolid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + pTextBuffer->WriteText( "staticsolid {\n" ); + pTextBuffer->WriteIntKey( "index", collideIndex ); + pTextBuffer->WriteIntKey( "contents", m_contentsMask ); + pTextBuffer->WriteText( "}\n" ); +} + +CPhysCollisionEntryStaticMesh::CPhysCollisionEntryStaticMesh( CPhysCollide *pCollide, const char *pMaterialName ) + : CPhysCollisionEntry( pCollide ) +{ + m_pMaterial = pMaterialName; +} + +void CPhysCollisionEntryStaticMesh::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + char tmp[128]; + sprintf( tmp, "mesh%02d", modelIndex ); + DumpCollideFileName( tmp, collideIndex, pTextBuffer ); +} + +void CPhysCollisionEntryStaticMesh::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + pTextBuffer->WriteText( "staticsolid {\n" ); + pTextBuffer->WriteIntKey( "index", collideIndex ); + pTextBuffer->WriteText( "}\n" ); +} + +class CPhysCollisionEntryFluid : public CPhysCollisionEntry +{ +public: + ~CPhysCollisionEntryFluid(); + CPhysCollisionEntryFluid( CPhysCollide *pCollide, const char *pSurfaceProp, float damping, const Vector &normal, float dist, int nContents ); + + virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + +private: + char *m_pSurfaceProp; + float m_damping; + Vector m_surfaceNormal; + float m_surfaceDist; + int m_contentsMask; +}; + + +CPhysCollisionEntryFluid::CPhysCollisionEntryFluid( CPhysCollide *pCollide, const char *pSurfaceProp, float damping, const Vector &normal, float dist, int nContents ) + : CPhysCollisionEntry( pCollide ) +{ + m_surfaceNormal = normal; + m_surfaceDist = dist; + m_pSurfaceProp = new char[strlen(pSurfaceProp)+1]; + strcpy( m_pSurfaceProp, pSurfaceProp ); + m_damping = damping; + m_contentsMask = nContents; +} + +CPhysCollisionEntryFluid::~CPhysCollisionEntryFluid() +{ + delete[] m_pSurfaceProp; +} + +void CPhysCollisionEntryFluid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + char tmp[128]; + sprintf( tmp, "water%02d", modelIndex ); + DumpCollideFileName( tmp, collideIndex, pTextBuffer ); +} + +void CPhysCollisionEntryFluid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + pTextBuffer->WriteText( "fluid {\n" ); + pTextBuffer->WriteIntKey( "index", collideIndex ); + pTextBuffer->WriteStringKey( "surfaceprop", m_pSurfaceProp ); // write out water material + pTextBuffer->WriteFloatKey( "damping", m_damping ); // write out water damping + pTextBuffer->WriteIntKey( "contents", m_contentsMask ); // write out water contents + float array[4]; + m_surfaceNormal.CopyToArray( array ); + array[3] = m_surfaceDist; + pTextBuffer->WriteFloatArrayKey( "surfaceplane", array, 4 ); // write out water surface plane + pTextBuffer->WriteFloatArrayKey( "currentvelocity", vec3_origin.Base(), 3 ); // write out water velocity + pTextBuffer->WriteText( "}\n" ); +} + +// Get an index into the prop list of this prop (add it if necessary) +static int PropIndex( CUtlVector<int> &propList, int propIndex ) +{ + for ( int i = 0; i < propList.Count(); i++ ) + { + if ( propList[i] == propIndex ) + return i+1; + } + + if ( propList.Count() < 126 ) + { + return propList.AddToTail( propIndex )+1; + } + + return 0; +} + +int RemapWorldMaterial( int materialIndexIn ) +{ + return PropIndex( s_WorldPropList, materialIndexIn ); +} + +typedef struct +{ + float normal[3]; + float dist; +} listplane_t; + +static void AddListPlane( CUtlVector<listplane_t> *list, float x, float y, float z, float d ) +{ + listplane_t plane; + plane.normal[0] = x; + plane.normal[1] = y; + plane.normal[2] = z; + plane.dist = d; + + list->AddToTail( plane ); +} + +class CPlaneList +{ +public: + + CPlaneList( float shrink, float merge ); + ~CPlaneList( void ); + + void AddConvex( CPhysConvex *pConvex ); + + // add the brushes to the model + int AddBrushes( void ); + + // Adds a single brush as a convex object + void ReferenceBrush( int brushnumber ); + bool IsBrushReferenced( int brushnumber ); + + void ReferenceLeaf( int leafIndex ); + bool IsLeafReferenced( int leafIndex ); + int GetFirstBrushSide(); + +private: + + CPhysConvex *BuildConvexForBrush( int brushnumber, float shrink, CPhysCollide *pCollideTest, float shrinkMinimum ); + +public: + CUtlVector<CPhysConvex *> m_convex; + + CUtlVector<int> m_leafList; + int m_contentsMask; + + float m_shrink; + float m_merge; + bool *m_brushAdded; + float m_totalVolume; +}; + +CPlaneList::CPlaneList( float shrink, float merge ) +{ + m_shrink = shrink; + m_merge = merge; + m_contentsMask = MASK_SOLID; + m_brushAdded = new bool[numbrushes]; + memset( m_brushAdded, 0, sizeof(bool) * numbrushes ); + m_totalVolume = 0; + m_leafList.Purge(); +} + + +CPlaneList::~CPlaneList( void ) +{ + delete[] m_brushAdded; +} + + +void CPlaneList::AddConvex( CPhysConvex *pConvex ) +{ + if ( pConvex ) + { + m_totalVolume += physcollision->ConvexVolume( pConvex ); + m_convex.AddToTail( pConvex ); + } +} + +// Adds a single brush as a convex object +void CPlaneList::ReferenceBrush( int brushnumber ) +{ + if ( !(dbrushes[brushnumber].contents & m_contentsMask) ) + return; + + m_brushAdded[brushnumber] = true; + +} + + +bool CPlaneList::IsBrushReferenced( int brushnumber ) +{ + return m_brushAdded[brushnumber]; +} + +CPhysConvex *CPlaneList::BuildConvexForBrush( int brushnumber, float shrink, CPhysCollide *pCollideTest, float shrinkMinimum ) +{ + CUtlVector<listplane_t> temp( 0, 32 ); + + for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ ) + { + dbrushside_t *pside = dbrushsides + i + dbrushes[brushnumber].firstside; + if ( pside->bevel ) + continue; + + dplane_t *pplane = dplanes + pside->planenum; + float shrinkThisPlane = shrink; + + if ( i < g_MainMap->mapbrushes[brushnumber].numsides ) + { + if ( !g_MainMap->mapbrushes[brushnumber].original_sides[i].visible ) + { + // don't shrink brush sides with no visible components. + // this produces something closer to the ideal shrink than simply shrinking all planes + shrinkThisPlane = 0; + } + } + // Make sure shrinking won't swallow geometry along this axis. + if ( pCollideTest && shrinkThisPlane != 0 ) + { + Vector start = physcollision->CollideGetExtent( pCollideTest, vec3_origin, vec3_angle, pplane->normal ); + Vector end = physcollision->CollideGetExtent( pCollideTest, vec3_origin, vec3_angle, -pplane->normal ); + float thick = DotProduct( (end-start), pplane->normal ); + // NOTE: The object must be at least "shrinkMinimum" inches wide on each axis + if ( fabs(thick) < shrinkMinimum ) + { +#if _DEBUG + Warning("Can't shrink brush %d, plane %d (%.2f, %.2f, %.2f)\n", brushnumber, pside->planenum, pplane->normal[0], pplane->normal[1], pplane->normal[2] ); +#endif + shrinkThisPlane = 0; + } + } + AddListPlane( &temp, pplane->normal[0], pplane->normal[1], pplane->normal[2], pplane->dist - shrinkThisPlane ); + } + return physcollision->ConvexFromPlanes( (float *)temp.Base(), temp.Count(), m_merge ); +} + +int CPlaneList::AddBrushes( void ) +{ + int count = 0; + for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ ) + { + if ( IsBrushReferenced(brushnumber) ) + { + CPhysConvex *pBrushConvex = NULL; + if ( m_shrink != 0 ) + { + // Make sure shrinking won't swallow this brush. + CPhysConvex *pConvex = BuildConvexForBrush( brushnumber, 0, NULL, 0 ); + CPhysCollide *pUnshrunkCollide = physcollision->ConvertConvexToCollide( &pConvex, 1 ); + pBrushConvex = BuildConvexForBrush( brushnumber, m_shrink, pUnshrunkCollide, m_shrink * 3 ); + physcollision->DestroyCollide( pUnshrunkCollide ); + } + else + { + pBrushConvex = BuildConvexForBrush( brushnumber, m_shrink, NULL, 1.0 ); + } + + if ( pBrushConvex ) + { + count++; + physcollision->SetConvexGameData( pBrushConvex, brushnumber ); + AddConvex( pBrushConvex ); + } + } + } + return count; +} + + +int CPlaneList::GetFirstBrushSide() +{ + for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ ) + { + if ( IsBrushReferenced(brushnumber) ) + { + for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ ) + { + int sideIndex = i + dbrushes[brushnumber].firstside; + dbrushside_t *pside = dbrushsides + sideIndex; + if ( pside->bevel ) + continue; + return sideIndex; + } + } + } + return 0; +} + +// UNDONE: Try using this kind of algorithm if we run into precision problems. +// NOTE: ConvexFromPlanes will be doing a bunch of matrix inversions that can suffer +// if plane normals are too close to each other... +#if 0 +void CPlaneList::AddBrushes( void ) +{ + CUtlVector<listplane_t> temp; + for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ ) + { + if ( IsBrushReferenced(brushnumber) ) + { + CUtlVector<winding_t *> windings; + + for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ ) + { + dbrushside_t *pside = dbrushsides + i + dbrushes[brushnumber].firstside; + if (pside->bevel) + continue; + dplane_t *pplane = dplanes + pside->planenum; + winding_t *w = BaseWindingForPlane( pplane->normal, pplane->dist - m_shrink ); + for ( int j = 0; j < dbrushes[brushnumber].numsides && w; j++ ) + { + if (i == j) + continue; + dbrushside_t *pClipSide = dbrushsides + j + dbrushes[brushnumber].firstside; + if (pClipSide->bevel) + continue; + dplane_t *pClipPlane = dplanes + pClipSide->planenum; + ChopWindingInPlace (&w, -pClipPlane->normal, -pClipPlane->dist+m_shrink, 0); //CLIP_EPSILON); + } + if ( w ) + { + windings.AddToTail( w ); + } + } + + CUtlVector<Vector *> vertList; + for ( int p = 0; p < windings.Count(); p++ ) + { + for ( int v = 0; v < windings[p]->numpoints; v++ ) + { + vertList.AddToTail( windings[p]->p + v ); + } + } + CPhysConvex *pConvex = physcollision->ConvexFromVerts( vertList.Base(), vertList.Count() ); + if ( pConvex ) + { + physcollision->SetConvexGameData( pConvex, brushnumber ); + AddConvex( pConvex ); + } + temp.RemoveAll(); + } + } +} +#endif + +// If I have a list of leaves, make sure this leaf is in it. +// Otherwise, process all leaves +bool CPlaneList::IsLeafReferenced( int leafIndex ) +{ + if ( !m_leafList.Count() ) + return true; + + for ( int i = 0; i < m_leafList.Count(); i++ ) + { + if ( m_leafList[i] == leafIndex ) + return true; + } + + return false; +} + +// Add a leaf to my list of interesting leaves +void CPlaneList::ReferenceLeaf( int leafIndex ) +{ + m_leafList.AddToTail( leafIndex ); +} + +static void VisitLeaves_r( CPlaneList &planes, int node ) +{ + if ( node < 0 ) + { + int leafIndex = -1 - node; + if ( planes.IsLeafReferenced(leafIndex) ) + { + int i; + + // Add the solids in the "empty" leaf + for ( i = 0; i < dleafs[leafIndex].numleafbrushes; i++ ) + { + int brushIndex = dleafbrushes[dleafs[leafIndex].firstleafbrush + i]; + planes.ReferenceBrush( brushIndex ); + } + } + } + else + { + dnode_t *pnode = dnodes + node; + + VisitLeaves_r( planes, pnode->children[0] ); + VisitLeaves_r( planes, pnode->children[1] ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +struct waterleaf_t +{ + Vector surfaceNormal; + float surfaceDist; + float minZ; + bool hasSurface; + int waterLeafIndex;// this is the submerged leaf + int planenum; //UNDONE: REMOVE + int surfaceTexInfo; // if hasSurface == true, this is the texinfo index for the water material + int outsideLeafIndex;// this is the leaf on the other side of the water surface + node_t *pNode; +}; + + + +// returns true if newleaf should appear before currentleaf in the list +static bool IsLowerLeaf( const waterleaf_t &newleaf, const waterleaf_t ¤tleaf ) +{ + if ( newleaf.hasSurface && currentleaf.hasSurface ) + { + // the one with the upmost pointing z goes first + if ( currentleaf.surfaceNormal.z > newleaf.surfaceNormal.z ) + return false; + + if ( fabs(currentleaf.surfaceNormal.z - newleaf.surfaceNormal.z) < 0.01 ) + { + if ( newleaf.surfaceDist < currentleaf.surfaceDist ) + return true; + } + return true; + } + else if ( newleaf.hasSurface ) // the leaf with a surface always goes first + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Water surfaces are stored in an RB tree and the tree is used to +// create one-off .vmt files embedded in the .bsp for each surface so that the +// water depth effect occurs on a per-water surface level. +//----------------------------------------------------------------------------- +struct WaterTexInfo +{ + // The mangled new .vmt name ( materials/levelename/oldmaterial_depth_xxx ) where xxx is + // the water depth (as an integer ) + CUtlSymbol m_FullName; + + // The original .vmt name + CUtlSymbol m_MaterialName; + + // The depth of the water this texinfo refers to + int m_nWaterDepth; + + // The texinfo id + int m_nTexInfo; + + // The subdivision size for the water surface +// float m_SubdivSize; +}; + +//----------------------------------------------------------------------------- +// Purpose: Helper for RB tree operations ( we compare full mangled names ) +// Input : src1 - +// src2 - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool WaterLessFunc( WaterTexInfo const& src1, WaterTexInfo const& src2 ) +{ + return src1.m_FullName < src2.m_FullName; +} + +//----------------------------------------------------------------------------- +// Purpose: A growable RB tree of water surfaces +//----------------------------------------------------------------------------- +static CUtlRBTree< WaterTexInfo, int > g_WaterTexInfos( 0, 32, WaterLessFunc ); + +#if 0 +float GetSubdivSizeForFogVolume( int fogVolumeID ) +{ + Assert( fogVolumeID >= 0 && fogVolumeID < g_WaterTexInfos.Count() ); + return g_WaterTexInfos[fogVolumeID].m_SubdivSize; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mapname - +// *materialname - +// waterdepth - +// *fullname - +//----------------------------------------------------------------------------- +void GetWaterTextureName( char const *mapname, char const *materialname, int waterdepth, char *fullname ) +{ + char temp[ 512 ]; + + // Construct the full name (prepend mapname to reduce name collisions) + sprintf( temp, "maps/%s/%s_depth_%i", mapname, materialname, (int)waterdepth ); + + // Make sure it's lower case + strlwr( temp ); + + strcpy( fullname, temp ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called to write procedural materials in the rb tree to the embedded +// pak file for this .bsp +//----------------------------------------------------------------------------- +void EmitWaterMaterialFile( WaterTexInfo *wti ) +{ + char waterTextureName[512]; + if ( !wti ) + { + return; + } + + GetWaterTextureName( mapbase, wti->m_MaterialName.String(), ( int )wti->m_nWaterDepth, waterTextureName ); + + // Convert to string + char szDepth[ 32 ]; + sprintf( szDepth, "%i", wti->m_nWaterDepth ); + CreateMaterialPatch( wti->m_MaterialName.String(), waterTextureName, "$waterdepth", szDepth, PATCH_INSERT ); +} + +//----------------------------------------------------------------------------- +// Purpose: Takes the texinfo_t referenced by the .vmt and the computed depth for the +// surface and looks up or creates a texdata/texinfo for the mangled one-off water .vmt file +// Input : *pBaseInfo - +// depth - +// Output : int +//----------------------------------------------------------------------------- +int FindOrCreateWaterTexInfo( texinfo_t *pBaseInfo, float depth ) +{ + char fullname[ 512 ]; + char materialname[ 512 ]; + + // Get the base texture/material name + char const *name = TexDataStringTable_GetString( GetTexData( pBaseInfo->texdata )->nameStringTableID ); + + GetWaterTextureName( mapbase, name, (int)depth, fullname ); + + // See if we already have an entry for this depth + WaterTexInfo lookup; + lookup.m_FullName = fullname; + int idx = g_WaterTexInfos.Find( lookup ); + + // If so, return the existing entry texinfo index + if ( idx != g_WaterTexInfos.InvalidIndex() ) + { + return g_WaterTexInfos[ idx ].m_nTexInfo; + } + + // Otherwise, fill in the rest of the data + lookup.m_nWaterDepth = (int)depth; + // Remember the current material name + sprintf( materialname, "%s", name ); + strlwr( materialname ); + lookup.m_MaterialName = materialname; + + texinfo_t ti; + // Make a copy + ti = *pBaseInfo; + // Create a texdata that is based on the underlying existing entry + ti.texdata = FindAliasedTexData( fullname, GetTexData( pBaseInfo->texdata ) ); + + // Find or create a new index + lookup.m_nTexInfo = FindOrCreateTexInfo( ti ); + + // Add the new texinfo to the RB tree + idx = g_WaterTexInfos.Insert( lookup ); + + // Msg( "created texinfo for %s\n", lookup.m_FullName.String() ); + + // Go ahead and create the new vmt file. + EmitWaterMaterialFile( &g_WaterTexInfos[idx] ); + + // Return the new texinfo + return g_WaterTexInfos[ idx ].m_nTexInfo; +} + +extern node_t *dfacenodes[MAX_MAP_FACES]; +static void WriteFogVolumeIDs( dmodel_t *pModel ) +{ + int i; + + // write fog volume ID to each face in this model + for( i = pModel->firstface; i < pModel->firstface + pModel->numfaces; i++ ) + { + dface_t *pFace = &dfaces[i]; + node_t *pFaceNode = dfacenodes[i]; + texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; + pFace->surfaceFogVolumeID = -1; + if ( pFaceNode ) + { + if ( (pTexInfo->flags & SURF_WARP ) && pFaceNode->planenum == PLANENUM_LEAF && pFaceNode->diskId >= 0 ) + { + pFace->surfaceFogVolumeID = dleafs[pFaceNode->diskId].leafWaterDataID; + dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[pFace->surfaceFogVolumeID]; + + // HACKHACK: Should probably mark these faces as water bottom or "bottommaterial" faces. + // HACKHACK: Use a heuristic, if it points up, it's the water top. + if ( dplanes[pFace->planenum].normal.z > 0 ) + { + pFace->texinfo = pLeafWaterData->surfaceTexInfoID; + } + } + else + { + // missed this face somehow? + Assert( !(pTexInfo->flags & SURF_WARP ) ); + } + + } + } +} + + +static bool PortalCrossesWater( waterleaf_t &baseleaf, portal_t *portal ) +{ + if ( baseleaf.hasSurface ) + { + int side = WindingOnPlaneSide( portal->winding, baseleaf.surfaceNormal, baseleaf.surfaceDist ); + if ( side == SIDE_CROSS || side == SIDE_FRONT ) + return true; + } + + return false; +} + + +static int FindOrCreateLeafWaterData( float surfaceZ, float minZ, int surfaceTexInfoID ) +{ + int i; + for( i = 0; i < numleafwaterdata; i++ ) + { + dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[i]; + if( pLeafWaterData->surfaceZ == surfaceZ && + pLeafWaterData->minZ == minZ && + pLeafWaterData->surfaceTexInfoID == surfaceTexInfoID ) + { + return i; + } + } + dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[numleafwaterdata]; + pLeafWaterData->surfaceZ = surfaceZ; + pLeafWaterData->minZ = minZ; + pLeafWaterData->surfaceTexInfoID = surfaceTexInfoID; + numleafwaterdata++; + return numleafwaterdata - 1; +} + + +// Enumerate all leaves under node with contents in contentsMask and add them to list +void EnumLeaves_r( CUtlVector<node_t *> &list, node_t *node, int contentsMask ) +{ + if ( node->planenum != PLANENUM_LEAF ) + { + EnumLeaves_r( list, node->children[0], contentsMask ); + EnumLeaves_r( list, node->children[1], contentsMask ); + return; + } + + if ( !(node->contents & contentsMask) ) + return; + + + // has the contents, put it in the list + list.AddToTail( node ); +} + + +// Builds a waterleaf_t for the given leaf +static void BuildWaterLeaf( node_t *pLeafIn, waterleaf_t &waterLeafOut ) +{ + waterLeafOut.pNode = pLeafIn; + waterLeafOut.waterLeafIndex = pLeafIn->diskId; + waterLeafOut.outsideLeafIndex = -1; + waterLeafOut.hasSurface = false; + waterLeafOut.surfaceDist = MAX_COORD_INTEGER; + waterLeafOut.surfaceNormal.Init( 0.f, 0.f, 1.f ); + waterLeafOut.planenum = -1; + waterLeafOut.surfaceTexInfo = -1; + waterLeafOut.minZ = MAX_COORD_INTEGER; + + // search the list of portals out of this leaf for one that leaves water + // If you find one, this leaf has a surface, so fill out the surface data + int oppositeNodeIndex = 0; + for (portal_t *p = pLeafIn->portals ; p ; p = p->next[!oppositeNodeIndex]) + { + oppositeNodeIndex = (p->nodes[0] == pLeafIn) ? 1 : 0; + + // not visible, can't be the portals we're looking for... + if ( !p->side ) + continue; + + // See if this portal crosses into air + node_t *pOpposite = p->nodes[oppositeNodeIndex]; + if ( !(pOpposite->contents & MASK_WATER) && !(pOpposite->contents & MASK_SOLID) ) + { + // it does, there must be a surface here + plane_t *plane = &g_MainMap->mapplanes[p->side->planenum]; + if ( waterLeafOut.hasSurface ) + { + // Sort to find the most upward facing normal (skips sides) + if ( waterLeafOut.surfaceNormal.z > plane->normal.z ) + continue; + if ( (waterLeafOut.surfaceNormal.z == plane->normal.z) && waterLeafOut.surfaceDist >= plane->dist ) + continue; + } + // water surface needs to point at least somewhat up, this is + // probably a map error + if ( plane->normal.z <= 0 ) + continue; + waterLeafOut.surfaceDist = plane->dist; + waterLeafOut.surfaceNormal = plane->normal; + waterLeafOut.hasSurface = true; + waterLeafOut.outsideLeafIndex = p->nodes[oppositeNodeIndex]->diskId; + waterLeafOut.surfaceTexInfo = p->side->texinfo; + } + } +} + + +static void InsertSortWaterLeaf( CUtlVector<waterleaf_t> &list, const waterleaf_t &leafInsert ) +{ + // insertion sort the leaf (lowest leaves go first) + // leaves that aren't actually on the surface of the water will have leaf.hasSurface == false. + for ( int i = 0; i < list.Count(); i++ ) + { + if ( IsLowerLeaf( leafInsert, list[i] ) ) + { + list.InsertBefore( i, leafInsert ); + return; + } + } + + // must the highest one, so stick it at the end. + list.AddToTail( leafInsert ); +} + + +// Flood fill the tree, finding neighboring water volumes and connecting them to this list +// Cut groups that try to cross the surface. +// Mark leaves that are in a group as "visited" so they won't be chosen by subsequent fills +static void Flood_FindConnectedWaterVolumes_r( CUtlVector<node_t *> &list, node_t *pLeaf, waterleaf_t &baseleaf, leafbitarray_t &visited ) +{ + // already visited, or not the same water contents + if ( pLeaf->diskId < 0 || visited.Get(pLeaf->diskId) || !(pLeaf->contents & (baseleaf.pNode->contents & MASK_WATER) ) ) + return; + + int oppositeNodeIndex = 0; + for (portal_t *p = pLeaf->portals ; p ; p = p->next[!oppositeNodeIndex]) + { + oppositeNodeIndex = (p->nodes[0] == pLeaf) ? 1 : 0; + + // If any portal crosses the water surface, don't flow through this leaf + if ( PortalCrossesWater( baseleaf, p ) ) + return; + } + + visited.Set( pLeaf->diskId ); + list.AddToTail( pLeaf ); + + baseleaf.minZ = min( pLeaf->mins.z, baseleaf.minZ ); + + for (portal_t *p = pLeaf->portals ; p ; p = p->next[!oppositeNodeIndex]) + { + oppositeNodeIndex = (p->nodes[0] == pLeaf) ? 1 : 0; + + Flood_FindConnectedWaterVolumes_r( list, p->nodes[oppositeNodeIndex], baseleaf, visited ); + } +} + +// UNDONE: This is a bit of a hack to avoid crashing when we can't find an +// appropriate texinfo for a water model (to get physics properties) +int FirstWaterTexinfo( bspbrush_t *brushlist, int contents ) +{ + while (brushlist) + { + if ( brushlist->original->contents & contents ) + { + for ( int i = 0; i < brushlist->original->numsides; i++ ) + { + if ( brushlist->original->original_sides[i].contents & contents ) + { + return brushlist->original->original_sides[i].texinfo; + } + } + } + brushlist = brushlist->next; + } + + Assert(0); + return 0; +} + +// This is a list of water data that will be turned into physics models +struct watermodel_t +{ + int modelIndex; + int contents; + waterleaf_t waterLeafData; + int depthTexinfo; + int firstWaterLeafIndex; + int waterLeafCount; + int fogVolumeIndex; +}; + +static CUtlVector<watermodel_t> g_WaterModels; +static CUtlVector<int> g_WaterLeafList; + +// Creates a list of watermodel_t for later processing by EmitPhysCollision +void EmitWaterVolumesForBSP( dmodel_t *pModel, node_t *node ) +{ + CUtlVector<node_t *> leafListAnyWater; + // build the list of all leaves containing water + EnumLeaves_r( leafListAnyWater, node, MASK_WATER ); + + // make a sorted list to flood fill + CUtlVector<waterleaf_t> list; + + int i; + for ( i = 0; i < leafListAnyWater.Count(); i++ ) + { + waterleaf_t waterLeaf; + BuildWaterLeaf( leafListAnyWater[i], waterLeaf ); + InsertSortWaterLeaf( list, waterLeaf ); + } + + leafbitarray_t visited; + CUtlVector<node_t *> waterAreaList; + for ( i = 0; i < list.Count(); i++ ) + { + Flood_FindConnectedWaterVolumes_r( waterAreaList, list[i].pNode, list[i], visited ); + + // did we find a list of leaves connected to this one? + // remember the list is sorted, so this one may have been attached to a previous + // leaf. So it could have nothing hanging off of it. + if ( waterAreaList.Count() ) + { + // yes, emit a watermodel + watermodel_t tmp; + tmp.modelIndex = nummodels; + tmp.contents = list[i].pNode->contents; + tmp.waterLeafData = list[i]; + tmp.firstWaterLeafIndex = g_WaterLeafList.Count(); + tmp.waterLeafCount = waterAreaList.Count(); + + float waterDepth = tmp.waterLeafData.surfaceDist - tmp.waterLeafData.minZ; + if ( tmp.waterLeafData.surfaceTexInfo < 0 ) + { + // the map has probably leaked in this case, but output something anyway. + Assert(list[i].pNode->planenum == PLANENUM_LEAF); + tmp.waterLeafData.surfaceTexInfo = FirstWaterTexinfo( list[i].pNode->brushlist, tmp.contents ); + } + tmp.depthTexinfo = FindOrCreateWaterTexInfo( &texinfo[ tmp.waterLeafData.surfaceTexInfo ], waterDepth ); + tmp.fogVolumeIndex = FindOrCreateLeafWaterData( tmp.waterLeafData.surfaceDist, tmp.waterLeafData.minZ, tmp.waterLeafData.surfaceTexInfo ); + + for ( int j = 0; j < waterAreaList.Count(); j++ ) + { + g_WaterLeafList.AddToTail( waterAreaList[j]->diskId ); + } + waterAreaList.RemoveAll(); + g_WaterModels.AddToTail( tmp ); + } + } + + WriteFogVolumeIDs( pModel ); +} + + +static void ConvertWaterModelToPhysCollide( CUtlVector<CPhysCollisionEntry *> &collisionList, int modelIndex, + float shrinkSize, float mergeTolerance ) +{ + dmodel_t *pModel = dmodels + modelIndex; + + for ( int i = 0; i < g_WaterModels.Count(); i++ ) + { + watermodel_t &waterModel = g_WaterModels[i]; + if ( waterModel.modelIndex != modelIndex ) + continue; + + CPlaneList planes( shrinkSize, mergeTolerance ); + int firstLeaf = waterModel.firstWaterLeafIndex; + planes.m_contentsMask = waterModel.contents; + + // push all of the leaves into the collision list + for ( int j = 0; j < waterModel.waterLeafCount; j++ ) + { + int leafIndex = g_WaterLeafList[firstLeaf + j]; + + dleaf_t *pLeaf = dleafs + leafIndex; + // fixup waterdata + pLeaf->leafWaterDataID = waterModel.fogVolumeIndex; + planes.ReferenceLeaf( leafIndex ); + } + + // visit the referenced leaves that belong to this model + VisitLeaves_r( planes, pModel->headnode ); + + // Now add the brushes from those leaves as convex + + // BUGBUG: NOTE: If your map has a brush that crosses the surface, it will be added to two water + // volumes. This only happens with connected water volumes with multiple surface heights + // UNDONE: Right now map makers must cut such brushes. It could be automatically cut by adding the + // surface plane to the list for each brush before calling ConvexFromPlanes() + planes.AddBrushes(); + + int count = planes.m_convex.Count(); + if ( !count ) + continue; + + // Save off the plane of the surface for this group as well as the collision model + // for all convex objects in the group. + CPhysCollide *pCollide = physcollision->ConvertConvexToCollide( planes.m_convex.Base(), count ); + if ( pCollide ) + { + int waterSurfaceTexInfoID = -1; + // use defaults + const char *pSurfaceProp = "water"; + float damping = 0.01; + if ( waterSurfaceTexInfoID >= 0 ) + { + // material override + int texdata = texinfo[waterSurfaceTexInfoID].texdata; + int prop = g_SurfaceProperties[texdata]; + pSurfaceProp = physprops->GetPropName( prop ); + } + + if ( !waterModel.waterLeafData.hasSurface ) + { + waterModel.waterLeafData.surfaceNormal.Init( 0,0,1 ); + Vector top = physcollision->CollideGetExtent( pCollide, vec3_origin, vec3_angle, waterModel.waterLeafData.surfaceNormal ); + waterModel.waterLeafData.surfaceDist = top.z; + } + CPhysCollisionEntryFluid *pCollisionEntryFuild = new CPhysCollisionEntryFluid( pCollide, + pSurfaceProp, damping, waterModel.waterLeafData.surfaceNormal, waterModel.waterLeafData.surfaceDist, waterModel.contents ); + collisionList.AddToTail( pCollisionEntryFuild ); + } + } +} + +// compute a normal for a triangle of the given three points (points are clockwise, normal points out) +static Vector TriangleNormal( const Vector &p0, const Vector &p1, const Vector &p2 ) +{ + Vector e0 = p1 - p0; + Vector e1 = p2 - p0; + Vector normal = CrossProduct( e1, e0 ); + VectorNormalize( normal ); + + return normal; +} + + +// find the side of the brush with the normal closest to the given normal +static dbrushside_t *FindBrushSide( int brushIndex, const Vector &normal ) +{ + dbrush_t *pbrush = &dbrushes[brushIndex]; + dbrushside_t *out = NULL; + float best = -1.f; + + for ( int i = 0; i < pbrush->numsides; i++ ) + { + dbrushside_t *pside = dbrushsides + i + pbrush->firstside; + dplane_t *pplane = dplanes + pside->planenum; + float dot = DotProduct( normal, pplane->normal ); + if ( dot > best ) + { + best = dot; + out = pside; + } + } + + return out; +} + + + +static void ConvertWorldBrushesToPhysCollide( CUtlVector<CPhysCollisionEntry *> &collisionList, float shrinkSize, float mergeTolerance, int contentsMask ) +{ + CPlaneList planes( shrinkSize, mergeTolerance ); + + planes.m_contentsMask = contentsMask; + + VisitLeaves_r( planes, dmodels[0].headnode ); + planes.AddBrushes(); + + int count = planes.m_convex.Count(); + if ( count ) + { + CPhysCollide *pCollide = physcollision->ConvertConvexToCollide( planes.m_convex.Base(), count ); + + ICollisionQuery *pQuery = physcollision->CreateQueryModel( pCollide ); + int convex = pQuery->ConvexCount(); + for ( int i = 0; i < convex; i++ ) + { + int triCount = pQuery->TriangleCount( i ); + int brushIndex = pQuery->GetGameData( i ); + + Vector points[3]; + for ( int j = 0; j < triCount; j++ ) + { + pQuery->GetTriangleVerts( i, j, points ); + Vector normal = TriangleNormal( points[0], points[1], points[2] ); + dbrushside_t *pside = FindBrushSide( brushIndex, normal ); + if ( pside->texinfo != TEXINFO_NODE ) + { + int prop = g_SurfaceProperties[texinfo[pside->texinfo].texdata]; + pQuery->SetTriangleMaterialIndex( i, j, RemapWorldMaterial( prop ) ); + } + } + } + physcollision->DestroyQueryModel( pQuery ); + pQuery = NULL; + + collisionList.AddToTail( new CPhysCollisionEntryStaticSolid( pCollide, contentsMask ) ); + } +} + +// adds any world, terrain, and water collision models to the collision list +static void BuildWorldPhysModel( CUtlVector<CPhysCollisionEntry *> &collisionList, float shrinkSize, float mergeTolerance ) +{ + ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, MASK_SOLID ); + ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, CONTENTS_PLAYERCLIP ); + ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, CONTENTS_MONSTERCLIP ); + + if ( !g_bNoVirtualMesh && Disp_HasPower4Displacements() ) + { + Warning("WARNING: Map using power 4 displacements, terrain physics cannot be compressed, map will need additional memory and CPU.\n"); + g_bNoVirtualMesh = true; + } + + // if there's terrain, save it off as a static mesh/polysoup + if ( g_bNoVirtualMesh || !physcollision->SupportsVirtualMesh() ) + { + Disp_AddCollisionModels( collisionList, &dmodels[0], MASK_SOLID ); + } + else + { + Disp_BuildVirtualMesh( MASK_SOLID ); + } + ConvertWaterModelToPhysCollide( collisionList, 0, shrinkSize, mergeTolerance ); +} + + +// adds a collision entry for this brush model +static void ConvertModelToPhysCollide( CUtlVector<CPhysCollisionEntry *> &collisionList, int modelIndex, int contents, float shrinkSize, float mergeTolerance ) +{ + int i; + CPlaneList planes( shrinkSize, mergeTolerance ); + + planes.m_contentsMask = contents; + + dmodel_t *pModel = dmodels + modelIndex; + VisitLeaves_r( planes, pModel->headnode ); + planes.AddBrushes(); + int count = planes.m_convex.Count(); + convertconvexparams_t params; + params.Defaults(); + params.buildOuterConvexHull = count > 1 ? true : false; + params.buildDragAxisAreas = true; + Vector size = pModel->maxs - pModel->mins; + + float minSurfaceArea = -1.0f; + for ( i = 0; i < 3; i++ ) + { + int other = (i+1)%3; + int cross = (i+2)%3; + float surfaceArea = size[other] * size[cross]; + if ( minSurfaceArea < 0 || surfaceArea < minSurfaceArea ) + { + minSurfaceArea = surfaceArea; + } + } + // this can be really slow with super-large models and a low error tolerance + // Basically you get a ray cast through each square of epsilon surface area on each OBB side + // So compute it for 1% error (on the smallest side, less on larger sides) + params.dragAreaEpsilon = clamp( minSurfaceArea * 1e-2f, 1.0f, 1024.0f ); + CPhysCollide *pCollide = physcollision->ConvertConvexToCollideParams( planes.m_convex.Base(), count, params ); + + if ( !pCollide ) + return; + + struct + { + int prop; + float area; + } proplist[256]; + int numprops = 1; + + proplist[0].prop = -1; + proplist[0].area = 1; + // compute the array of props on the surface of this model + + // NODRAW brushes no longer have any faces + if ( !dmodels[modelIndex].numfaces ) + { + int sideIndex = planes.GetFirstBrushSide(); + int texdata = texinfo[dbrushsides[sideIndex].texinfo].texdata; + int prop = g_SurfaceProperties[texdata]; + proplist[numprops].prop = prop; + proplist[numprops].area = 2; + numprops++; + } + + for ( i = 0; i < dmodels[modelIndex].numfaces; i++ ) + { + dface_t *face = dfaces + i + dmodels[modelIndex].firstface; + int texdata = texinfo[face->texinfo].texdata; + int prop = g_SurfaceProperties[texdata]; + int j; + for ( j = 0; j < numprops; j++ ) + { + if ( proplist[j].prop == prop ) + { + proplist[j].area += face->area; + break; + } + } + + if ( (!numprops || j >= numprops) && numprops < ARRAYSIZE(proplist) ) + { + proplist[numprops].prop = prop; + proplist[numprops].area = face->area; + numprops++; + } + } + + + // choose the prop with the most surface area + int maxIndex = -1; + float maxArea = 0; + float totalArea = 0; + + for ( i = 0; i < numprops; i++ ) + { + if ( proplist[i].area > maxArea ) + { + maxIndex = i; + maxArea = proplist[i].area; + } + // add up the total surface area + totalArea += proplist[i].area; + } + + float mass = 1.0f; + const char *pMaterial = "default"; + if ( maxIndex >= 0 ) + { + int prop = proplist[maxIndex].prop; + + // use default if this material has no prop + if ( prop < 0 ) + prop = 0; + + pMaterial = physprops->GetPropName( prop ); + float density, thickness; + physprops->GetPhysicsProperties( prop, &density, &thickness, NULL, NULL ); + + // if this is a "shell" material (it is hollow and encloses some empty space) + // compute the mass with a constant surface thickness + if ( thickness != 0 ) + { + mass = totalArea * thickness * density * CUBIC_METERS_PER_CUBIC_INCH; + } + else + { + // material is completely solid, compute total mass as if constant density throughout. + mass = planes.m_totalVolume * density * CUBIC_METERS_PER_CUBIC_INCH; + } + } + + // Clamp mass to 100,000 kg + if ( mass > VPHYSICS_MAX_MASS ) + { + mass = VPHYSICS_MAX_MASS; + } + + collisionList.AddToTail( new CPhysCollisionEntrySolid( pCollide, pMaterial, mass ) ); +} + +static void ClearLeafWaterData( void ) +{ + int i; + + for( i = 0; i < numleafs; i++ ) + { + dleafs[i].leafWaterDataID = -1; + dleafs[i].contents &= ~CONTENTS_TESTFOGVOLUME; + } +} + + +// This is the only public entry to this file. +// The global data touched in the file is: +// from bsplib.h: +// g_pPhysCollide : This is an output from this file. +// g_PhysCollideSize : This is set in this file. +// g_numdispinfo : This is an input to this file. +// g_dispinfo : This is an input to this file. +// numnodewaterdata : This is an output from this file. +// dleafwaterdata : This is an output from this file. +// from vbsp.h: +// g_SurfaceProperties : This is an input to this file. +void EmitPhysCollision() +{ + ClearLeafWaterData(); + + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); + if ( physicsFactory ) + { + physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); + } + + if ( !physcollision ) + { + Warning("!!! WARNING: Can't build collision data!\n" ); + return; + } + + CUtlVector<CPhysCollisionEntry *> collisionList[MAX_MAP_MODELS]; + CTextBuffer *pTextBuffer[MAX_MAP_MODELS]; + + int physModelCount = 0, totalSize = 0; + + int start = Plat_FloatTime(); + + Msg("Building Physics collision data...\n" ); + + int i, j; + for ( i = 0; i < nummodels; i++ ) + { + // Build a list of collision models for this brush model section + if ( i == 0 ) + { + // world is the only model that processes water separately. + // other brushes are assumed to be completely solid or completely liquid + BuildWorldPhysModel( collisionList[i], NO_SHRINK, VPHYSICS_MERGE); + } + else + { + ConvertModelToPhysCollide( collisionList[i], i, MASK_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|MASK_WATER, VPHYSICS_SHRINK, VPHYSICS_MERGE ); + } + + pTextBuffer[i] = NULL; + if ( !collisionList[i].Count() ) + continue; + + // if we've got collision models, write their script for processing in the game + pTextBuffer[i] = new CTextBuffer; + for ( j = 0; j < collisionList[i].Count(); j++ ) + { + // dump a text file for visualization + if ( dumpcollide ) + { + collisionList[i][j]->DumpCollide( pTextBuffer[i], i, j ); + } + // each model knows how to write its script + collisionList[i][j]->WriteToTextBuffer( pTextBuffer[i], i, j ); + // total up the binary section's size + totalSize += collisionList[i][j]->GetCollisionBinarySize() + sizeof(int); + } + + // These sections only appear in the world's collision text + if ( i == 0 ) + { + if ( !g_bNoVirtualMesh && physcollision->SupportsVirtualMesh() ) + { + pTextBuffer[i]->WriteText("virtualterrain {}\n"); + } + if ( s_WorldPropList.Count() ) + { + pTextBuffer[i]->WriteText( "materialtable {\n" ); + for ( j = 0; j < s_WorldPropList.Count(); j++ ) + { + int propIndex = s_WorldPropList[j]; + if ( propIndex < 0 ) + { + pTextBuffer[i]->WriteIntKey( "default", j+1 ); + } + else + { + pTextBuffer[i]->WriteIntKey( physprops->GetPropName( propIndex ), j+1 ); + } + } + pTextBuffer[i]->WriteText( "}\n" ); + } + } + + pTextBuffer[i]->Terminate(); + + // total lump size includes the text buffers (scripts) + totalSize += pTextBuffer[i]->GetSize(); + + physModelCount++; + } + + // add one for tail of list marker + physModelCount++; + + // DWORD align the lump because AddLump assumes that it is DWORD aligned. + byte *ptr ; + g_PhysCollideSize = totalSize + (physModelCount * sizeof(dphysmodel_t)); + g_pPhysCollide = (byte *)malloc(( g_PhysCollideSize + 3 ) & ~3 ); + memset( g_pPhysCollide, 0, g_PhysCollideSize ); + ptr = g_pPhysCollide; + + for ( i = 0; i < nummodels; i++ ) + { + if ( pTextBuffer[i] ) + { + int j; + + dphysmodel_t model; + + model.modelIndex = i; + model.solidCount = collisionList[i].Count(); + model.dataSize = sizeof(int) * model.solidCount; + + for ( j = 0; j < model.solidCount; j++ ) + { + model.dataSize += collisionList[i][j]->GetCollisionBinarySize(); + } + model.keydataSize = pTextBuffer[i]->GetSize(); + + // store the header + memcpy( ptr, &model, sizeof(model) ); + ptr += sizeof(model); + + for ( j = 0; j < model.solidCount; j++ ) + { + int collideSize = collisionList[i][j]->GetCollisionBinarySize(); + + // write size + memcpy( ptr, &collideSize, sizeof(int) ); + ptr += sizeof(int); + + // now write the collision model + collisionList[i][j]->WriteCollisionBinary( reinterpret_cast<char *>(ptr) ); + ptr += collideSize; + } + + memcpy( ptr, pTextBuffer[i]->GetData(), pTextBuffer[i]->GetSize() ); + ptr += pTextBuffer[i]->GetSize(); + } + + delete pTextBuffer[i]; + } + + dphysmodel_t model; + + // Mark end of list + model.modelIndex = -1; + model.dataSize = -1; + model.keydataSize = 0; + model.solidCount = 0; + memcpy( ptr, &model, sizeof(model) ); + ptr += sizeof(model); + Assert( (ptr-g_pPhysCollide) == g_PhysCollideSize); + Msg("done (%d) (%d bytes)\n", (int)(Plat_FloatTime() - start), g_PhysCollideSize ); + + // UNDONE: Collision models (collisionList) memory leak! +} diff --git a/mp/src/utils/vbsp/ivp.h b/mp/src/utils/vbsp/ivp.h index d3c310dc..318a199d 100644 --- a/mp/src/utils/vbsp/ivp.h +++ b/mp/src/utils/vbsp/ivp.h @@ -1,75 +1,75 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-
-#ifndef IVP_H
-#define IVP_H
-#ifdef _WIN32
-#pragma once
-#endif
-
-#include "utlvector.h"
-
-class CPhysCollide;
-class CTextBuffer;
-class IPhysicsCollision;
-extern IPhysicsCollision *physcollision;
-
-// a list of all of the materials in the world model
-extern int RemapWorldMaterial( int materialIndexIn );
-
-class CPhysCollisionEntry
-{
-public:
- CPhysCollisionEntry( CPhysCollide *pCollide );
-
- virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) = 0;
- virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) = 0;
-
- unsigned int GetCollisionBinarySize();
- unsigned int WriteCollisionBinary( char *pDest );
-
-protected:
- void DumpCollideFileName( const char *pName, int modelIndex, CTextBuffer *pTextBuffer );
-
-protected:
- CPhysCollide *m_pCollide;
-};
-
-class CPhysCollisionEntryStaticMesh : public CPhysCollisionEntry
-{
-public:
- CPhysCollisionEntryStaticMesh( CPhysCollide *pCollide, const char *pMaterialName );
-
- virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex );
- virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex );
-
-private:
- const char *m_pMaterial;
-};
-
-
-class CTextBuffer
-{
-public:
- CTextBuffer( void );
- ~CTextBuffer( void );
- inline int GetSize( void ) { return m_buffer.Count(); }
- inline char *GetData( void ) { return m_buffer.Base(); }
-
- void WriteText( const char *pText );
- void WriteIntKey( const char *pKeyName, int outputData );
- void WriteStringKey( const char *pKeyName, const char *outputData );
- void WriteFloatKey( const char *pKeyName, float outputData );
- void WriteFloatArrayKey( const char *pKeyName, const float *outputData, int count );
-
- void CopyStringQuotes( const char *pString );
- void Terminate( void );
-private:
- void CopyData( const char *pData, int len );
- CUtlVector<char> m_buffer;
-};
-
-#endif // IVP_H
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef IVP_H +#define IVP_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utlvector.h" + +class CPhysCollide; +class CTextBuffer; +class IPhysicsCollision; +extern IPhysicsCollision *physcollision; + +// a list of all of the materials in the world model +extern int RemapWorldMaterial( int materialIndexIn ); + +class CPhysCollisionEntry +{ +public: + CPhysCollisionEntry( CPhysCollide *pCollide ); + + virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) = 0; + virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) = 0; + + unsigned int GetCollisionBinarySize(); + unsigned int WriteCollisionBinary( char *pDest ); + +protected: + void DumpCollideFileName( const char *pName, int modelIndex, CTextBuffer *pTextBuffer ); + +protected: + CPhysCollide *m_pCollide; +}; + +class CPhysCollisionEntryStaticMesh : public CPhysCollisionEntry +{ +public: + CPhysCollisionEntryStaticMesh( CPhysCollide *pCollide, const char *pMaterialName ); + + virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + +private: + const char *m_pMaterial; +}; + + +class CTextBuffer +{ +public: + CTextBuffer( void ); + ~CTextBuffer( void ); + inline int GetSize( void ) { return m_buffer.Count(); } + inline char *GetData( void ) { return m_buffer.Base(); } + + void WriteText( const char *pText ); + void WriteIntKey( const char *pKeyName, int outputData ); + void WriteStringKey( const char *pKeyName, const char *outputData ); + void WriteFloatKey( const char *pKeyName, float outputData ); + void WriteFloatArrayKey( const char *pKeyName, const float *outputData, int count ); + + void CopyStringQuotes( const char *pString ); + void Terminate( void ); +private: + void CopyData( const char *pData, int len ); + CUtlVector<char> m_buffer; +}; + +#endif // IVP_H diff --git a/mp/src/utils/vbsp/leakfile.cpp b/mp/src/utils/vbsp/leakfile.cpp index bd8a9b28..d6038830 100644 --- a/mp/src/utils/vbsp/leakfile.cpp +++ b/mp/src/utils/vbsp/leakfile.cpp @@ -1,168 +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 );
+//========= 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/mp/src/utils/vbsp/manifest.cpp b/mp/src/utils/vbsp/manifest.cpp index 44dd07b8..c72a9564 100644 --- a/mp/src/utils/vbsp/manifest.cpp +++ b/mp/src/utils/vbsp/manifest.cpp @@ -1,568 +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;
- }
-}
+//========= 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/mp/src/utils/vbsp/manifest.h b/mp/src/utils/vbsp/manifest.h index f3b915a1..e7b801e1 100644 --- a/mp/src/utils/vbsp/manifest.h +++ b/mp/src/utils/vbsp/manifest.h @@ -1,73 +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
+//========= 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/mp/src/utils/vbsp/map.cpp b/mp/src/utils/vbsp/map.cpp index 34219bd4..a5456c32 100644 --- a/mp/src/utils/vbsp/map.cpp +++ b/mp/src/utils/vbsp/map.cpp @@ -1,3304 +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, "", 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;
-}
-
-
+//========= 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, "", 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/mp/src/utils/vbsp/map.h b/mp/src/utils/vbsp/map.h index 44b1d934..c7b0e5b8 100644 --- a/mp/src/utils/vbsp/map.h +++ b/mp/src/utils/vbsp/map.h @@ -1,18 +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
+//========= 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/mp/src/utils/vbsp/materialpatch.cpp b/mp/src/utils/vbsp/materialpatch.cpp index e05b979d..96a30edb 100644 --- a/mp/src/utils/vbsp/materialpatch.cpp +++ b/mp/src/utils/vbsp/materialpatch.cpp @@ -1,440 +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 );
-}
+//========= 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/mp/src/utils/vbsp/materialpatch.h b/mp/src/utils/vbsp/materialpatch.h index 334fe013..f04a95d9 100644 --- a/mp/src/utils/vbsp/materialpatch.h +++ b/mp/src/utils/vbsp/materialpatch.h @@ -1,60 +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
+//========= 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/mp/src/utils/vbsp/materialsub.cpp b/mp/src/utils/vbsp/materialsub.cpp index d8eb40df..b02063ef 100644 --- a/mp/src/utils/vbsp/materialsub.cpp +++ b/mp/src/utils/vbsp/materialsub.cpp @@ -1,90 +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 );
+//========= 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/mp/src/utils/vbsp/materialsub.h b/mp/src/utils/vbsp/materialsub.h index c1de0698..ce6cac42 100644 --- a/mp/src/utils/vbsp/materialsub.h +++ b/mp/src/utils/vbsp/materialsub.h @@ -1,25 +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 );
-
+//========= 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/mp/src/utils/vbsp/nodraw.cpp b/mp/src/utils/vbsp/nodraw.cpp index 4fa493ea..954ea671 100644 --- a/mp/src/utils/vbsp/nodraw.cpp +++ b/mp/src/utils/vbsp/nodraw.cpp @@ -1,32 +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)
-{
-}
+//========= 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/mp/src/utils/vbsp/normals.cpp b/mp/src/utils/vbsp/normals.cpp index 1696342c..3a910cb6 100644 --- a/mp/src/utils/vbsp/normals.cpp +++ b/mp/src/utils/vbsp/normals.cpp @@ -1,50 +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++;
- }
-}
+//========= 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/mp/src/utils/vbsp/overlay.cpp b/mp/src/utils/vbsp/overlay.cpp index fcd2e924..edceba9a 100644 --- a/mp/src/utils/vbsp/overlay.cpp +++ b/mp/src/utils/vbsp/overlay.cpp @@ -1,487 +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;
- }
- }
- }
-
-}
+//========= 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/mp/src/utils/vbsp/portals.cpp b/mp/src/utils/vbsp/portals.cpp index ec2a2695..6c59cffa 100644 --- a/mp/src/utils/vbsp/portals.cpp +++ b/mp/src/utils/vbsp/portals.cpp @@ -1,1684 +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 );
-}
-
+//========= 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/mp/src/utils/vbsp/portals.h b/mp/src/utils/vbsp/portals.h index e8b45c7b..cf65ac7c 100644 --- a/mp/src/utils/vbsp/portals.h +++ b/mp/src/utils/vbsp/portals.h @@ -1,19 +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
+//========= 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/mp/src/utils/vbsp/prtfile.cpp b/mp/src/utils/vbsp/prtfile.cpp index c192176b..71d7e5cc 100644 --- a/mp/src/utils/vbsp/prtfile.cpp +++ b/mp/src/utils/vbsp/prtfile.cpp @@ -1,374 +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) );
-}
-
+//========= 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/mp/src/utils/vbsp/staticprop.cpp b/mp/src/utils/vbsp/staticprop.cpp index 810465b3..b7b9b6cb 100644 --- a/mp/src/utils/vbsp/staticprop.cpp +++ b/mp/src/utils/vbsp/staticprop.cpp @@ -1,741 +1,741 @@ -//========= 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;
-};
-
-
-//-----------------------------------------------------------------------------
-// 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;
- }
- }
-
- // 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;
- }
-
- 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;
-}
-
+//========= 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; +}; + + +//----------------------------------------------------------------------------- +// 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; + } + } + + // 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; + } + + 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/mp/src/utils/vbsp/textures.cpp b/mp/src/utils/vbsp/textures.cpp index fc2034eb..4f49c5d4 100644 --- a/mp/src/utils/vbsp/textures.cpp +++ b/mp/src/utils/vbsp/textures.cpp @@ -1,737 +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();
-}
-
-
+//========= 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/mp/src/utils/vbsp/tree.cpp b/mp/src/utils/vbsp/tree.cpp index 0f72844c..529e9838 100644 --- a/mp/src/utils/vbsp/tree.cpp +++ b/mp/src/utils/vbsp/tree.cpp @@ -1,207 +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);
-}
-
-//===========================================================
+//========= 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/mp/src/utils/vbsp/vbsp.cpp b/mp/src/utils/vbsp/vbsp.cpp index 1c7b7ec9..97a4df4c 100644 --- a/mp/src/utils/vbsp/vbsp.cpp +++ b/mp/src/utils/vbsp/vbsp.cpp @@ -1,1404 +1,1404 @@ -//========= 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];
-
-// 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 );
-
- 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" ) )
- {
- ++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 (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"
- "\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 );
- }
-
- 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 ) );
- }
- }
-
- char platformBSPFileName[1024];
- GetPlatformMapPath( source, platformBSPFileName, g_nDXLevel, 1024 );
-
- // 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 (platformBSPFileName);
- 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 (platformBSPFileName);
- }
- else if (onlyprops)
- {
- // In the only props case, deal with static + detail props only
- LoadBSPFile (platformBSPFileName);
-
- 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 (platformBSPFileName);
- }
- else
- {
- //
- // start from scratch
- //
-
- // Load just the file system from the bsp
- if( g_bKeepStaleZip && FileExists( platformBSPFileName ) )
- {
- LoadBSPFile_FileSystemOnly (platformBSPFileName);
- // 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 ();
- }
-
- 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 );
-}
-
-
+//========= 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]; + +// 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 ); + + 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" ) ) + { + ++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 (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" + "\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 ); + } + + 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 ) ); + } + } + + char platformBSPFileName[1024]; + GetPlatformMapPath( source, platformBSPFileName, g_nDXLevel, 1024 ); + + // 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 (platformBSPFileName); + 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 (platformBSPFileName); + } + else if (onlyprops) + { + // In the only props case, deal with static + detail props only + LoadBSPFile (platformBSPFileName); + + 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 (platformBSPFileName); + } + else + { + // + // start from scratch + // + + // Load just the file system from the bsp + if( g_bKeepStaleZip && FileExists( platformBSPFileName ) ) + { + LoadBSPFile_FileSystemOnly (platformBSPFileName); + // 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 (); + } + + 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/mp/src/utils/vbsp/vbsp.h b/mp/src/utils/vbsp/vbsp.h index 9b994d38..689bbaa8 100644 --- a/mp/src/utils/vbsp/vbsp.h +++ b/mp/src/utils/vbsp/vbsp.h @@ -1,657 +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
-
+//========= 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/mp/src/utils/vbsp/vbsp.vpc b/mp/src/utils/vbsp/vbsp.vpc index 4c6d1862..7065f0a5 100644 --- a/mp/src/utils/vbsp/vbsp.vpc +++ b/mp/src/utils/vbsp/vbsp.vpc @@ -1,184 +1,184 @@ -//-----------------------------------------------------------------------------
-// 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"
- }
-}
-
-$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
- }
-
- $File "notes.txt"
-}
+//----------------------------------------------------------------------------- +// 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" + } +} + +$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 + } + + $File "notes.txt" +} diff --git a/mp/src/utils/vbsp/worldvertextransitionfixup.cpp b/mp/src/utils/vbsp/worldvertextransitionfixup.cpp index 5d00f3f5..f97f3933 100644 --- a/mp/src/utils/vbsp/worldvertextransitionfixup.cpp +++ b/mp/src/utils/vbsp/worldvertextransitionfixup.cpp @@ -1,212 +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 );
- }
+//========= 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/mp/src/utils/vbsp/worldvertextransitionfixup.h b/mp/src/utils/vbsp/worldvertextransitionfixup.h index 6297dca5..9a1f0d38 100644 --- a/mp/src/utils/vbsp/worldvertextransitionfixup.h +++ b/mp/src/utils/vbsp/worldvertextransitionfixup.h @@ -1,15 +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
+//========= 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/mp/src/utils/vbsp/writebsp.cpp b/mp/src/utils/vbsp/writebsp.cpp index c776bede..60d05980 100644 --- a/mp/src/utils/vbsp/writebsp.cpp +++ b/mp/src/utils/vbsp/writebsp.cpp @@ -1,1552 +1,1552 @@ -//========= 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 targetPath[1024];
- GetPlatformMapPath( source, targetPath, g_nDXLevel, 1024 );
- Msg ("Writing %s\n", targetPath);
- WriteBSPFile (targetPath);
-}
-
-
-/*
-==================
-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;
- }
- }
-}
-
-
+//========= 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 targetPath[1024]; + GetPlatformMapPath( source, targetPath, g_nDXLevel, 1024 ); + Msg ("Writing %s\n", targetPath); + WriteBSPFile (targetPath); +} + + +/* +================== +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/mp/src/utils/vbsp/writebsp.h b/mp/src/utils/vbsp/writebsp.h index e1ad084e..5dfce099 100644 --- a/mp/src/utils/vbsp/writebsp.h +++ b/mp/src/utils/vbsp/writebsp.h @@ -1,34 +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
+//========= 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 |