//**************************************************************************** //* MODULE: Gel/Components //* FILENAME: BouncyComponent.cpp //* OWNER: Mick West //* CREATION DATE: 10/17/2002 //**************************************************************************** //#define __DEBUG_BOUNCY__ #include #include #include #include #include #include #include #include #include #include #include #include //#include // TODO: remove - only used for getting the bouncing box via collision #include // and this #include // and this namespace Obj { #define SWITCH_STATE( x ) m_state = x /******************************************************************/ /* */ /* */ /******************************************************************/ CBaseComponent * CBouncyComponent::s_create() { return static_cast(new CBouncyComponent); } /******************************************************************/ /* */ /* */ /******************************************************************/ CBouncyComponent::CBouncyComponent() : CBaseComponent() { SetType(CRC_BOUNCY); } /******************************************************************/ /* */ /* */ /******************************************************************/ CBouncyComponent::~CBouncyComponent() { if ( mp_bounce_slerp ) delete mp_bounce_slerp; if (mp_collide_script_params) delete mp_collide_script_params; if (mp_bounce_script_params) delete mp_bounce_script_params; } /******************************************************************/ /* */ /* */ /******************************************************************/ // Returns // 0 (MF_FALSE) if executed and returned false // 1 (MF_TRUE) if executed and returned true // 2 (MF_NOT_EXECUTRED) if not executed CBaseComponent::EMemberFunctionResult CBouncyComponent::CallMemberFunction( uint32 Checksum, Script::CScriptStructure *pParams, Script::CScript *pScript ) { switch ( Checksum ) { // @script | BouncyObj_PlayerCollisionOn | turn player collision on case ( 0xf9055d81 ): // "BouncyObj_PlayerCollisionOn" m_bouncyobj_flags &= ~BOUNCYOBJ_FLAG_PLAYER_COLLISION_OFF; return MF_TRUE; // @script | BouncyObj_PlayerCollisionOff | turn player collision off case ( 0xf64ef88e ): // "BouncyObj_PlayerCollisionOff" m_bouncyobj_flags |= BOUNCYOBJ_FLAG_PLAYER_COLLISION_OFF; return MF_TRUE; // @script | BouncyObj_Go | The vector can be anything you want... // the initial velocity. It's optional, without it the object will // fall from it's current location. If it's already on the ground // probably nothing will happen // @uparmopt (0, 0, 0) | vector case ( 0xfea0e952 ): // "BouncyObj_Go" { Mth::Vector vel; vel.Set( ); pParams->GetVector( NONAME, &vel ); if ( m_state == BOUNCYOBJ_STATE_BOUNCING ) { Dbg_Message( "\n%s\nWarning: Calling BouncyObj_Go when object is already bouncing.", pScript->GetScriptInfo( ) ); } if ( pParams->ContainsFlag( 0x26af9dc9 ) ) // "FLAG_MAX_COORDS" { vel.ConvertToMaxCoords( ); } bounce( vel ); return MF_TRUE; } } return MF_NOT_EXECUTED; } /******************************************************************/ /* */ /* */ /******************************************************************/ void CBouncyComponent::InitFromStructure( Script::CStruct* pParams ) { m_time = 0.01666666f; // Patch!!!! m_state = BOUNCYOBJ_STATE_INIT; m_bounce_mult = 0.9f; m_bouncyobj_flags = 0; // Bouncy object properties: m_random_dir = 10; pParams->GetInteger( 0x767f323f, &m_random_dir ); // RandomDirectionOnBounce m_bounce_mult = 0.6f; pParams->GetFloat( 0xaf6034df, &m_bounce_mult ); // BounceMult m_min_bounce_vel = 3; pParams->GetFloat( 0xff08ee44, &m_min_bounce_vel ); // MinBounceVel m_min_bounce_vel = FEET_TO_INCHES( m_min_bounce_vel ); m_rot_per_axis = 360; pParams->GetInteger( 0x33ee064f, &m_rot_per_axis ); // ConstRot m_bounce_rot = 45; pParams->GetInteger( 0xde14151d, &m_bounce_rot ); // BounceRot m_rest_orientation_type=0; pParams->GetInteger( 0xc0f30f40, &m_rest_orientation_type ); // RestOrientationType m_gravity = 32; pParams->GetFloat( 0xa5e2da58, &m_gravity ); // Gravity m_gravity = FEET_TO_INCHES( m_gravity ); m_bounciness = 1.0f; pParams->GetFloat( 0x373d4e7d, &m_bounciness ); // Bounciness m_bounce_sound=0; pParams->GetChecksum( 0x1fb2f60c, &m_bounce_sound ); // BounceSound m_hit_sound=0; pParams->GetChecksum( 0x29e2e9e0, &m_hit_sound ); // HitSound m_up_mag = 32.0f; pParams->GetFloat( 0x3eca5c95, &m_up_mag ); // UpMagnitude m_up_mag = FEET_TO_INCHES( m_up_mag ); m_destroy_when_done = !pParams->ContainsFlag( 0x65ba5a75 ); // NoDestroy m_min_initial_vel = 15; // feet per second... pParams->GetFloat( 0x96763c79, &m_min_initial_vel ); // MinInitialVel m_min_initial_vel = FEET_TO_INCHES( m_min_initial_vel ); // Set up collision uint32 col_type_checksum = Nx::vCOLL_TYPE_NONE; pParams->GetChecksum("CollisionMode", &col_type_checksum); #if 0 // TODO - handle getting bouncing box from a collision component Nx::CollType col_type = Nx::CCollObj::sConvertTypeChecksum(col_type_checksum); if (col_type != Nx::vCOLL_TYPE_NONE) { m_bounce_collision_radius=0.0f; m_skater_collision_radius_squared=0.0f; // InitCollision(col_type, p_coll_tri_data); // Calc radius from bounding box. if (((CMovingObject*)(GetObject()))->GetCollisionObject()) { Nx::CCollObjTriData *p_tri_data=((CMovingObject*)(GetObject()))->GetCollisionObject()->GetGeometry(); Dbg_MsgAssert(p_tri_data,("NULL p_tri_data")); Mth::CBBox bbox=p_tri_data->GetBBox(); m_bounce_collision_radius=Mth::Distance(bbox.GetMax(),bbox.GetMin())/2.0f; m_skater_collision_radius_squared=m_bounce_collision_radius*m_bounce_collision_radius; } } else #endif { m_bounce_collision_radius=0.0f; m_skater_collision_radius_squared=50.0f; pParams->GetFloat("CollisionRadius",&m_skater_collision_radius_squared); m_skater_collision_radius_squared*=m_skater_collision_radius_squared; } // We need to copy in any script struct parameters, as they might have generated temporarily pParams->GetChecksum(CRCD(0x58cdc0f0,"CollideScript"),&m_collide_script); Script::CStruct * p_collide_script_params = NULL; pParams->GetStructure(CRCD(0x24031741,"CollideScriptParams"),&p_collide_script_params); if (p_collide_script_params) { mp_collide_script_params = new Script::CStruct; *mp_collide_script_params += *p_collide_script_params; } pParams->GetChecksum(CRCD(0x2d075f90,"BounceScript"),&m_bounce_script); Script::CStruct * p_bounce_script_params = NULL; pParams->GetStructure(CRCD(0x346b0c03,"BounceScriptParams"),&p_bounce_script_params); if (p_bounce_script_params) { mp_bounce_script_params = new Script::CStruct; *mp_bounce_script_params += *p_bounce_script_params; } // ((CMovingObject*)(GetObject()))->RunNodeScript(0x2d075f90/*BounceScript*/,0x346b0c03/*BounceScriptParams*/); m_pos = GetObject()->m_pos; m_matrix = GetObject()->m_matrix; } /******************************************************************/ /* */ /* */ /******************************************************************/ void CBouncyComponent::Update() { Mdl::Skate * skate_mod = Mdl::Skate::Instance(); uint32 numSkaters = skate_mod->GetNumSkaters( ); m_time = Tmr::FrameLength(); if ( !( m_bouncyobj_flags & BOUNCYOBJ_FLAG_PLAYER_COLLISION_OFF ) ) { if ( m_state != BOUNCYOBJ_STATE_BOUNCING || ( ( !m_destroy_when_done ) && m_bounce_count ) ) { uint32 i; for ( i = 0; i < numSkaters; i++ ) { CSkater *pSkater = skate_mod->GetSkater( i ); // Very simple collision check ... // Seems to work fine. if ( Mth::DistanceSqr( m_pos, pSkater->m_pos ) < m_skater_collision_radius_squared ) { // Trigger any collide-script specified in the object's node. #if 0 // TODO - handle this RunNodeScript - IF IT IS ACTUALLY EVER USED!!!!! ((CMovingObject*)GetObject())->RunNodeScript(0x58cdc0f0/*CollideScript*/,0x24031741/*CollideScriptParams*/); #else // This is also rather messy. Seems like it would be better // to have this handled by an exception if (m_collide_script) { Script::CScript *p_script=Script::SpawnScript(m_collide_script,mp_collide_script_params); #ifdef __NOPT_ASSERT__ p_script->SetCommentString("CBouncyComponent collide script"); #endif Dbg_MsgAssert(p_script,("NULL p_script")); p_script->mpObject=GetObject();; p_script->Update(); } #endif // Trigger the bounce. Mth::Vector vel; vel = pSkater->GetVel( ); bounce_from_object_vel( vel, pSkater->m_pos ); continue; } } } } switch ( m_state ) { case ( BOUNCYOBJ_STATE_INIT ): m_state = BOUNCYOBJ_STATE_IDLE; break; case ( BOUNCYOBJ_STATE_IDLE ): break; case ( BOUNCYOBJ_STATE_BOUNCING ): // TODO: The following is needed only to update the position of the object // This should be done by updating the postion of the model component #if 0 // TODO - delete this code if it is not used ((CMovingObject*)GetObject())->MovingObj_Update( ); #endif do_bounce( ); break; default: Dbg_MsgAssert( 0,( "Unknown substate." )); break; } } /******************************************************************/ /* */ /* */ /******************************************************************/ #define DTR DEGREES_TO_RADIANS float get_closest_allowable_angle( float current, float allowableAngle ) { float ang; float halfAllowable = allowableAngle / 2.0f; ang = DTR( -180.0f ); ang += halfAllowable; while ( ang < DTR( 180.0f ) ) { if ( current < ang ) { return ( ang - ( halfAllowable ) ); } ang += allowableAngle; } return ( ang - halfAllowable ); } void CBouncyComponent::land_on_top_or_bottom( Mth::Vector &rot ) { // x to nearest acceptable: if ( ( rot[ X ] > DTR( 90.0f ) ) || ( rot[ X ] < DTR( -90.0f ) ) ) rot[ X ] = DTR( 180.0f ); else rot[ X ] = 0.0f; // z to nearest acceptable: if ( ( rot[ Z ] > DTR( 90.0f ) ) || ( rot[ Z ] < DTR( -90.0f ) ) ) rot[ Z ] = DTR( 180.0f ); else rot[ Z ] = 0.0f; } void CBouncyComponent::land_on_any_face( Mth::Vector &rot ) { rot[ X ] = get_closest_allowable_angle( rot[ X ], DTR( 90.0f ) ); rot[ Y ] = get_closest_allowable_angle( rot[ Y ], DTR( 90.0f ) ); rot[ Z ] = get_closest_allowable_angle( rot[ Z ], DTR( 90.0f ) ); } #define CONE_ANGLE 110.0f void CBouncyComponent::land_traffic_cone( Mth::Vector &rot ) { /* if ( rot[ X ] < -DTR( CONE_ANGLE / 2.0f ) ) rot[ X ] = -DTR( CONE_ANGLE ); else if ( rot[ X ] > DTR( CONE_ANGLE / 2.0f ) ) rot[ X ] = DTR( CONE_ANGLE ); else*/ rot[ X ] = 0; if ( rot[ Z ] < -DTR( CONE_ANGLE / 2.0f ) ) rot[ Z ] = -DTR( CONE_ANGLE ); else if ( rot[ Z ] > DTR( CONE_ANGLE / 2.0f ) ) rot[ Z ] = DTR( CONE_ANGLE ); else rot[ Z ] = 0; // rot[ Y ] = 0; } void CBouncyComponent::set_up_quats( void ) { Mth::Vector rot;// = currentRot; m_matrix.GetEulers( rot ); Dbg_MsgAssert( rot[ X ] <= DTR( 180.0f ),( "Trig functions range from 0 to 360, not -180 to 180." )); Dbg_MsgAssert( rot[ Y ] <= DTR( 180.0f ),( "Trig functions range from 0 to 360, not -180 to 180." )); Dbg_MsgAssert( rot[ Z ] <= DTR( 180.0f ),( "Trig functions range from 0 to 360, not -180 to 180." )); m_quatrot_time_elapsed = 0.0f; // find out the amount we have to rotate: switch ( m_rest_orientation_type ) { case ( 1 ): // Upside down, or rightside up: land_on_top_or_bottom( rot ); break; case ( 2 ): land_on_any_face( rot ); break; case ( 3 ): land_traffic_cone( rot ); break; case ( 0 ): default: Dbg_MsgAssert( 0,( "Unknown rest orientation type: %d", m_rest_orientation_type )); return; break; } if ( !mp_bounce_slerp ) { mp_bounce_slerp = new Mth::SlerpInterpolator; } Mth::Matrix temp( rot[ X ], rot[ Y ], rot[ Z ] ); mp_bounce_slerp->setMatrices( &m_matrix, &temp ); m_quat_initialized = true; } #define QUAT_ROT_TIME 0.5f bool CBouncyComponent::rotate_to_quat( void ) { float percent; m_quatrot_time_elapsed += m_time; if ( m_quatrot_time_elapsed >= QUAT_ROT_TIME ) { percent = 1.0f; } else { percent = m_quatrot_time_elapsed / QUAT_ROT_TIME; } mp_bounce_slerp->getMatrix( &m_matrix, percent ); return ( percent == 1.0f ); } #define MAX_SKATER_VEL 1000.0f // not exact... just about what the max would be. void CBouncyComponent::do_bounce( void ) { m_old_pos = m_pos; int temp; switch ( m_substate ) { case ( 0 ): // generate a new constant rotation: temp = Mth::Rnd( 8 ); m_rotation[ X ] = DEGREES_TO_RADIANS( ( float ) ( ( m_rot_per_axis >> 2 ) + ( Mth::Rnd( ( m_rot_per_axis >> 2 ) * 3 ) ) ) ); if ( temp & 1 ) { m_rotation[ X ] *= -1.0f; } m_rotation[ Y ] = DEGREES_TO_RADIANS( ( float ) ( ( m_rot_per_axis >> 2 ) + ( Mth::Rnd( ( m_rot_per_axis >> 2 ) * 3 ) ) ) ); if ( temp & 2 ) { m_rotation[ Y ] *= -1.0f; } m_rotation[ Z ] = DEGREES_TO_RADIANS( ( float ) ( ( m_rot_per_axis >> 2 ) + ( Mth::Rnd( ( m_rot_per_axis >> 2 ) * 3 ) ) ) ); if ( temp & 4 ) { m_rotation[ Z ] *= -1.0f; } m_rotation[ W ] = 1.0f; m_quat_initialized = false; m_substate++; // intentional fall through: case ( 1 ): { Mth::Vector rot; m_pos += m_vel * m_time; if ( m_quat_initialized ) { rotate_to_quat( ); } else { rot = m_rotation; rot *= m_time; m_matrix.RotateLocal( rot ); } // add the radius of the object so we don't stick through the ground: Mth::Vector velNormalized = m_vel; velNormalized.Normalize( ); Mth::Vector colPoint = m_pos + ( velNormalized * m_bounce_collision_radius ); m_vel[ Y ] -= m_gravity * m_time; CFeeler feeler; feeler.SetLine(m_old_pos, colPoint); if ( feeler.GetCollision()) { #if 0 // TODO - make it work, or remove if not used ((CMovingObject*)(GetObject()))->RunNodeScript(0x2d075f90/*BounceScript*/,0x346b0c03/*BounceScriptParams*/); #else // This is also rather messy. Seems like it would be better // to have this handled by an exception if (m_bounce_script) { Script::CScript *p_script=Script::SpawnScript(m_bounce_script,mp_bounce_script_params); Dbg_MsgAssert(p_script,("NULL p_script")); #ifdef __NOPT_ASSERT__ p_script->SetCommentString("CBouncyComponent bounce script"); #endif p_script->mpObject=GetObject();; p_script->Update(); } #endif m_bounce_count++; Mth::Vector v; Mth::Vector r; float dP; v = m_pos - m_old_pos; Mth::Vector n; n = feeler.GetNormal(); dP = Mth::DotProduct( v, n ); r = ( dP * 2.0f ) * n; r -= v; float origVel = m_vel.Length( ); m_vel = -r; m_vel.Normalize( origVel * m_bounce_mult ); m_vel.RotateY( DTR( ( float ) ( Mth::Rnd( 2 * m_random_dir ) - m_random_dir ) ) ); m_pos = feeler.GetPoint() - ( velNormalized * m_bounce_collision_radius ); if ( fabsf( m_vel[ Y ] ) < ( m_min_bounce_vel + ( 2.0f *( m_gravity * m_time ) ) ) ) { if ( m_rest_orientation_type ) { if ( !m_quat_initialized ) { set_up_quats( ); } m_substate++; break; } #if 0 // TODO - make it work, or remove if not used // This "HigerLevel" is probably redundant for bouncy obs ((CMovingObject*)(GetObject()))->HigherLevel( false ); #endif SWITCH_STATE( BOUNCYOBJ_STATE_IDLE ); if ( m_destroy_when_done ) { GetObject()->MarkAsDead( ); } else { #if 0 // TODO - get exceptions working GetExceptionComponentFromObject( GetObject() )->FlagException( 0x61682835 ); // DoneBouncing #endif } return; } if ( origVel > m_min_bounce_vel * 2.0f ) { if ( m_bounce_sound ) { // adjust volume depending on magnitude of current velocity: #if 0 // TODO - Get Bouncy object sounds working with components float vol; vol = 100.0f * ( origVel / ( MAX_SKATER_VEL * m_bounciness ) ); float pitch = 80.0f + ( float ) Mth::Rnd( 40 ); ((CMovingObject*)(GetObject()))->PlaySound_VolumeAndPan( m_bounce_sound, vol, pitch ); #endif } // bounce rot: if ( m_bounce_rot ) { rot[ X ] = DEGREES_TO_RADIANS( ( float )( Mth::Rnd( m_bounce_rot * 2 ) - m_bounce_rot ) ); rot[ Y ] = DEGREES_TO_RADIANS( ( float )( Mth::Rnd( m_bounce_rot * 2 ) - m_bounce_rot ) ); rot[ Z ] = DEGREES_TO_RADIANS( ( float )( Mth::Rnd( m_bounce_rot * 2 ) - m_bounce_rot ) ); m_matrix.RotateLocal( rot ); } m_substate = 0; } else if ( m_rest_orientation_type ) { if ( !m_quat_initialized ) { // rotate to the resting orientation: set_up_quats( ); } } } break; } case ( 2 ): if ( rotate_to_quat( ) ) { #if 0 ((CMovingObject*)(GetObject()))->HigherLevel( false ); #endif SWITCH_STATE( BOUNCYOBJ_STATE_IDLE ); if ( m_destroy_when_done ) { GetObject()->MarkAsDead( ); } } break; default: Dbg_Message( 0, "Woah! Fire matt immediately." ); break; } #ifdef __DEBUG_BOUNCY__ // printf ("%d: %s: (%.2f,%.2f,%.2f)\n",__LINE__,__PRETTY_FUNCTION__,m_pos[X],m_pos[Y],m_pos[Z]); #endif GetObject()->m_pos = m_pos; GetObject()->m_matrix = m_matrix; GetObject()->SetDisplayMatrix(m_matrix); } void CBouncyComponent::bounce( const Mth::Vector &vel ) { #ifdef __DEBUG_BOUNCY__ // printf ("%d: %s: (%.2f,%.2f,%.2f),(%.2f,%.2f,%.2f)\n",__LINE__,__PRETTY_FUNCTION__,vel[X],vel[Y],vel[Z]); #endif m_vel = vel; if ( m_hit_sound ) { // adjust volume depending on magnitude of current velocity: #if 0 // TODO - sound components for bouncy object float vol; vol = 100.0f * ( m_vel.Length( ) / MAX_SKATER_VEL ); float pitch = 80.0f + ( float ) Mth::Rnd( 40 ); ((CMovingObject*)(GetObject()))->PlaySound_VolumeAndPan( m_hit_sound, vol, pitch ); #endif } if ( m_bounciness != 1.0f ) { m_vel *= m_bounciness; } m_bounce_count = 0; #if 0 // TODO - is HigherLevel even needed here? or just cut and paste from some other object? ((CMovingObject*)(GetObject()))->HigherLevel( true ); #endif SWITCH_STATE( BOUNCYOBJ_STATE_BOUNCING ); #if 0 // TODO - exceptions working as a component GetExceptionComponentFromObject( GetObject() )->FlagException( 0x7dc30ba6 ); // Bounce #endif } // Given an object velocity, then calculate the velocity to bounce by void CBouncyComponent::bounce_from_object_vel( const Mth::Vector &vel, const Mth::Vector &pos ) { #ifdef __DEBUG_BOUNCY__ // printf ("%d: %s: (%.2f,%.2f,%.2f),(%.2f,%.2f,%.2f)\n",__LINE__,__PRETTY_FUNCTION__,vel[X],vel[Y],vel[Z],pos[X],pos[Y],pos[Z]); #endif Mth::Vector vel2 = vel; float mag; mag = vel2.Length( ); if ( mag < m_min_initial_vel ) { mag = m_min_initial_vel; vel2.Normalize( mag ); } Mth::Vector dir = m_pos - pos; dir.Normalize( mag ); vel2 += dir * ( ( ( float ) ( 50 + Mth::Rnd( 100 ) ) ) / 100.0f ); vel2.Normalize( mag ); vel2[ Y ] = ( m_up_mag * ( mag + MAX_SKATER_VEL ) ) / ( MAX_SKATER_VEL * 2.0f ); bounce(vel2); } void CBouncyComponent::GetDebugInfo( Script::CStruct* p_info ) { #ifdef __DEBUG_CODE__ Dbg_MsgAssert(p_info,("NULL p_info sent to CBouncyComponent::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_collide_script",m_collide_script); p_info->AddStructure("mp_collide_script_params",mp_collide_script_params); p_info->AddChecksum("m_bounce_script",m_bounce_script); p_info->AddStructure("mp_bounce_script_params",mp_bounce_script_params); #endif } }