quake4-sdk/source/mpgame/client/ClientModel.cpp

451 lines
10 KiB
C++

//----------------------------------------------------------------
// ClientModel.cpp
//
// A non-interactive client-side model
//
// Copyright 2002-2004 Raven Software
//----------------------------------------------------------------
#include "../../idlib/precompiled.h"
#pragma hdrstop
#include "../Game_local.h"
#include "ClientModel.h"
/*
===============================================================================
rvClientModel
===============================================================================
*/
CLASS_DECLARATION( rvClientEntity, rvClientModel )
END_CLASS
/*
================
rvClientModel::rvClientModel
================
*/
rvClientModel::rvClientModel ( void ) {
memset ( &renderEntity, 0, sizeof(renderEntity) );
worldAxis = mat3_identity;
entityDefHandle = -1;
}
/*
================
rvClientModel::~rvClientModel
================
*/
rvClientModel::~rvClientModel ( void ) {
FreeEntityDef ( );
}
/*
================
rvClientModel::FreeEntityDef
================
*/
void rvClientModel::FreeEntityDef ( void ) {
if ( entityDefHandle >= 0 ) {
gameRenderWorld->FreeEntityDef ( entityDefHandle );
entityDefHandle = -1;
}
}
/*
================
rvClientModel::Spawn
================
*/
void rvClientModel::Spawn ( void ) {
const char* spawnarg;
spawnArgs.GetString ( "classname", "", classname );
// parse static models the same way the editor display does
gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity );
renderEntity.entityNum = entityNumber;
spawnarg = spawnArgs.GetString( "model" );
if ( spawnarg && *spawnarg ) {
SetModel( spawnarg );
}
}
/*
================
rvClientModel::Think
================
*/
void rvClientModel::Think ( void ) {
if( bindMaster && (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) {
return;
}
UpdateBind();
Present();
}
/*
================
rvClientModel::Present
================
*/
void rvClientModel::Present(void) {
// Hide client entities bound to a hidden entity
if ( bindMaster && (bindMaster->IsHidden ( ) || (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) ) {
return;
}
renderEntity.origin = worldOrigin;
renderEntity.axis = worldAxis;
// add to refresh list
if ( entityDefHandle == -1 ) {
entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity );
} else {
gameRenderWorld->UpdateEntityDef( entityDefHandle, &renderEntity );
}
}
/*
================
rvClientModel::SetCustomShader
================
*/
bool rvClientModel::SetCustomShader ( const char* shaderName ) {
if ( shaderName == NULL ) {
return false;
}
const idMaterial* material = declManager->FindMaterial( shaderName );
if ( material == NULL ) {
return false;
}
renderEntity.customShader = material;
return true;
}
/*
================
rvClientModel::Save
================
*/
void rvClientModel::Save( idSaveGame *savefile ) const {
savefile->WriteRenderEntity( renderEntity );
savefile->WriteInt( entityDefHandle );
savefile->WriteString ( classname ); // cnicholson: Added unsaved var
}
/*
================
rvClientModel::Restore
================
*/
void rvClientModel::Restore( idRestoreGame *savefile ) {
savefile->ReadRenderEntity( renderEntity, NULL );
savefile->ReadInt( entityDefHandle );
savefile->ReadString ( classname ); // cnicholson: Added unrestored var
// restore must retrieve entityDefHandle from the renderer
if ( entityDefHandle != -1 ) {
entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity );
}
}
/*
================
rvClientModel::SetModel
================
*/
void rvClientModel::SetModel( const char* modelname ) {
FreeEntityDef();
renderEntity.hModel = renderModelManager->FindModel( modelname );
if ( renderEntity.hModel ) {
renderEntity.hModel->Reset();
}
renderEntity.callback = NULL;
renderEntity.numJoints = 0;
renderEntity.joints = NULL;
if ( renderEntity.hModel ) {
renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity );
} else {
renderEntity.bounds.Zero();
}
}
/*
==============
rvClientModel::ProjectOverlay
==============
*/
void rvClientModel::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) {
float s, c;
idMat3 axis, axistemp;
idVec3 localOrigin, localAxis[2];
idPlane localPlane[2];
// make sure the entity has a valid model handle
if ( entityDefHandle < 0 ) {
return;
}
// only do this on dynamic md5 models
if ( renderEntity.hModel->IsDynamicModel() != DM_CACHED ) {
return;
}
idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c );
axis[2] = -dir;
axis[2].NormalVectors( axistemp[0], axistemp[1] );
axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s;
axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c;
renderEntity.axis.ProjectVector( origin - renderEntity.origin, localOrigin );
renderEntity.axis.ProjectVector( axis[0], localAxis[0] );
renderEntity.axis.ProjectVector( axis[1], localAxis[1] );
size = 1.0f / size;
localAxis[0] *= size;
localAxis[1] *= size;
localPlane[0] = localAxis[0];
localPlane[0][3] = -( localOrigin * localAxis[0] ) + 0.5f;
localPlane[1] = localAxis[1];
localPlane[1][3] = -( localOrigin * localAxis[1] ) + 0.5f;
const idMaterial *mtr = declManager->FindMaterial( material );
// project an overlay onto the model
gameRenderWorld->ProjectOverlay( entityDefHandle, localPlane, mtr );
// make sure non-animating models update their overlay
UpdateVisuals();
}
/*
================
rvClientModel::UpdateRenderEntity
================
*/
bool rvClientModel::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) {
if ( gameLocal.inCinematic && gameLocal.skipCinematic ) {
return false;
}
idAnimator *animator = GetAnimator();
if ( animator ) {
return animator->CreateFrame( gameLocal.time, false );
}
return false;
}
/*
================
rvClientModel::ModelCallback
NOTE: may not change the game state whatsoever!
================
*/
bool rvClientModel::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) {
rvClientEntity *cent;
cent = gameLocal.clientEntities[ renderEntity->entityNum ];
if ( !cent ) {
gameLocal.Error( "rvClientModel::ModelCallback: callback with NULL client entity '%d'", renderEntity->entityNum );
return false;
}
if( !cent->IsType( rvClientModel::GetClassType() ) ) {
gameLocal.Error( "rvClientModel::ModelCallback: callback with non-client model on client entity '%d'", renderEntity->entityNum );
return false;
}
return ((rvClientModel*)cent)->UpdateRenderEntity( renderEntity, renderView );
}
/*
================
rvClientModel::GetPhysicsToVisualTransform
================
*/
bool rvClientModel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) {
return false;
}
/*
================
rvClientModel::UpdateModelTransform
================
*/
void rvClientModel::UpdateModelTransform( void ) {
idVec3 origin;
idMat3 axis;
if ( GetPhysicsToVisualTransform( origin, axis ) ) {
renderEntity.axis = axis * worldAxis;
renderEntity.origin = worldOrigin + origin * renderEntity.axis;
} else {
renderEntity.axis = worldAxis;
renderEntity.origin = worldOrigin;
}
}
/*
================
rvClientModel::UpdateModel
================
*/
void rvClientModel::UpdateModel( void ) {
UpdateModelTransform();
idAnimator *animator = GetAnimator();
if ( animator && animator->ModelHandle() ) {
// set the callback to update the joints
renderEntity.callback = rvClientModel::ModelCallback;
}
}
/*
================
rvClientModel::UpdateVisuals
================
*/
void rvClientModel::UpdateVisuals( void ) {
UpdateModel();
UpdateSound();
}
/*
================
rvClientModel::SetSkin
================
*/
void rvClientModel::SetSkin( const idDeclSkin *skin ) {
renderEntity.customSkin = skin;
UpdateVisuals();
}
/*
===============================================================================
rvAnimatedClientEntity
===============================================================================
*/
CLASS_DECLARATION( rvClientModel, rvAnimatedClientEntity )
END_CLASS
/*
================
rvAnimatedClientEntity::rvAnimatedClientEntity
================
*/
rvAnimatedClientEntity::rvAnimatedClientEntity ( void ) {
}
/*
================
rvAnimatedClientEntity::~rvAnimatedClientEntity
================
*/
rvAnimatedClientEntity::~rvAnimatedClientEntity ( void ) {
}
/*
================
rvAnimatedClientEntity::Spawn
================
*/
void rvAnimatedClientEntity::Spawn( void ) {
SetModel( spawnArgs.GetString( "model" ) );
}
/*
================
rvAnimatedClientEntity::Think
================
*/
void rvAnimatedClientEntity::Think ( void ) {
UpdateAnimation();
rvClientEntity::Think();
}
/*
================
rvAnimatedClientEntity::UpdateAnimation
================
*/
void rvAnimatedClientEntity::UpdateAnimation( void ) {
// is the model an MD5?
if ( !animator.ModelHandle() ) {
// no, so nothing to do
return;
}
// call any frame commands that have happened in the past frame
animator.ServiceAnims( gameLocal.previousTime, gameLocal.time );
// if the model is animating then we have to update it
if ( !animator.FrameHasChanged( gameLocal.time ) ) {
// still fine the way it was
return;
}
// get the latest frame bounds
animator.GetBounds( gameLocal.time, renderEntity.bounds );
if ( renderEntity.bounds.IsCleared() ) {
gameLocal.DPrintf( "%d: inside out bounds\n", gameLocal.time );
}
// update the renderEntity
UpdateVisuals();
Present();
// the animation is updated
animator.ClearForceUpdate();
}
/*
================
rvAnimatedClientEntity::SetModel
================
*/
void rvAnimatedClientEntity::SetModel( const char *modelname ) {
FreeEntityDef();
renderEntity.hModel = animator.SetModel( modelname );
if ( !renderEntity.hModel ) {
rvClientModel::SetModel( modelname );
return;
}
if ( !renderEntity.customSkin ) {
renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin();
}
// set the callback to update the joints
renderEntity.callback = rvClientModel::ModelCallback;
animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints );
animator.GetBounds( gameLocal.time, renderEntity.bounds );
//UpdateVisuals();
Present();
}