thug/Code/Gel/Components/lockobjcomponent.cpp
2016-02-14 08:39:12 +11:00

490 lines
18 KiB
C++

//****************************************************************************
//* MODULE: Gel/Components
//* FILENAME: LockObjComponent.cpp
//* OWNER: Gary Jesdanun
//* CREATION DATE: 10/31/2002
//****************************************************************************
#include <gel/components/lockobjcomponent.h>
#include <core/singleton.h>
#include <gel/object/compositeobject.h>
#include <gel/objman.h>
#include <gel/scripting/checksum.h>
#include <gel/scripting/struct.h>
#include <gel/scripting/script.h>
#include <gfx/nxmodel.h>
#include <gfx/skeleton.h>
// for kludgy skater stuff
#include <sk/modules/skate/skate.h>
#include <gel/object/compositeobjectmanager.h>
#include <gel/components/modelcomponent.h>
#include <gel/components/skeletoncomponent.h>
// For the manager ... messy
#include <gel/mainloop.h>
namespace Obj
{
/******************************************************************/
/* */
/* */
/******************************************************************/
// This static function is what is registered with the component factory
// object, (currently the CCompositeObjectManager)
CBaseComponent* CLockObjComponent::s_create()
{
return static_cast< CBaseComponent* >( new CLockObjComponent );
}
// And this one is the optional "register" function that provides on-time initilization
// usually of a component manager
void CLockObjComponent::s_register()
{
// Create and initilize the manager
LockOb::Manager::Create();
}
/******************************************************************/
/* */
/* */
/******************************************************************/
CLockObjComponent::CLockObjComponent() : CBaseComponent()
{
SetType( CRC_LOCKOBJ );
m_lock_enabled = false;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
CLockObjComponent::~CLockObjComponent()
{
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CLockObjComponent::InitFromStructure( Script::CStruct* pParams )
{
m_locked_to_object_name=0;
m_locked_to_object_id=Obj::CBaseManager::vNO_OBJECT_ID;
m_locked_to_object_bone_name=0;
m_lock_offset.Set();
if (pParams->ContainsFlag("Off"))
{
EnableLock( false );
return;
}
EnableLock( true );
#if 0
// SHOULD ALSO CLEAR OUT SOME OF THE OTHER
// FLAGS THAT CANNOT CO-EXIST!
GetObject()->m_movingobj_status &= ~MOVINGOBJ_STATUS_FOLLOWING_LEADER;
GetObject()->m_movingobj_status &= ~MOVINGOBJ_STATUS_MOVETO;
#endif
pParams->GetChecksum("ObjectName",&m_locked_to_object_name);
pParams->GetVector(NONAME,&m_lock_offset);
pParams->GetChecksum("id",&m_locked_to_object_id);
pParams->GetChecksum("bone",&m_locked_to_object_bone_name);
printf( "locking to object %s\n", Script::FindChecksumName(m_locked_to_object_id) );
m_no_rotation=pParams->ContainsFlag("NoRotation");
m_lag=false;
m_lag_factor_range_a=1.0f;
m_lag_factor_range_b=1.0f;
if (pParams->GetPair("LagRange",&m_lag_factor_range_a,&m_lag_factor_range_b))
{
m_lag=true;
}
m_lag_freq_a=0.0f;
m_lag_freq_b=0.0f;
if (pParams->GetPair("LagWobble",&m_lag_freq_a,&m_lag_freq_b))
{
m_lag=true;
}
// The random lag phase is to ensure that two objects using the same script won't
// wobble in phase.
m_lag_phase=Mth::Rnd(1024);
uint32 t=Tmr::ElapsedTime(0)+m_lag_phase;
t&=1023;
float x=sinf(m_lag_freq_a*t*2.0f*3.1415926f/1024.0f) *
sinf(m_lag_freq_b*t*2.0f*3.1415926f/1024.0f);
m_lag_factor=m_lag_factor_range_a+(x+1)*(m_lag_factor_range_b-m_lag_factor_range_a)/2.0f;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CLockObjComponent::Update()
{
// Doing nothing, so tell code to do nothing next time around
Suspend(true);
}
/******************************************************************/
/* */
/* */
/******************************************************************/
CBaseComponent::EMemberFunctionResult CLockObjComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
{
switch ( Checksum )
{
// @script | Obj_LockToObject | Makes the object be locked a fixed offset from some other
// object, and use the same orientation.
// @uparmopt tev_TramA01 | Name of the object to be locked to
// @uparmopt (0,0,0) | Offset from the host object.
// @flag Off | Switch off lock so that object can be moved independently again.
// @flag NoRotation | Makes the objects rotation be left unaffected.
// @parmopt pair | LagRange | (1,1) | The range of the lag value, which determines how quickly
// the object will catch up to the object being followed. The lower the value the further
// the object will lag behind. 1 means no lag at all.
// Example which works quite well: (0.05,0.07)
// @parmopt pair | LagWobble | (0,0) | The frequencies used to wobble the lag value between
// the two ranges. Two frequencies are specified so that the lag will wobble in an
// irregular manner. Example which works quite well: (0.779,0.65)
case 0x785e57c7: // Obj_LockToObject
{
#if 0
#ifdef __NOPT_ASSERT__
uint32 foo=0;
if (!pParams->GetChecksum("ObjectName",&foo))
{
Dbg_MsgAssert(0,("\n%s\nMissing ObjectName parameter in Obj_LockToObject",pScript->GetScriptInfo()));
}
#endif
#endif
InitFromStructure( pParams );
break;
}
default:
return CBaseComponent::MF_NOT_EXECUTED;
}
// the "default" case of the switch statement handles
// unrecognized functions; if we make it down here,
// that means that the component already handled it
// somehow
return CBaseComponent::MF_TRUE;
}
void CLockObjComponent::GetDebugInfo(Script::CStruct *p_info)
{
#ifdef __DEBUG_CODE__
Dbg_MsgAssert(p_info,("NULL p_info sent to CLockObjComponent::GetDebugInfo"));
// we call the base component's GetDebugInfo, so we can add info from the common base component
CBaseComponent::GetDebugInfo(p_info);
p_info->AddChecksum("m_locked_to_object_name",m_locked_to_object_name);
p_info->AddChecksum("m_locked_to_object_id",m_locked_to_object_id);
p_info->AddChecksum("m_locked_to_object_bone_name",m_locked_to_object_bone_name);
p_info->AddVector("m_lock_offset",m_lock_offset.GetX(),m_lock_offset.GetY(),m_lock_offset.GetZ());
p_info->AddFloat("m_lag_factor",m_lag_factor);
p_info->AddFloat("m_lag_factor_range_a",m_lag_factor_range_a);
p_info->AddFloat("m_lag_factor_range_b",m_lag_factor_range_b);
p_info->AddFloat("m_lag_freq_a",m_lag_freq_a);
p_info->AddFloat("m_lag_freq_b",m_lag_freq_b);
p_info->AddFloat("m_lag_phase",m_lag_phase);
p_info->AddChecksum("m_lag",m_lag ? 0x203b372/*True*/:0xd43297cf/*False*/);
p_info->AddChecksum("m_no_rotation",m_no_rotation ? 0x203b372/*True*/:0xd43297cf/*False*/);
p_info->AddChecksum("m_lock_enabled",m_lock_enabled ? 0x203b372/*True*/:0xd43297cf/*False*/);
#endif
}
/******************************************************************/
/* */
/* */
/******************************************************************/
CCompositeObject* CLockObjComponent::get_locked_to_object()
{
CCompositeObject* p_obj=NULL;
if (m_locked_to_object_id!=CBaseManager::vNO_OBJECT_ID)
{
p_obj=(CCompositeObject*)Obj::ResolveToObject(m_locked_to_object_id);
}
else if (m_locked_to_object_name==0x5b8ab877) // Skater
{
// GJ: phase this usage out... we shouldn't have anything
// skater-specific in here!
Dbg_MsgAssert(Mdl::Skate::Instance()->GetNumSkaters()==1,("Called get_locked_to_object with skater as parent when there is more than one skater"));
p_obj=(CCompositeObject*)Mdl::Skate::Instance()->GetLocalSkater();
}
else
{
//p_obj=CMovingObject::m_hash_table.GetItem(m_locked_to_object_name);
p_obj = (Obj::CCompositeObject*) Obj::CCompositeObjectManager::Instance()->GetObjectByID( m_locked_to_object_name );
}
return p_obj;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CLockObjComponent::LockToObject( void )
{
Mth::Matrix old_matrix=GetObject()->m_matrix;
CCompositeObject* p_obj=get_locked_to_object();
if (!p_obj)
{
// Can't find the object, oh well.
return;
}
Mth::Vector target_pos = GetObject()->m_pos;
if ( m_locked_to_object_bone_name )
{
Gfx::CSkeleton* pSkeleton = NULL;
if ( p_obj->GetComponent(CRC_SKELETON) )
{
pSkeleton = GetSkeletonComponentFromObject(p_obj)->GetSkeleton();
}
Dbg_MsgAssert( pSkeleton, ( "Locking object to bone on non-skeletal object" ) );
if ( pSkeleton && pSkeleton->GetBoneMatrix( m_locked_to_object_bone_name, &GetObject()->m_matrix ) )
{
Mth::Vector boneOffset = GetObject()->m_matrix[Mth::POS];
GetObject()->m_matrix[Mth::POS] = Mth::Vector( 0.0f, 0.0f, 0.0f, 1.0f );
GetObject()->m_matrix = GetObject()->GetMatrix() * p_obj->GetMatrix();
target_pos = p_obj->GetDisplayMatrix().Transform( boneOffset );
target_pos += p_obj->m_pos+m_lock_offset*GetObject()->GetMatrix();
}
}
else
{
// Using the display matrix otherwise the object will jitter if following the skater
// up a half pipe.
// K: If the parent object is doing some extra rotations (by using the RotateDisplay command),
// the calculation of target pos is slightly different.
// However, as a speed optimization only do this for the skater because currently that
// is the only object that does extra rotations.
// Fixes bug where the pizza box special item was not being displayed correctly when used in
// a create-a-trick with rotations.
bool calculate_extra_rotation = false;
CCompositeObject* p_composite_object = (CCompositeObject*)p_obj;
Obj::CModelComponent* p_model_component = (Obj::CModelComponent*)p_composite_object->GetComponent( CRC_MODEL );
Dbg_Assert( p_model_component );
if ( p_model_component->HasRefObject() )
{
// GJ THPS5: We're now starting to have non-skater objects needing the extra rotate
// display (i.e. the CAT preview skater)... in theory, we could still do an optimization
// where each lockobjcomponent is flagged to support the rotatedisplay, but i decided
// to just kludge it for now by checking for a refObject.
calculate_extra_rotation = true;
}
else if ( p_obj->GetID() < Mdl::Skate::vMAX_SKATERS )
{
// If it's the skater ...
calculate_extra_rotation = true;
}
if ( calculate_extra_rotation )
{
Mth::Matrix display_matrix;
p_model_component->GetDisplayMatrixWithExtraRotation( display_matrix );
// Note that p_obj->GetPos() is not added because the offset is contained in the matrix.
target_pos=m_lock_offset*display_matrix;
// but now zero out the translation part of the matrix otherwise the object will be rendered wrong.
Mth::Vector zero(0.0f,0.0f,0.0f,1.0f);
display_matrix.SetPos(zero);
GetObject()->m_matrix=display_matrix;
}
else
{
GetObject()->m_matrix=((CCompositeObject*)p_obj)->GetDisplayMatrix();
target_pos=p_obj->GetPos()+m_lock_offset*GetObject()->m_matrix;
}
}
if (m_lag)
{
GetObject()->m_pos=GetObject()->GetPos()+(target_pos-GetObject()->GetPos())*m_lag_factor;
uint32 t=Tmr::ElapsedTime(0)+m_lag_phase;
t&=1023;
float x=sinf(m_lag_freq_a*t*2.0f*Mth::PI/1024.0f) *
sinf(m_lag_freq_b*t*2.0f*Mth::PI/1024.0f);
m_lag_factor=m_lag_factor_range_a+(x+1)*(m_lag_factor_range_b-m_lag_factor_range_a)/2.0f;
}
else
{
GetObject()->m_pos=target_pos;
}
// take the object's velocity as well
GetObject()->SetVel(p_obj->GetVel());
// If the objects rotation is required to be preserved, recover the old value.
// Alan's ferris wheel cars use this.
if (m_no_rotation)
{
GetObject()->m_matrix[Mth::UP]=old_matrix[Mth::UP];
GetObject()->m_matrix[Mth::AT]=old_matrix[Mth::AT];
GetObject()->m_matrix[Mth::RIGHT]=old_matrix[Mth::RIGHT];
//printf("m_pos=(%f,%f,%f)\n",m_pos[X],m_pos[Y],m_pos[Z]);
}
// GJ: This function is called from a task that
// happens after all the matrices have already
// been sent to the rendering code for this frame...
// so we'll need to re-apply any new m_pos/m_matrix
// changes to the rendered model
// (suggestion for THPS5... have a render task
// that sends all the matrices to the model after
// the object update task has finished...)
Nx::CModel* pModel = NULL;
if ( GetObject()->GetComponent( CRC_MODEL ) )
{
pModel = ((Obj::CModelComponent*)GetObject()->GetComponent(CRC_MODEL))->GetModel();
}
if ( pModel )
{
Gfx::CSkeleton* pSkeleton = NULL;
if ( GetObject()->GetComponent( CRC_SKELETON ) )
{
pSkeleton = ((Obj::CSkeletonComponent*)GetObject()->GetComponent(CRC_SKELETON))->GetSkeleton();
}
Mth::Matrix rootMatrix;
rootMatrix = GetObject()->GetDisplayMatrix();
rootMatrix[Mth::POS] = GetObject()->GetPos();
rootMatrix[Mth::POS][W] = 1.0f;
bool should_animate = false;
pModel->Render( &rootMatrix, !should_animate, pSkeleton );
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
}
// The following is the LockOb manager, which iterates over the lockob components
// to lock them to whatever they are locked to
namespace LockOb
{
// The purpose of this manager is just to update all the locked objects, by calling
// CMovingObject::LockToObject on all objects that have had Obj_LockToObject run on them.
// It is a manager so that it can be given a task with a priority such that it is called
// after all the objects are updated.
// The reason the lock logic cannot be done in the object update is because sometimes the parent's
// update might happen after the child's, causing a frame lag. This was making the gorilla appear
// behind the tram seat in the zoo when the tram was being skitched. (TT2324)
DefineSingletonClass( Manager, "Locked object Manager" )
/******************************************************************/
/* */
/* */
/******************************************************************/
Manager::Manager()
{
mp_task = new Tsk::Task< Manager > ( s_code, *this,
Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_LOCKED_OBJECT_MANAGER_LOGIC );
Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
mlp_manager->AddLogicTask( *mp_task );
}
/******************************************************************/
/* */
/* */
/******************************************************************/
Manager::~Manager()
{
delete mp_task;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void Manager::s_code( const Tsk::Task< Manager >& task )
{
// if (Mdl::FrontEnd::Instance()->GamePaused())
// {
// // Must not update if game paused, because the game could be running a replay,
// // which would mean the objects' models will have been deleted, causing the
// // LockToObject function to assert.
// return;
// }
// new fast way, just go directly to the components, if any
Obj::CLockObjComponent *p_lock_obj_component = static_cast<Obj::CLockObjComponent*>( Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType( CRC_LOCKOBJ ));
while( p_lock_obj_component )
{
if ( p_lock_obj_component->IsLockEnabled() )
{
p_lock_obj_component->LockToObject();
}
p_lock_obj_component = static_cast<Obj::CLockObjComponent*>( p_lock_obj_component->GetNextSameType());
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
}