// Critterwall.cpp: implementation of various cCritterWall and cCritterBullet classes.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "critterwall.h"
#include "spritepolygon.h"
#include "collider.h"

//============================= cCritter ==========================
IMPLEMENT_SERIAL( cCritterWall, cCritter, 0 );

//Statics
Real cCritterWall::THICKNESS = 0.2;
Real cCritterWall::CORNERJIGGLE = PI/30.0;
COLORREF cCritterWall::WALLFILLCOLOR = cColorStyle::CN_LIGHTGRAY;

//==================cCritterWall==================
cCritterWall::cCritterWall(cGame *pownergame):
cCritter(pownergame),
_enda(-0.5, 0), //Default is origin
_endb(0.5, 0.0),
_radiusnormal(cCritterWall::THICKNESS / 2.0),
_radiustangent(0.5),
_defaultfillcolor(cCritterWall::WALLFILLCOLOR)
{
	_defaultprismdz = cSprite::WALLPRISMDZ;
	setFixedflag(TRUE); //By default a wall is fixed; remember this if you want to use one for a paddle.
	_collidepriority = cCollider::CP_WALL; /* Don't use the setCollidePriority mutator, as that
		forces a call to pgame()->buildCollider(); */
	_wrapflag = cCritter::WRAP; //In case a wall extends across the _border, don't bounce it.
}

cCritterWall::cCritterWall(const cVector &enda, const cVector &endb, Real thickness,cGame* pownergame):
cCritter(pownergame),
_enda(-0.5, 0), //Put in default values even though we'll change with setEndsAndThickness.
_endb(0.5, 0.0),
_radiusnormal(cCritterWall::THICKNESS / 2.0),
_radiustangent(0.5),
_defaultfillcolor(cCritterWall::WALLFILLCOLOR)
{
	_defaultprismdz = cSprite::WALLPRISMDZ;
	setFixedflag(TRUE); //By default a wall is fixed; remember this if you want to use one for a paddle.
	_collidepriority = cCollider::CP_WALL; /* Don't use the setCollidePriority mutator, as that
		forces a call to pgame()->buildCollider(); */
	_wrapflag = cCritter::WRAP; //In case a wall extends across the _border, don't bounce it.
	setEndsAndThickness(enda, endb, thickness);
}

void cCritterWall::setEndsAndThickness(const cVector &enda, const cVector &endb, Real thickness)
{
	_enda = enda;
	_endb = endb;
	_radiusnormal = thickness/2.0;
	_position = 0.5 * (_enda + _endb);
	_wrapposition3 = _wrapposition2 = _wrapposition1 = _position; /* This line
		is important, as otherwise the cCritter::draw will thing this thing was wrapped,
		and it'll get drawn in two places. */
	cVector attitudetangent = _endb - _enda;
	_radiustangent = attitudetangent.magnitude()/2.0;
	attitudetangent.normalize();
	cVector attitudenormal = attitudetangent.defaultNormal(); /* We orient so that the normal is oriented
		to the tangent as the "y-axis" is to the the "x-axis".*/
	_tangent = attitudetangent;
	setSpeed(0.0); //Also sets _velocity to ZEROVECTOR, but doesn't wipe out _direction.
	_normal = attitudenormal;
	_binormal = attitudetangent*attitudenormal;
	_attitude = cMatrix(_tangent, _normal, _binormal, _position);
/*	Real *aelements = _attitude.transpose().elements();
	TRACE("3D version of cCritter attitude() matrix in cCritterWall::setEndsandThickness call.\n");
	TRACE("%f  %f  %f  %f\n", aelements[0], aelements[4], aelements[8], aelements[12]);
	TRACE("%f  %f  %f  %f\n", aelements[1], aelements[5], aelements[9], aelements[13]);
	TRACE("%f  %f  %f  %f\n", aelements[2], aelements[6], aelements[10], aelements[14]);
	TRACE("%f  %f  %f  %f\n", aelements[3], aelements[7], aelements[11], aelements[15]);
*/
/*We will draw the wall as a rectangle along the _enda to _endb axis with half of _radiusnormal
on either side. In looking at these settings, think of the wall as aligned horizontally with
the tangent pointing to the right and the normal pointing up, and with the _radiustangent half
the horizontal length and the _radiusnormal half the vertical height.*/
	cPolygon *ppolygon = new cPolygon(4);
	ppolygon->setEdged(TRUE);
	ppolygon->setFillColor(cColorStyle::CN_GRAY);
	ppolygon->setLineWidthWeight(0.0); //Means draw a one-pixel edge line.
	ppolygon->setVertex(0, cVector(_radiustangent, _radiusnormal));
	ppolygon->setVertex(1, cVector(-_radiustangent, _radiusnormal));
	ppolygon->setVertex(2, cVector(-_radiustangent, -_radiusnormal));
	ppolygon->setVertex(3, cVector( _radiustangent, -_radiusnormal));
	ppolygon->fixCenterAndRadius(); //Use this call after a bunch of setVertex if points are just where you want.
	setSprite(ppolygon); /* Also sets cSprite::_prismdz to cCritter::_defaultprismdz, which we
		set to CritterWall::WALLPRISMDZ in our cCritterWall constructor. */
}	

void cCritterWall::setEnds(const cVector &enda, const cVector &endb)
{
	setEndsAndThickness(enda, endb, 2.0*_radiusnormal);
}

void cCritterWall::setThickness(Real thickness)
{
	setEndsAndThickness(_enda, _endb, thickness);
}

void cCritterWall::setCorners(const cVector &corner1, const cVector &corner2)
{
/* Don't depend on the user to have put in the corners in the right order.  Also you need to
look at the corners to decide whether to make this a vertical or a horizontal wall.  If you 
want a diagonal wall, then you should not be using this constructor. */

	Real lox = __min(corner1.x(), corner2.x());
	Real hix = __max(corner1.x(), corner2.x());
	Real thicknessx = hix - lox;
	Real midx = (lox + hix)/2.0;
	Real loy = __min(corner1.y(), corner2.y());
	Real hiy = __max(corner1.y(), corner2.y());
	Real thicknessy = hiy - loy;
	Real midy = (loy + hiy)/2.0;
	if (thicknessx <= thicknessy) //make vertical wall
		setEndsAndThickness(cVector(midx, loy), cVector(midx, hiy), thicknessx);
	else //make horizontal wall
		setEndsAndThickness(cVector(lox, midy), cVector(hix, midy), thicknessy);
}

void cCritterWall::setFillColor(COLORREF fillcolor)
{
	_defaultfillcolor = fillcolor;
	if(_psprite)_psprite->setFillColor(fillcolor);
}

void cCritterWall::Serialize(CArchive &ar)
{

	cCritter::Serialize(ar);
	if (ar.IsStoring()) //Writing data.
	{
		ar << _enda << _endb << _radiusnormal << _radiustangent << _defaultfillcolor;
	}
	else //reading data
	{
		ar >> _enda >> _endb >> _radiusnormal >> _radiustangent >> _defaultfillcolor;
	}
}

int cCritterWall::dragTo(const cVector &newposition, Real dt)
{
	if (!draggable())
		return BOX_INSIDE; //Don't change the velocity.
	_position = newposition;
	return clamp(_dragbox);
}

int cCritterWall::collidesWith(cCritter *pcritterother)
{
	/* Make sure I don't ever waste time colliding walls with walls.  The cRuntimeClass check
	is expensive, but remember that I only do this the one time that I enroll the cCritterWall
	into the cGame's _pcollider. */
	if(pcritterother->IsKindOf(RUNTIME_CLASS(cCritterWall)))
		return cCollider::DONTCOLLIDE;
	return cCritter::collidesWith(pcritterother);
}

void cCritterWall::copy(cCritter *pcritter)
{
/*We need to overload the cCritter copy because if I have cCritterWallRobot which is
a cCritterWall, and it gets split in two by a replicate call, then I want the new
copy to have all the same shooting behavior as the old one.  In general, I should
overload copy for each of my critter child classes, but for now this one is the
most important.  The overload does the regular copy, then looks if the thing 
being copied is a cCritterWall, and if it is then it copies the additional fields. */
	cCritter::copy(pcritter);	
	if (!pcritter->IsKindOf(RUNTIME_CLASS(cCritterWall)))
		return; //You're done if pcritter isn't a cCritterWall*.
	cCritterWall *pcritterwall = (cCritterWall *)(pcritter); /* I know it is a
		cCritterWall at this point, but I need to do a cast, so the compiler will let me
		call a bunch of cCritterWall methods. */
	_enda = pcritterwall->_enda;
	_endb = pcritterwall->_endb;
	_radiusnormal = pcritterwall->_radiusnormal;
	_radiustangent = pcritterwall->_radiustangent;
	_defaultfillcolor = pcritterwall->_defaultfillcolor;
}

void cCritterWall::mutate(int mutationflags, Real mutationstrength)
{ /* This is exactly the same as the base class cCritter::mutate except that we comment out the
last line, which would mutate the sprite.  We don't want to mutate a wall's sprite. */
	if (mutationflags & MF_NUDGE)  //Special kind of weak mutation
	{
		Real turnangle = cRandomizer::pinstance()->randomSign() * cRandomizer::pinstance()->randomReal(-PI/2, PI/2);
		_velocity.turn(turnangle);
		_velocity *= cRandomizer::pinstance()->randomReal(0.5, 1.5);
		randomizePosition(realBox()); /* cCritter::realBox() is the
			smallest box holding the current sprite. */
	}
	if (mutationflags & MF_POSITION)
		randomizePosition(_movebox);
	if (mutationflags & MF_VELOCITY)
		randomizeVelocity(cCritter::MINSPEED, _maxspeedstandard);
 /* The following line is the cCritter::mutate line
		that we MUST comment out. */
	//_psprite->mutate(mutationflags, mutationstrength);
}

//================Distance methods=====================

cVector cCritterWall::globalToLocalVector(const cVector &globalvec)
{
	return cVector(globalvec%attitudeTangent(), globalvec%attitudeNormal());
}

cVector cCritterWall::localToGlobalVector(const cVector &localvec)
{
	return localvec.x()*attitudeTangent() + localvec.y()*attitudeNormal();
}

cVector cCritterWall::globalToLocalPosition(const cVector &globalpos)
{
	cVector localpos = globalpos - _position;
	return globalToLocalVector(localpos);
}

cVector cCritterWall::localToGlobalPosition(const cVector &localpos)
{
	return _position + localToGlobalVector(localpos);
}

int cCritterWall::outcode(const cVector &globalpos) 
{
	return outcodeLocal(globalToLocalPosition(globalpos));
}

int cCritterWall::outcodeLocal(const cVector &localpos) 
{ /* This tells you which of the nine possible positions a localpos has
relative to the wall's rectangle */
	int outcode = BOX_INSIDE; //This is 0.
	if (localpos.x() < -_radiustangent)
		outcode |= BOX_LOX;
	if (localpos.x() > _radiustangent)
		outcode |= BOX_HIX;
	if (localpos.y() < -_radiusnormal)
		outcode |= BOX_LOY;
	if (localpos.y() > _radiusnormal)
		outcode |= BOX_HIY;
	return outcode;
}

Real cCritterWall::distanceToLocal(const cVector &localpos) 
{ /*This gives the distance from a localpos to the closest point of the
cRealBox.  If you are in a "side" zone your nearest distance is a point
on the side.  If you are in a "corner" zone your nearest distance is a
corner.  If you are inside, we call the distance 0.*/
	int outcode = outcodeLocal(localpos); //This is 0.
	return distanceToLocal(localpos, outcode);
}

Real cCritterWall::distanceToLocal(const cVector &localpos, int outcode) 
{ /*This gives the distance from a localpos to the closest point of the
cRealBox.  If you are in a "side" zone your nearest distance is a point
on the side.  If you are in a "corner" zone your nearest distance is a
corner.  If you are inside, we call the distance 0.*/
	switch(outcode)
	{
		case BOX_INSIDE:
			return 0.0;
		case BOX_LOX:
			return -localpos.x() - _radiustangent ;
		case BOX_HIX:
			return localpos.x() - _radiustangent;
		case BOX_LOY:
			return -localpos.y() - _radiusnormal;
		case BOX_HIY:
			return localpos.y() - _radiusnormal;
		case BOX_LOY_LOX:
			return localpos.distanceTo(cVector(-_radiustangent, -_radiusnormal));
		case BOX_LOY_HIX:
			return localpos.distanceTo(cVector(_radiustangent, -_radiusnormal));
		case BOX_HIY_LOX:
			return localpos.distanceTo(cVector(-_radiustangent, _radiusnormal));
		case BOX_HIY_HIX:
			return localpos.distanceTo(cVector(_radiustangent, _radiusnormal));
	}
	return 0.0; //Unreachable default case.
}

Real cCritterWall::distanceTo(const cVector &vpoint)
{
	return distanceToLocal(globalToLocalPosition(vpoint));
}

//-----------------------Other Overloads-----------------
int cCritterWall::clamp(const cRealBox &border)
{  /* We don't change _enda and _endb as these have geometric info.  We just
	change position. */
	cRealBox effectivebox = border; //.innerBox(__min(_radiustangent, _radiusnormal));
	cVector enda = _position - _radiustangent*attitudeTangent();
	int outcodea = effectivebox.clamp(enda);
	_position = enda + _radiustangent*attitudeTangent();
	cVector endb = _position + _radiustangent*attitudeTangent();
	int	outcodeb = effectivebox.clamp(endb);
	_position = endb - _radiustangent*attitudeTangent();
	_wrapposition3 = _wrapposition2 = _wrapposition1 = _position;
	return outcodea | outcodeb;
}

//-------------Colliding and Blocking--------------------

BOOL cCritterWall::blocks(const cVector& start, const cVector& end)
{
	BOOL crossed;
	int endoutcode = outcode(end);
	int startoutcode = outcode(start);
	/* If crossed is TRUE then moving from start to end means moving across the wall, even 
	though neither start nor end has to be close to the wall.  NOte that we are checking for 
	cutting across corners as well. We do this instead use a simpler check like like
		BOOL crossed = (startoutcode == BOX_LOY && endoutcode == BOX_HIY) ||
			startoutcode == BOX_HIY && endoutcode == BOX_LOY)), etc.
	A different approach would be to look  at the HIY LOY parts of the wallbox corners' 
	outcodes relative to the line connecting position and oldposition; if some are HIY 
	and some are LOY then the line crosses. */

	crossed = (startoutcode == BOX_LOY && !(endoutcode & BOX_LOY)) ||
		(startoutcode == BOX_HIY && !(endoutcode & BOX_HIY)); /* First check for moving across
			in direction of normal */
	crossed |=  (startoutcode == BOX_LOX && !(endoutcode & BOX_LOX)) ||
		(startoutcode == BOX_HIX && !(endoutcode & BOX_HIX)); /* Then Check for moving
			across in direction of tangent (can happen either if someone wants to 
			cut across a corner, or, less often, across whole tangent length (usually this direction 
			is long, but someone could change this by choosing close endpoints and a big
			thickness). */
	return crossed;
}

BOOL cCritterWall::collide(cCritter *pcritter)
{
	cVector localpos = globalToLocalPosition(pcritter->position());
	int newoutcode = outcodeLocal(localpos);
	Real distance = distanceToLocal(localpos, newoutcode);
	int oldoutcode = outcode(pcritter->oldposition());
	cVector localvelocity;
	cVector cornerpoint;
	cVector fromcorner;

//Use the same test here as in cCritterWall::crossed(pcritter->oldposition(), pcritter->position());
	BOOL crossed = (oldoutcode == BOX_LOY && !(newoutcode & BOX_LOY)) ||
		(oldoutcode == BOX_HIY && !(newoutcode & BOX_HIY)); /* First check for moving across
			in direction of normal */
	crossed |=  (oldoutcode == BOX_LOX && !(newoutcode & BOX_LOX)) ||
		(oldoutcode == BOX_HIX && !(newoutcode & BOX_HIX)); /* Then Check for moving
			across in direction of tangent (can happen either if someone wants to 
			cut across a corner, or, less often, across whole tangent length (usually this direction 
			is long, but someone could change this by choosing close endpoints and a big
			thickness). */

			/* If crossed is TRUE then we moved across the wall, even though we may no longer
			be touching it. Note that we are checking for cutting across corners as well to make it 
			harder to sneak 	out of a box of walls.  We do this instead use a simpler check like 
			like BOOL crossed = (oldoutcode == BOX_LOY && newoutcode == BOX_HIY) ||
				oldoutcode == BOX_HIY && newoutcode == BOX_LOY)), 	etc.  A different approach
			would be to look  at the HIY LOY parts of the wallbox corners' outcodes relative
			to the line connecting position and oldposition; if some are HIY and some are 
			LOY then the line crosses. */

	if (distance >= pcritter->radius() && !crossed) //No collision
		return FALSE; /*See if there's a collision at all. We say there's a collision if 
			crossed or if the cCritterWall::distance is less than radius.  Remember 
			that cCritterWall::distance measures the distance to the OUTSIDE PERIMETER of
			the box, not the distance to the box's center. cCritterWall::distance is 0 for 
			any point inside the wall's box. */

		/* Now pick an newoutcode to determine which region outside of the wall that you want
			to bounce into. */
	if (newoutcode == BOX_INSIDE || newoutcode != oldoutcode) 
				//If inside, or you passed through the wall.
		newoutcode = oldoutcode;
	if (newoutcode == BOX_INSIDE)
				//You're inside, or you passed through, and oldoutcode was inside.  Back up.
	{
		cVector backupposition = pcritter->position();
		int backupcount = 0; //Avoid faint possibility of being in the while loop forever.
		while (newoutcode == BOX_INSIDE && backupcount<10)
		{
			backupposition -= _radiusnormal * pcritter->tangent();
			newoutcode = outcode(backupposition);
			backupcount++;
		}
	}
	if (newoutcode == BOX_INSIDE)
			//If somehow STILL not a good newoutcode, just use direction of attitudeNormal().
		newoutcode = BOX_HIY;

	localvelocity = globalToLocalVector(pcritter->velocity()); 

	switch(newoutcode)
	{
		case BOX_LOX:
			localpos.set(-_radiustangent - pcritter->radius(), localpos.y());
			localvelocity.set(-fabs(localvelocity.x()), localvelocity.y());
			break;
		case BOX_HIX:
			localpos.set(_radiustangent + pcritter->radius(), localpos.y());
			localvelocity.set(fabs(localvelocity.x()), localvelocity.y());
			break;
		case BOX_LOY:
			localpos.set(localpos.x(), -_radiusnormal - pcritter->radius());
			localvelocity.set(localvelocity.x(), -fabs(localvelocity.y()));
			break;
		case BOX_HIY:
			localpos.set(localpos.x(), _radiusnormal + pcritter->radius());
			localvelocity.set(localvelocity.x(), fabs(localvelocity.y()));
			break;
		case BOX_LOY_LOX:
		case BOX_LOY_HIX:
		case BOX_HIY_LOX:
		case BOX_HIY_HIX: /* For the corner outcodes we bounce off the corner point. */
			switch(newoutcode)
			{
				case BOX_LOY_LOX:
					cornerpoint.set(-_radiustangent, -_radiusnormal);
					break;
				case BOX_LOY_HIX:
					cornerpoint.set(_radiustangent, -_radiusnormal);
					break;
				case BOX_HIY_LOX:
					cornerpoint.set(-_radiustangent, _radiusnormal);
					break;
				case BOX_HIY_HIX:
					cornerpoint.set(_radiustangent, _radiusnormal);
					break;
			}
			fromcorner = (localpos - cornerpoint).normalize();
			localpos = cornerpoint + pcritter->radius()*fromcorner;
			localvelocity = -localvelocity;
				/* To keep someone from unrealistically bouncing up and down on a corner, we
					jiggle the bounce. */
			localvelocity.turn(cRandomizer::pinstance()->randomReal(-cCritterWall::CORNERJIGGLE,
				-cCritterWall::CORNERJIGGLE));
			break;
	}
	pcritter->moveTo(localToGlobalPosition(localpos));
	pcritter->setVelocity(localToGlobalVector(localvelocity));
	return TRUE;
}

