/*  $Id: collision_outline.cpp,v 1.6 2004/05/08 21:20:19 harry Exp $
**
**  ClanLib Game SDK
**  Copyright (C) 2003  The ClanLib Team
**  For a total list of contributers see the file CREDITS.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This library 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
**  Lesser General Public License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
**
*/

#include "Display/display_precomp.h"
#include "API/Display/Collision/collision_outline.h"
#include "API/Display/Collision/outline_provider_bitmap.h"
#include "API/Display/Collision/outline_provider_file.h"
#include "API/Display/Collision/outline_circle.h"
#include "API/Display/pixel_format.h"
#include "API/Core/System/clanstring.h"
#include "API/Display/Providers/provider_factory.h"
#include "API/Display/pixel_buffer.h"
#include "collision_outline_generic.h"

/////////////////////////////////////////////////////////////////////////////
// CL_CollisionOutline Construction:

CL_CollisionOutline::CL_CollisionOutline()
 : impl(0)
{
	impl = new CL_CollisionOutline_Generic();
}


CL_CollisionOutline::CL_CollisionOutline(const CL_CollisionOutline &copy)
 : impl(0)
{
	impl = new CL_CollisionOutline_Generic();

	impl->contours = copy.get_contours();
	impl->obb = copy.get_object_bounding_box();
	impl->do_obb_test = copy.get_obb_test();
	impl->do_inside_test = copy.get_inside_test();
	impl->width = copy.get_width();
	impl->height = copy.get_height();
	impl->position = copy.get_translation();
	impl->scale_factor = copy.get_scale();
	impl->angle = copy.get_angle();
	impl->radius = copy.get_radius();

	CL_Origin origin;
	float x, y;
	copy.get_alignment(origin,x,y);

	impl->translation_origin = origin;
	impl->translation_offset.x = x;
	impl->translation_offset.y = y;

	copy.get_rotation_hotspot(origin,x,y);

	impl->rotation_origin = origin;
	impl->rotation_hotspot.x = x;
	impl->rotation_hotspot.y = y;
}


CL_CollisionOutline::CL_CollisionOutline(
	const std::string &filename,
	int alpha_limit,
	CL_OutlineAccuracy accuracy )
 : impl(0)
{
	if( CL_String::right(filename, 3) == "out" )
	{
		impl = new CL_CollisionOutline_Generic( new CL_OutlineProviderFile(filename), accuracy_raw );
		return;
	}

	CL_PixelBuffer *pbuf = CL_ProviderFactory::load(filename);
	
	if( pbuf->get_format() == CL_PixelFormat::rgba8888 )
	{
		impl = new CL_CollisionOutline_Generic(
			new CL_OutlineProviderBitmap(pbuf, alpha_limit), accuracy );
	}
	else
	{
		impl = new CL_CollisionOutline_Generic(
			new CL_OutlineProviderBitmap(pbuf, alpha_limit), accuracy_raw );
	}

	set_rotation_hotspot(origin_center);
}

CL_CollisionOutline::CL_CollisionOutline(
	CL_PixelBuffer *pbuf,
	int alpha_limit,
	CL_OutlineAccuracy accuracy)
{
	if( pbuf->get_format() == CL_PixelFormat::rgba8888 )
	{
		impl = new CL_CollisionOutline_Generic(
			new CL_OutlineProviderBitmap(pbuf, alpha_limit), accuracy );
	}
	else
	{
		impl = new CL_CollisionOutline_Generic(
			new CL_OutlineProviderBitmap(pbuf, alpha_limit), accuracy_raw );
	}
	
	set_rotation_hotspot(origin_center);
}

CL_CollisionOutline::~CL_CollisionOutline()
{
	delete impl;
}

/////////////////////////////////////////////////////////////////////////////
// CL_CollisionOutline Attributes:

std::vector<CL_Contour> &CL_CollisionOutline::get_contours() const
{
	return impl->contours;
}

const CL_Contour &CL_CollisionOutline::get_object_bounding_box() const
{
	return impl->obb;
}

float CL_CollisionOutline::get_radius() const
{
	return impl->radius;
}

CL_Pointf CL_CollisionOutline::get_translation() const
{
	return impl->position;
}

float CL_CollisionOutline::get_angle() const
{
	return impl->angle;
}

CL_Pointf CL_CollisionOutline::get_scale() const
{
	return impl->scale_factor;
}

bool CL_CollisionOutline::get_obb_test() const
{
	return impl->do_obb_test;
}

bool CL_CollisionOutline::get_inside_test() const
{
	return impl->do_inside_test;
}

void CL_CollisionOutline::get_alignment( CL_Origin &origin, float &x, float &y ) const
{
	origin = impl->translation_origin;
	x = impl->translation_offset.x;
	y = impl->translation_offset.y;
}

void CL_CollisionOutline::get_rotation_hotspot( CL_Origin &origin, float &x, float &y) const
{
	origin = impl->rotation_origin;
	x = impl->rotation_hotspot_offset.x;
	y = impl->rotation_hotspot_offset.y;
}

unsigned int CL_CollisionOutline::get_width() const
{
	return impl->width;
}

unsigned int CL_CollisionOutline::get_height() const
{
	return impl->height;
}

const std::vector<CL_Pointf> &CL_CollisionOutline::get_collision_points() const
{
	return impl->collision_points;
}

const std::vector<CL_Pointf> &CL_CollisionOutline::get_collision_normals() const
{
	return impl->collision_normals;
}

/////////////////////////////////////////////////////////////////////////////
// CL_CollisionOutline Operations:

void CL_CollisionOutline::optimize(unsigned char check_distance, float corner_angle )
{
	impl->optimize(check_distance, corner_angle);
}

void CL_CollisionOutline::set_translation(float x, float y)
{
	impl->set_translation(x,y);
}

void CL_CollisionOutline::set_scale(float x, float y)
{
	impl->set_scale(x,y);
}

void CL_CollisionOutline::set_angle(float angle)
{
	impl->set_angle(angle);
}

void CL_CollisionOutline::rotate(float angle)
{
	impl->rotate(angle);
}

void CL_CollisionOutline::set_obb_test(bool value)
{
	impl->do_obb_test = value;
}

void CL_CollisionOutline::set_inside_test(bool value)
{
	impl->do_inside_test = value;
}

void CL_CollisionOutline::enable_collision_info( bool value )
{
	impl->collision_info = value;
}

void CL_CollisionOutline::enable_collision_info_normals( bool value )
{
	impl->collision_info_normals = value;
}

void CL_CollisionOutline::calculate_radius()
{
	impl->calculate_radius();
}

void CL_CollisionOutline::calculate_sub_circles()
{
	impl->calculate_sub_circles();
}

void CL_CollisionOutline::save(const std::string &filename, CL_OutputSourceProvider *provider) const
{
	impl->save(filename, provider);
}

bool CL_CollisionOutline::collide(const CL_CollisionOutline &outline)
{
	return impl->collide(outline);
}

bool CL_CollisionOutline::point_inside(const CL_Pointf &point) const
{
	return impl->point_inside(point);
}

void CL_CollisionOutline::set_alignment( CL_Origin origin, float x, float y )
{
	CL_Origin old_origin = impl->translation_origin;

	// undo previous translation offset.
	impl->set_translation(-impl->translation_offset.x, -impl->translation_offset.y, true);
	
	// undo previous origin.
	if( origin != impl->translation_origin )
	{
		float fix_x = 0, fix_y = 0;

		float width = impl->width/2;
		float height = impl->height/2;

		switch( old_origin )
		{
		 case origin_top_left:
			fix_x = -width;
			fix_y = -height;
			break;
		 case origin_top_center:
			fix_x = 0;
			fix_y = -height;
			break;
		 case origin_top_right:
			fix_x = width;
			fix_y = -height;
			break;

		 case origin_center_left:
			fix_x = -width;
			fix_y = 0;
			break;
		 case origin_center:
			fix_x = 0;
			fix_y = 0;
			break;
		 case origin_center_right:
			fix_x = width;
			fix_y = 0;
			break;

		 case origin_bottom_left:
			fix_x = -width;
			fix_y = height;
			break;
		 case origin_bottom_center:
			fix_x = 0;
			fix_y = height;
			break;
		 case origin_bottom_right:
			fix_x = width;
			fix_y = height;
			break;
		}

		impl->set_translation(fix_x, fix_y, true); // offset outline points

		impl->rotation_hotspot.x += fix_x;
		impl->rotation_hotspot.y += fix_y;
	}

	
	impl->translation_origin = origin;
	impl->translation_offset.x = x;
	impl->translation_offset.y = y;
	
	impl->set_translation(x, y, true); // offset outline points

	// set new origin offset.
	if( origin != origin_center )
	{
		float fix_x = 0, fix_y = 0;

		float width = impl->width/2;
		float height = impl->height/2;

		switch( origin )
		{
		 case origin_top_left:
			fix_x = width;
			fix_y = height;
			break;
		 case origin_top_center:
			fix_x = 0;
			fix_y = height;
			break;
		 case origin_top_right:
			fix_x = -width;
			fix_y = height;
			break;

		 case origin_center_left:
			fix_x = width;
			fix_y = 0;
			break;
		 case origin_center:
			fix_x = 0;
			fix_y = 0;
			break;
		 case origin_center_right:
			fix_x = -width;
			fix_y = 0;
			break;

		 case origin_bottom_left:
			fix_x = width;
			fix_y = -height;
			break;
		 case origin_bottom_center:
			fix_x = 0;
			fix_y = -height;
			break;
		 case origin_bottom_right:
			fix_x = -width;
			fix_y = -height;
			break;
		}
		
		impl->set_translation(fix_x, fix_y, true); // offset outline points

		impl->rotation_hotspot.x += fix_x;
		impl->rotation_hotspot.y += fix_y;
	}

	impl->calculate_radius();
}

void CL_CollisionOutline::set_rotation_hotspot( CL_Origin origin, float x, float y )
{
	// undo translation origin
	CL_Origin trans_origin = impl->translation_origin;	
	set_alignment(origin_center);

	// undo the rotation of the current rotation hotspot
	float angle = impl->angle;
	impl->set_angle(0.0f);

	float fix_x = 0, fix_y = 0;

	// set new origin offset.
	if( origin != origin_center )
	{
		float width = impl->width/2;
		float height = impl->height/2;

		switch( origin )
		{
		 case origin_top_left:
			fix_x = -width;
			fix_y = -height;
			break;
		 case origin_top_center:
			fix_x = 0;
			fix_y = -height;
			break;
		 case origin_top_right:
			fix_x = width;
			fix_y = -height;
			break;

		 case origin_center_left:
			fix_x = -width;
			fix_y = 0;
			break;
		 case origin_center:
			fix_x = 0;
			fix_y = 0;
			break;
		 case origin_center_right:
			fix_x = width;
			fix_y = 0;
			break;

		 case origin_bottom_left:
			fix_x = -width;
			fix_y = height;
			break;
		 case origin_bottom_center:
			fix_x = 0;
			fix_y = height;
			break;
		 case origin_bottom_right:
			fix_x = width;
			fix_y = height;
			break;
		}
	}

	impl->rotation_origin = origin;
	impl->rotation_hotspot_offset.x = x;
	impl->rotation_hotspot_offset.y = y;

	impl->rotation_hotspot.x = fix_x + x;
	impl->rotation_hotspot.y = fix_y + y;

	// transform data using new rotation hotspot
	impl->set_angle(angle);

	set_alignment(trans_origin);

	impl->calculate_radius();
}

