/*
**	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/pscene_projectors.cpp                 $*
 *                                                                                             *
 *              Original Author:: Greg Hjelstrom                                               *
 *                                                                                             *
 *                      $Author:: Greg_h                                                      $*
 *                                                                                             *
 *                     $Modtime:: 1/11/02 3:12p                                               $*
 *                                                                                             *
 *                    $Revision:: 50                                                          $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include "pscene.h"
#include "colmathaabox.h"
#include "rinfo.h"
#include "staticaabtreecull.h"
#include "dynamicaabtreecull.h"
#include "physgridcull.h"
#include "lightcull.h"
#include "staticanimphys.h"
#include "assetmgr.h"
#include "refcount.h"
#include "camera.h"
#include "quat.h"
#include "win.h"
#include "vertmaterial.h"
#include "wwprofile.h"
#include "texture.h"
#include "dx8wrapper.h"
#include "pot.h"
#include "materialeffect.h"
#include "wwmemlog.h"
#include "targa.h"
#include "bitmaphandler.h"
#include "dx8caps.h"
#include "vertmaterial.h"


#define DEBUG_SHADOW_RENDERING				0

#if (DEBUG_SHADOW_RENDERING)
#define SHADOW_WINDOW_STYLE		WS_POPUP | WS_CAPTION | WS_BORDER | WS_VISIBLE
#else
#define SHADOW_WINDOW_STYLE		WS_POPUP | WS_CAPTION | WS_BORDER
#endif

const int		SHADOW_CLIP_FAR							= 500;
const int		STATIC_PROJECTOR_RESOLUTION			= 256; //128;
const float		STATIC_SHADOW_INTENSITY					= 0.6f;
const float		MIN_STATIC_SHADOW_COS_HALF_THETA		= cos(DEG_TO_RADF(10.0f)/2.0f);	// angle to allow shadow tex re-use

const int		DEFAULT_MAX_DYNAMIC_SHADOWS			= 6;
const int		DEFAULT_DYNAMIC_SHADOW_RESOLUTION	= 256;

/**
** StaticShadowTexMgrClass
** This object simply manages the list of unique shadow textures being used by the
** static shadow projectors.  For example, all instances of the same tree model
** re-use the same shadow texture...
*/
class StaticShadowTexMgrClass
{
public:
	StaticShadowTexMgrClass(void);
	virtual ~StaticShadowTexMgrClass(void);
	void						Reset(void);

	TextureClass *			Peek_Shadow_Texture(uint32 obj_type_id,const Quaternion & orientation);
	void						Add_Shadow_Texture(uint32 obj_type_id,const Quaternion & orientation,TextureClass * tex);
	void						Remove_Shadow_Texture(TextureClass * tex);

private:

	class	 ShadowTexClass
	{
	public:
		ShadowTexClass(void);
		ShadowTexClass(uint32 obj_type_id,const Quaternion & orientation,TextureClass * tex);
		ShadowTexClass(const ShadowTexClass & that);
		~ShadowTexClass(void);
		const ShadowTexClass & operator = (const ShadowTexClass &);

		bool					operator == (const ShadowTexClass &)						{ return false; }
		bool					operator != (const ShadowTexClass & that)					{ return true; }
		uint32				ObjectTypeID;			// use the Definition ID to uniquely identify object types
		Quaternion			ObjectOrientation;	// orientation of the object when this shadow was generated
		TextureClass *		Texture;					// texture

	};

	DynamicVectorClass<ShadowTexClass>	ShadowTextures;
};


/**
** DynamicShadowTexMgrClass
** This class manages a pool of render target textures which are shared by the currently
** active shadow projectors.
*/
class DynamicShadowTexMgrClass
{
public:

	DynamicShadowTexMgrClass(void);
	virtual ~DynamicShadowTexMgrClass(void);
//	void					Reset(); // Jani: Disabling reset. Re-allocating render targets if the device is out of
										// free texture memory causes problems on at least TNT2.

	void					Set_Max_Simultaneous_Shadows(unsigned int max);
	unsigned int		Get_Max_Simultaneous_Shadows(void);

	void					Set_Shadow_Resolution(unsigned int size);
	unsigned int		Get_Shadow_Resolution(void);

	void					Per_Frame_Reset(void);
	void					Assign_Render_Target_Texture(TexProjectClass * tex_proj);

private:

	TextureClass *		Allocate_Render_Target_Texture(void);

	unsigned int		CurShadow;
	unsigned int		TextureResolution;
	SimpleVecClass<TextureClass *>	ShadowTextures;

};


/*
**
** Instantiate the Shadow Texture Managers.
**
*/
static StaticShadowTexMgrClass		_StaticShadowTexMgr;
static DynamicShadowTexMgrClass		_DynamicShadowTexMgr;


static TextureClass* Create_Projector_Render_Target(unsigned w,unsigned h)
{
	WW3DFormat format=WW3D_FORMAT_UNKNOWN;
	// Try if 8 or 16 bit render target formats would be supported
	if (DX8Wrapper::Get_Current_Caps()->Support_Render_To_Texture_Format(WW3D_FORMAT_R3G3B2)) format=WW3D_FORMAT_R3G3B2;
	else if (DX8Wrapper::Get_Current_Caps()->Support_Render_To_Texture_Format(WW3D_FORMAT_R5G6B5)) format=WW3D_FORMAT_R5G6B5;
	else if (DX8Wrapper::Get_Current_Caps()->Support_Render_To_Texture_Format(WW3D_FORMAT_A4R4G4B4)) format=WW3D_FORMAT_A4R4G4B4;
	else if (DX8Wrapper::Get_Current_Caps()->Support_Render_To_Texture_Format(WW3D_FORMAT_X1R5G5B5)) format=WW3D_FORMAT_X1R5G5B5;

	TextureClass* texture = DX8Wrapper::Create_Render_Target(w,h,format);
	if (texture) return texture;

	// As a last resort, try creating with unknown format (which means using the current display resolution)
	if (format!=WW3D_FORMAT_UNKNOWN) {
		format=WW3D_FORMAT_UNKNOWN;
		return DX8Wrapper::Create_Render_Target(w,h,format);
	}
	return NULL;

}

/************************************************************************************
**
** ShadowTexClass Implemenation
**
************************************************************************************/
StaticShadowTexMgrClass::ShadowTexClass::ShadowTexClass(void) :
	ObjectTypeID(0),
	ObjectOrientation(1),
	Texture(NULL)
{
}

StaticShadowTexMgrClass::ShadowTexClass::ShadowTexClass
(
	uint32 obj_type_id,
	const Quaternion & orientation,
	TextureClass * tex
) :
	ObjectTypeID(obj_type_id),
	ObjectOrientation(orientation),
	Texture(NULL)
{
	REF_PTR_SET(Texture,tex);
}

StaticShadowTexMgrClass::ShadowTexClass::ShadowTexClass(const ShadowTexClass & that) :
	ObjectTypeID(0),
	ObjectOrientation(1),
	Texture(NULL)
{
	*this = that;
}

const StaticShadowTexMgrClass::ShadowTexClass &
StaticShadowTexMgrClass::ShadowTexClass::operator = (const ShadowTexClass & that)
{
	ObjectTypeID = that.ObjectTypeID;
	ObjectOrientation = that.ObjectOrientation;
	REF_PTR_SET(Texture,that.Texture);
	return *this;
}

StaticShadowTexMgrClass::ShadowTexClass::~ShadowTexClass(void)
{
	REF_PTR_RELEASE(Texture);
}


/************************************************************************************
**
** StaticShadowTexMgrClass Implemenation
**
************************************************************************************/
StaticShadowTexMgrClass::StaticShadowTexMgrClass(void)
{
}

StaticShadowTexMgrClass::~StaticShadowTexMgrClass(void)
{
}

void StaticShadowTexMgrClass::Reset(void)
{
	ShadowTextures.Delete_All();
}

TextureClass * StaticShadowTexMgrClass::Peek_Shadow_Texture
(
	uint32					obj_type_id,
	const Quaternion &	orientation
)
{
	for (int i=0; i<ShadowTextures.Count(); i++) {
		Quaternion deltaq = ShadowTextures[i].ObjectOrientation * Inverse(orientation);
		if (	(ShadowTextures[i].ObjectTypeID == obj_type_id) &&
				(deltaq.W > MIN_STATIC_SHADOW_COS_HALF_THETA))
		{
			return ShadowTextures[i].Texture;
		}
	}
	return NULL;
}

void StaticShadowTexMgrClass::Add_Shadow_Texture
(
	uint32					obj_type_id,
	const Quaternion &	orientation,
	TextureClass *			tex
)
{
	WWASSERT(Peek_Shadow_Texture(obj_type_id,orientation) == NULL);
	ShadowTexClass record(obj_type_id,orientation,tex);
	ShadowTextures.Add(record);
}

void StaticShadowTexMgrClass::Remove_Shadow_Texture
(
	TextureClass *			tex
)
{
	for (int i=0; i<ShadowTextures.Count(); i++) {
		if (ShadowTextures[i].Texture == tex) {
			ShadowTextures.Delete(i);
			return;
		}
	}
}


/************************************************************************************
**
** DynamicShadowTexMgrClass Implementation
**
************************************************************************************/
DynamicShadowTexMgrClass::DynamicShadowTexMgrClass(void) :
	CurShadow(0),
	TextureResolution(DEFAULT_DYNAMIC_SHADOW_RESOLUTION)
{
	WWMEMLOG(MEM_GAMEDATA);

	ShadowTextures.Resize(DEFAULT_MAX_DYNAMIC_SHADOWS);
	for (int i=0; i<ShadowTextures.Length(); i++) {
		ShadowTextures[i] = NULL;
	}
}

DynamicShadowTexMgrClass::~DynamicShadowTexMgrClass(void)
{
//	Reset();
	for (int i=0; i<ShadowTextures.Length(); i++) {
		REF_PTR_RELEASE(ShadowTextures[i]);
	}
}
/*
void DynamicShadowTexMgrClass::Reset(void)
{
	for (int i=0; i<ShadowTextures.Length(); i++) {
		REF_PTR_RELEASE(ShadowTextures[i]);
	}
}
*/

// Set the maximum number of dynamic render targets. Allocate all
// textures at this point!
void DynamicShadowTexMgrClass::Set_Max_Simultaneous_Shadows(unsigned int max)
{
	int curlen = ShadowTextures.Length();
	for (int i=max;i<curlen; i++) {
		REF_PTR_RELEASE(ShadowTextures[i]);
	}
	ShadowTextures.Resize(max);

	if (curlen>ShadowTextures.Length()) curlen=ShadowTextures.Length();
	for (i=0; i<curlen; i++) {
		if (!ShadowTextures[i]) {
			ShadowTextures[i] = Allocate_Render_Target_Texture();
		}
	}
	for (; i<ShadowTextures.Length(); i++) {
		ShadowTextures[i] = Allocate_Render_Target_Texture();
	}
}

unsigned int DynamicShadowTexMgrClass::Get_Max_Simultaneous_Shadows(void)
{
	return ShadowTextures.Length();
}

void DynamicShadowTexMgrClass::Set_Shadow_Resolution(unsigned int res)
{
	unsigned int oksize = ::Find_POT(res);
	if (oksize > 256) {
		oksize = 256;
	}
	if (oksize < 16) {
		oksize = 16;
	}

	if (oksize != TextureResolution) {

		int i;
		for (i=0; i<ShadowTextures.Length(); i++) {
			REF_PTR_RELEASE(ShadowTextures[i]);
		}

		TextureResolution = oksize;

		for (i=0; i<ShadowTextures.Length(); i++) {
			ShadowTextures[i] = Allocate_Render_Target_Texture();
		}
	}
}

unsigned int DynamicShadowTexMgrClass::Get_Shadow_Resolution(void)
{
	return TextureResolution;
}

void DynamicShadowTexMgrClass::Per_Frame_Reset(void)
{
	CurShadow = 0;
}

void DynamicShadowTexMgrClass::Assign_Render_Target_Texture(TexProjectClass * tex_proj)
{
	if (CurShadow < (unsigned int)ShadowTextures.Length()) {
		if (ShadowTextures[CurShadow] == NULL) {
			ShadowTextures[CurShadow] = Allocate_Render_Target_Texture();
		}
		tex_proj->Set_Render_Target(ShadowTextures[CurShadow]);
		CurShadow++;
	}
}

TextureClass * DynamicShadowTexMgrClass::Allocate_Render_Target_Texture(void)
{
	TextureClass * texture=Create_Projector_Render_Target(TextureResolution,TextureResolution);

	if (texture != NULL) {

		SET_REF_OWNER(texture);
		texture->Set_U_Addr_Mode(TextureClass::TEXTURE_ADDRESS_CLAMP);
		texture->Set_V_Addr_Mode(TextureClass::TEXTURE_ADDRESS_CLAMP);
	}
	return texture;
}




/************************************************************************************
**
** PhysicsSceneClass Texture Projection Code
**
************************************************************************************/
void PhysicsSceneClass::Release_Projector_Resources(void)
{
	if (ShadowRenderContext != NULL) {
		delete ShadowRenderContext;
		ShadowRenderContext=NULL;
	}

	REF_PTR_RELEASE(ShadowMaterialPass);
	REF_PTR_RELEASE(ShadowCamera);
	REF_PTR_RELEASE(ShadowBlobTexture);
	_StaticShadowTexMgr.Reset();
//	_DynamicShadowTexMgr.Reset();
}


void PhysicsSceneClass::Set_Shadow_Resolution(unsigned int res)
{
	_DynamicShadowTexMgr.Set_Shadow_Resolution(res);
}

unsigned int PhysicsSceneClass::Get_Shadow_Resolution(void)
{
	return _DynamicShadowTexMgr.Get_Shadow_Resolution();
}

void PhysicsSceneClass::Set_Max_Simultaneous_Shadows(unsigned int count)
{
	_DynamicShadowTexMgr.Set_Max_Simultaneous_Shadows(count);
}

unsigned int PhysicsSceneClass::Get_Max_Simultaneous_Shadows(void)
{
	return _DynamicShadowTexMgr.Get_Max_Simultaneous_Shadows();
}


SpecialRenderInfoClass *
PhysicsSceneClass::Get_Shadow_Render_Context(int width,int height)
{
	if (ShadowRenderContext == NULL) {
		/*
		** Create a camera for shadow rendering to use
		*/
		if (ShadowCamera == NULL) {
			ShadowCamera = NEW_REF(CameraClass,());
			ShadowCamera->Set_Clip_Planes(0.2f,SHADOW_CLIP_FAR);
			ShadowCamera->Set_View_Plane(DEG_TO_RAD(90.0f),DEG_TO_RAD(90.0f));
			ShadowCamera->Set_Viewport(Vector2(0,0),Vector2(1,1));
		}

		/*
		** Create the render context
		*/
		ShadowRenderContext = new SpecialRenderInfoClass(*ShadowCamera,SpecialRenderInfoClass::RENDER_SHADOW);
	}

	return ShadowRenderContext;
}

MaterialPassClass * PhysicsSceneClass::Get_Shadow_Material_Pass(void)
{
	if (ShadowMaterialPass == NULL) {

		VertexMaterialClass * vmtl = NEW_REF(VertexMaterialClass,());
		vmtl->Set_Ambient(0,0,0);
		vmtl->Set_Diffuse(0,0,0);
		vmtl->Set_Specular(0,0,0);
		vmtl->Set_Emissive(0,0,0);
		vmtl->Set_Lighting(true);

		ShaderClass shader = ShaderClass::_PresetOpaqueShader;
		shader.Set_Depth_Compare(ShaderClass::PASS_ALWAYS);
		shader.Set_Depth_Mask(ShaderClass::DEPTH_WRITE_DISABLE);
		shader.Set_Texturing(ShaderClass::TEXTURING_DISABLE);

		ShadowMaterialPass = NEW_REF(MaterialPassClass,());
		ShadowMaterialPass->Set_Material(vmtl);
		ShadowMaterialPass->Set_Shader(shader);
		ShadowMaterialPass->Enable_On_Translucent_Meshes(false);

		REF_PTR_RELEASE(vmtl);
	}

	ShadowMaterialPass->Add_Ref();
	return ShadowMaterialPass;
}

void PhysicsSceneClass::Enable_Static_Projectors(bool onoff)
{
	StaticProjectorsEnabled = onoff;
}

bool PhysicsSceneClass::Are_Static_Projectors_Enabled(void)
{
	return StaticProjectorsEnabled;
}

void PhysicsSceneClass::Enable_Dynamic_Projectors(bool onoff)
{
	DynamicProjectorsEnabled = onoff;
}

bool PhysicsSceneClass::Are_Dynamic_Projectors_Enabled(void)
{
	return DynamicProjectorsEnabled;
}

void PhysicsSceneClass::Set_Shadow_Mode(ShadowEnum shadow_mode)
{
	if (((int)shadow_mode >= 0) && ((int)shadow_mode < SHADOW_MODE_COUNT)) {
		if (ShadowMode!=shadow_mode) {
			ShadowMode = shadow_mode;

			switch (ShadowMode) {
			default:
			case SHADOW_MODE_NONE:			// no shadows at all
			case SHADOW_MODE_BLOBS:			// projected blob shadows
				Set_Max_Simultaneous_Shadows(0);
				break;
			case SHADOW_MODE_BLOBS_PLUS:	// projected blobs with main character having a rendered shadow
				Set_Max_Simultaneous_Shadows(1);
				break;
			case SHADOW_MODE_HARDWARE:		// use render-to-texture hardware
				Set_Max_Simultaneous_Shadows(4);
				break;
			}
		}
	}
}

PhysicsSceneClass::ShadowEnum
PhysicsSceneClass::Get_Shadow_Mode(void)
{
	return ShadowMode;
}

void PhysicsSceneClass::Set_Shadow_Attenuation(float atten_start_distance,float atten_end_distance)
{
	if (atten_start_distance < 0.0f) {
		atten_start_distance = 0.0f;
	}
	if (atten_end_distance < atten_start_distance) {
		atten_end_distance = atten_start_distance;
	}
	ShadowAttenStart = atten_start_distance;
	ShadowAttenEnd = atten_end_distance;
}

void PhysicsSceneClass::Get_Shadow_Attenuation(float * set_atten_start,float * set_atten_end)
{
	if (set_atten_start != NULL) {
		*set_atten_start = ShadowAttenStart;
	}

	if (set_atten_end != NULL) {
		*set_atten_end = ShadowAttenEnd;
	}
}

void PhysicsSceneClass::Set_Shadow_Normal_Intensity(float normal_intensity)
{
	if (normal_intensity < 0.0f) {
		normal_intensity = 0.0f;
	}
	if (normal_intensity > 1.0f) {
		normal_intensity = 1.0f;
	}
	ShadowNormalIntensity = normal_intensity;
}

float PhysicsSceneClass::Get_Shadow_Normal_Intensity(void)
{
	return ShadowNormalIntensity;
}

void PhysicsSceneClass::Add_Static_Texture_Projector(TexProjectClass * newprojector)
{
	WWASSERT(newprojector);
	WWASSERT(!StaticProjectorList.Is_In_List(newprojector));

	StaticProjectorList.Add(newprojector);
	StaticProjectorCullingSystem->Add_Object(newprojector);
}

void PhysicsSceneClass::Remove_Static_Texture_Projector(TexProjectClass * projector)
{
	WWASSERT(projector);

	if (projector->Get_Culling_System() == StaticProjectorCullingSystem) {
		WWASSERT(StaticProjectorList.Is_In_List(projector));
		StaticProjectorList.Remove(projector);
		StaticProjectorCullingSystem->Remove_Object(projector);
	}
}

void PhysicsSceneClass::Add_Dynamic_Texture_Projector(TexProjectClass * newprojector)
{
	WWASSERT(newprojector);
	WWASSERT(!DynamicProjectorList.Is_In_List(newprojector));

	DynamicProjectorList.Add(newprojector);
	DynamicProjectorCullingSystem->Add_Object(newprojector);
}

void PhysicsSceneClass::Remove_Dynamic_Texture_Projector(TexProjectClass * projector)
{
	WWASSERT(projector);

	if (projector->Get_Culling_System() == DynamicProjectorCullingSystem) {
		WWASSERT(DynamicProjectorList.Is_In_List(projector));
		DynamicProjectorList.Remove(projector);
		DynamicProjectorCullingSystem->Remove_Object(projector);
	}
}

void PhysicsSceneClass::Remove_Texture_Projector(TexProjectClass * projector)
{
	WWASSERT(projector);

	if (DynamicProjectorList.Is_In_List(projector)) {
		DynamicProjectorCullingSystem->Remove_Object(projector);
		DynamicProjectorList.Remove(projector);
	} else if (StaticProjectorList.Is_In_List(projector)) {
		StaticProjectorCullingSystem->Remove_Object(projector);
		StaticProjectorList.Remove(projector);
	}
}

bool PhysicsSceneClass::Contains(TexProjectClass * projector)
{
	return (DynamicProjectorList.Is_In_List(projector) || StaticProjectorList.Is_In_List(projector));
}

float PhysicsSceneClass::Compute_Projector_Attenuation(TexProjectClass * dynamic_projector,const Vector3 & view_pos,const Vector3 & view_dir)
{
	Vector3 r;
	Vector3::Subtract(dynamic_projector->Get_Bounding_Volume().Center,view_pos,&r);
	float dist = Vector3::Dot_Product(r,view_dir);
	if (dist > ShadowAttenEnd) {
		return 0.0f;
	}
	if (dist < ShadowAttenStart) {
		return 1.0f;
	}
	return 1.0f - (dist - ShadowAttenStart) / (ShadowAttenEnd - ShadowAttenStart);
}


void PhysicsSceneClass::Apply_Projectors
(
	const CameraClass &	camera
)
{
	WWPROFILE("pscene::Apply_Projectors");

	Vector3 view_pos;
	Vector3 view_dir;
	camera.Get_Transform().Get_Translation(&view_pos);
	camera.Get_Transform().Get_Z_Vector(&view_dir);
	view_dir = -view_dir;

	if (StaticProjectorsEnabled) {

		/*
		** collect the visible static projectors
		*/
		StaticProjectorCullingSystem->Reset_Collection();
		StaticProjectorCullingSystem->Collect_Objects(camera.Get_Frustum());

		TexProjectClass * static_projector = StaticProjectorCullingSystem->Get_First_Collected_Object();
		while (static_projector != NULL) {

			/*
			** only keep considering this projector if its intensity is above ZERO
			*/
			if (!static_projector->Is_Intensity_Zero()) {

				/*
				** attenuate the projector with distance from the camera (if needed)
				*/
				float attenuation = 1.0f;
				if (static_projector->Is_Attenuation_Enabled()) {
					attenuation = Compute_Projector_Attenuation(static_projector,view_pos,view_dir);
					static_projector->Set_Attenuation(attenuation);
				}

				/*
				** if the projector is completely attenuated, don't process it
				*/
				if (attenuation > 0.0f) {

					if (ProjectorDebugDisplayEnabled) {
						DEBUG_RENDER_OBBOX(static_projector->Get_Bounding_Volume(),Vector3(0,1,0),0.25f);
					}

					Apply_Projector_To_Objects(static_projector,camera);
				}
			}
			static_projector = StaticProjectorCullingSystem->Get_Next_Collected_Object(static_projector);
		}
	}

	/*
	** Build a list of the dynamic shadow textures that need to be rendered
	*/
	TexProjListClass rt_projector_list;
	unsigned int count = 0;

	if (DynamicProjectorsEnabled) {

		/*
		** Collect the visible dynamic projectors
		*/
		DynamicProjectorCullingSystem->Reset_Collection();
		DynamicProjectorCullingSystem->Collect_Objects(camera.Get_Frustum());

		TexProjectClass * dynamic_projector = DynamicProjectorCullingSystem->Get_First_Collected_Object();
		while (dynamic_projector != NULL) {

			/*
			** only keep considering this projector if its intensity is above ZERO
			*/
			if (!dynamic_projector->Is_Intensity_Zero()) {

				/*
				** attenuate the projector with distance from the camera (if needed)
				*/
				float attenuation = 1.0f;
				if (dynamic_projector->Is_Attenuation_Enabled()) {
					attenuation = Compute_Projector_Attenuation(dynamic_projector,view_pos,view_dir);
					dynamic_projector->Set_Attenuation(attenuation);
				}

				/*
				** if the projector is completely attenuated, don't process it
				*/
				if (attenuation > 0.0f) {

					if (ProjectorDebugDisplayEnabled) {
						DEBUG_RENDER_OBBOX(dynamic_projector->Get_Bounding_Volume(),Vector3(1,0,0),0.25f);
					}

					/*
					** If this projector needs to recompute its texture, add it to the list,
					** otherwise set it up now.
					*/
					if (dynamic_projector->Needs_Render_Target()) {
						rt_projector_list.Add(dynamic_projector);
						count++;
					} else {
						Apply_Projector_To_Objects(dynamic_projector,camera);
					}
				}
			}
			dynamic_projector = DynamicProjectorCullingSystem->Get_Next_Collected_Object(dynamic_projector);
		}
	}

	/*
	** Reject texture projectors until we are below the maximum allowed simultaneous shadows
	*/
	Vector3 cam_pos;
	camera.Get_Transform().Get_Translation(&cam_pos);

	TexProjListIterator it(&rt_projector_list);
	while (count > _DynamicShadowTexMgr.Get_Max_Simultaneous_Shadows()) {

		/*
		** Find the projector farthest from the camera
		*/
		it.First();
		TexProjectClass * farthest_shadow = it.Peek_Obj();
		float farthest_dist = (farthest_shadow->Get_Bounding_Volume().Center - cam_pos).Length2();
		it.Next();

		while (!it.Is_Done()) {
			float dist = (it.Peek_Obj()->Get_Bounding_Volume().Center - cam_pos).Length2();
			if (dist > farthest_dist) {
				farthest_dist = dist;
				farthest_shadow = it.Peek_Obj();
			}
			it.Next();
		}

		/*
		** Remove it from the list and disable it.
		*/
		rt_projector_list.Remove(farthest_shadow);
		farthest_shadow->Set_Render_Target(NULL);
		count--;
	}

	/*
	** Process the most "important" active dynamic shadows
	*/
	_DynamicShadowTexMgr.Per_Frame_Reset();
	it.First();

	while (!it.Is_Done()) {

		TexProjectClass * projector = it.Peek_Obj();
		_DynamicShadowTexMgr.Assign_Render_Target_Texture(projector);

		if (projector->Peek_Render_Target() != NULL) {
			Apply_Projector_To_Objects(projector,camera);
		}

		it.Next();
	}

	DX8Wrapper::Set_Render_Target((IDirect3DSurface8 *)NULL);
}

void PhysicsSceneClass::Apply_Projector_To_Objects
(
	TexProjectClass * tex_proj,
	const CameraClass &	camera
)
{
	bool projector_update_needed = false;	// Needed if just one mesh uses it
	SimpleEffectClass * effect = NEW_REF(SimpleEffectClass,(tex_proj->Peek_Material_Pass()));
	effect->Enable_Auto_Remove(true);

	/*
	** collect the static objects intersecting the projector's volume
	*/
	if (tex_proj->Is_Affect_Static_Objects_Enabled()) {
		StaticCullingSystem->Reset_Collection();
		StaticCullingSystem->Collect_Objects(tex_proj->Get_Bounding_Volume());

		StaticPhysClass * static_obj = (StaticPhysClass *)StaticCullingSystem->Get_First_Collected_Object();
		while (static_obj) {

			/*
			** for each static object, if it also in our visible list, add this projector to it
			** since the visible static objects are split into two lists we check both.
			*/
			if (VisibleStaticObjectList.Contains(static_obj) || VisibleWSMeshList.Is_In_List(static_obj)) {

				// check if our volume actually intersects the mesh!
				if (static_obj->Intersects(tex_proj->Get_Bounding_Volume())) {
					static_obj->Add_Effect_To_Me(effect);
					projector_update_needed = true;
				}
			}
			static_obj = (StaticPhysClass *)StaticCullingSystem->Get_Next_Collected_Object(static_obj);
		}
	}

	/*
	** collect the dynamic objects intersecting the projector's volume
	*/
	if (tex_proj->Is_Affect_Dynamic_Objects_Enabled()) {
		DynamicCullingSystem->Reset_Collection();
		DynamicCullingSystem->Collect_Objects(tex_proj->Get_Bounding_Volume());

		PhysClass * dyn_obj = DynamicCullingSystem->Get_First_Collected_Object();
		while (dyn_obj) {

			/*
			** for each dynamic object, if it also in our visible list and is not the
			** object that generated this shadow, add this projector to it
			*/
			if ((VisibleDynamicObjectList.Contains(dyn_obj)) && (dyn_obj != tex_proj->Get_Projection_Object_ID())) {

				dyn_obj->Add_Effect_To_Me(effect);
				projector_update_needed = true;
			}
			dyn_obj = DynamicCullingSystem->Get_Next_Collected_Object(dyn_obj);
		}
	}

	// if this projector is needed, update it
	if (projector_update_needed) {
		tex_proj->Pre_Render_Update(camera.Get_Transform());
	}

	REF_PTR_RELEASE(effect);
}

static void Create_Render_Target_Test(TextureClass* render_target)
{
	Matrix4 old_view_transform;
	Matrix4 old_world_transform;
	Matrix4 old_projection_transform;
	DX8Wrapper::Get_Transform(D3DTS_VIEW,old_view_transform);
	DX8Wrapper::Get_Transform(D3DTS_WORLD,old_world_transform);
	DX8Wrapper::Get_Transform(D3DTS_PROJECTION,old_projection_transform);

	/*
	** Set the render target
	*/
	DX8Wrapper::Set_Render_Target(render_target);

		/*
		** Set up the camera
		*/
//		Configure_Camera(context->Camera);

	/*
	** Render the object
	*/
	Vector3 color(0.0f,0.0f,0.0f);
	WW3D::Begin_Render(true,false,color);	// false to zclear as we don't have z-buffer

	DX8Wrapper::Set_World_Identity();
	DX8Wrapper::Set_View_Identity();
	DX8Wrapper::Set_Transform(D3DTS_PROJECTION,Matrix4(true));

	const int vertex_count=9;
	const int polygon_count=3;
	DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,vertex_count);
	{
		DynamicVBAccessClass::WriteLockClass lock(&vb_access);
		VertexFormatXYZNDUV2* verts=lock.Get_Formatted_Vertex_Array();

		Vector3 vertices[vertex_count]={
			Vector3(-1.0f, 1.0f,0.0f),
			Vector3(-1.0f,-1.0f,0.0f),
			Vector3( 0.0f,-1.0f,0.0f),

			Vector3(-1.0f,-1.0f,0.0f),
			Vector3( 1.0f,-1.0f,0.0f),
			Vector3( 1.0f, 0.0f,0.0f),

			Vector3( 1.0f,-1.0f,0.0f),
			Vector3( 1.0f, 1.0f,0.0f),
			Vector3( 0.0f, 1.0f,0.0f)
		};

		unsigned colors[vertex_count]={
			0xff0000,
			0xff0000,
			0xff0000,
			0x00ff00,
			0x00ff00,
			0x00ff00,
			0x0000ff,
			0x0000ff,
			0x0000ff
		};

		for (int v=0;v<vertex_count;++v) {
			verts[v].x=vertices[v][0];
			verts[v].y=vertices[v][1];
			verts[v].z=vertices[v][2];
			verts[v].nx=0.0f;
			verts[v].ny=0.0f;
			verts[v].nz=0.0f;
			verts[v].diffuse=colors[v];
			verts[v].u1=0.0f;
			verts[v].v1=0.0f;
			verts[v].u2=0.0f;
			verts[v].v2=0.0f;
		}
	}

	DX8Wrapper::Set_Vertex_Buffer(vb_access);

	DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC_DX8,polygon_count*3);
	{
		DynamicIBAccessClass::WriteLockClass lock(&ib_access);
		unsigned short* inds=lock.Get_Index_Array();

		for (int i=0;i<polygon_count*3;++i) {
			*inds++=short(i);
		}
	}

	ShaderClass shader;
	shader.Set_Cull_Mode( ShaderClass::CULL_MODE_DISABLE );
	shader.Set_Depth_Mask( ShaderClass::DEPTH_WRITE_DISABLE );
	shader.Set_Depth_Compare( ShaderClass::PASS_ALWAYS );
	shader.Set_Dst_Blend_Func( ShaderClass::DSTBLEND_ONE );
	shader.Set_Src_Blend_Func( ShaderClass::SRCBLEND_ONE );
	shader.Set_Fog_Func( ShaderClass::FOG_DISABLE );
	shader.Set_Primary_Gradient( ShaderClass::GRADIENT_MODULATE );
	shader.Set_Texturing( ShaderClass::TEXTURING_DISABLE );

	DX8Wrapper::Set_Index_Buffer(ib_access,0);
	DX8Wrapper::Set_Shader(shader);
	DX8Wrapper::Set_Texture(0,NULL);
	DX8Wrapper::Draw_Triangles(0,polygon_count,0,vertex_count);

//	WW3D::Render(*model,*context);
	WW3D::End_Render(false);

	DX8Wrapper::Set_Render_Target((IDirect3DSurface8 *)NULL);
	DX8Wrapper::Set_Transform(D3DTS_PROJECTION,old_projection_transform);
	DX8Wrapper::Set_Transform(D3DTS_VIEW,old_view_transform);
	DX8Wrapper::Set_Transform(D3DTS_WORLD,old_world_transform);
}

static bool Test_Render_Target_Surface(TextureClass* render_target)
{
	SurfaceClass * surf = render_target->Get_Surface_Level();
	SurfaceClass::SurfaceDescription desc;
	surf->Get_Description(desc);
	SurfaceClass * new_surf = NEW_REF(SurfaceClass,(desc.Width,desc.Height,desc.Format));
	DX8Wrapper::_Copy_DX8_Rects(surf->Peek_D3D_Surface(),NULL,0,new_surf->Peek_D3D_Surface(),NULL);
	REF_PTR_RELEASE(surf);

	int pitch;
	unsigned char* tmpbits=(unsigned char*)new_surf->Lock(&pitch);

	unsigned color1,color2,color3,color4;
	BitmapHandlerClass::Read_B8G8R8A8(
		color1, tmpbits, desc.Format,
		1*desc.Width/4,
		1*desc.Height/4,
		desc.Width,	desc.Height, NULL, 0);

	BitmapHandlerClass::Read_B8G8R8A8(
		color2, tmpbits, desc.Format,
		3*desc.Width/4,
		1*desc.Height/4,
		desc.Width,	desc.Height, NULL, 0);

	BitmapHandlerClass::Read_B8G8R8A8(
		color3, tmpbits, desc.Format,
		3*desc.Width/4,
		3*desc.Height/4,
		desc.Width,	desc.Height, NULL, 0);

	BitmapHandlerClass::Read_B8G8R8A8(
		color4, tmpbits, desc.Format,
		1*desc.Width/4,
		3*desc.Height/4,
		desc.Width,	desc.Height, NULL, 0);

	new_surf->Unlock();
	REF_PTR_RELEASE(new_surf);

	WWDEBUG_SAY(("Render target test: 0x%8.8x, 0x%8.8x, 0x%8.8x, 0x%8.8x\n",
		color1&0x00ffffff,
		color2&0x00ffffff,
		color3&0x00ffffff,
		color4&0x00ffffff));
	// color1 is 0x00000000
	if (color1&0x00ffffff) return false;
	// color2 is 0x000000ff
	if (color2&0x00ffff00) return false;
	if (!(color2&0x000000ff)) return false;
	// color3 is 0x0000ff00
	if (color3&0x00ff00ff) return false;
	if (!(color3&0x0000ff00)) return false;
	// color4 is 0x00ff0000
	if (color4&0x0000ffff) return false;
	if (!(color4&0x00ff0000)) return false;
	return true;
}

void PhysicsSceneClass::Invalidate_Static_Shadow_Projectors()
{
	/*
	** Collect a list of all static objects who want to generate a shadow
	** Tell each to destroy their shadow
	*/
	RefPhysListClass shadow_gen_list;
	RefPhysListIterator static_anim_iterator(&StaticAnimList);
	for (static_anim_iterator.First(); !static_anim_iterator.Is_Done(); static_anim_iterator.Next()) {
		StaticAnimPhysClass * obj = (StaticAnimPhysClass *)static_anim_iterator.Peek_Obj();

		if (obj != NULL) {
			StaticAnimPhysDefClass * def = obj->Get_StaticAnimPhysDef();
			if (def && def->Shadow_Dynamic_Objs()) {
				obj->Set_Shadow(NULL);
				shadow_gen_list.Add(obj);
			}
		}
	}

	StaticProjectorsDirty=true;

	/*
	** Release all of the textures we were using for static shadows
	*/
	_StaticShadowTexMgr.Reset();
}

void PhysicsSceneClass::Generate_Static_Shadow_Projectors(void)
{
	if (!StaticProjectorsDirty) return;

	// Don't operate if the device is lost!
	if (DX8Wrapper::Is_Device_Lost() || !DX8Wrapper::Is_Initted()) return;

	/*
	** Collect a list of all static objects who want to generate a shadow
	** Tell each to destroy their shadow
	*/
	RefPhysListClass shadow_gen_list;
	RefPhysListIterator static_anim_iterator(&StaticAnimList);
	for (static_anim_iterator.First(); !static_anim_iterator.Is_Done(); static_anim_iterator.Next()) {
		StaticAnimPhysClass * obj = (StaticAnimPhysClass *)static_anim_iterator.Peek_Obj();

		if (obj != NULL) {
			StaticAnimPhysDefClass * def = obj->Get_StaticAnimPhysDef();
			if (def && def->Shadow_Dynamic_Objs()) {
				obj->Set_Shadow(NULL);
				shadow_gen_list.Add(obj);
			}
		}
	}

	/*
	** Release all of the textures we were using for static shadows
	*/
	_StaticShadowTexMgr.Reset();

	/*
	** Allocate a render target texture for all of the static shadows to share
	*/
	TextureClass * render_target = Create_Projector_Render_Target(STATIC_PROJECTOR_RESOLUTION,STATIC_PROJECTOR_RESOLUTION);
//	TextureClass * render_target = DX8Wrapper::Create_Render_Target(STATIC_PROJECTOR_RESOLUTION,STATIC_PROJECTOR_RESOLUTION);

	// Test render target functionality. Some NVidia driver versions have issues with rendering to a texture and
	// copying surface to another texture. If this fails, we'll not use static shadow projectors. Dynamic shadow
	// projectors may still work however, as they work differently.
	if (render_target) {
		Create_Render_Target_Test(render_target);
		if (!Test_Render_Target_Surface(render_target)) {
			WWDEBUG_SAY(("Render target locking doesn't work correctly. Disabling static projectors\n"));
			REF_PTR_RELEASE(render_target);
		}

/*
		char* bits=new char[desc.Width*desc.Height*3];
		//memcpy(bits,tmpbits,desc.Width*desc.Height*4);
		for (unsigned int y=0;y<desc.Height;++y) {
			for (unsigned int x=0;x<desc.Width;++x) {
				// index for image
				unsigned index=3*(x+y*desc.Width);
				// index for fb
				unsigned index2=y*pitch+4*x;

				bits[index]=*(tmpbits + index2+2);
				bits[index+1]=*(tmpbits + index2+1);
				bits[index+2]=*(tmpbits + index2+0);
			}
		}
		new_surf->Unlock();
		Targa targ;
		memset(&targ.Header,0,sizeof(targ.Header));
		targ.Header.Width=desc.Width;
		targ.Header.Height=desc.Height;
		targ.Header.PixelDepth=24;
		targ.Header.ImageType=TGA_TRUECOLOR;
		targ.SetImage((char*)bits);
		targ.YFlip();

		const char* filename="shadowtex.tga";

		targ.Save(filename,TGAF_IMAGE,false);
		delete[] bits;
*/

	}

	if (render_target != NULL) {
		SET_REF_OWNER(render_target);

		/*
		** Generate a new shadow for each one.  Each time we find a new object-lightsource
		** combination, generate a new texture.
		*/
		RefPhysListIterator shadow_gen_iterator(&shadow_gen_list);
		for (shadow_gen_iterator.First(); !shadow_gen_iterator.Is_Done(); shadow_gen_iterator.Next()) {

			StaticAnimPhysClass * obj = (StaticAnimPhysClass *)shadow_gen_iterator.Peek_Obj();

			/*
			** Setup the shadow projector for this object
			*/
			Vector3 sunvector;
			Get_Sun_Light_Vector(&sunvector);
			Setup_Static_Directional_Shadow(*obj,sunvector,render_target);
		}

		DX8Wrapper::Set_Render_Target((IDirect3DSurface8 *)NULL);
		REF_PTR_RELEASE(render_target);
	}
	StaticProjectorsDirty=false;
}

void PhysicsSceneClass::Setup_Static_Directional_Shadow
(
	StaticAnimPhysClass & obj,
	const Vector3 & light_dir,
	TextureClass * render_target
)
{
	/*
	** Get the definition for this object
	*/
	StaticAnimPhysDefClass * def = obj.Get_StaticAnimPhysDef();
	if (def == NULL) {
		return;
	}

	int type_id = def->Get_ID();
	Quaternion obj_orientation(1);

	if (def->Shadow_Ignores_Z_Rotation() == false) {
		obj_orientation = Build_Quaternion(obj.Get_Transform());
	}

	/*
	** Create the projector
	*/
	PhysTexProjectClass * shadow_projector = NEW_REF(PhysTexProjectClass,());

	shadow_projector->Set_Texture_Size(STATIC_PROJECTOR_RESOLUTION);
	shadow_projector->Set_Intensity(def->Shadow_Intensity(),true);

	if (def->Shadow_Is_Additive()) {
		shadow_projector->Init_Additive();
	} else {
		shadow_projector->Init_Multiplicative();

		/*
		** (gth) override the static shadow intensity with the global setting.
		*/
		shadow_projector->Set_Intensity(Get_Shadow_Normal_Intensity(),true);
	}

	shadow_projector->Compute_Ortho_Projection(&obj,light_dir,def->Shadow_NearZ(),def->Shadow_FarZ());
	shadow_projector->Enable_Attenuation(true);
	shadow_projector->Enable_Depth_Gradient(false);
	shadow_projector->Peek_Material_Pass()->Enable_On_Translucent_Meshes(false);

	/*
	** See if we already have a suitable texture.
	*/
	TextureClass * existing_texture = _StaticShadowTexMgr.Peek_Shadow_Texture(type_id,obj_orientation);
	if (existing_texture != NULL) {
		shadow_projector->Set_Texture(existing_texture);
	}

	/*
	** If no texture was a available, we have to generate it.
	** Then add it to the cache so others can use it
	*/
	if (existing_texture == NULL) {

		shadow_projector->Set_Render_Target(render_target);
		shadow_projector->Compute_Texture(&obj,def->Shadow_Is_Additive());

		SurfaceClass * surf = render_target->Get_Surface_Level();

		SurfaceClass::SurfaceDescription desc;
		surf->Get_Description(desc);
		SurfaceClass * new_surf = NEW_REF(SurfaceClass,(desc.Width,desc.Height,desc.Format));
		DX8Wrapper::_Copy_DX8_Rects(surf->Peek_D3D_Surface(),NULL,0,new_surf->Peek_D3D_Surface(),NULL);

		TextureClass * new_texture = NEW_REF(TextureClass,(new_surf));

		shadow_projector->Set_Render_Target(NULL);
		shadow_projector->Set_Texture(new_texture);

		REF_PTR_RELEASE(surf);
		REF_PTR_RELEASE(new_surf);
		REF_PTR_RELEASE(new_texture);

		_StaticShadowTexMgr.Add_Shadow_Texture(type_id,obj_orientation,shadow_projector->Peek_Texture());
	}

	/*
	** Give the projector to the object
	*/
	obj.Set_Shadow(shadow_projector);

	/*
	** Release resources
	*/
	REF_PTR_RELEASE(shadow_projector);
}
