/**
 * $Id: SM_Object.cpp,v 1.3 2002/11/25 15:29:54 mein Exp $
 * Copyright (C) 2001 NaN Technologies B.V.
 * The basic physics object.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef WIN32
// This warning tells us about truncation of __long__ stl-generated names.
// It can occasionally cause DevStudio to have internal compiler warnings.
#pragma warning( disable : 4786 )     
#endif

#include "SM_Object.h"
#include "SM_Scene.h"
#include "SM_FhObject.h"

#include "MT_MinMax.h"


// Tweak parameters
static MT_Scalar ImpulseThreshold = 0.5;

SM_Object::SM_Object(
	DT_ShapeHandle shape, 
	const SM_MaterialProps *materialProps,
	const SM_ShapeProps *shapeProps,
	SM_Object *dynamicParent
) :
	m_shape(shape),
	m_materialProps(materialProps),
	m_materialPropsBackup(0),
	m_shapeProps(shapeProps),
	m_shapePropsBackup(0),
	m_dynamicParent(dynamicParent),
	m_object(DT_CreateObject(this, shape)),
	m_margin(0.0),
	m_scaling(1.0, 1.0, 1.0),
	m_reaction_impulse(0.0, 0.0, 0.0),
	m_reaction_force(0.0, 0.0, 0.0),
	m_kinematic(false),
	m_prev_kinematic(false),
	m_is_rigid_body(false),
	m_lin_mom(0.0, 0.0, 0.0),
	m_ang_mom(0.0, 0.0, 0.0),
	m_force(0.0, 0.0, 0.0),
	m_torque(0.0, 0.0, 0.0),
	m_client_object(0),
	m_fh_object(0),

	m_combined_lin_vel (0.0, 0.0, 0.0),
	m_combined_ang_vel (0.0, 0.0, 0.0)
{
	m_xform.setIdentity();
	m_xform.getValue(m_ogl_matrix);
	if (shapeProps && 
		(shapeProps->m_do_fh || shapeProps->m_do_rot_fh)) {
		MT_Vector3 ray(0.0, 0.0, -10.0);
		m_fh_object = new SM_FhObject(ray, this);
	}
	m_suspended = false;
}


	void 
SM_Object::
integrateForces(
	MT_Scalar timeStep
){
	if (!m_suspended) {
		m_prev_state = *this;
		m_prev_state.setLinearVelocity(actualLinVelocity());
		m_prev_state.setAngularVelocity(actualAngVelocity());
		if (isDynamic()) {
			// Integrate momentum (forward Euler)
			m_lin_mom += m_force * timeStep;
			m_ang_mom += m_torque * timeStep;
			// Drain momentum because of air/water resistance
			m_lin_mom *= pow(m_shapeProps->m_lin_drag, timeStep);
			m_ang_mom *= pow(m_shapeProps->m_ang_drag, timeStep);
			// Set velocities according momentum
			m_lin_vel = m_lin_mom / m_shapeProps->m_mass;
			m_ang_vel = m_ang_mom / m_shapeProps->m_inertia;
		}
	}	

};

	void 
SM_Object::
integrateMomentum(
	MT_Scalar timeStep
){
	// Integrate position and orientation

	// only do it for objects with linear and/or angular velocity
	// else clients with hierarchies may get into trouble
	if (!actualLinVelocity().fuzzyZero() || !actualAngVelocity().fuzzyZero()) 
	{

	// those MIDPOINT and BACKWARD integration methods are
	// in this form not ok with some testfiles ! 
	// For a release build please use forward euler unless completely tested

//#define MIDPOINT
//#define BACKWARD
#ifdef  MIDPOINT
// Midpoint rule
		m_pos += (m_prev_state.getLinearVelocity() + actualLinVelocity()) * (timeStep * 0.5);
		m_orn += (m_prev_state.getAngularVelocity() * m_prev_state.getOrientation() + actualAngVelocity() * m_orn) * (timeStep * 0.25);
#elif defined BACKWARD
// Backward Euler
		m_pos += actualLinVelocity() * timeStep;
		m_orn += actualAngVelocity() * m_orn * (timeStep * 0.5);
#else 
// Forward Euler

		m_pos += m_prev_state.getLinearVelocity() * timeStep;
		m_orn += m_prev_state.getAngularVelocity() * m_orn * (timeStep * 0.5);
#endif
		m_orn.normalize(); // I might not be necessary to do this every call

		calcXform();
		notifyClient();		

	}
}


void SM_Object::boing(
	void *client_data,  
	void *object1,
	void *object2,
	const DT_CollData *coll_data
){
	SM_Scene  *scene = (SM_Scene *)client_data; 
	SM_Object *obj1  = (SM_Object *)object1;  
	SM_Object *obj2  = (SM_Object *)object2;  
	
	scene->addPair(obj1, obj2); // Record this collision for client callbacks
	

	// If one of the objects is a ghost then ignore it for the dynamics
	if (obj1->isGhost() || obj2->isGhost()) {
		return;
	}

	
	if (!obj2->isDynamic()) {
		std::swap(obj1, obj2);
	}

	if (!obj2->isDynamic()) {
		return;
	}

	// obj1 points to a dynamic object


	// This distinction between dynamic and non-dynamic objects should not be 
	// necessary. Non-dynamic objects are assumed to have infinite mass.

	if (obj1->isDynamic()) {
		// normal to the contact plane 
		MT_Vector3 normal = obj2->m_pos - obj1->m_pos;
		MT_Scalar  dist = normal.length(); 
		
		if (MT_EPSILON < dist && dist <= obj1->getMargin() + obj2->getMargin())
		{
			normal /= dist;
			// relative velocity of the contact points
			MT_Vector3 rel_vel       = obj2->actualLinVelocity() - obj1->actualLinVelocity();
			// relative velocity projected onto the normal
			MT_Scalar rel_vel_normal = normal.dot(rel_vel);
			
			// if the objects are approaching eachother...
			if (rel_vel_normal < 0.0) {
				MT_Scalar restitution;
				// ...and the approaching velocity is large enough... 
				if (-rel_vel_normal > ImpulseThreshold) {
					// ...this must be a true collision.
					restitution = 
						MT_min(obj1->getMaterialProps()->m_restitution,
								obj2->getMaterialProps()->m_restitution);
				}
				else {
					// otherwise, it is a resting contact, and thus the 
					// relative velocity must be made zero
					restitution = 0.0;
					// We also need to interfere with the positions of the
					// objects, otherwise they might drift into eachother. 
					MT_Vector3 penalty = normal * 
						(0.5 * (obj1->getMargin() + obj2->getMargin() - dist));
					obj1->m_pos -= penalty;
					obj2->m_pos += penalty;
				}
				
				// Compute the impulse necessary to yield the desired relative velocity...
                MT_Scalar impulse = -(1.0 + restitution) * rel_vel_normal /
                    (obj1->getInvMass() + obj2->getInvMass());
               
				// ... and apply it.
				obj1->applyCenterImpulse(-impulse * normal);
				obj2->applyCenterImpulse( impulse * normal);
            }
		}
	}
	else {
		// Same again but now obj1 is non-dynamic

		// Compute the point on obj1 closest to obj2 (= sphere with radius = 0)
		MT_Point3 local1, local2;
		MT_Scalar dist = DT_GetClosestPair(obj1->m_object, obj2->m_object,
										   local1.getValue(), local2.getValue());
		
        // local1 is th point closest to obj2
        // local2 is the local origin of obj2 

		if (MT_EPSILON < dist && dist <= obj2->getMargin()) {
			MT_Point3 world1 = obj1->getWorldCoord(local1);
			MT_Point3 world2 = obj2->getWorldCoord(local2);
			MT_Vector3 vel1  = obj1->getVelocity(local1) + obj1->m_combined_lin_vel;
			MT_Vector3 vel2  = obj2->getVelocity(local2) + obj2->m_combined_lin_vel;
            
            // the normal to the contact plane
			MT_Vector3 normal = world2 - world1; 
			normal /= dist;

			// wr2 points from obj2's origin to the global contact point
			// wr2 is only needed for rigid bodies (objects for which the 
			// friction can change the angular momentum).
			// vel2 is adapted to denote the velocity of the contact point 
			MT_Vector3 wr2;
			if (obj2->isRigidBody()) {
				wr2 = -obj2->getMargin() * normal;
				vel2 += obj2->actualAngVelocity().cross(wr2);
			}


			// This should look familiar....
			MT_Vector3 rel_vel        = vel2 - vel1;
			MT_Scalar  rel_vel_normal = normal.dot(rel_vel);
			
			if (rel_vel_normal < 0.0) {
				MT_Scalar restitution;
				if (-rel_vel_normal > ImpulseThreshold) {
					restitution = 
						MT_min(obj1->getMaterialProps()->m_restitution,
								obj2->getMaterialProps()->m_restitution);
				}
				else {
					restitution = 0.0;
					// We fix drift by moving obj2 only, since obj1 was 
					// non-dynamic.
					obj2->m_pos += normal * (obj2->getMargin() - dist);

				}
				
				MT_Scalar impulse = -(1.0 + restitution) * rel_vel_normal /
					(obj1->getInvMass() + obj2->getInvMass());


				obj2->applyCenterImpulse( impulse * normal);
	   	
// The friction part starts here!!!!!!!!

				// Compute the lateral component of the relative velocity
				// lateral actually points in the opposite direction, i.e.,
				// into the direction of the friction force.

#if 1
				// test - only do friction on the physics part of the 
				// velocity.
				vel1  -= obj1->m_combined_lin_vel;
				vel2  -= obj2->m_combined_lin_vel;

				// This should look familiar....
				rel_vel        = vel2 - vel1;
				rel_vel_normal = normal.dot(rel_vel);
#endif
				
				MT_Vector3 lateral = normal * rel_vel_normal - rel_vel;
				
				const SM_ShapeProps *shapeProps = obj2->getShapeProps();
				
				if (shapeProps->m_do_anisotropic) {

					// For anisotropic friction we scale the lateral component,
					// rather than compute a direction-dependent fricition 
					// factor. For this the lateral component is transformed to
					// local coordinates.

					MT_Matrix3x3 lcs(obj2->m_orn);
					// We cannot use m_xform.getBasis() for the matrix, since 
					// it might contain a non-uniform scaling. 
					// OPT: it's a bit daft to compute the matrix since the 
					// quaternion itself can be used to do the transformation.

					MT_Vector3 loc_lateral = lateral * lcs;
					// lcs is orthogonal so lcs.inversed() == lcs.transposed(),
					// and lcs.transposed() * lateral == lateral * lcs.

					const MT_Vector3& friction_scaling = 
						shapeProps->m_friction_scaling; 
					
					// Scale the local lateral...
					loc_lateral.scale(friction_scaling[0], 
									  friction_scaling[1], 
									  friction_scaling[2]);
					// ... and transform it back to global coordinates
					lateral = lcs * loc_lateral;
				}
				
				// A tiny Coulomb friction primer:
				// The Coulomb friction law states that the magnitude of the
				// maximum possible friction force depends linearly on the 
				// magnitude of the normal force.
				//
				// F_max_friction = friction_factor * F_normal 
				//
				// (NB: independent of the contact area!!)
				//
				// The friction factor depends on the material. 
				// We use impulses rather than forces but let us not be 
				// bothered by this. 


				MT_Scalar  rel_vel_lateral = lateral.length();
				
				if (rel_vel_lateral > MT_EPSILON) {
					lateral /= rel_vel_lateral;
					
					MT_Scalar friction_factor = 
						MT_min(obj1->getMaterialProps()->m_friction, 
								obj2->getMaterialProps()->m_friction);
					
					// Compute the maximum friction impulse

					MT_Scalar max_friction = 
						friction_factor * MT_max(0.0, impulse);
					
					// I guess the GEN_max is not necessary, so let's check it

					assert(impulse >= 0.0);

					// Here's the trick. We compute the impulse to make the
					// lateral velocity zero. (Make the objects stick together
					// at the contact point. If this impulse is larger than
					// the maximum possible friction impulse, then shrink its
					// magnitude to the maximum friction.

					if (obj2->isRigidBody()) {
						
						// For rigid bodies we take the inertia into account, 
						// since the friciton impulse is going to change the
						// angular momentum as well.
						MT_Scalar impulse_lateral = rel_vel_lateral /
							(obj1->getInvMass() + obj2->getInvMass() +
							 ((obj2->getInvInertia() * wr2.cross(lateral)).cross(wr2)).dot(lateral));
						MT_Scalar friction = MT_min(impulse_lateral, max_friction);
						obj2->applyImpulse(world2 + wr2,  friction * lateral);
					}
					else {
						MT_Scalar impulse_lateral = rel_vel_lateral /
							(obj1->getInvMass() + obj2->getInvMass());
						
						MT_Scalar friction = MT_min(impulse_lateral, max_friction);
						obj2->applyCenterImpulse( friction * lateral);
					}
				}	
				
				obj2->calcXform();
				obj2->notifyClient();		
			}
		}
	}
}



SM_Object::SM_Object(
) {
	// warning no initialization of variables done by moto.
}

SM_Object::
~SM_Object() { 
	delete m_fh_object;
	DT_DeleteObject(m_object); 
}

	bool 
SM_Object::
isDynamic(
) const {
	return m_shapeProps != 0; 
} 

/* nzc experimental. There seem to be two places where kinematics
 * are evaluated: proceedKinematic (called from SM_Scene) and
 * proceed() in this object. I'll just try and bunge these out for
 * now.  */
	void 
SM_Object::
suspend(
){
	if (!m_suspended) {
		m_suspended = true;
		suspendDynamics();
	}
}

	void 
SM_Object::
resume(
) {
	if (m_suspended) {
		m_suspended = false;
		restoreDynamics();
	}
}

	void 
SM_Object::
suspendDynamics(
) {
	if (m_shapeProps) {
		m_shapePropsBackup = m_shapeProps;
		m_shapeProps = 0;
	}
}

	void 
SM_Object::
restoreDynamics(
) {
	if (m_shapePropsBackup) {
		m_shapeProps = m_shapePropsBackup;
		m_shapePropsBackup = 0;
	}
}

	bool 
SM_Object::
isGhost(
) const {
	return m_materialProps == 0;
} 

	void 
SM_Object::
suspendMaterial(
) {
	if (m_materialProps) {
		m_materialPropsBackup = m_materialProps;
		m_materialProps = 0;
	}
}

	void 
SM_Object::
restoreMaterial(
) {
	if (m_materialPropsBackup) {
		m_materialProps = m_materialPropsBackup;
		m_materialPropsBackup = 0;
	}
}

	SM_FhObject *
SM_Object::
getFhObject(
) const {
	return m_fh_object;
} 

	void 
SM_Object::
registerCallback(
	SM_Callback& callback
) {
	m_callbackList.push_back(&callback);
}

// Set the local coordinate system according to the current state 
	void 
SM_Object::
calcXform() {
	m_xform.setOrigin(m_pos);
	m_xform.setBasis(MT_Matrix3x3(m_orn, m_scaling));
	m_xform.getValue(m_ogl_matrix);
	DT_SetMatrixd(m_object, m_ogl_matrix);
	if (m_fh_object) {
		m_fh_object->setPosition(m_pos);
		m_fh_object->calcXform();
	}
}

// Call callbacks to notify the client of a change of placement
	void 
SM_Object::
notifyClient() {
	T_CallbackList::iterator i;
	for (i = m_callbackList.begin(); i != m_callbackList.end(); ++i) {
		(*i)->do_me();
	}
}


// Save the current state information for use in the velocity computation in the next frame.  
	void 
SM_Object::
proceedKinematic(
	MT_Scalar timeStep
) {
	/* nzc: need to bunge this for the logic bubbling as well? */
	if (!m_suspended) {
		m_prev_kinematic = m_kinematic;              
		if (m_kinematic) {
			m_prev_xform = m_xform;
			m_timeStep = timeStep;
			calcXform();
			m_kinematic  = false;
		}
	}
}

	void 
SM_Object::
saveReactionForce(
	MT_Scalar timeStep
) {
	if (isDynamic()) {
		m_reaction_force   = m_reaction_impulse / timeStep;
		m_reaction_impulse.setValue(0.0, 0.0, 0.0);
	}
}

	void 
SM_Object::
clearForce(
) {
	m_force.setValue(0.0, 0.0, 0.0);
	m_torque.setValue(0.0, 0.0, 0.0);
}

	void 
SM_Object::
clearMomentum(
) {
	m_lin_mom.setValue(0.0, 0.0, 0.0);
	m_ang_mom.setValue(0.0, 0.0, 0.0);
}

	void 
SM_Object::
setMargin(
	MT_Scalar margin
) {
	m_margin = margin;
    DT_SetMargin(m_object, margin);
}

	MT_Scalar 
SM_Object::
getMargin(
) const {
    return m_margin;
}

const 
	SM_MaterialProps *
SM_Object::
getMaterialProps(
) const {
    return m_materialProps;
}

const 
	SM_ShapeProps *
SM_Object::
getShapeProps(
) const {
    return m_shapeProps;
}

	void 
SM_Object::
setPosition(
	const MT_Point3& pos
){
	m_kinematic = true;
	m_pos = pos;
}

	void 
SM_Object::
setOrientation(
	const MT_Quaternion& orn
){
	assert(!orn.fuzzyZero());
	m_kinematic = true;
	m_orn = orn;
}

	void 
SM_Object::
setScaling(
	const MT_Vector3& scaling
){
	m_kinematic = true;
	m_scaling = scaling;
}

/**
 * Functions to handle linear velocity
 */

	void 
SM_Object::
setExternalLinearVelocity(
	const MT_Vector3& lin_vel
) {
	m_combined_lin_vel=lin_vel;
}

	void 
SM_Object::
addExternalLinearVelocity(
	const MT_Vector3& lin_vel
) {
	m_combined_lin_vel+=lin_vel;
}

	void 
SM_Object::
addLinearVelocity(
	const MT_Vector3& lin_vel
){
	m_lin_vel += lin_vel;
	if (m_shapeProps) {
		m_lin_mom = m_lin_vel * m_shapeProps->m_mass;
	}
}

	void 
SM_Object::
setLinearVelocity(
	const MT_Vector3& lin_vel
){
	m_lin_vel = lin_vel;
	if (m_shapeProps) {
		m_lin_mom = m_lin_vel * m_shapeProps->m_mass;
	}
}

/**
 * Functions to handle angular velocity
 */

	void 
SM_Object::
setExternalAngularVelocity(
	const MT_Vector3& ang_vel
) {
	m_combined_ang_vel = ang_vel;
}

	void
SM_Object::
addExternalAngularVelocity(
	const MT_Vector3& ang_vel
) {
	m_combined_ang_vel += ang_vel;
}

	void 
SM_Object::
setAngularVelocity(
	const MT_Vector3& ang_vel
) {
	m_ang_vel = ang_vel;
	if (m_shapeProps) {
		m_ang_mom = m_ang_vel * m_shapeProps->m_inertia;
	}
}

	void
SM_Object::
addAngularVelocity(
	const MT_Vector3& ang_vel
) {
	m_ang_vel += ang_vel;
	if (m_shapeProps) {
		m_ang_mom = m_ang_vel * m_shapeProps->m_inertia;
	}
}


	void 
SM_Object::
clearCombinedVelocities(
) {
	m_combined_lin_vel = MT_Vector3(0,0,0);
	m_combined_ang_vel = MT_Vector3(0,0,0);
}

	void 
SM_Object::
resolveCombinedVelocities(
	const MT_Vector3 & lin_vel,
	const MT_Vector3 & ang_vel
) {

	// Different behaviours for dynamic and non-dynamic 
	// objects. For non-dynamic we just set the velocity to 
	// zero. For dynmic the physics velocity has to be 
	// taken into account. We must make an arbitrary decision
	// on how to resolve the 2 velocities. Choices are
	// Add the physics velocity to the linear velocity. Objects
	// will just keep on moving in the direction they were
	// last set in - untill external forces affect them.
	// Set the combinbed linear and physics velocity to zero.
	// Set the physics velocity in the direction of the set velocity
	// zero.
	if (isDynamic()) {		

#if 1
		m_lin_vel += lin_vel;
		m_ang_vel += ang_vel;
#else

		//compute the component of the physics velocity in the 
		// direction of the set velocity and set it to zero.
		MT_Vector3 lin_vel_norm = lin_vel.normalized();

		m_lin_vel -= (m_lin_vel.dot(lin_vel_norm) * lin_vel_norm);
#endif
		m_lin_mom = m_lin_vel * m_shapeProps->m_mass;
		m_ang_mom = m_ang_vel * m_shapeProps->m_inertia;
		clearCombinedVelocities();

	}

}		


	MT_Scalar 
SM_Object::
getInvMass(
) const { 
	return m_shapeProps ? 1.0 / m_shapeProps->m_mass : 0.0;
	// OPT: cache the result of this division rather than compute it each call
}

	MT_Scalar 
SM_Object::
getInvInertia(
) const { 
	return m_shapeProps ? 1.0 / m_shapeProps->m_inertia : 0.0;
	// OPT: cache the result of this division rather than compute it each call
}

	void 
SM_Object::
applyForceField(
	const MT_Vector3& accel
) {
	if (m_shapeProps) {
		m_force += m_shapeProps->m_mass * accel;  // F = m * a
	}
}

	void 
SM_Object::
applyCenterForce(
	const MT_Vector3& force
) {
	m_force += force;
}

	void 
SM_Object::
applyTorque(
	const MT_Vector3& torque
) {
	m_torque += torque;
}

	void 
SM_Object::
applyImpulse(
	const MT_Point3& attach, const MT_Vector3& impulse
) {
	applyCenterImpulse(impulse);                          // Change in linear momentum
	applyAngularImpulse((attach - m_pos).cross(impulse)); // Change in angular momentump
}

	void 
SM_Object::
applyCenterImpulse(
	const MT_Vector3& impulse
) {
	if (m_shapeProps) {
		m_lin_mom          += impulse;
		m_reaction_impulse += impulse;
		m_lin_vel           = m_lin_mom / m_shapeProps->m_mass;

		// The linear velocity is immedialtely updated since otherwise
		// simultaneous collisions will get a double impulse. 
	}
}

	void 
SM_Object::
applyAngularImpulse(
	const MT_Vector3& impulse
) {
	if (m_shapeProps) {
		m_ang_mom += impulse;
		m_ang_vel = m_ang_mom / m_shapeProps->m_inertia;
	}
}

	MT_Point3 
SM_Object::
getWorldCoord(
	const MT_Point3& local
) const {
    return m_xform(local);
}

	MT_Vector3 
SM_Object::
getVelocity(
	const MT_Point3& local
) const {
	// For displaced objects the velocity is faked using the previous state. 
	// Dynamic objects get their own velocity, not the faked velocity.
	// (Dynamic objects shouldn't be displaced in the first place!!)

	return m_prev_kinematic && !isDynamic() ? 
		(m_xform(local) - m_prev_xform(local)) / m_timeStep :
		m_lin_vel +	m_ang_vel.cross(m_xform.getBasis() * local);

	// NB: m_xform.getBasis() * local == m_xform(local) - m_xform.getOrigin()

}


const 
	MT_Vector3& 
SM_Object::
getReactionForce(
) const {
	return m_reaction_force;
}

	void 
SM_Object::
getMatrix(
	double *m
) const {
    std::copy(&m_ogl_matrix[0], &m_ogl_matrix[16], &m[0]);
}

const 
	double *
SM_Object::
getMatrix(
) const {
	return m_ogl_matrix;
}

// Still need this???
const 
	MT_Transform&  
SM_Object::
getScaledTransform(
) const {
	return m_xform;
}

	DT_ObjectHandle 
SM_Object::
getObjectHandle(
) const {
	return m_object;
}

	DT_ShapeHandle 
SM_Object::
getShapeHandle(
) const { 
	return m_shape;
}

	void  
SM_Object::
setClientObject(
	void *clientobj
) {
	m_client_object = clientobj;
}

	void *
SM_Object::
getClientObject(
){ 
	return m_client_object;
}

	SM_Object *
SM_Object::
getDynamicParent(
) {
	return m_dynamicParent;
}

	void 
SM_Object::
setRigidBody(
	bool is_rigid_body
) { 
	m_is_rigid_body = is_rigid_body;
} 

	bool 
SM_Object::
isRigidBody(
) const {
	return m_is_rigid_body;
}

const 
	MT_Vector3
SM_Object::
actualLinVelocity(
) const {
	return m_combined_lin_vel + m_lin_vel;
};

const 
	MT_Vector3
SM_Object::
actualAngVelocity(
) const {
	return m_combined_ang_vel + m_ang_vel;
};





