/*
**	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;
	}
}

