source-engine/hammer/maplightcone.cpp

686 lines
18 KiB
C++
Raw Permalink Normal View History

2020-04-23 00:56:21 +08:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Renders a cone for spotlight entities. Only renders when the parent
// entity is selected.
//
//=============================================================================//
#include "stdafx.h"
#include "Box3D.h"
#include "fgdlib/HelperInfo.h"
#include "MapDefs.h" // dvs: For COORD_NOTINIT
#include "MapEntity.h"
#include "MapLightCone.h"
#include "Render3D.h"
#include "Material.h"
#include "materialsystem/imaterialsystem.h"
#include "TextureSystem.h"
#include "hammer.h"
#include "Options.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
#define NUM_LIGHTCONE_ZONES 5
IMPLEMENT_MAPCLASS(CMapLightCone)
//-----------------------------------------------------------------------------
// Purpose: Factory function. Used for creating a CMapLightCone helper from a
// set of string parameters from the FGD file.
// Input : *pInfo - Pointer to helper info class which gives us information
// about how to create the helper.
// Output : Returns a pointer to the helper, NULL if an error occurs.
//-----------------------------------------------------------------------------
CMapClass *CMapLightCone::Create(CHelperInfo *pHelperInfo, CMapEntity *pParent)
{
CMapLightCone *new1=new CMapLightCone;
if( new1 != NULL )
{
//
// The first parameter should be the inner fov key name. If it isn't
// there we assume "_inner_cone".
//
const char *pszKeyName = pHelperInfo->GetParameter(0);
if (pszKeyName != NULL)
{
strcpy(new1->m_szInnerConeKeyName, pszKeyName);
}
else
{
strcpy(new1->m_szInnerConeKeyName, "_inner_cone");
}
//
// The second parameter should be the outer fov key name. If it isn't
// there we assume "_cone".
//
pszKeyName = pHelperInfo->GetParameter(1);
if (pszKeyName != NULL)
{
strcpy(new1->m_szOuterConeKeyName, pszKeyName);
}
else
{
strcpy(new1->m_szOuterConeKeyName, "_cone");
}
//
// The third parameter should be the color of the light. If it isn't
// there we assume "_light".
//
pszKeyName = pHelperInfo->GetParameter(2);
if (pszKeyName != NULL)
{
strcpy(new1->m_szColorKeyName, pszKeyName);
}
else
{
strcpy(new1->m_szColorKeyName, "_light");
}
pszKeyName = pHelperInfo->GetParameter(3);
if (pszKeyName != NULL)
{
new1->m_flPitchScale = Q_atof( pszKeyName );
}
else
{
new1->m_flPitchScale = 1.0f;
}
}
return new1;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CMapLightCone::CMapLightCone(void)
{
m_fQuadraticAttn = 1;
m_fLinearAttn = 0;
m_fConstantAttn = 0;
m_bPitchSet = false;
m_fPitch = 0;
m_fFocus = 1;
m_flPitchScale = 1;
m_fBrightness = 100;
m_fInnerConeAngle = 0;
m_fOuterConeAngle = 45;
m_fFiftyPercentDistance = -1; // disabled - use attenuation
m_Angles.Init();
SignalUpdate( EVTYPE_LIGHTING_CHANGED );
}
//-----------------------------------------------------------------------------
// Purpose: Destructor. Deletes faces allocated by BuildCone.
//-----------------------------------------------------------------------------
CMapLightCone::~CMapLightCone(void)
{
for (int i = 0; i < m_Faces.Count(); i++)
{
CMapFace *pFace = m_Faces.Element(i);
delete pFace;
}
SignalUpdate( EVTYPE_LIGHTING_CHANGED );
}
//-----------------------------------------------------------------------------
// Purpose: Builds the light cone faces in local space. Does NOT call CalcBounds,
// because that CalcBounds updates the parent, which causes problems
// in the undo system.
//-----------------------------------------------------------------------------
void CMapLightCone::BuildCone(void)
{
//
// Delete the current face list.
//
for (int i = 0; i < m_Faces.Count(); i++)
{
CMapFace *pFace = m_Faces.Element(i);
delete pFace;
}
m_Faces.RemoveAll();
//
// Make sure at least one of the lighting coefficients is nonzero.
//
if ((m_fQuadraticAttn == 0) && (m_fLinearAttn == 0) && (m_fConstantAttn == 0))
{
m_fConstantAttn = 1;
}
//
// Solve for the lighting scale factor by which the brightness will be multiplied.
//
float fScaleFactor = m_fQuadraticAttn * 10000 + m_fLinearAttn * 100 + m_fConstantAttn;
if (fScaleFactor == 0)
{
return;
}
//
// Calculate the distances from the light origin to the various zones.
//
float fOffsetDist = 0;
// Constant attenuation factor doesn't actually offset the cone yet. If it does, uncomment this:
//SolveQuadratic(fOffsetDist, 0, m_fQuadraticAttn, m_fLinearAttn, -m_fConstantAttn);
float fZoneDist[NUM_LIGHTCONE_ZONES];
memset( fZoneDist, 0, sizeof( fZoneDist ) );
fZoneDist[0] = 0;
SolveQuadratic(fZoneDist[1], 0.25 * fScaleFactor, m_fQuadraticAttn, m_fLinearAttn, m_fConstantAttn);
SolveQuadratic(fZoneDist[2], fScaleFactor, m_fQuadraticAttn, m_fLinearAttn, m_fConstantAttn);
SolveQuadratic(fZoneDist[3], 4 * fScaleFactor, m_fQuadraticAttn, m_fLinearAttn, m_fConstantAttn);
SolveQuadratic(fZoneDist[4], Options.view3d.fLightConeLength * fScaleFactor, m_fQuadraticAttn, m_fLinearAttn, m_fConstantAttn);
//
// there's no cone if it's greater then 90 degrees
//
if (m_fOuterConeAngle < 90)
{
//
// Calculate the cone radius at each zone.
//
float fZoneRadius[NUM_LIGHTCONE_ZONES];
for (int i = 0; i < NUM_LIGHTCONE_ZONES; i++)
{
fZoneRadius[i] = (fOffsetDist + fZoneDist[i]) * tan(DEG2RAD(m_fOuterConeAngle));
}
//
// Build the new face list using the new parameters.
//
float fStepSize = 360.0 / 15.0;
for (int nZone = 0; nZone < NUM_LIGHTCONE_ZONES - 1; nZone++)
{
float fSin0 = 0;
float fCos0 = 1;
float fTopDist = fZoneDist[nZone];
float fBottomDist = fZoneDist[nZone + 1];
float fTopRadius = fZoneRadius[nZone];
float fBottomRadius = fZoneRadius[nZone + 1];
for (int fAngle = fStepSize; fAngle <= 361; fAngle += fStepSize)
{
float fSin1 = sin(DEG2RAD(fAngle));
float fCos1 = cos(DEG2RAD(fAngle));
Vector Points[4];
Points[0][2] = fBottomRadius * fCos1;
Points[0][1] = fBottomRadius * fSin1;
Points[0][0] = fBottomDist;
Points[1][2] = fBottomRadius * fCos0;
Points[1][1] = fBottomRadius * fSin0;
Points[1][0] = fBottomDist;
Points[2][2] = fTopRadius * fCos0;
Points[2][1] = fTopRadius * fSin0;
Points[2][0] = fTopDist;
int nPoints = 3;
if (fTopRadius != 0)
{
Points[3][2] = fTopRadius * fCos1;
Points[3][1] = fTopRadius * fSin1;
Points[3][0] = fTopDist;
nPoints = 4;
}
CMapFace *pFace = new CMapFace;
pFace->SetRenderColor(r * (1 - nZone / (float)NUM_LIGHTCONE_ZONES), g * (1 - nZone / (float)NUM_LIGHTCONE_ZONES), b * (1 - nZone / (float)NUM_LIGHTCONE_ZONES));
pFace->SetRenderAlpha(180);
pFace->CreateFace(Points, nPoints);
pFace->RenderUnlit(true);
m_Faces.AddToTail(pFace);
fSin0 = fSin1;
fCos0 = fCos1;
}
}
}
//
// Lobe's aren't defined for > 90
//
if (m_fOuterConeAngle > 90)
return;
//
// Build the a face list that shows light-angle falloff
//
float fStepSize = 360.0 / 15.0;
float fPitchStepSize = 90.0 / 15.0;
float fFocusRadius0 = 0;
float fFocusDist0 = fZoneDist[1];
float fInnerDot = cos(DEG2RAD(m_fInnerConeAngle));
float fOuterDot = cos(DEG2RAD(m_fOuterConeAngle));
for (float fPitch = fPitchStepSize; fPitch < m_fOuterConeAngle + fPitchStepSize; fPitch += fPitchStepSize)
{
float fSin0 = 0;
float fCos0 = 1;
// clamp to edge of cone
if (fPitch > m_fOuterConeAngle)
fPitch = m_fOuterConeAngle;
float fIllumination = 0;
if (fPitch <= m_fInnerConeAngle)
{
fIllumination = 1.0;
}
else
{
float fPitchDot = cos(DEG2RAD(fPitch));
fIllumination = (fPitchDot - fOuterDot) / (fInnerDot - fOuterDot);
if ((m_fFocus != 1) && (m_fFocus != 0))
{
fIllumination = pow( fIllumination, m_fFocus );
}
}
// cosine falloff ^ exponent
// draw as lobe
float fFocusDist1 = cos(DEG2RAD(fPitch)) * fIllumination * fZoneDist[1];
float fFocusRadius1 = sin(DEG2RAD(fPitch)) * fIllumination * fZoneDist[1];
// draw as disk
// float fFocusDist1 = fZoneDist[1];
// float fFocusRadius1 = sin(DEG2RAD(fPitch)) * fZoneRadius[1] / sin(DEG_RAD * m_fConeAngle);
for (int fAngle = fStepSize; fAngle <= 361; fAngle += fStepSize)
{
float fSin1 = sin(DEG2RAD(fAngle));
float fCos1 = cos(DEG2RAD(fAngle));
Vector Points[4];
Points[0][2] = fFocusRadius1 * fCos0;
Points[0][1] = fFocusRadius1 * fSin0;
Points[0][0] = fFocusDist1;
Points[1][2] = fFocusRadius1 * fCos1;
Points[1][1] = fFocusRadius1 * fSin1;
Points[1][0] = fFocusDist1;
Points[2][2] = fFocusRadius0 * fCos1;
Points[2][1] = fFocusRadius0 * fSin1;
Points[2][0] = fFocusDist0;
int nPoints = 3;
if (fFocusRadius0 != 0)
{
Points[3][2] = fFocusRadius0 * fCos0;
Points[3][1] = fFocusRadius0 * fSin0;
Points[3][0] = fFocusDist0;
nPoints = 4;
}
CMapFace *pFace = new CMapFace;
pFace->SetRenderColor(r * fIllumination, g * fIllumination, b * fIllumination);
pFace->SetRenderAlpha(180);
pFace->CreateFace(Points, nPoints);
pFace->RenderUnlit(true);
m_Faces.AddToTail(pFace);
fSin0 = fSin1;
fCos0 = fCos1;
}
fFocusRadius0 = fFocusRadius1;
fFocusDist0 = fFocusDist1;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : bFullUpdate -
//-----------------------------------------------------------------------------
void CMapLightCone::CalcBounds(BOOL bFullUpdate)
{
CMapClass::CalcBounds(bFullUpdate);
//
// HACK: Update our origin to stick to our parent.
//
if (m_pParent != NULL)
{
GetParent()->GetOrigin(m_Origin);
}
//
// Pretend to be very small for the 2D view. Won't be necessary when 2D
// rendering is done in the map classes.
//
m_Render2DBox.ResetBounds();
m_Render2DBox.UpdateBounds(m_Origin);
SetCullBoxFromFaceList( &m_Faces );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : Angles -
//-----------------------------------------------------------------------------
void CMapLightCone::GetAngles(QAngle &Angles)
{
Angles = m_Angles;
if (m_bPitchSet)
{
Angles[PITCH] = m_fPitch;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : CMapClass
//-----------------------------------------------------------------------------
CMapClass *CMapLightCone::Copy(bool bUpdateDependencies)
{
CMapLightCone *pCopy = new CMapLightCone;
if (pCopy != NULL)
{
pCopy->CopyFrom(this, bUpdateDependencies);
}
return(pCopy);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pObject -
// Output : CMapClass
//-----------------------------------------------------------------------------
CMapClass *CMapLightCone::CopyFrom(CMapClass *pObject, bool bUpdateDependencies)
{
Assert(pObject->IsMapClass(MAPCLASS_TYPE(CMapLightCone)));
CMapLightCone *pFrom = (CMapLightCone *)pObject;
CMapClass::CopyFrom(pObject, bUpdateDependencies);
m_fBrightness = pFrom->m_fBrightness;
m_fQuadraticAttn = pFrom->m_fQuadraticAttn;
m_fLinearAttn = pFrom->m_fLinearAttn;
m_fConstantAttn = pFrom->m_fConstantAttn;
m_flPitchScale = pFrom->m_flPitchScale;
m_fInnerConeAngle = pFrom->m_fInnerConeAngle;
m_fOuterConeAngle = pFrom->m_fOuterConeAngle;
m_Angles = pFrom->m_Angles;
m_bPitchSet = pFrom->m_bPitchSet;
m_fPitch = pFrom->m_fPitch;
m_fFocus = pFrom->m_fFocus;
m_fFiftyPercentDistance = pFrom->m_fFiftyPercentDistance;
m_fZeroPercentDistance = pFrom->m_fZeroPercentDistance;
m_LightColor = pFrom->m_LightColor;
Q_strncpy( m_szColorKeyName, pFrom->m_szColorKeyName, sizeof( m_szColorKeyName ) );
Q_strncpy( m_szInnerConeKeyName, pFrom->m_szInnerConeKeyName, sizeof( m_szInnerConeKeyName ) );
Q_strncpy( m_szOuterConeKeyName, pFrom->m_szOuterConeKeyName, sizeof( m_szOuterConeKeyName ) );
BuildCone();
SignalUpdate( EVTYPE_LIGHTING_CHANGED );
return(this);
}
//-----------------------------------------------------------------------------
// Purpose: Notifies that this object's parent entity has had a key value change.
// Input : szKey - The key that changed.
// szValue - The new value of the key.
//-----------------------------------------------------------------------------
void CMapLightCone::OnParentKeyChanged(const char *szKey, const char *szValue)
{
bool bRebuild = true;
if (!stricmp(szKey, "angles"))
{
sscanf(szValue, "%f %f %f", &m_Angles[PITCH], &m_Angles[YAW], &m_Angles[ROLL]);
}
else if (!stricmp(szKey, m_szColorKeyName))
{
int nRed;
int nGreen;
int nBlue;
int nBrightness;
sscanf(szValue, "%d %d %d %d", &nRed, &nGreen, &nBlue, &nBrightness);
r = m_LightColor.x = nRed;
g = m_LightColor.y = nGreen;
b = m_LightColor.z = nBlue;
m_fBrightness = nBrightness;
}
else if (!stricmp(szKey, "pitch"))
{
// Pitch
m_bPitchSet = true;
m_fPitch = atof(szValue);
}
else if (!stricmp(szKey, "_constant_attn"))
{
// Constant attenuation
m_fConstantAttn = atof(szValue);
}
else if (!stricmp(szKey, "_linear_attn"))
{
// Linear attenuation
m_fLinearAttn = atof(szValue);
}
else if (!stricmp(szKey, "_quadratic_attn"))
{
// Quadratic attenuation
m_fQuadraticAttn = atof(szValue);
}
else if (!stricmp(szKey, "_exponent"))
{
// Focus
m_fFocus = atof(szValue);
}
else if (!stricmp(szKey, "_fifty_percent_distance"))
{
// Focus
m_fFiftyPercentDistance = atof(szValue);
}
else if (!stricmp(szKey, "_zero_percent_distance"))
{
// Focus
m_fZeroPercentDistance = atof(szValue);
}
else if (!stricmp(szKey, m_szInnerConeKeyName) || !stricmp(szKey, m_szOuterConeKeyName))
{
// check both of these together since they might be the same key.
if( !stricmp(szKey, m_szInnerConeKeyName ))
{
// Inner Cone angle
m_fInnerConeAngle = atof(szValue);
}
if( !stricmp(szKey, m_szOuterConeKeyName ))
{
// Outer Cone angle
m_fOuterConeAngle = atof(szValue);
}
}
else
{
bRebuild = false;
}
if (bRebuild)
{
SignalUpdate( EVTYPE_LIGHTING_CHANGED );
BuildCone();
PostUpdate(Notify_Changed);
}
}
//-----------------------------------------------------------------------------
// Purpose: Called after the entire map has been loaded. This allows the object
// to perform any linking with other map objects or to do other operations
// that require all world objects to be present.
// Input : pWorld - The world that we are in.
//-----------------------------------------------------------------------------
void CMapLightCone::PostloadWorld(CMapWorld *pWorld)
{
CMapClass::PostloadWorld(pWorld);
BuildCone();
SignalUpdate( EVTYPE_LIGHTING_CHANGED );
CalcBounds();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pRender -
//-----------------------------------------------------------------------------
void CMapLightCone::Render3D(CRender3D *pRender)
{
if (m_pParent->IsSelected())
{
CMatRenderContextPtr pRenderContext( MaterialSystemInterface() );
pRenderContext->MatrixMode(MATERIAL_MODEL);
pRenderContext->PushMatrix();
pRenderContext->Translate(m_Origin[0], m_Origin[1], m_Origin[2]);
QAngle Angles;
GetAngles(Angles);
pRenderContext->Rotate(Angles[YAW], 0, 0, 1);
pRenderContext->Rotate(m_flPitchScale * Angles[PITCH], 0, -1, 0);
pRenderContext->Rotate(Angles[ROLL], 1, 0, 0);
if (
(pRender->GetCurrentRenderMode() != RENDER_MODE_LIGHT_PREVIEW2) &&
(pRender->GetCurrentRenderMode() != RENDER_MODE_LIGHT_PREVIEW_RAYTRACED) &&
(GetSelectionState() != SELECT_MODIFY )
)
{
// Render the cone faces flatshaded.
pRender->PushRenderMode( RENDER_MODE_TRANSLUCENT_FLAT );
for (int i = 0; i < m_Faces.Count(); i++)
{
CMapFace *pFace = m_Faces.Element(i);
pFace->Render3D(pRender);
}
pRender->PopRenderMode();
}
//
// Render the cone faces in yellow wireframe (on top)
//
pRender->PushRenderMode( RENDER_MODE_WIREFRAME );
for (int i = 0; i < m_Faces.Count(); i++)
{
CMapFace *pFace = m_Faces.Element(i);
pFace->Render3D(pRender);
}
//
// Restore the default rendering mode.
//
pRender->PopRenderMode();
pRenderContext->PopMatrix();
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : File -
// bRMF -
// Output : int
//-----------------------------------------------------------------------------
int CMapLightCone::SerializeRMF(std::fstream &File, BOOL bRMF)
{
return(0);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : File -
// bRMF -
// Output : int
//-----------------------------------------------------------------------------
int CMapLightCone::SerializeMAP(std::fstream &File, BOOL bRMF)
{
return(0);
}
//-----------------------------------------------------------------------------
// Purpose: Solves a quadratic equation with the given coefficients.
// Input : x - Receives solution.
// y - Root to solve for.
// A, B, C - Quadratic, linear, and constant coefficients.
// Output : Returns true if a real solution was found, false if not.
//-----------------------------------------------------------------------------
bool CMapLightCone::SolveQuadratic(float &x, float y, float A, float B, float C)
{
C -= y;
if (A == 0)
{
if (B != 0)
{
x = -C / B;
return(true);
}
}
else
{
float fDeterminant = B * B - 4 * A * C;
if (fDeterminant > 0)
{
x = (-B + sqrt(fDeterminant)) / (2 * A);
return(true);
}
}
return(false);
}
//-----------------------------------------------------------------------------
// Purpose: Never select anything because of this helper.
//-----------------------------------------------------------------------------
CMapClass *CMapLightCone::PrepareSelection(SelectMode_t eSelectMode)
{
return NULL;
}