//
//  SuperTuxKart - a fun racing game with go-kart
//  Copyright (C) 2004-2015 Steve Baker <sjbaker1@airmail.net>
//  Copyright (C) 2006-2015 Joerg Henrichs, Steve Baker
//
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License
//  as published by the Free Software Foundation; either version 3
//  of the License, or (at your option) any later version.
//
//  This program 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 General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

#include <math.h>
#include "karts/moveable.hpp"

#include "graphics/irr_driver.hpp"
#include "graphics/material.hpp"
#include "graphics/material_manager.hpp"
#include "modes/world.hpp"
#include "tracks/track.hpp"

#include "ISceneNode.h"

Moveable::Moveable()
{
    m_body            = 0;
    m_motion_state    = 0;
    m_mesh            = NULL;
    m_node            = NULL;
    m_heading         = 0;
}   // Moveable

//-----------------------------------------------------------------------------
Moveable::~Moveable()
{
    // The body is being removed from the world in kart/projectile
    if(m_body)         delete m_body;
    if(m_motion_state) delete m_motion_state;
    if(m_node) irr_driver->removeNode(m_node);
    if(m_mesh) irr_driver->removeMeshFromCache(m_mesh);
}   // ~Moveable

//-----------------------------------------------------------------------------
/** Sets the mesh for this model.
 *  \param n The scene node.
 */
void Moveable::setNode(scene::ISceneNode *n)
{
    m_node          = n;
}   // setNode

//-----------------------------------------------------------------------------
/** Updates the graphics model. Mainly set the graphical position to be the
 *  same as the physics position, but uses offsets to position and rotation
 *  for special gfx effects (e.g. skidding will turn the karts more).
 *  \param offset_xyz Offset to be added to the position.
 *  \param rotation Additional rotation.
 */
void Moveable::updateGraphics(float dt, const Vec3& offset_xyz,
                              const btQuaternion& rotation)
{
    Vec3 xyz=getXYZ()+offset_xyz;
    m_node->setPosition(xyz.toIrrVector());
    btQuaternion r_all = getRotation()*rotation;
    if(btFuzzyZero(r_all.getX()) && btFuzzyZero(r_all.getY()-0.70710677f) &&
       btFuzzyZero(r_all.getZ()) && btFuzzyZero(r_all.getW()-0.70710677f)   )
        r_all.setX(0.000001f);
    Vec3 hpr;
    hpr.setHPR(r_all);
    m_node->setRotation(hpr.toIrrHPR());
}   // updateGraphics

//-----------------------------------------------------------------------------
/** The reset position must be set before calling reset
 */
void Moveable::reset()
{
    if(m_body)
    {
        m_body->setLinearVelocity(btVector3(0.0, 0.0, 0.0));
        m_body->setAngularVelocity(btVector3(0, 0, 0));
        m_body->setCenterOfMassTransform(m_transform);
    }
    m_node->setVisible(true);  // In case that the objects was eliminated

    Vec3 up       = getTrans().getBasis().getColumn(1);
    m_pitch       = atan2(up.getZ(), fabsf(up.getY()));
    m_roll        = atan2(up.getX(), up.getY());
    m_velocityLC  = Vec3(0, 0, 0);
    Vec3 forw_vec = m_transform.getBasis().getColumn(0);
    m_heading     = -atan2f(forw_vec.getZ(), forw_vec.getX());

}   // reset

//-----------------------------------------------------------------------------

void Moveable::flyUp()
{
    m_body->setGravity(btVector3(0.0, 8.0, 0.0));
    m_body->applyCentralImpulse(btVector3(0.0, 100.0, 0.0));
}   // flyUp

// ----------------------------------------------------------------------------
void Moveable::flyDown()
{
    m_body->applyCentralImpulse(btVector3(0.0, -100.0, 0.0));
}   // flyDown

// ----------------------------------------------------------------------------
void Moveable::stopFlying()
{
    m_body->setGravity(btVector3(0.0, -World::getWorld()->getTrack()->getGravity(), 0.0));
}   // stopFlying

//-----------------------------------------------------------------------------
/** Updates the current position and rotation from the corresponding physics
 *  body, and then calls updateGraphics to position the model correctly.
 *  \param float dt Time step size.
 */
void Moveable::update(float dt)
{
    if(m_body->getInvMass()!=0)
        m_motion_state->getWorldTransform(m_transform);
    m_velocityLC  = getVelocity()*m_transform.getBasis();
    Vec3 forw_vec = m_transform.getBasis().getColumn(0);
    m_heading     = -atan2f(forw_vec.getZ(), forw_vec.getX());

    // The pitch in hpr is in between -pi and pi. But for the camera it
    // must be restricted to -pi/2 and pi/2 - so recompute it by restricting
    // y to positive values, i.e. no pitch of more than pi/2.
    Vec3 up       = getTrans().getBasis().getColumn(1);
    m_pitch       = atan2(up.getZ(), fabsf(up.getY()));
    m_roll        = atan2(up.getX(), up.getY());

    updateGraphics(dt, Vec3(0,0,0), btQuaternion(0, 0, 0, 1));
}   // update

//-----------------------------------------------------------------------------
/** Creates the bullet rigid body for this moveable.
 *  \param mass Mass of this object.
 *  \param trans Transform (=position and orientation) for this object).
 *  \param shape Bullet collision shape for this object.
 */
void Moveable::createBody(float mass, btTransform& trans,
                          btCollisionShape *shape,
                          float restitution)
{
    btVector3 inertia;
    shape->calculateLocalInertia(mass, inertia);
    m_transform = trans;
    m_motion_state = new KartMotionState(trans);

    btRigidBody::btRigidBodyConstructionInfo info(mass, m_motion_state,
                                                  shape, inertia);
    info.m_restitution = restitution;

    // Then create a rigid body
    // ------------------------
    m_body = new btRigidBody(info);
    if(mass==0)
    {
        // Create a kinematic object
        m_body->setCollisionFlags(m_body->getCollisionFlags() |
                                  btCollisionObject::CF_KINEMATIC_OBJECT );
        m_body->setActivationState(DISABLE_DEACTIVATION);
    }

    // The value of user_pointer must be set from the actual class, otherwise this
    // is only a pointer to moveable, not to (say) kart, and virtual
    // functions are not called correctly. So only init the pointer to zero.
    m_user_pointer.zero();
    m_body->setUserPointer(&m_user_pointer);
}   // createBody

//-----------------------------------------------------------------------------
/** Places this moveable at a certain location and stores this transform in
 *  this Moveable, so that it can be accessed easily.
 *  \param t New transform for this moveable.
 */
void Moveable::setTrans(const btTransform &t)
{
    m_transform=t;
    if(m_motion_state)
        m_motion_state->setWorldTransform(t);
}   // setTrans
