/*
**	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 : leveledit                                                    *
 *                                                                                             *
 *                     $Archive:: /Commando/Code/Tools/LevelEdit/heightfieldeditor.cpp        $*
 *                                                                                             *
 *                       Author:: Patrick Smith                                                *
 *                                                                                             *
 *                     $Modtime:: 3/05/02 3:21p                                               $*
 *                                                                                             *
 *                    $Revision:: 3                                                           $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include "stdafx.h"
#include "heightfieldeditor.h"
#include "sceneeditor.h"
#include "mover.h"
#include "rendobj.h"
#include "phys.h"
#include "editableheightfield.h"
#include "mousemgr.h"
#include "texture.h"
#include "surface.h"
#include "terrainmaterial.h"
#include "heightfieldpage.h"
#include "textureloader.h"
#include "d3d8types.h"
#include "dx8wrapper.h"
#include "bitmaphandler.h"


//////////////////////////////////////////////////////////////////////
//	Forward declarations
//////////////////////////////////////////////////////////////////////
float				HeightfieldEditorClass::BrushInnerRadius					= 10.0F;
float				HeightfieldEditorClass::BrushOutterRadius					= 20.0F;
float				HeightfieldEditorClass::BrushAmount							= 1.0F;
int				HeightfieldEditorClass::CurrentTextureIndex				= 0;

EditableHeightfieldClass *							HeightfieldEditorClass::CurrentHeightfield = NULL;
HeightfieldEditorClass::EDITING_MODE			HeightfieldEditorClass::Mode = HeightfieldEditorClass::MODE_DEFORM;
DynamicVectorClass<TerrainMaterialClass *>	HeightfieldEditorClass::MaterialList;


//////////////////////////////////////////////////////////////////////
//	Local constants
//////////////////////////////////////////////////////////////////////
static const int	MAX_HEIGHTFIELD_MATERIALS	= 10;


//////////////////////////////////////////////////////////////////////
//
//	Initialize
//
//////////////////////////////////////////////////////////////////////
void
HeightfieldEditorClass::Initialize (void)
{
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Shutdown
//
//////////////////////////////////////////////////////////////////////
void
HeightfieldEditorClass::Shutdown (void)
{
	//
	//	Free the list of materials
	//
	for (int index = 0; index < MaterialList.Count (); index ++) {
		REF_PTR_RELEASE (MaterialList[index]);
	}

	MaterialList.Delete_All ();
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Set_Brush_Radii
//
//////////////////////////////////////////////////////////////////////
void
HeightfieldEditorClass::Set_Brush_Radii (float inner_radius, float outter_radius)
{
	BrushInnerRadius	= inner_radius;
	BrushOutterRadius	= outter_radius;
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Get_Brush_Radii
//
//////////////////////////////////////////////////////////////////////
void
HeightfieldEditorClass::Get_Brush_Radii (float &inner_radius, float &outter_radius)
{
	inner_radius	= BrushInnerRadius;
	outter_radius	= BrushOutterRadius;
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Set_Mode
//
//////////////////////////////////////////////////////////////////////
void
HeightfieldEditorClass::Set_Mode (EDITING_MODE mode)
{
	Mode = mode;
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	On_Frame_Update
//
//////////////////////////////////////////////////////////////////////
void
HeightfieldEditorClass::On_Frame_Update (void)
{
	//
	//	Don't do any processing if we're not in heightfield editing mode
	//
	if (::Get_Mouse_Mgr ()->Get_Mouse_Mode () != MouseMgrClass::MODE_HEIGHTFIELD_EDIT) {
		return ;
	}

	//
	//	Get a ray from the current view through the mouse cursor position
	//
	Vector3 v_start;
	Vector3 v_end;
	MoverClass::Get_Mouse_Ray (1000.0F, v_start, v_end);

	//
	//	Cast a ray into the world and see what we've hit
	//
	CastResultStruct res;
	res.ComputeContactPoint = true;
	PhysClass *phys_obj = MoverClass::Cast_Ray (res, v_start, v_end);
	if (phys_obj != NULL) {

		//
		//	For right now, add a debug box to show where the operation will occur
		//
		PhysicsSceneClass::Get_Instance ()->Add_Debug_AABox (AABoxClass (res.ContactPoint + Vector3 (0, 0, 2.0F), Vector3 (1.0F, 1.0F, 1.0F)), Vector3 (1, 0, 0));

		//
		//	Check to see if this is a heightfield
		//
		RenderObjClass *model = phys_obj->Peek_Model ();
		if (model != NULL && model->Class_ID () == RenderObjClass::CLASSID_RENEGADE_TERRAIN && CurrentHeightfield != NULL) {
			
			EditableHeightfieldClass *heightfield = CurrentHeightfield;

			//
			//	Is the left button or right button down?
			//
			if (::Get_Mouse_Mgr ()->Is_LButton_Down ()) {
				
				//
				//	Determine what operation to perform
				//
				if (Mode == MODE_DEFORM) {
					heightfield->Deform_Heightfield (res.ContactPoint, BrushAmount, BrushInnerRadius, BrushOutterRadius);
				} else if (Mode == MODE_DEFORM_SMOOTH) {
					heightfield->Smooth_Heightfield (res.ContactPoint, BrushAmount, BrushInnerRadius, BrushOutterRadius);
				} else if (Mode == MODE_DEFORM_SMOOTH_FOUNDATION) {
					heightfield->Smooth_Foundation_Heightfield (res.ContactPoint, BrushAmount, BrushInnerRadius, BrushOutterRadius);
				} else if (Mode == MODE_QUAD_CUTOUT) {
					heightfield->Cutout_Heightfield (res.ContactPoint, BrushOutterRadius, true);
				} else if (Mode == MODE_TEXTURING) {
					heightfield->Paint_Heightfield (res.ContactPoint, CurrentTextureIndex, BrushAmount, BrushInnerRadius, BrushOutterRadius);
				}

			} else if (::Get_Mouse_Mgr ()->Is_RButton_Down ()) {

				//
				//	Determine what operation to perform
				//
				if (Mode == MODE_DEFORM) {
					heightfield->Deform_Heightfield (res.ContactPoint, -BrushAmount, BrushInnerRadius, BrushOutterRadius);
				} else if (Mode == MODE_QUAD_CUTOUT) {
					heightfield->Cutout_Heightfield (res.ContactPoint, BrushOutterRadius, false);
				}
			}
		}
	}
	
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Render
//
//////////////////////////////////////////////////////////////////////
void
HeightfieldEditorClass::Render (void)
{
	
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Create_Texture_Thumbnail
//
//////////////////////////////////////////////////////////////////////
HBITMAP
HeightfieldEditorClass::Create_Texture_Thumbnail (TextureClass *texture, int width, int height)
{
	if (texture == NULL) {
		return NULL;
	}

	//
	//	Create the thumbnail
	//
	StringClass filename = texture->Get_Full_Path ();
	HBITMAP thumbnail = Create_Texture_Thumbnail (filename, width, height);
	
	//
	//	Release our hold on the texture
	//
	REF_PTR_RELEASE (texture);

	return thumbnail;
}


//////////////////////////////////////////////////////////////////////
//
//	Create_Texture_Thumbnail
//
//////////////////////////////////////////////////////////////////////
HBITMAP
HeightfieldEditorClass::Create_Texture_Thumbnail (const char *filename, int width, int height)
{
	if (filename == NULL) {
		return NULL;
	}

	//
	//	Load the d3d surface for this texture
	//	
	IDirect3DSurface8 *d3d_surface = TextureLoader::Load_Surface_Immediate (filename, WW3D_FORMAT_R8G8B8, false);

	//
	//	Get information about the texture
	//
	D3DSURFACE_DESC sd;
	::ZeroMemory (&sd, sizeof(sd));
	DX8_ErrorCode (d3d_surface->GetDesc (&sd));

	//
	//	Get the texture's bits so we can copy tthem
	//
	D3DLOCKED_RECT lock_rect;	
	::ZeroMemory (&lock_rect, sizeof (D3DLOCKED_RECT));
	DX8_ErrorCode (d3d_surface->LockRect (&lock_rect, 0, 0));
	int src_pitch		= lock_rect.Pitch;
	uint8 *src_bits	= (unsigned char *)lock_rect.pBits;

	//
	// Set-up the fields of the BITMAPINFOHEADER
	//	Note: Top-down DIBs use negative height in Win32.
	//
	BITMAPINFOHEADER bitmap_info = { 0 };
	bitmap_info.biSize				= sizeof (BITMAPINFOHEADER);
	bitmap_info.biWidth				= width;
	bitmap_info.biHeight				= -height;
	bitmap_info.biPlanes				= 1;
	bitmap_info.biBitCount			= 24;
	bitmap_info.biCompression		= BI_RGB;
	bitmap_info.biSizeImage			= ((width * height) * 3);
	bitmap_info.biXPelsPerMeter	= 0;
	bitmap_info.biYPelsPerMeter	= 0;
	bitmap_info.biClrUsed			= 0;
	bitmap_info.biClrImportant		= 0;

	//
	// Create a bitmap that we can access the bits directly of
	//
	uint8 *dest_bits	= NULL;
	HDC screen_dc		= ::GetDC (NULL);
	HBITMAP bitmap		= ::CreateDIBSection (screen_dc, (const BITMAPINFO *)&bitmap_info,
														DIB_RGB_COLORS, (void **)&dest_bits, NULL, 0L);	

	int dest_pitch = (((width * 3) + 3) & ~3);

	//
	//	Scale the texture to the size requested
	//
	BitmapHandlerClass::Copy_Image (dest_bits, width, height, dest_pitch, WW3D_FORMAT_R8G8B8,
								src_bits, sd.Width, sd.Height, src_pitch, WW3D_FORMAT_R8G8B8,
								NULL, 0, false);

	//
	//	Free the surface
	//
	DX8_ErrorCode (d3d_surface->UnlockRect ());
	d3d_surface->Release ();
	d3d_surface = NULL;

	return bitmap;
}


//////////////////////////////////////////////////////////////////////
//
//	Set_Material
//
//////////////////////////////////////////////////////////////////////
void
HeightfieldEditorClass::Set_Material (int index, TerrainMaterialClass *material)
{	
	//
	//	Add enough entries to the array until we've got a slot for "index"
	//
	while (index >= MaterialList.Count ()) {
		MaterialList.Add (NULL);
	}

	//
	//	Add this material to its slot
	//
	REF_PTR_SET (MaterialList[index], material);
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Peek_Material
//
//////////////////////////////////////////////////////////////////////
TerrainMaterialClass *
HeightfieldEditorClass::Peek_Material (int index)
{	
	TerrainMaterialClass *retval = NULL;

	//
	//	Simply return the material in this slot (if it exists)
	//
	if (index >= 0 && index < MaterialList.Count ()) {
		retval = MaterialList[index];
	}

	return retval;
}


//////////////////////////////////////////////////////////////////////
//
//	Get_Material
//
//////////////////////////////////////////////////////////////////////
TerrainMaterialClass *
HeightfieldEditorClass::Get_Material (int index)
{	
	//
	//	Simply add a reference to the material and return its pointer
	//
	TerrainMaterialClass *retval = Peek_Material (index);
	if (retval != NULL) {
		retval->Add_Ref ();
	}

	return retval;
}


//////////////////////////////////////////////////////////////////////
//
//	Load_Materials
//
//////////////////////////////////////////////////////////////////////
void
HeightfieldEditorClass::Load_Materials (EditableHeightfieldClass *heightfield)
{
	if (heightfield == NULL) {
		return ;
	}

	//
	//	Simply loop over and store all the materials
	//
	for (int index = 0; index < MAX_HEIGHTFIELD_MATERIALS; index ++) {
		TerrainMaterialClass *material = heightfield->Peek_Material (index);		
		Set_Material (index, material);

		//
		//	Update the UI
		//
		HeightfieldPageClass::Get_Instance ()->Update_Material_Button (index);
	}
		
	CurrentHeightfield = heightfield;
	return ;	
}


//////////////////////////////////////////////////////////////////////
//
//	Set_Current_Heightfield
//
//////////////////////////////////////////////////////////////////////
void
HeightfieldEditorClass::Set_Current_Heightfield (EditableHeightfieldClass *heightfield)
{
	CurrentHeightfield = heightfield;
	Load_Materials (CurrentHeightfield);
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	On_Material_Changed
//
//////////////////////////////////////////////////////////////////////
void
HeightfieldEditorClass::On_Material_Changed (int material_index)
{
	//
	//	Update the material panel UI
	//
	HeightfieldPageClass::Get_Instance ()->Update_Material_Button (material_index);

	//
	//	Apply these changes to the heightfield itself
	//
	if (CurrentHeightfield != NULL) {
		CurrentHeightfield->Set_Material (material_index, MaterialList[material_index]);
		CurrentHeightfield->On_Material_Changed (material_index);
	}
	
	return ;
}
