/*****************************************************************************
 * $Id: eng_filter.cc,v 1.130 2006/08/01 23:14:02 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Functions to test visibility of objects.
 * @remarks	Most objects are expected to not be visible.  Therefore, net time
 *			can be saved by conducting visibility tests on a few vertexes before
 *			a model algorithm proceeds to transform/submit vertexs.
 *
 *          The virtual methods Object::Visible(), Object::CalcRadius*()
 *          and Object::FinalConstruction() affect the visibility tests.
 *          For flexibility, they can be redefined by a derived class,
 *          in case a class needs special visibility tests (or none at all).
 *          Visible() does several tests: distance, facing, and projection.
 *          CalcRadius*() produce values that are used for the visibility tests.
 *          FinalConstruction() (which every Object construction must call),
 *          calls CalcRadius*().  Thereafter, Draw() calls Visible() and
 *			bypasses rendering if an object isn't visible.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define ENG_FILTER_CC 1
#include "eng.hh"
#include "eng_filter.hh"

// -- Remember to test if Z is facing using IS_Z_FACING() macro --

/*****************************************************************************
 * This visibility test simply determines if the object is entirely
 * behind the viewpoint.  Appropriate for extremely large objects which
 * the regular Object::Visible() can't test properly.
 * A derived Object class can override Visible() and call this instead.
 *****************************************************************************/
int
ObjectMesh::VisibleSimple( void )
{
	// The Object is visible unless all Z eye coords are negative (behind viewpoint).
	const WorldVertex* wv = GetWorldVertexs();
	const uint vcnt = GetVertexCnt();
ASSERT( vcnt > 0 );
	for ( uint i = 0; i < vcnt; ++i )
	{
		fp z = TRANSFORM_Z( wv[i] );
		//if ( z > 0 ) return 100;						// WRONG.  Use IS_Z_FACING().
		if ( IS_Z_FACING(z) ) return VISIBLE_DEFAULT;	// one point is in front of viewpoint
	}													// VISIBLE_DEFAULT is a guess at 2D pixel width
	return INVISIBLE;	// not visible (entirely behind viewpoint)
}

/*****************************************************************************
 * hi-freq
 * @return Zero if not visible, or width (height) of 2D pixels object would occupy.
 *****************************************************************************/
static fp xd_vis, yd_vis, zd_vis; // abs
int
Object::Visible( void )
{
	// Is object behind the viewpoint?
    // Large objects could still be visible (both behind and in front).
	const Position wvPos = GetPosition();
	fp distMin = gWorld.mObjectLargeDist;
	if ( EX( gWorld.IfSmallObject(*this) ) )
	{
        if ( ! IS_Z_FACING( TRANSFORM_Z(wvPos) ) )
			return 0;
		distMin = gWorld.mObjectSmallDist;
	}

	// Is object too far away to be visible?
	// Eye origin is negative.
	xd_vis = ABS(wvPos.x + gEng.mEye.mCm[Ox]); if ( xd_vis > distMin ) return 0;
	yd_vis = ABS(wvPos.y + gEng.mEye.mCm[Oy]); if ( yd_vis > distMin ) return 0;
	zd_vis = ABS(wvPos.z + gEng.mEye.mCm[Oz]); if ( zd_vis > distMin ) return 0;

	// Test if the object would be inside the 2D viewport.
    // Using the object's center in eye space, add and subtract
    // the object's radius to form a 2D rectangle in eye space,
    // then project the two vertexs (NW and SE).
	const fp radius = GetRadius();
	ASSERT2( radius > 0 );
	EyeVertex evPos, ev[2];
	TRANSFORM_VERTEX( evPos, wvPos );
	ev[0].x = evPos.x - radius;
	ev[0].y = evPos.y - radius;
	ev[1].x = evPos.x + radius;
	ev[1].y = evPos.y + radius;
	ev[0].z = ev[1].z = evPos.z;
	return gEng.TestViewport( ev, 2 );
}
// RESTRICTED: For use by ObjectVisExt class only.
// Depends on side-effects from Visible().
// Do not call if Visible() returned 0 as xd_vis will be undefined!
fp
Object::VisibleDistance( void )
{
	return Max( Max(xd_vis,yd_vis), zd_vis );
}

/*****************************************************************************
 * hi-freq
 * Test if any part of 3D object is inside the 2D viewport.
 * @return Zero if not visible, or 2D width of projected object.
 *****************************************************************************/
int
Engine::TestViewport( const EyeVertex* vertexs, int vertexCnt )
{
	// Project the 3D vertexs to 2D viewport points.
	// Accumulate a pair of min/max 2D points which form the corners
	// of a 2D rectangle that encloses the projected 3D object.
    Rect<int> rect;
	int pt[2];
	GfxDriverProject( pt, vertexs[0] );
	rect.x1 = rect.x2 = pt[XX];
	rect.y1 = rect.y2 = pt[YY];
	for ( int i = 1; i < vertexCnt; ++i )
	{
		GfxDriverProject( pt, vertexs[i] );
		rect.Grow( pt[XX], pt[YY] );
	}

	// If all 2D coords of the object rectange lie outside of any one
    // of the four viewport lines then the object lies outside the viewport.
	if ( gEng.mView.GetRectTestViewport().IfOutside( rect ) )
		return 0;
	else
		return Max( rect.x2-rect.x1, rect.y2-rect.y1 ); // larger of width,height
}
