306 lines
10 KiB
C++
306 lines
10 KiB
C++
/*
|
|
** Command & Conquer Renegade(tm)
|
|
** Copyright 2025 Electronic Arts Inc.
|
|
**
|
|
** This program is free software: you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation, either version 3 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/***********************************************************************************************
|
|
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
|
|
***********************************************************************************************
|
|
* *
|
|
* Project Name : WWPhys *
|
|
* *
|
|
* $Archive:: /Commando/Code/wwphys/dynamicshadowmanager.cpp $*
|
|
* *
|
|
* Original Author:: Greg Hjelstrom *
|
|
* *
|
|
* $Author:: Greg_h $*
|
|
* *
|
|
* $Modtime:: 12/07/01 4:39p $*
|
|
* *
|
|
* $Revision:: 14 $*
|
|
* *
|
|
*---------------------------------------------------------------------------------------------*
|
|
* Functions: *
|
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
|
|
#include "dynamicshadowmanager.h"
|
|
#include "chunkio.h"
|
|
#include "phys.h"
|
|
#include "dyntexproject.h"
|
|
#include "pscene.h"
|
|
#include "physcoltest.h"
|
|
#include "light.h"
|
|
#include "texture.h"
|
|
#include "physresourcemgr.h"
|
|
|
|
|
|
#define SINGLE_SHADOW_CODE 1
|
|
|
|
DynamicShadowManagerClass::DynamicShadowManagerClass(PhysClass & parent) :
|
|
Parent(parent),
|
|
Shadow(NULL),
|
|
ShadowNearZ(-1.0f),
|
|
ShadowFarZ(-1.0f),
|
|
ForceUseBlobBox(false),
|
|
BlobBoxProjectionScale(1,1,1)
|
|
{
|
|
}
|
|
|
|
DynamicShadowManagerClass::~DynamicShadowManagerClass(void)
|
|
{
|
|
Release_Shadow();
|
|
}
|
|
|
|
|
|
void DynamicShadowManagerClass::Update_Shadow(void)
|
|
{
|
|
#if SINGLE_SHADOW_CODE
|
|
/*
|
|
** Shadow Update
|
|
** - if shadows are off, release projector and RETURN
|
|
** - find dominant light source (or multiple sources?)
|
|
** - if no light sources, set projector intensity to 0.0 and RETURN
|
|
** - for each light source
|
|
** - re-use projector from previous frame or allocate a new projector
|
|
** - initialize the projection parameters, depending on: blob/real,point/directional
|
|
** - set the shadow's intensity by attenuating it with distance from lightsource
|
|
*/
|
|
PhysicsSceneClass * scene = PhysicsSceneClass::Get_Instance();
|
|
PhysicsSceneClass::ShadowEnum shadow_mode = scene->Get_Shadow_Mode();
|
|
|
|
float near_atten;
|
|
float far_atten;
|
|
scene->Get_Shadow_Attenuation(&near_atten,&far_atten);
|
|
|
|
RenderObjClass * model = Parent.Peek_Model();
|
|
|
|
/*
|
|
** Grab the bounding box info based on whether we are using blobs or real projections
|
|
** use blobs IF(the mode is BLOBS or (the mode is BLOBS_PLUS and we're not the 'star'))
|
|
*/
|
|
AABoxClass objbox;
|
|
|
|
bool use_blob = (shadow_mode == PhysicsSceneClass::SHADOW_MODE_BLOBS) ||
|
|
((shadow_mode == PhysicsSceneClass::SHADOW_MODE_BLOBS_PLUS) && (!Parent.Is_Force_Projection_Shadow_Enabled()));
|
|
|
|
if (use_blob) {
|
|
Parent.Get_Shadow_Blob_Box(&objbox);
|
|
} else {
|
|
if (ForceUseBlobBox) {
|
|
Parent.Get_Shadow_Blob_Box(&objbox);
|
|
objbox.Extent.Scale(BlobBoxProjectionScale);
|
|
} else {
|
|
model->Get_Obj_Space_Bounding_Box(objbox);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Check if the center of the bounding box is well beyond the shadow fading
|
|
** distance. If so, we will exit early
|
|
*/
|
|
Vector3 position;
|
|
Matrix3D::Transform_Vector(Parent.Get_Transform(),objbox.Center,&position);
|
|
float shadow_shutoff2 = far_atten * 1.3f * far_atten * 1.3f;
|
|
float shadow_dist2 = (position - scene->Get_Last_Camera_Position()).Length2();
|
|
|
|
/*
|
|
** If shadow generation is disabled or we are farther than 1.2x the shadow attenuation distance,
|
|
** release any projector that we may have and return
|
|
*/
|
|
if ( (Parent.Do_Any_Effects_Suppress_Shadows()) ||
|
|
(Parent.Is_Shadow_Generation_Enabled() == false) ||
|
|
(shadow_mode == PhysicsSceneClass::SHADOW_MODE_NONE) ||
|
|
(shadow_dist2 > shadow_shutoff2) ||
|
|
(model == NULL) ||
|
|
(model->Is_Hidden()) ||
|
|
(objbox.Extent.Length2() < 0.1f) )
|
|
{
|
|
if (Shadow != NULL) {
|
|
scene->Remove_Dynamic_Texture_Projector(Shadow);
|
|
Shadow->Release_Ref();
|
|
Shadow = NULL;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
** Find dominant light source. First check if sun is available, then find closest
|
|
** static light.
|
|
*/
|
|
bool found_light = false;
|
|
Vector3 sunlight;
|
|
scene->Get_Sun_Light_Vector(&sunlight);
|
|
|
|
if (Parent.Is_In_The_Sun()) {
|
|
|
|
/*
|
|
** We can see the sunlight so set our projector up for it.
|
|
** Our shadow is attenuated only by distance from the camera so
|
|
** set the intensity to 'normal' and set the flag which causes
|
|
** the physics scene to attenuate it with distance.
|
|
*/
|
|
Allocate_Shadow();
|
|
|
|
/*
|
|
** Save parameters in the projector so it can continue to
|
|
** update later if it needs to fade out
|
|
*/
|
|
LightClass * sun = scene->Get_Sun_Light();
|
|
Shadow->Enable_Perspective(false);
|
|
Shadow->Set_Light_Source_ID((uint32)sun);
|
|
Shadow->Set_Light_Vector(sunlight);
|
|
sun->Release_Ref();
|
|
found_light = true;
|
|
|
|
} else {
|
|
|
|
#pragma message ("(gth) Disabling local shadows")
|
|
#if 0
|
|
/*
|
|
** We couldn't use the sunlight so now we look for the nearest
|
|
** local light source which casts shadows. If we find one, initialize
|
|
** our shadow projector with it.
|
|
*/
|
|
NonRefPhysListClass lightlist;
|
|
scene->Collect_Lights(position,true,false,&lightlist);
|
|
|
|
if (!lightlist.Is_Empty()) {
|
|
|
|
/*
|
|
** Ensure that a shadow is allocated!
|
|
*/
|
|
Allocate_Shadow();
|
|
|
|
LightClass * best_light = NULL;
|
|
NonRefPhysListIterator it(&lightlist);
|
|
for (it.First(); !it.Is_Done(); it.Next()) {
|
|
best_light = (LightClass *)(it.Peek_Obj()->Peek_Model());
|
|
break;
|
|
}
|
|
|
|
if (best_light) {
|
|
|
|
#if TRUE_PERSPECTIVE_SHADOWS // This code uses true perspective projection for local light sources
|
|
Shadow->Enable_Perspective(true);
|
|
Shadow->Set_Light_Source_ID((uint32)best_light);
|
|
Shadow->Set_Light_Vector(best_light->Get_Position());
|
|
|
|
#else // This code uses an orthographic approximation
|
|
Shadow->Enable_Perspective(false);
|
|
Shadow->Set_Light_Source_ID((uint32)best_light);
|
|
|
|
Vector3 direction;
|
|
Get_Position(&direction);
|
|
direction -= best_light->Get_Position();
|
|
direction.Normalize();
|
|
Shadow->Set_Light_Vector(direction);
|
|
#endif
|
|
found_light = true;
|
|
|
|
DEBUG_RENDER_VECTOR(position,best_light->Get_Position()-position,Vector3(1,1,1));
|
|
}
|
|
}
|
|
#endif //0
|
|
}
|
|
|
|
if (found_light) {
|
|
|
|
/*
|
|
** We have a shadow and a light so update the projection parameters.
|
|
** If we are using blob shadows, just plug in the blob texture and
|
|
** clear the dirty flag. Otherwise, mark the texture dirty so it will
|
|
** be re-generated if the shadow actually gets projected onto something
|
|
*/
|
|
Shadow->Update_Projection(objbox,Parent.Get_Transform(),ShadowNearZ,ShadowFarZ);
|
|
Shadow->Set_Intensity(scene->Get_Shadow_Normal_Intensity());
|
|
|
|
if (use_blob) {
|
|
TextureClass * shadow_texture = PhysResourceMgrClass::Get_Shadow_Blob_Texture();
|
|
WWASSERT(shadow_texture != NULL);
|
|
Shadow->Set_Texture(shadow_texture);
|
|
shadow_texture->Release_Ref();
|
|
Shadow->Set_Texture_Dirty(false);
|
|
} else {
|
|
Shadow->Set_Texture_Dirty(true);
|
|
}
|
|
|
|
} else {
|
|
|
|
/*
|
|
** If we have a shadow but we don't want it any more, wait until
|
|
** it fades out before we release it
|
|
*/
|
|
if (Shadow) {
|
|
Shadow->Set_Intensity(0.0f);
|
|
if (!Shadow->Is_Intensity_Zero()) {
|
|
Shadow->Update_Projection(objbox,Parent.Get_Transform(),ShadowNearZ,ShadowFarZ);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
/*
|
|
** - Collect all lights that want to and can cast a shadow with this object (raytest, etc)
|
|
** - Reduce number of lights in list to MaxShadowsPerObject
|
|
** - Create new empty shadow object list
|
|
** - For each light
|
|
** - Try to find shadow which used this light from prev-frame's shadow list, if found
|
|
** remove it from prev-frame's list and put in current frame's shadow list
|
|
** Else create a new shadow object, immediately set its intensity to zero (since it is turning on)
|
|
** - Initialize projector with light parameters
|
|
** - For each shadow still in prev-frame's list
|
|
** - If intensity is zero, destroy it.
|
|
** Else
|
|
** - Set target intensity to zero
|
|
** - Update projection parameters, move into this frame's shadow list
|
|
*/
|
|
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
void DynamicShadowManagerClass::Allocate_Shadow(void)
|
|
{
|
|
if (Shadow == NULL) {
|
|
PhysicsSceneClass * scene = PhysicsSceneClass::Get_Instance();
|
|
|
|
Shadow = NEW_REF(DynTexProjectClass,(&Parent));
|
|
Shadow->Enable_Attenuation(true);
|
|
Shadow->Enable_Depth_Gradient(true);
|
|
Shadow->Enable_Affect_Dynamic_Objects(false);
|
|
Shadow->Set_Intensity(scene->Get_Shadow_Normal_Intensity(),true);
|
|
Shadow->Peek_Material_Pass()->Enable_On_Translucent_Meshes(false);
|
|
|
|
scene->Add_Dynamic_Texture_Projector(Shadow);
|
|
}
|
|
}
|
|
|
|
|
|
void DynamicShadowManagerClass::Release_Shadow(void)
|
|
{
|
|
if (Shadow) {
|
|
if (PhysicsSceneClass::Get_Instance()->Contains(Shadow)) {
|
|
PhysicsSceneClass::Get_Instance()->Remove_Dynamic_Texture_Projector(Shadow);
|
|
}
|
|
Shadow->Release_Ref();
|
|
Shadow = NULL;
|
|
}
|
|
}
|
|
|