//
// DX9ViewportC.cpp
//
// DirectX 9 viewport.
//
// Copyright (c) 2004 memon/moppi productions
//

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
#include "PajaTypes.h"
#include "DeviceInterfaceI.h"
#include "Vector2C.h"
#include "BBox2C.h"
#include <math.h>
#include <windows.h>
#include <stdio.h>
#include "DX9ViewportC.h"


using namespace PajaTypes;
using namespace PajaSystem;
using namespace PluginClass;
using namespace Edit;



DX9ViewportC::DX9ViewportC( DX9DeviceC* pDevice ) :
	m_pDevice( pDevice ),
	m_i32Width( 0 ),
	m_i32Height( 0 ),
	m_i32PosX( 0 ),
	m_i32PosY( 0 ),
	m_f64ClientAspectX( 0 ),
	m_f64ClientAspectY( 0 ),
	m_f64LayoutAspectX( 0 ),
	m_f64LayoutAspectY( 0 ),
	m_f32PixelsAspect( 1.0f ),
	m_ui32ViewportType( OPENGL_VIEWPORT_ORTHO ),
	m_i32ScissorX( 0 ),
	m_i32ScissorY( 0 ),
	m_i32ScissorWidth( 1 ),
	m_i32ScissorHeight( 1 ),
	m_f64FrustumLeft( -1 ),
	m_f64FrustumRight( 1 ),
	m_f64FrustumBottom( -1 ),
	m_f64FrustumTop( 1 ),
	m_f64FrustumNearZ( -1 ),
	m_f64FrustumFarZ( 1 )
{
}

DX9ViewportC::~DX9ViewportC()
{
	// empty
}

DataBlockI*
DX9ViewportC::create()
{
	return new DX9ViewportC( m_pDevice );
}

DX9ViewportC*
DX9ViewportC::create_new( DX9DeviceC* pDevice )
{
	return new DX9ViewportC( pDevice );
}

PluginClass::ClassIdC
DX9ViewportC::get_class_id() const
{
	return CLASS_DX9_VIEWPORT;
}

const char*
DX9ViewportC::get_class_name()
{
	return "DX9 Viewport";
}

void
DX9ViewportC::recalc_mapping()
{
	if( m_i32Width <= 1 || m_i32Height <= 1 ||
		m_rViewport.width() == 0.0f || m_rViewport.height() == 0.0f ) {
		// layout to client
		m_f64ClientAspectX = 0;
		m_f64ClientAspectY = 0;
		// client to layout
		m_f64LayoutAspectX = 0;
		m_f64LayoutAspectY = 0;
	}
	else {
		// layout to client
		m_f64ClientAspectX = (float64)m_i32Width / (float64)m_rViewport.width();
		m_f64ClientAspectY = (float64)m_i32Height / (float64)m_rViewport.height();
		// client to layout
		m_f64LayoutAspectX = (float64)m_rViewport.width() / (float64)m_i32Width;
		m_f64LayoutAspectY = (float64)m_rViewport.height() / (float64)m_i32Height;
	}
}


void
DX9ViewportC::set_dimension( int32 i32PosX, int32 i32PosY, int32 i32Width, int32 i32Height )
{
	m_i32PosX = i32PosX;
	m_i32PosY = i32PosY;
	m_i32Width = i32Width;
	m_i32Height = i32Height;

	recalc_mapping();
}

void
DX9ViewportC::set_viewport( const BBox2C& rViewport )
{
	m_rViewport = rViewport;

	recalc_mapping();
}

void
DX9ViewportC::set_layout( const BBox2C& rLayout )
{
	m_rLayout = rLayout;
}

void
DX9ViewportC::set_perspective( const BBox2C& rBBox, float32 f32FOV, float32 f32Aspect, float32 f32ZNear, float32 f32ZFar )
{
	Vector2C	rCenter = rBBox.center();
	Vector2C	rSize = rBBox.size();

	rCenter = layout_to_client( rCenter );
	rSize = delta_layout_to_client( rSize );

	float64	f64Aspect = (float64)m_i32Height / (float64)m_i32Width * f32Aspect * m_f32PixelsAspect;
	float64	f64CenterX = 1.0 - (rCenter[0] / (float64)(m_i32Width - 1)) * 2.0;
	float64	f64CenterY = 1.0 - (rCenter[1] / (float64)(m_i32Height - 1)) * 2.0;
	float64	f64Scale = (float64)(m_i32Width - 1) / (float64)rSize[0];
	float64	f64MinX, f64MaxX, f64MinY, f64MaxY;

	f64MaxX = f32ZNear * tan( (f32FOV / 2.0) * (M_PI / 180.0) ) * f64Scale;
	f64MinX = -f64MaxX;

	f64MinY = f64MinX * f64Aspect;
	f64MaxY = f64MaxX * f64Aspect;

	f64CenterX *= f64MaxX;
	f64CenterY *= f64MaxY;

	m_f64FrustumLeft = f64MinX + f64CenterX;
	m_f64FrustumRight = f64MaxX + f64CenterX;
	m_f64FrustumBottom = f64MinY + f64CenterY;
	m_f64FrustumTop = f64MaxY + f64CenterY;
	m_f64FrustumNearZ = f32ZNear;
	m_f64FrustumFarZ = f32ZFar;

	LPDIRECT3DDEVICE9			pD3DDevice = m_pDevice->get_d3ddevice();

	D3DXMATRIX	Identity;
	D3DXMATRIX	Proj;
	D3DXMatrixIdentity( &Identity );
	D3DXMatrixPerspectiveOffCenterLH( &Proj, (FLOAT)m_f64FrustumLeft, (FLOAT)m_f64FrustumRight, (FLOAT)m_f64FrustumBottom, (FLOAT)m_f64FrustumTop, (FLOAT)m_f64FrustumNearZ, (FLOAT)m_f64FrustumFarZ );

	pD3DDevice->SetTransform( D3DTS_PROJECTION, &Proj );
	pD3DDevice->SetTransform( D3DTS_VIEW, &Identity );
	pD3DDevice->SetTransform( D3DTS_WORLD, &Identity );

	set_scissor( rBBox );

	m_ui32ViewportType = OPENGL_VIEWPORT_PERSPECTIVE;
}


void
DX9ViewportC::set_ortho( const BBox2C& rBBox,
							 float32 f32Left, float32 f32Right, float32 f32Top, float32 f32Bottom,
							 float32 f32ZNear, float32 f32ZFar )
{
	Vector2C	rMin = rBBox[0];
	Vector2C	rMax = rBBox[1];


	float64	f64OrthoWidth = f32Right - f32Left;
	float64	f64OrthoHeight = f32Top - f32Bottom;

	f64OrthoWidth /= rBBox.width();
	f64OrthoHeight /= rBBox.height();

	m_f64FrustumLeft = f32Left + (m_rViewport[0][0] - rBBox[0][0]) * f64OrthoWidth;
	m_f64FrustumRight = f32Right + (m_rViewport[1][0] - rBBox[1][0]) * f64OrthoWidth;
	m_f64FrustumBottom = f32Bottom + (m_rViewport[0][1] - rBBox[0][1]) * f64OrthoHeight;
	m_f64FrustumTop = f32Top + (m_rViewport[1][1] - rBBox[1][1]) * f64OrthoHeight;
	m_f64FrustumNearZ = f32ZNear;
	m_f64FrustumFarZ = f32ZFar;

	LPDIRECT3DDEVICE9			pD3DDevice = m_pDevice->get_d3ddevice();

	D3DXMATRIX	Identity;
	D3DXMATRIX	Proj;
	D3DXMatrixIdentity( &Identity );
	D3DXMatrixOrthoOffCenterLH( &Proj, (FLOAT)m_f64FrustumLeft, (FLOAT)m_f64FrustumRight, (FLOAT)m_f64FrustumBottom, (FLOAT)m_f64FrustumTop, (FLOAT)m_f64FrustumNearZ, (FLOAT)m_f64FrustumFarZ );
//	D3DXMatrixOrthoOffCenterLH( &Proj, m_f64FrustumLeft, m_f64FrustumRight, m_f64FrustumTop, m_f64FrustumBottom, m_f64FrustumNearZ, m_f64FrustumFarZ );

	pD3DDevice->SetTransform( D3DTS_PROJECTION, &Proj );
	pD3DDevice->SetTransform( D3DTS_VIEW, &Identity );
	pD3DDevice->SetTransform( D3DTS_WORLD, &Identity );

	set_scissor( rBBox );

	m_ui32ViewportType = OPENGL_VIEWPORT_ORTHO;
}

void
DX9ViewportC::set_ortho_pixel( const BBox2C& rBBox, float32 f32ZNear, float32 f32ZFar )
{
	m_f64FrustumNearZ = f32ZNear;
	m_f64FrustumFarZ = f32ZFar;


	LPDIRECT3DDEVICE9			pD3DDevice = m_pDevice->get_d3ddevice();

	D3DXMATRIX	Identity;
	D3DXMATRIX	Proj;
	D3DXMatrixIdentity( &Identity );
	D3DXMatrixOrthoOffCenterLH( &Proj, 0, (FLOAT)m_i32Width, 0, (FLOAT)m_i32Height, (FLOAT)m_f64FrustumNearZ, (FLOAT)m_f64FrustumFarZ );
//	D3DXMatrixOrthoOffCenterLH( &Proj, 0, m_i32Width, m_i32Height, 0, m_f64FrustumNearZ, m_f64FrustumFarZ );

	pD3DDevice->SetTransform( D3DTS_PROJECTION, &Proj );
	pD3DDevice->SetTransform( D3DTS_VIEW, &Identity );
	pD3DDevice->SetTransform( D3DTS_WORLD, &Identity );

	set_scissor( rBBox );

	m_ui32ViewportType = OPENGL_VIEWPORT_ORTHO_PIXEL;
}

void
DX9ViewportC::set_scissor( const PajaTypes::BBox2C& rBBox )
{
	BBox2C	rTrimEffect = rBBox.trim( m_rLayout );

	Vector2C	rTrimMin = rTrimEffect[0];
	Vector2C	rTrimSize = rTrimEffect.size();

//	char	szMsg[256];
//	_snprintf( szMsg, 255, "****   rTrimEffect( %f, %f, %f, %f );\n", rTrimEffect[0][0], rTrimEffect[0][1], rTrimEffect[1][0], rTrimEffect[1][1] );
//	OutputDebugString( szMsg );

	rTrimMin = layout_to_client( rTrimMin );
	rTrimSize = delta_layout_to_client( rTrimSize );

//	_snprintf( szMsg, 255, "       rTrimEffect( %f, %f, %f, %f );\n", rTrimEffect[0][0], rTrimEffect[0][1], rTrimEffect[1][0], rTrimEffect[1][1] );
//	OutputDebugString( szMsg );

	m_i32ScissorX = (int32)(m_i32PosX + (int32)(rTrimMin[0] + 0.5f));
	m_i32ScissorY = (int32)(m_i32PosY +(int32)(rTrimMin[1] + 0.5f));
	m_i32ScissorWidth = (int32)(rTrimSize[0] + 0.5f);
	m_i32ScissorHeight = (int32)(rTrimSize[1] + 0.5f);

	// Make sure scissor box fits inside the viewport.
	if( m_i32ScissorX < 0 )
	{
		m_i32ScissorWidth += m_i32ScissorX;
		m_i32ScissorX = 0;
	}
	if( m_i32ScissorX >= m_i32Width )
	{
		m_i32ScissorWidth = 0;
		m_i32ScissorX = 0;
	}
//	if( m_i32ScissorX + m_i32ScissorWidth >= m_i32Width )
//	{
//		m_i32ScissorWidth = (m_i32Width - 1) - m_i32ScissorX;
//	}
	if( m_i32ScissorX + m_i32ScissorWidth > m_i32Width )
	{
		m_i32ScissorWidth = m_i32Width - m_i32ScissorX;
	}
	if( m_i32ScissorWidth < 0 )
		m_i32ScissorWidth = 0;

	if( m_i32ScissorY < 0 )
	{
		m_i32ScissorHeight += m_i32ScissorY;
		m_i32ScissorY = 0;
	}
	if( m_i32ScissorY >= m_i32Height )
	{
		m_i32ScissorHeight = 0;
		m_i32ScissorY = 0;
	}
//	if( m_i32ScissorY + m_i32ScissorHeight >= m_i32Height )
//	{
//		m_i32ScissorHeight = (m_i32Height - 1) - m_i32ScissorY;
//	}
	if( m_i32ScissorY + m_i32ScissorHeight > m_i32Height )
	{
		m_i32ScissorHeight = m_i32Height - m_i32ScissorY;
	}
	if( m_i32ScissorHeight < 0 )
		m_i32ScissorHeight = 0;

	LPDIRECT3DDEVICE9			pD3DDevice = m_pDevice->get_d3ddevice();

	RECT	Scissor;
	Scissor.left = m_i32ScissorX;
	Scissor.right = m_i32ScissorX + m_i32ScissorWidth;
	Scissor.bottom = m_i32Height - m_i32ScissorY;
	Scissor.top = m_i32Height - (m_i32ScissorY + m_i32ScissorHeight);

	pD3DDevice->SetScissorRect( &Scissor );
	pD3DDevice->SetRenderState( D3DRS_SCISSORTESTENABLE, TRUE );


//	glScissor( m_i32ScissorX, m_i32ScissorY, m_i32ScissorWidth, m_i32ScissorHeight );
//	glEnable( GL_SCISSOR_TEST );

/*	char	szMsg[256];
	_snprintf( szMsg, 255, "****   m_rLayout( %f, %f, %f, %f );\n", m_rLayout[0][0], m_rLayout[0][1], m_rLayout[1][0], m_rLayout[1][1] );
	OutputDebugString( szMsg );
	_snprintf( szMsg, 255, "****   rTrimEffect( %f, %f, %f, %f );\n", rTrimEffect[0][0], rTrimEffect[0][1], rTrimEffect[1][0], rTrimEffect[1][1] );
	OutputDebugString( szMsg );
	_snprintf( szMsg, 255, "****   glScissor( %d, %d, %d, %d );\n", m_i32ScissorX, m_i32ScissorY, m_i32ScissorWidth, m_i32ScissorHeight );
	OutputDebugString( szMsg );
*/
}

Vector2C
DX9ViewportC::client_to_layout( const Vector2C& rVec )
{
	return Vector2C( m_rViewport[0][0] + (rVec[0] * (float32)m_f64LayoutAspectX), m_rViewport[0][1] + (rVec[1] * (float32)m_f64LayoutAspectY) );
}

Vector2C
DX9ViewportC::layout_to_client( const Vector2C& rVec )
{
	return Vector2C( (rVec[0] - m_rViewport[0][0]) *  (float32)m_f64ClientAspectX, (rVec[1] - m_rViewport[0][1]) * (float32)m_f64ClientAspectY );
}

Vector2C
DX9ViewportC::delta_client_to_layout( const Vector2C& rVec )
{
	return Vector2C( rVec[0] * (float32)m_f64LayoutAspectX, rVec[1] * (float32)m_f64LayoutAspectY );
}

Vector2C
DX9ViewportC::delta_layout_to_client( const Vector2C& rVec )
{
	return Vector2C( rVec[0] *  (float32)m_f64ClientAspectX, rVec[1] * (float32)m_f64ClientAspectY );
}

const BBox2C&
DX9ViewportC::get_viewport()
{
	return m_rViewport;
}

const BBox2C&
DX9ViewportC::get_layout()
{
	return m_rLayout;
}

int32
DX9ViewportC::get_width()
{
	return m_i32Width;
}

int32
DX9ViewportC::get_height()
{
	return m_i32Height;
}

void
DX9ViewportC::activate()
{
	LPDIRECT3DDEVICE9			pD3DDevice = m_pDevice->get_d3ddevice();

	D3DXMATRIX	Identity;
	D3DXMATRIX	Proj;
	D3DXMatrixIdentity( &Identity );

	if( m_ui32ViewportType == OPENGL_VIEWPORT_PERSPECTIVE )
	{
		// perspective
		D3DXMatrixPerspectiveOffCenterLH( &Proj, (FLOAT)m_f64FrustumLeft, (FLOAT)m_f64FrustumRight, (FLOAT)m_f64FrustumBottom, (FLOAT)m_f64FrustumTop, (FLOAT)m_f64FrustumNearZ, (FLOAT)m_f64FrustumFarZ );
	}
	else if( m_ui32ViewportType == OPENGL_VIEWPORT_ORTHO )
	{
		// ortho
		D3DXMatrixOrthoOffCenterLH( &Proj, (FLOAT)m_f64FrustumLeft, (FLOAT)m_f64FrustumRight, (FLOAT)m_f64FrustumBottom, (FLOAT)m_f64FrustumTop, (FLOAT)m_f64FrustumNearZ, (FLOAT)m_f64FrustumFarZ );
//		D3DXMatrixOrthoOffCenterLH( &Proj, m_f64FrustumLeft, m_f64FrustumRight, m_f64FrustumTop, m_f64FrustumBottom, m_f64FrustumNearZ, m_f64FrustumFarZ );
	}
	else
	{
		// pixel ortho
		D3DXMatrixOrthoOffCenterLH( &Proj, 0, (FLOAT)m_i32Width, 0, (FLOAT)m_i32Height, (FLOAT)m_f64FrustumNearZ, (FLOAT)m_f64FrustumFarZ );
//		D3DXMatrixOrthoOffCenterLH( &Proj, 0, m_i32Width, m_i32Height, 0, m_f64FrustumNearZ, m_f64FrustumFarZ );
	}

	pD3DDevice->SetTransform( D3DTS_PROJECTION, &Proj );
	pD3DDevice->SetTransform( D3DTS_VIEW, &Identity );
	pD3DDevice->SetTransform( D3DTS_WORLD, &Identity );

	// Make sure scissor box fits inside the viewport.
	if( m_i32ScissorX < 0 )
	{
		m_i32ScissorWidth += m_i32ScissorX;
		m_i32ScissorX = 0;
	}
	if( m_i32ScissorX >= m_i32Width )
	{
		m_i32ScissorWidth = 0;
		m_i32ScissorX = 0;
	}
	if( m_i32ScissorX + m_i32ScissorWidth >= m_i32Width )
	{
		m_i32ScissorWidth = (m_i32Width - 1) - m_i32ScissorX;
	}
	if( m_i32ScissorWidth < 0 )
		m_i32ScissorWidth = 0;

	if( m_i32ScissorY < 0 )
	{
		m_i32ScissorHeight += m_i32ScissorY;
		m_i32ScissorY = 0;
	}
	if( m_i32ScissorY >= m_i32Height )
	{
		m_i32ScissorHeight = 0;
		m_i32ScissorY = 0;
	}
	if( m_i32ScissorY + m_i32ScissorHeight >= m_i32Height )
	{
		m_i32ScissorHeight = (m_i32Height - 1) - m_i32ScissorY;
	}
	if( m_i32ScissorHeight < 0 )
		m_i32ScissorHeight = 0;

	RECT	Scissor;
	Scissor.left = m_i32ScissorX;
	Scissor.right = m_i32ScissorX + m_i32ScissorWidth;
	Scissor.bottom = m_i32Height - m_i32ScissorY;
	Scissor.top = m_i32Height - (m_i32ScissorY + m_i32ScissorHeight);

	pD3DDevice->SetScissorRect( &Scissor );
	pD3DDevice->SetRenderState( D3DRS_SCISSORTESTENABLE, TRUE );

}

float32
DX9ViewportC::get_pixel_aspect_ratio()
{
	return m_f32PixelsAspect;
}

void
DX9ViewportC::set_pixel_aspect_ratio( float32 f32PixelAspect )
{
	m_f32PixelsAspect = f32PixelAspect;
}
