// Copyright (C) 2007 Id Software, Inc. // #include "../precompiled.h" #pragma hdrstop #if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE ) #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #include "VehicleIK.h" #include "TransportComponents.h" #include "../Entity.h" #include "../anim/Anim.h" #include "../physics/Physics_RigidBodyMultiple.h" #include "Transport.h" #include "../Player.h" #include "../ContentMask.h" #include "VehicleSuspension.h" #include "VehicleWeapon.h" /* =============================================================================== sdIK_WheeledVehicle =============================================================================== */ CLASS_DECLARATION( idIK, sdIK_WheeledVehicle ) END_CLASS /* ================ sdIK_WheeledVehicle::sdIK_WheeledVehicle ================ */ sdIK_WheeledVehicle::sdIK_WheeledVehicle( void ) { } /* ================ sdIK_WheeledVehicle::~sdIK_WheeledVehicle ================ */ sdIK_WheeledVehicle::~sdIK_WheeledVehicle( void ) { } /* ================ sdIK_WheeledVehicle::AddWheel ================ */ void sdIK_WheeledVehicle::AddWheel( sdVehicleRigidBodyWheel& _wheel ) { wheels.Alloc() = &_wheel; } /* ================ sdIK_WheeledVehicle::ClearWheels ================ */ void sdIK_WheeledVehicle::ClearWheels( void ) { wheels.Clear(); } /* ================ sdIK_WheeledVehicle::ClearJointMods ================ */ void sdIK_WheeledVehicle::ClearJointMods( void ) { if ( !self ) { return; } vehicleDriveObjectList_t& list = rbParent->GetDriveObjects(); for ( int i = 0; i < list.Num(); i++ ) { sdVehicleDriveObject* object = list[ i ]; object->ClearSuspensionIK(); } } /* ================ sdIK_WheeledVehicle::Evaluate ================ */ bool sdIK_WheeledVehicle::Evaluate( void ) { idAnimator* animator = self->GetAnimator(); bool changed = false; vehicleDriveObjectList_t& list = rbParent->GetDriveObjects(); for ( int i = 0; i < list.Num(); i++ ) { sdVehicleDriveObject* object = list[ i ]; changed |= object->UpdateSuspensionIK(); } for ( int i = 0; i < wheels.Num(); i++ ) { sdVehicleRigidBodyWheel* wheel = wheels[ i ]; if ( !wheel->HasVisualStateChanged() ) { continue; } wheel->ResetVisualState(); const idMat3& frictionAxes = wheel->GetFrictionAxes(); const idMat3& baseAxes = wheel->GetBaseAxes(); idRotation rotation; rotation.SetVec( wheel->GetRotationAxis() ); rotation.SetAngle( wheel->GetWheelAngle() ); animator->SetJointAxis( wheel->GetWheelJoint(), JOINTMOD_WORLD_OVERRIDE, baseAxes * rotation.ToMat3() * frictionAxes ); changed = true; } return changed; } /* ================ sdIK_WheeledVehicle::Init ================ */ bool sdIK_WheeledVehicle::Init( sdTransport_RB* self, const char *anim, const idVec3 &modelOffset ) { rbParent = self; if( !idIK::Init( self, anim, modelOffset ) ) { return false; } initialized = true; return true; } /* =============================================================================== sdIK_Walker =============================================================================== */ CLASS_DECLARATION( idIK, sdIK_Walker ) END_CLASS /* ================ sdIK_Walker::sdIK_Walker ================ */ sdIK_Walker::sdIK_Walker( void ) { } /* ================ sdIK_Walker::~sdIK_Walker ================ */ sdIK_Walker::~sdIK_Walker( void ) { gameLocal.clip.DeleteClipModel( footModel ); } /* ================ sdIK_Walker::Init ================ */ bool sdIK_Walker::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { float footSize; idVec3 verts[ 4 ]; idTraceModel trm; static idVec3 footWinding[ 4 ] = { idVec3( 1.0f, 1.0f, 0.0f ), idVec3( -1.0f, 1.0f, 0.0f ), idVec3( -1.0f, -1.0f, 0.0f ), idVec3( 1.0f, -1.0f, 0.0f ) }; waistOffset.Zero(); oldWaistHeight = 0; oldHeightsValid = false; isStable = true; assert( self ); const idDict& spawnArgs = self->spawnArgs; int numLegs = spawnArgs.GetInt( "ik_numLegs" ); if ( numLegs == 0 ) { return true; } if ( !idIK::Init( self, anim, modelOffset ) ) { return false; } float tweakFactor = spawnArgs.GetFloat( "ik_tweakFactor", "0.5" ); compressionInterpolate.Init( MS2SEC( gameLocal.time ), 1.f, tweakFactor, tweakFactor ); // get all the joints for ( int i = 0; i < numLegs; i++ ) { leg_t& leg = legs.Alloc(); const char* jointName; jointName = spawnArgs.GetString( va( "ik_foot%d", i + 1 ) ); leg.footJoint = animator->GetJointHandle( jointName ); if ( leg.footJoint == INVALID_JOINT ) { gameLocal.Error( "idIK_Walk::Init: invalid foot joint '%s'", jointName ); } jointName = spawnArgs.GetString( va( "ik_ankle%d", i + 1 ) ); leg.ankleJoint = animator->GetJointHandle( jointName ); if ( leg.ankleJoint == INVALID_JOINT ) { gameLocal.Error( "idIK_Walk::Init: invalid ankle joint '%s'", jointName ); } jointName = spawnArgs.GetString( va( "ik_knee%d", i + 1 ) ); leg.kneeJoint = animator->GetJointHandle( jointName ); if ( leg.kneeJoint == INVALID_JOINT ) { gameLocal.Error( "idIK_Walk::Init: invalid knee joint '%s'", jointName ); } jointName = spawnArgs.GetString( va( "ik_mid%d", i + 1 ) ); leg.midJoint = animator->GetJointHandle( jointName ); if ( leg.midJoint == INVALID_JOINT ) { gameLocal.Error( "idIK_Walk::Init: invalid mid joint '%s'", jointName ); } jointName = spawnArgs.GetString( va( "ik_hip%d", i + 1 ) ); leg.hipJoint = animator->GetJointHandle( jointName ); if ( leg.hipJoint == INVALID_JOINT ) { gameLocal.Error( "idIK_Walk::Init: invalid hip joint '%s'", jointName ); } } const char* jointName = spawnArgs.GetString( "ik_waist" ); waistJoint = animator->GetJointHandle( jointName ); if ( waistJoint == INVALID_JOINT ) { gameLocal.Error( "idIK_Walk::Init: invalid waist joint '%s'", jointName ); } // get the leg bone lengths and rotation matrices for ( int i = 0; i < legs.Num(); i++ ) { leg_t& leg = legs[ i ]; idVec3 dir, ankleOrigin, kneeOrigin, midOrigin, hipOrigin, footOrigin, dirOrigin; idVec3 dir2; idMat3 axis, ankleAxis, kneeAxis, midAxis, hipAxis, footAxis; leg.oldAnkleHeight = 0.0f; animator->GetJointTransform( leg.ankleJoint, gameLocal.time, ankleOrigin, ankleAxis ); animator->GetJointTransform( leg.kneeJoint, gameLocal.time, kneeOrigin, kneeAxis ); animator->GetJointTransform( leg.midJoint, gameLocal.time, midOrigin, midAxis ); animator->GetJointTransform( leg.hipJoint, gameLocal.time, hipOrigin, hipAxis ); animator->GetJointTransform( leg.footJoint, gameLocal.time, footOrigin, footAxis ); // get the IK direction dir = spawnArgs.GetVector( "ik_direction", "1 0 0" ); dir.Normalize(); dir2 = spawnArgs.GetVector( "ik_direction2", "0 1 0" ); dir2.Normalize(); leg.hipForward = dir * hipAxis.Transpose(); leg.midForward = dir * midAxis.Transpose(); leg.kneeForward = dir * kneeAxis.Transpose(); leg.midSide = dir2 * midAxis.Transpose(); // conversion from upper leg bone axis to hip joint axis leg.upperLegLength = GetBoneAxis( hipOrigin, midOrigin, dir, axis ); leg.upperLegToHipJoint = hipAxis * axis.Transpose(); // conversion from upper leg bone axis to hip joint axis leg.midLegLength = GetBoneAxis( midOrigin, kneeOrigin, dir2, axis ); leg.midToUpperLegJoint = midAxis * axis.Transpose(); // conversion from lower leg bone axis to knee joint axis leg.lowerLegLength = GetBoneAxis( kneeOrigin, ankleOrigin, dir, axis ); leg.lowerLegToKneeJoint = kneeAxis * axis.Transpose(); leg.lastAnkleAxes.Identity(); } smoothing = spawnArgs.GetFloat( "ik_smoothing", "0.75" ); downsmoothing = spawnArgs.GetFloat( "ik_downsmoothing", "0.25" ); waistSmoothing = spawnArgs.GetFloat( "ik_waistSmoothing", "0.75" ); footShift = spawnArgs.GetFloat( "ik_footShift", "0" ); waistShift = spawnArgs.GetFloat( "ik_waistShift", "0" ); minWaistFloorDist = spawnArgs.GetFloat( "ik_minWaistFloorDist", "0" ); minWaistAnkleDist = spawnArgs.GetFloat( "ik_minWaistAnkleDist", "0" ); footUpTrace = spawnArgs.GetFloat( "ik_footUpTrace", "32" ); footDownTrace = spawnArgs.GetFloat( "ik_footDownTrace", "32" ); tiltWaist = spawnArgs.GetBool( "ik_tiltWaist", "0" ); usePivot = spawnArgs.GetBool( "ik_usePivot", "0" ); invertedLegDirOffset= spawnArgs.GetFloat( "ik_invertlegoffset", "-2" ); // Gordon: Hack to tweak the inverted dir for the back section of the legs // setup a clip model for the feet footSize = spawnArgs.GetFloat( "ik_footSize", "4" ) * 0.5f; if ( footSize > 0.0f ) { for ( int i = 0; i < 4; i++ ) { verts[ i ] = footWinding[ i ] * footSize; } trm.SetupPolygon( verts, 4 ); footModel = new idClipModel( trm, false ); } initialized = true; return true; } /* ================ sdIK_Walker::SetCompressionScale ================ */ void sdIK_Walker::SetCompressionScale( float scale, float length ) { float current = compressionInterpolate.GetCurrentValue( MS2SEC( gameLocal.time ) ); compressionInterpolate.Init( MS2SEC( gameLocal.time ), length, current, scale ); } /* ================ sdIK_Walker::FindSmallest ================ */ float sdIK_Walker::FindSmallest( float min1, float max1, float min2, float max2, float& sizeMin1, float& sizeMax1, float& sizeMin2, float& sizeMax2 ) { if ( max1 < min2 ) { sizeMin1 = sizeMax1 = max1; sizeMin2 = sizeMax2 = min2; return min2 - max1; } if ( max2 < min1 ) { sizeMin2 = sizeMin2 = max2; sizeMin1 = sizeMax1 = min1; return min1 - max2; } if ( max2 > max1 ) { sizeMax1 = sizeMax2 = max1; } else { sizeMax1 = sizeMax2 = max2; } if ( min2 < min1 ) { sizeMin1 = sizeMin2 = min1; } else { sizeMin1 = sizeMin2 = min2; } return 0; } /* ================ sdIK_Walker::FindSmallest ================ */ float sdIK_Walker::FindSmallest( float size1, float min2, float max2, float& sizeMin2, float& sizeMax2 ) { if ( size1 < min2 ) { sizeMin2 = sizeMax2 = min2; return min2 - size1; } if ( max2 < size1 ) { sizeMin2 = sizeMin2 = max2; return size1 - max2; } if ( max2 > size1 ) { sizeMax2 = size1; } else { sizeMax2 = max2; } if ( min2 < size1 ) { sizeMin2 = size1; } else { sizeMin2 = min2; } return 0; } /* ================ sdIK_Walker::Evaluate ================ */ bool sdIK_Walker::Evaluate( void ) { trace_t results; // clear joint mods ClearJointMods(); animator->CreateFrame( gameLocal.time, true ); const renderEntity_t* renderEnt = self->GetRenderEntity(); idVec3 normal = -self->GetPhysics()->GetGravityNormal(); const idVec3& modelOrigin = renderEnt->origin; const idMat3& modelAxis = renderEnt->axis; float modelHeight = modelOrigin * normal; isStable = true; // get the joint positions for the feet for ( int i = 0; i < legs.Num(); i++ ) { leg_t& leg = legs[ i ]; idVec3 footOrigin; animator->GetJointTransform( leg.footJoint, gameLocal.time, footOrigin ); leg.current.jointWorldOrigin = modelOrigin + footOrigin * modelAxis; idVec3 start = leg.current.jointWorldOrigin + ( normal * footUpTrace ); idVec3 end = leg.current.jointWorldOrigin - ( normal * footDownTrace ); // gameLocal.clip.Translation( results, start, end, footModel, modelAxis, CONTENTS_SOLID, self ); if ( !gameLocal.clip.TracePoint( CLIP_DEBUG_PARMS results, start, end, MASK_VEHICLESOLID | CONTENTS_MONSTER, self ) ) { isStable = false; } leg.current.floorHeight = results.endpos * normal; idMat3 newAxes; if ( results.fraction != 1.f ) { idVec3 normal = results.c.normal * modelAxis.Transpose(); idVec3 vec3_forward( 1.f, 0.f, 0.f ); newAxes[ 0 ] = vec3_forward - ( normal * ( vec3_forward * normal ) ); newAxes[ 0 ].Normalize(); newAxes[ 2 ] = normal; newAxes[ 1 ] = newAxes[ 2 ].Cross( newAxes[ 0 ] ); } else { newAxes.Identity(); } idQuat newQuat; newQuat.Slerp( leg.lastAnkleAxes.ToQuat(), newAxes.ToQuat(), 0.1f ); leg.lastAnkleAxes = newQuat.ToMat3(); leg.lastAnkleAxes.FixDenormals(); if ( ik_debug.GetBool() && footModel ) { idFixedWinding w; for ( int j = 0; j < footModel->GetTraceModel()->numVerts; j++ ) { w += footModel->GetTraceModel()->verts[j]; } gameRenderWorld->DebugWinding( colorRed, w, results.endpos, results.endAxis ); } } // adjust heights of the ankles float smallestShift = idMath::INFINITY; float largestAnkleHeight = -idMath::INFINITY; for ( int i = 0; i < legs.Num(); i++ ) { leg_t& leg = legs[ i ]; idVec3 ankleOrigin; idVec3 footOrigin; animator->GetJointTransform( leg.ankleJoint, gameLocal.time, ankleOrigin, leg.current.ankleAxis ); animator->GetJointTransform( leg.footJoint, gameLocal.time, footOrigin ); float shift = leg.current.floorHeight - modelHeight + footShift; if ( shift < smallestShift ) { smallestShift = shift; } leg.current.jointWorldOrigin = modelOrigin + ankleOrigin * modelAxis; float height = leg.current.jointWorldOrigin * normal; if ( oldHeightsValid ) { float step = height + shift - leg.oldAnkleHeight; if ( step < 0 ) { shift -= smoothing * step; } else { shift -= downsmoothing * step; } } float newHeight = height + shift; if ( newHeight > largestAnkleHeight ) { largestAnkleHeight = newHeight; } leg.oldAnkleHeight = newHeight; leg.current.jointWorldOrigin += shift * normal; } idVec3 waistOrigin; animator->GetJointTransform( waistJoint, gameLocal.time, waistOrigin ); waistOrigin = modelOrigin + waistOrigin * modelAxis; // adjust position of the waist waistOffset = ( smallestShift + waistShift ) * normal; // if the waist should be at least a certain distance above the floor if ( minWaistFloorDist > 0.0f && waistOffset * normal < 0.0f ) { idVec3 start = waistOrigin; idVec3 end = waistOrigin + waistOffset - normal * minWaistFloorDist; gameLocal.clip.Translation( CLIP_DEBUG_PARMS results, start, end, footModel, modelAxis, CONTENTS_SOLID, self ); float height = ( waistOrigin + waistOffset - results.endpos ) * normal; if ( height < minWaistFloorDist ) { waistOffset += ( minWaistFloorDist - height ) * normal; } } // if the waist should be at least a certain distance above the ankles if ( minWaistAnkleDist > 0.0f ) { float height = ( waistOrigin + waistOffset ) * normal; if ( height - largestAnkleHeight < minWaistAnkleDist ) { waistOffset += ( minWaistAnkleDist - ( height - largestAnkleHeight ) ) * normal; } } if ( oldHeightsValid ) { // smoothly adjust height of waist float newHeight = ( waistOrigin + waistOffset ) * normal; float step = newHeight - oldWaistHeight; waistOffset -= waistSmoothing * step * normal; } // save height of waist for smoothing oldWaistHeight = ( waistOrigin + waistOffset ) * normal; if ( !oldHeightsValid ) { oldHeightsValid = true; return false; } // solve IK for ( int i = 0; i < legs.Num(); i++ ) { leg_t& leg = legs[ i ]; idVec3 hipOrigin; idMat3 axis; // get the position of the hip in world space animator->GetJointTransform( leg.hipJoint, gameLocal.time, hipOrigin, axis ); hipOrigin = modelOrigin + waistOffset + hipOrigin * modelAxis; // DebugAxis( hipOrigin, axis * modelAxis ); idVec3 hipDir = leg.hipForward * axis * modelAxis; idVec3 midOrigin; // get the IK bend direction animator->GetJointTransform( leg.midJoint, gameLocal.time, midOrigin, axis ); midOrigin = modelOrigin + waistOffset + midOrigin * modelAxis; // DebugAxis( midOrigin, axis * modelAxis ); idVec3 midDir = leg.midForward * axis * modelAxis; idVec3 midSideDir = leg.midSide * axis * modelAxis; idVec3 kneeOrigin; // get the IK bend direction animator->GetJointTransform( leg.kneeJoint, gameLocal.time, kneeOrigin, axis ); kneeOrigin = modelOrigin + waistOffset + kneeOrigin * modelAxis; // DebugAxis( kneeOrigin, axis * modelAxis ); idVec3 kneeDir = leg.kneeForward * axis * modelAxis; idVec3 ankleOrigin; animator->GetJointTransform( leg.ankleJoint, gameLocal.time, ankleOrigin, axis ); ankleOrigin = modelOrigin + waistOffset + ankleOrigin * modelAxis; float len1 = leg.upperLegLength; float minLen2 = fabs( leg.midLegLength - leg.lowerLegLength ); float maxLen2 = leg.midLegLength + leg.lowerLegLength; float wantLen = ( hipOrigin - leg.current.jointWorldOrigin ).Length(); float constrainedMaxLen2, constrainedMinLen2; float lenTotal = 0; if( minLen2 < fabs( wantLen - len1 ) ) { minLen2 = fabs( wantLen - len1 ); } float minTotal = FindSmallest( len1, minLen2, maxLen2, constrainedMaxLen2, constrainedMinLen2 ); float maxTotal = len1 + maxLen2; if ( maxTotal < wantLen ) { lenTotal = maxTotal; } else if ( minTotal > wantLen ) { lenTotal = constrainedMaxLen2; } else { float scale = compressionInterpolate.GetCurrentValue( MS2SEC( gameLocal.time ) ); lenTotal = Lerp( minLen2, maxLen2, scale ); } // solve IK and calculate upper knee position SolveTwoBones( hipOrigin, leg.current.jointWorldOrigin, midDir, leg.upperLegLength, lenTotal, midOrigin ); float d1 = ( hipOrigin - midOrigin ).Length(); idVec3 kneeDirTest = kneeDir; kneeDirTest.z += invertedLegDirOffset; kneeDirTest.NormalizeFast(); // solve IK and calculate lower knee position, using -kneeDir, as lower leg is inverted SolveTwoBones( midOrigin, leg.current.jointWorldOrigin, -kneeDirTest, leg.midLegLength, leg.lowerLegLength, kneeOrigin ); float d2 = ( midOrigin - kneeOrigin ).Length(); if ( ik_debug.GetBool() ) { gameRenderWorld->DebugLine( colorCyan, hipOrigin, midOrigin ); gameRenderWorld->DebugLine( colorRed, midOrigin, kneeOrigin ); gameRenderWorld->DebugLine( colorBlue, kneeOrigin, leg.current.jointWorldOrigin ); gameRenderWorld->DebugLine( colorYellow, midOrigin, midOrigin + ( midSideDir * 32 ) ); gameRenderWorld->DebugLine( colorGreen, hipOrigin, hipOrigin + ( hipDir * 32 ) ); gameRenderWorld->DebugLine( colorGreen, midOrigin, midOrigin + ( midDir * 32 ) ); gameRenderWorld->DebugLine( colorGreen, kneeOrigin, kneeOrigin + ( -kneeDirTest * 32 ) ); } // get the axis for the hip joint GetBoneAxis( hipOrigin, midOrigin, hipDir, axis ); leg.current.hipAxis = leg.upperLegToHipJoint * ( axis * modelAxis.Transpose() ); // get the axis for the knee joint GetBoneAxis( midOrigin, kneeOrigin, midSideDir, axis ); leg.current.midAxis = leg.midToUpperLegJoint * ( axis * modelAxis.Transpose() ); // get the axis for the knee joint GetBoneAxis( kneeOrigin, leg.current.jointWorldOrigin, kneeDir, axis ); leg.current.kneeAxis = leg.lowerLegToKneeJoint * ( axis * modelAxis.Transpose() ); } // set the joint mods animator->SetJointPos( waistJoint, JOINTMOD_WORLD_OVERRIDE, ( waistOrigin + waistOffset - modelOrigin ) * modelAxis.Transpose() ); for ( int i = 0; i < legs.Num(); i++ ) { leg_t& leg = legs[ i ]; animator->SetJointAxis( leg.hipJoint, JOINTMOD_WORLD_OVERRIDE, leg.current.hipAxis ); animator->SetJointAxis( leg.midJoint, JOINTMOD_WORLD_OVERRIDE, leg.current.midAxis ); animator->SetJointAxis( leg.kneeJoint, JOINTMOD_WORLD_OVERRIDE, leg.current.kneeAxis ); animator->SetJointAxis( leg.ankleJoint, JOINTMOD_WORLD_OVERRIDE, leg.current.ankleAxis * leg.lastAnkleAxes ); } return true; } /* ================ sdIK_Walker::ClearJointMods ================ */ void sdIK_Walker::ClearJointMods( void ) { animator->SetJointPos( waistJoint, JOINTMOD_NONE, vec3_origin ); for ( int i = 0; i < legs.Num(); i++ ) { leg_t& leg = legs[ i ]; animator->SetJointAxis( leg.hipJoint, JOINTMOD_NONE, mat3_identity ); animator->SetJointAxis( leg.midJoint, JOINTMOD_NONE, mat3_identity ); animator->SetJointAxis( leg.kneeJoint, JOINTMOD_NONE, mat3_identity ); animator->SetJointAxis( leg.ankleJoint, JOINTMOD_NONE, mat3_identity ); } } /* =============================================================================== sdVehicleIKSystem =============================================================================== */ ABSTRACT_DECLARATION( idClass, sdVehicleIKSystem ) END_CLASS /* ================ sdVehicleIKSystem::Setup ================ */ bool sdVehicleIKSystem::Setup( sdTransport* _vehicle, const angleClamp_t& yaw, const angleClamp_t& pitch, const idDict& ikParms ) { vehicle = _vehicle; clampYaw = yaw; clampPitch = pitch; const char* weaponName = ikParms.GetString( "weapon" ); if ( *weaponName ) { weapon = _vehicle->GetWeapon( weaponName ); if ( !weapon ) { gameLocal.Warning( "sdVehicleIKSystem::Setup Invalid Weapon '%s'", weaponName ); return false; } } else { weapon = NULL; } return true; } /* ================ sdVehicleIKSystem::GetPlayer ================ */ idPlayer* sdVehicleIKSystem::GetPlayer( void ) { return weapon ? weapon->GetPlayer() : position->GetPlayer(); } /* =============================================================================== sdVehicleIKArms =============================================================================== */ CLASS_DECLARATION( sdVehicleIKSystem, sdVehicleIKArms ) END_CLASS /* ================ sdVehicleIKArms::Setup ================ */ bool sdVehicleIKArms::Setup( sdTransport* _vehicle, const angleClamp_t& yaw, const angleClamp_t& pitch, const idDict& ikParms ) { for ( int i = 0; i < ARM_JOINT_NUM_JOINTS; i++ ) { ikJoints[ i ] = INVALID_JOINT; } if ( !sdVehicleIKSystem::Setup( _vehicle, yaw, pitch, ikParms ) ) { return false; } yawSound = NULL; if ( clampYaw.sound != NULL ) { yawSound = vehicle->GetMotorSounds().Alloc(); yawSound->Start( clampYaw.sound ); } pitchSound = NULL; if ( clampPitch.sound != NULL ) { pitchSound = vehicle->GetMotorSounds().Alloc(); pitchSound->Start( clampPitch.sound ); } jointAngles.Zero(); idAnimator* animator = vehicle->GetAnimator(); const idDict& dict = ikParms; const char* joint; joint = dict.GetString( "jointWrist" ); if ( *joint ) { ikJoints[ ARM_JOINT_INDEX_WRIST ] = animator->GetJointHandle( joint ); } joint = dict.GetString( "jointMuzzle" ); if ( *joint ) { ikJoints[ ARM_JOINT_INDEX_MUZZLE ] = animator->GetJointHandle( joint ); } joint = dict.GetString( "jointElbow" ); if ( *joint ) { ikJoints[ ARM_JOINT_INDEX_ELBOW ] = animator->GetJointHandle( joint ); } ikJoints[ ARM_JOINT_INDEX_SHOULDER ] = animator->GetJointParent( ikJoints[ ARM_JOINT_INDEX_ELBOW ] ); for ( int i = 0; i < ARM_JOINT_NUM_JOINTS; i++ ) { animator->GetJointTransform( ikJoints[ i ], gameLocal.time, baseJointPositions[ i ], baseJointAxes[ i ] ); } pitchAxis = dict.GetInt( "pitchAxis", "2" ); requireTophat = dict.GetBool( "require_tophat" ); oldParentAxis = mat3_identity; return true; } /* ================ sdVehicleIKArms::Update ================ */ void sdVehicleIKArms::Update( void ) { idEntity* vehicleEnt = vehicle; if ( !vehicleEnt ) { return; } idVec3 shoulderPos; idMat3 temp; idAnimator* animator = vehicleEnt->GetAnimator(); animator->GetJointTransform( ikJoints[ ARM_JOINT_INDEX_SHOULDER ], gameLocal.time, shoulderPos, temp ); idVec3 shoulderToElbow = baseJointPositions[ ARM_JOINT_INDEX_ELBOW ] - baseJointPositions[ ARM_JOINT_INDEX_SHOULDER ]; idVec3 elbowToWrist = baseJointPositions[ ARM_JOINT_INDEX_WRIST ] - baseJointPositions[ ARM_JOINT_INDEX_ELBOW ]; idVec3 wristToMuzzle = baseJointPositions[ ARM_JOINT_INDEX_MUZZLE ] - baseJointPositions[ ARM_JOINT_INDEX_WRIST ]; idVec3 elbowToMuzzle = baseJointPositions[ ARM_JOINT_INDEX_MUZZLE ] - baseJointPositions[ ARM_JOINT_INDEX_ELBOW ]; idMat3 shoulderAxis; TransposeMultiply( baseJointAxes[ ARM_JOINT_INDEX_SHOULDER ], temp, shoulderAxis ); idMat3 transposedShoulderAxis = shoulderAxis.Transpose(); idPlayer* player = GetPlayer(); if ( player && requireTophat ) { if ( !gameLocal.usercmds[ player->entityNumber ].buttons.btn.tophat ) { player = NULL; } } bool changed = false; changed |= !oldParentAxis.Compare( temp, 0.005f ); idAngles newAngles; renderView_t* view = player ? player->GetRenderView() : NULL; renderEntity_t* renderEnt = vehicleEnt->GetRenderEntity(); trace_t trace; idVec3 modelTarget; if( view ) { idVec3 end = view->vieworg + ( 8192 * view->viewaxis[ 0 ] ); gameLocal.clip.TracePoint( CLIP_DEBUG_PARMS trace, view->vieworg, end, CONTENTS_SOLID | CONTENTS_OPAQUE, player ); modelTarget = trace.endpos; modelTarget -= renderEnt->origin; modelTarget *= renderEnt->axis.Transpose(); modelTarget -= shoulderPos; modelTarget *= transposedShoulderAxis; modelTarget -= shoulderToElbow; } if ( view ) { idVec3 target = modelTarget; const idVec3& dir = baseJointAxes[ ARM_JOINT_INDEX_MUZZLE ][ 0 ]; target -= elbowToMuzzle - ( ( dir * elbowToMuzzle ) * dir ); target *= baseJointAxes[ ARM_JOINT_INDEX_MUZZLE ].Transpose(); newAngles.yaw = RAD2DEG( atan2( target[ 1 ], target[ 0 ] ) ); } else { newAngles.yaw = 0; } bool yawChanged = !sdVehiclePosition::ClampAngle( newAngles, jointAngles, clampYaw, 1, 0.1f ); if ( yawSound != NULL ) { yawSound->Update( yawChanged ); } changed |= yawChanged; idMat3 yawMat; idAngles::YawToMat3( newAngles.yaw, yawMat ); if ( view ) { idVec3 target = modelTarget; idVec3 newElbowToWrist = elbowToWrist * yawMat; idVec3 newWristToMuzzle = wristToMuzzle * yawMat; idMat3 muzzleAxis = baseJointAxes[ ARM_JOINT_INDEX_MUZZLE ] * yawMat; target -= newElbowToWrist; target -= newWristToMuzzle - ( ( muzzleAxis[ 0 ] * newWristToMuzzle ) * muzzleAxis[ 0 ] ); target *= muzzleAxis.Transpose(); newAngles.pitch = -RAD2DEG( atan2( target[ 2 ], target[ 0 ] ) ); } else { newAngles.pitch = 0; } bool pitchChanged = !sdVehiclePosition::ClampAngle( newAngles, jointAngles, clampPitch, 0, 0.1f ); if ( pitchSound != NULL ) { pitchSound->Update( pitchChanged ); } changed |= pitchChanged; // configurable pitching axis - to support vertically oriented arms (eg badger, bumblebee) // as well as horizontally oriented arms (eg goliath) int truePitchAxis = pitchAxis; if ( truePitchAxis < 0 ) { newAngles.pitch = -newAngles.pitch; truePitchAxis = -truePitchAxis; } idAngles pitchAngles( 0.0f, 0.0f, 0.0f ); if ( truePitchAxis == 1 ) { // x-axis pitchAngles.roll = newAngles.pitch; } else if ( truePitchAxis == 3 ) { // z-axis pitchAngles.yaw = newAngles.pitch; } else { // y-axis pitchAngles.pitch = newAngles.pitch; } idMat3 pitchMat = pitchAngles.ToMat3(); if( changed ) { oldParentAxis = temp; jointAngles = newAngles; animator->SetJointAxis( ikJoints[ ARM_JOINT_INDEX_ELBOW ], JOINTMOD_WORLD_OVERRIDE, baseJointAxes[ ARM_JOINT_INDEX_ELBOW ] * yawMat * shoulderAxis ); animator->SetJointAxis( ikJoints[ ARM_JOINT_INDEX_WRIST ], JOINTMOD_WORLD_OVERRIDE, pitchMat * baseJointAxes[ ARM_JOINT_INDEX_WRIST ] * yawMat * shoulderAxis ); } } /* =============================================================================== sdVehicleSwivel =============================================================================== */ CLASS_DECLARATION( sdVehicleIKSystem, sdVehicleSwivel ) END_CLASS /* ================ sdVehicleSwivel::Setup ================ */ bool sdVehicleSwivel::Setup( sdTransport* _vehicle, const angleClamp_t& yaw, const angleClamp_t& pitch, const idDict& ikParms ) { joint = INVALID_JOINT; if ( !sdVehicleIKSystem::Setup( _vehicle, yaw, pitch, ikParms ) ) { return false; } angles.Zero(); idAnimator* animator = vehicle->GetAnimator(); joint = animator->GetJointHandle( ikParms.GetString( "joint" ) ); animator->GetJointTransform( joint, gameLocal.time, baseAxis ); yawSound = NULL; if ( clampYaw.sound != NULL ) { yawSound = vehicle->GetMotorSounds().Alloc(); yawSound->Start( clampYaw.sound ); } return true; } /* ================ sdVehicleSwivel::Update ================ */ void sdVehicleSwivel::Update( void ) { idEntity* vehicleEnt = vehicle; if ( !vehicleEnt ) { return; } idAnimator* animator = vehicleEnt->GetAnimator(); idPlayer* player = GetPlayer(); idAngles newAngles; if ( player ) { float diff = idMath::AngleDelta( player->clientViewAngles.yaw, angles.yaw ); newAngles.yaw = angles.yaw + diff * (1.f-clampYaw.filter);//clampYaw.filter * angles.yaw + (1.f - clampYaw.filter) * player->clientViewAngles.yaw; } else { newAngles.yaw = 0; } bool changed = !sdVehiclePosition::ClampAngle( newAngles, angles, clampYaw, 1, 0.1f ); if ( yawSound != NULL ) { yawSound->Update( changed ); } if ( changed ) { angles = newAngles; idMat3 yawAxis; idAngles::YawToMat3( angles.yaw, yawAxis ); animator->SetJointAxis( joint, JOINTMOD_WORLD, yawAxis ); } } /* =============================================================================== sdVehicleJointAimer =============================================================================== */ CLASS_DECLARATION( sdVehicleIKSystem, sdVehicleJointAimer ) END_CLASS /* ================ sdVehicleJointAimer::Setup ================ */ bool sdVehicleJointAimer::Setup( sdTransport* _vehicle, const angleClamp_t& yaw, const angleClamp_t& pitch, const idDict& ikParms ) { joint = INVALID_JOINT; if ( !sdVehicleIKSystem::Setup( _vehicle, yaw, pitch, ikParms ) ) { return false; } yawSound = NULL; if ( clampYaw.sound != NULL ) { yawSound = vehicle->GetMotorSounds().Alloc(); yawSound->Start( clampYaw.sound ); } pitchSound = NULL; if ( clampPitch.sound != NULL ) { pitchSound = vehicle->GetMotorSounds().Alloc(); pitchSound->Start( clampPitch.sound ); } idAnimator* animator = vehicle->GetAnimator(); joint = animator->GetJointHandle( ikParms.GetString( "joint" ) ); if ( joint == INVALID_JOINT ) { return false; } animator->GetJointTransform( joint, gameLocal.time, baseAxis ); angles = baseAxis.ToAngles(); const char* weapon2Name = ikParms.GetString( "weapon2" ); if ( *weapon2Name ) { weapon2 = _vehicle->GetWeapon( weapon2Name ); if ( !weapon2 ) { gameLocal.Warning( "sdVehicleIKSystem::Setup Invalid Weapon '%s'", weapon2Name ); return false; } } else { weapon2 = NULL; } return true; } /* ================ sdVehicleIKSystem::GetPlayer ================ */ idPlayer* sdVehicleJointAimer::GetPlayer( void ) { idPlayer* player = weapon ? weapon->GetPlayer() : position->GetPlayer(); if ( !player ) { return weapon2 ? weapon2->GetPlayer() : position->GetPlayer(); } return player; } /* ================ sdVehicleJointAimer::Update ================ */ void sdVehicleJointAimer::Update( void ) { idEntity* vehicleEnt = vehicle; if ( !vehicleEnt ) { return; } if ( joint == INVALID_JOINT ) { return; } idAnimator* animator = vehicleEnt->GetAnimator(); idPlayer* player = GetPlayer(); idMat3 tempJointAxis; idVec3 jointPos; animator->GetJointTransform( joint, gameLocal.time, jointPos, tempJointAxis ); renderView_t* view = player ? player->GetRenderView() : NULL; renderEntity_t* renderEnt = vehicleEnt->GetRenderEntity(); trace_t trace; idVec3 modelTarget; if( view ) { // find what is being aimed at idVec3 end = view->vieworg + ( 8192 * view->viewaxis[ 0 ] ); gameLocal.clip.TracePoint( CLIP_DEBUG_PARMS trace, view->vieworg, end, CONTENTS_SOLID | CONTENTS_OPAQUE, player ); modelTarget = trace.endpos; // transform modelTarget into entity space modelTarget -= renderEnt->origin; modelTarget *= renderEnt->axis.Transpose(); // make target relative to joint modelTarget -= jointPos; // calculate the vector to the target idVec3 direction = modelTarget; direction.Normalize(); idMat3 newAxis = direction.ToMat3(); idAngles newAngles = newAxis.ToAngles(); // clamp the angles bool yawChanged = !sdVehiclePosition::ClampAngle( newAngles, angles, clampYaw, 1, 0.1f ); if ( yawSound != NULL ) { yawSound->Update( yawChanged ); } bool pitchChanged = !sdVehiclePosition::ClampAngle( newAngles, angles, clampPitch, 0, 0.1f ); if ( pitchSound != NULL ) { pitchSound->Update( pitchChanged ); } if ( yawChanged || pitchChanged ) { // set the angles angles = newAngles; animator->SetJointAxis( joint, JOINTMOD_WORLD_OVERRIDE, newAngles.ToMat3() ); } } } /* =============================================================================== sdVehicleIK_Steering =============================================================================== */ CLASS_DECLARATION( sdVehicleIKSystem, sdVehicleIK_Steering ) END_CLASS /* ================ sdVehicleIK_Steering::sdVehicleIK_Steering ================ */ sdVehicleIK_Steering::sdVehicleIK_Steering( void ) { } /* ================ sdVehicleIK_Steering::~sdVehicleIK_Steering ================ */ sdVehicleIK_Steering::~sdVehicleIK_Steering( void ) { } /* ================ sdVehicleIK_Steering::Init ================ */ bool sdVehicleIK_Steering::Setup( sdTransport* _vehicle, const angleClamp_t& yaw, const angleClamp_t& pitch, const idDict& ikParms ) { if ( !sdVehicleIKSystem::Setup( _vehicle, yaw, pitch, ikParms ) ) { return false; } if ( !ik.Init( vehicle, ikParms ) ) { return false; } return true; } /* ================ sdVehicleIK_Steering::Update ================ */ void sdVehicleIK_Steering::Update( void ) { idPlayer* player = GetPlayer(); if ( player == NULL ) { return; } ik.Update( player, vehicle ); } /* =============================================================================== sdVehicleWeaponAimer =============================================================================== */ CLASS_DECLARATION( sdVehicleIKSystem, sdVehicleWeaponAimer ) END_CLASS /* ================ sdVehicleWeaponAimer::Update ================ */ void sdVehicleWeaponAimer::Update( void ) { idPlayer* player = GetPlayer(); if ( player ) { const renderView_t& renderView = player->renderView; trace_t trace; gameLocal.clip.TracePoint( CLIP_DEBUG_PARMS trace, renderView.vieworg, renderView.vieworg + ( renderView.viewaxis[ 0 ] * 4096 ), CONTENTS_SOLID | CONTENTS_OPAQUE, player ); aimer.SetTarget( trace.endpos ); } else { aimer.ClearTarget(); } aimer.Update(); } /* ================ sdVehicleWeaponAimer::Setup ================ */ bool sdVehicleWeaponAimer::Setup( sdTransport* _vehicle, const angleClamp_t& yaw, const angleClamp_t& pitch, const idDict& ikParms ) { if ( !sdVehicleIKSystem::Setup( _vehicle, yaw, pitch, ikParms ) ) { return false; } idAnimator* animator = vehicle->GetAnimator(); jointHandle_t pitchJoint = animator->GetJointHandle( ikParms.GetString( "jointWrist" ) ); jointHandle_t yawJoint = animator->GetJointHandle( ikParms.GetString( "jointElbow" ) ); jointHandle_t muzzleJoint = animator->GetJointHandle( ikParms.GetString( "jointMuzzle" ) ); jointHandle_t shoulderJoint = animator->GetJointHandle( ikParms.GetString( "jointShoulder" ) ); if ( shoulderJoint == INVALID_JOINT ) { shoulderJoint = animator->GetJointParent( yawJoint ); } int anim = animator->GetAnim( ikParms.GetString( "deployed_anim" ) ); aimer.Init( ikParms.GetBool( "fix_barrel" ), ikParms.GetBool( "invert_pitch" ), _vehicle, anim, yawJoint, pitchJoint, muzzleJoint, shoulderJoint, clampYaw, clampPitch ); return true; }