//**************************************************************************** //* MODULE: Sk/Components //* FILENAME: SkaterNonLocalNetLogicComponent.cpp //* OWNER: Dan //* CREATION DATE: 3/11/3 //**************************************************************************** #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Obj { /******************************************************************/ /* */ /* */ /******************************************************************/ CBaseComponent* CSkaterNonLocalNetLogicComponent::s_create() { return static_cast< CBaseComponent* >( new CSkaterNonLocalNetLogicComponent ); } /******************************************************************/ /* */ /* */ /******************************************************************/ CSkaterNonLocalNetLogicComponent::CSkaterNonLocalNetLogicComponent() : CBaseComponent() { SetType( CRC_SKATERNONLOCALNETLOGIC ); mp_state_history_component = NULL; mp_state_component = NULL; mp_endrun_component = NULL; mp_animation_component = NULL; mp_model_component = NULL; mp_flip_and_rotate_component = NULL; mp_shadow_component = NULL; m_last_pos_index = 0; m_interp_pos = Mth::Vector( 0, 0, 0 ); m_old_interp_pos = Mth::Vector( 0, 0, 0 ); m_num_mags = 0; m_last_anm_update_time = 0; } /******************************************************************/ /* */ /* */ /******************************************************************/ CSkaterNonLocalNetLogicComponent::~CSkaterNonLocalNetLogicComponent() { } /******************************************************************/ /* */ /* */ /******************************************************************/ void CSkaterNonLocalNetLogicComponent::InitFromStructure( Script::CStruct* pParams ) { Dbg_MsgAssert(GetObject()->GetType() == SKATE_TYPE_SKATER, ("CSkaterNonLocalNetLogicComponent added to non-skater composite object")); Dbg_MsgAssert(!GetSkater()->IsLocalClient(), ("CSkaterNonLocalNetLogicComponent added to non-local skater")); } /******************************************************************/ /* */ /* */ /******************************************************************/ void CSkaterNonLocalNetLogicComponent::RefreshFromStructure( Script::CStruct* pParams ) { InitFromStructure(pParams); } /******************************************************************/ /* */ /* */ /******************************************************************/ void CSkaterNonLocalNetLogicComponent::Finalize ( ) { mp_state_history_component = GetSkaterStateHistoryComponentFromObject(GetObject()); mp_state_component = GetSkaterStateComponentFromObject(GetObject()); mp_endrun_component = GetSkaterEndRunComponentFromObject(GetObject()); mp_animation_component = GetAnimationComponentFromObject(GetObject()); mp_flip_and_rotate_component = GetSkaterFlipAndRotateComponentFromObject(GetObject()); mp_model_component = GetModelComponentFromObject(GetObject()); mp_shadow_component = GetShadowComponentFromObject(GetObject()); Dbg_Assert(mp_state_history_component); Dbg_Assert(mp_state_component); Dbg_Assert(mp_endrun_component); Dbg_Assert(mp_animation_component); Dbg_Assert(mp_flip_and_rotate_component); Dbg_Assert(mp_model_component); Dbg_Assert(mp_shadow_component); } /******************************************************************/ /* */ /* */ /******************************************************************/ void CSkaterNonLocalNetLogicComponent::Update() { GameNet::PlayerInfo* local_player; GameNet::Manager* gamenet_man = GameNet::Manager::Instance(); m_frame_length = Tmr::UncappedFrameLength(); bool BeganFrameInLipState = mp_state_component->GetState() == LIP; Mdl::Skate * skate_mod = Mdl::Skate::Instance(); if( mp_state_component->GetState() != AIR ) { mp_state_component->m_camera_display_normal = GetObject()->GetMatrix().GetUp(); mp_state_component->m_camera_current_normal = GetObject()->GetMatrix().GetUp(); } setup_brightness_and_shadow(); m_old_extrap_pos = GetObject()->m_pos; GetObject()->m_old_pos = GetObject()->m_pos; interpolate_client_position(); // Only extrapolate if we can collide with other players. Otherwise, interpolation looks // much better. local_player = gamenet_man->GetLocalPlayer(); if( !local_player->IsObserving() && !local_player->IsSurveying()) { if( ( GameNet::Manager::Instance()->PlayerCollisionEnabled()) || ( skate_mod->GetGameMode()->GetNameChecksum() == CRCD(0xbff33600,"netfirefight")) || ( skate_mod->GetGameMode()->GetNameChecksum() == CRCD(0x3d6d444f,"firefight"))) { extrapolate_client_position(); } } do_client_animation_update(); // NOTE: Below logic is copied from CSkaterFinalizePhysicsComponent and should instead be merged. // Logic for setting/not setting the flag for telling the camera whether to look down on the skater or not. if (BeganFrameInLipState && mp_state_component->GetState() != LIP) { // This flag is sort of badly named now, it really means we changed from the lip state to something other than GROUND or LIP. mp_state_component->mJumpedOutOfLipTrick = false; if (mp_state_component->GetState() == AIR) { // we only want to set this flag if we jumped straight up; meaning the x and z velocities are close to zero if (Mth::Abs(GetObject()->m_vel[X]) < 1.0f && Mth::Abs(GetObject()->m_vel[Z]) < 1.0f) { mp_state_component->mJumpedOutOfLipTrick = true; } } } // this flag needs clearing whenever we get out of the air if (mp_state_component->GetState() != AIR) { mp_state_component->mJumpedOutOfLipTrick = false; } // take the non-local client in and out of the car if (mp_state_component->GetDriving() && mp_state_history_component->GetCurrentVehicleControlType() != 0) { Script::CStruct* pParams = new Script::CStruct; pParams->AddChecksum(CRCD(0x5b24faaa, "SkaterId"), GetObject()->GetID()); pParams->AddChecksum(CRCD(0x81cff663, "control_type"), mp_state_history_component->GetCurrentVehicleControlType()); RunScript(CRCD(0x7853f8c4, "NonLocalClientInVehicle"), pParams); delete pParams; } else if (!mp_state_component->GetDriving() && m_last_driving) { Script::CStruct* pParams = new Script::CStruct; pParams->AddChecksum(CRCD(0x5b24faaa, "SkaterId"), GetObject()->GetID()); RunScript(CRCD(0xf02e44f, "NonLocalClientExitVehicle"), pParams); delete pParams; } m_last_driving = mp_state_component->GetDriving(); // update the looping sound component CSkaterLoopingSoundComponent* p_looping_sound_component = GetSkaterLoopingSoundComponentFromObject(GetObject()); Dbg_Assert(p_looping_sound_component); if (mp_state_component->m_physics_state == SKATING) { p_looping_sound_component->SetActive(true); float speed_fraction = sqrtf(GetObject()->m_vel[X] * GetObject()->m_vel[X] + GetObject()->m_vel[Z] * GetObject()->m_vel[Z]) / 1000.0f; p_looping_sound_component->SetSpeedFraction(speed_fraction); p_looping_sound_component->SetState(mp_state_component->m_state); p_looping_sound_component->SetTerrain(mp_state_component->m_terrain); p_looping_sound_component->SetIsBailing(mp_state_component->GetFlag(IS_BAILING)); p_looping_sound_component->SetIsRailSliding(mp_state_component->GetFlag(RAIL_SLIDING)); } else { p_looping_sound_component->SetActive(false); } } /******************************************************************/ /* */ /* */ /******************************************************************/ CBaseComponent::EMemberFunctionResult CSkaterNonLocalNetLogicComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript ) { switch ( Checksum ) { default: return CBaseComponent::MF_NOT_EXECUTED; } return CBaseComponent::MF_TRUE; } /******************************************************************/ /* */ /* */ /******************************************************************/ void CSkaterNonLocalNetLogicComponent::GetDebugInfo(Script::CStruct *p_info) { Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterNonLocalNetLogicComponent::GetDebugInfo")); CBaseComponent::GetDebugInfo(p_info); } /******************************************************************/ /* */ /* */ /******************************************************************/ void CSkaterNonLocalNetLogicComponent::Resync ( ) { m_client_initial_update_time = 0; m_last_anm_update_time = 0; } /******************************************************************/ /* */ /* */ /******************************************************************/ void CSkaterNonLocalNetLogicComponent::snap_to_ground( void ) { float up_dot = 0.0f; Mth::Vector col_start, col_end; CFeeler feeler; // Since we really don't want to loose contact with the ground while skitching, we use a much bigger snap up dist // The problem will come when we get dragged down a slope. The car will flatten out well ahead of us, so pushing us down through the slop // (as we are a few feet behind it) and we will be so far under the ground that our normal snap up will not be able to dig us out of it, // so we go in air, uberfrig, and get dragged to a random spot under the level. // (This would not happen if we just skitch on flat ground) // if (mp_state_component->m_skater_flags[SKITCHING].Get()) // { // col_start = GetObject()->m_matrix[Y] * GetPhysicsFloat(CRCD(0x5c0d9610,"Physics_Ground_Snap_Up_SKITCHING")); // much above feet // } // else // { col_start = GetObject()->m_matrix[Y] * GetPhysicsFloat(CRCD(0xe4d79235, "Physics_Ground_Snap_Up")); // bit above the feet // } col_end = GetObject()->m_matrix[Y] * -200.0f; // WAY! below the feet, we check distance later col_start += GetObject()->m_pos; col_end += GetObject()->m_pos; bool sticking = false; feeler.SetLine( col_start, col_end ); feeler.SetIgnore( mFD_NON_COLLIDABLE, 0 ); // get disatnce to ground and snap the skater to it, but only if the ground is skatable, otherwise we just go to "AIR" if( feeler.GetCollision()) { Mth::Vector movement = GetObject()->m_pos - feeler.GetPoint(); uint16 flags = feeler.GetFlags(); float drop_dist = movement.Length(); float drop_sign = Mth::DotProduct(GetObject()->m_matrix[Y], movement); // might be approx +/- 0.00001f if( (!flags & mFD_SKATABLE ) || ( flags & mFD_NOT_SKATABLE ) || (!flags & mFD_VERT) || (flags & mFD_WALL_RIDABLE) || (feeler.GetNormal()[Y] < sinf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x3eede4d3,"Wall_Non_Skatable_Angle")))))) { // if below the face (or very close to it), then push us away from it if (drop_sign < 0.001f) { // at point of contact, and move away from surface GetObject()->m_pos = feeler.GetPoint() + feeler.GetNormal(); } } else { sticking = true; // Note the two ways of calculating the angle between two faces // the more "accurate" method simply takes angle between the two normals // however, this fails to account for the direction the skater is travelling // when in conjunction with the "snap" up. // If the skater approaches a slope from the side, then he can snap up to the slope // however, if the angle between the ground and the face to which we are snapping up to // is too great, then we transition to in-air // and will drop through the slope // the solution is to take the angle between the front vector rotated onto each face. // Firstly we check the angle between the two faces Mth::Vector normal = feeler.GetNormal(); Mth::Vector forward = GetObject()->m_matrix[Z]; float front_dot = Mth::DotProduct(forward,normal); Mth::Vector old_forward = forward; forward.RotateToPlane(normal); old_forward.RotateToPlane(m_current_normal); // angle between front vectors, projected onto faces up_dot = Mth::DotProduct(forward, old_forward); float stick_angle_cosine; stick_angle_cosine = cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x4138283e, "Ground_stick_angle_forward")))); if (front_dot > 0.0f && up_dot > 0.0f && up_dot < stick_angle_cosine) { sticking = false; } else { // only need to test if snap is downwards if (drop_sign > 0.0f) { // angle between the old plane and the new plane is either the same, or withing the set limits // (like, < 60 degrees, or so, see physics.q) now calculate the drop distance, based on the angle //float max_drop = 60.0f;////last_move.Length() * tanf(angle); #ifdef __PLAT_NGC__ float angle = acosf(Mth::Clamp(up_dot, -1.0f, 1.0f)); #else float angle = acosf(up_dot); #endif // __PLAT_NGC__ Mth::Vector last_move = GetObject()->m_pos - m_old_extrap_pos; float max_drop = last_move.Length() * tanf(angle); float min_drop = GetPhysicsFloat(CRCD(0x899ba3d0, "Physics_Ground_Snap_Down")); // if (mp_state_component->m_skater_flags[SKITCHING].Get()) // { // min_drop = GetPhysicsFloat(CRCD(0x20df7e33, "Physics_Ground_Snap_Down_Skitching")); // } if (max_drop < min_drop) { max_drop = min_drop; } if (drop_dist > max_drop) { sticking = false; } } if (sticking) { //new_normal(normal); m_current_normal = normal; // remember this, for detecting if it changes GetObject()->m_matrix[Y] = normal; GetObject()->m_matrix.OrthoNormalizeAbout(Y); // set regular normal immediately } } } if (sticking) { // if there is a collision, then snap to it GetObject()->m_pos = feeler.GetPoint(); } } } /******************************************************************/ /* */ /* */ /******************************************************************/ CRailNode* CSkaterNonLocalNetLogicComponent::travel_on_rail( CRailNode* start_node, float frame_length ) { bool on_rail; Dbg_Assert( start_node ); on_rail = true; CRailManager* p_rail_man = Mdl::Skate::Instance()->GetRailManager(); const CRailNode* pStart = start_node; const CRailNode* pEnd = pStart->GetNextLink(); if( pEnd == NULL ) { Dbg_MsgAssert(pEnd,("NonLocal Skater on Rail node (%d) with no next link\n",start_node->GetNode())); return NULL; } //const CRailNode* pFrom = pStart; const CRailNode* pOnto = NULL; Mth::Vector dir = p_rail_man->GetPos(pEnd) - p_rail_man->GetPos(pStart); float segment_length = dir.Length(); dir *= (1.0f / segment_length); // sign is which way we are going along the rail //float old_sign = Mth::Sgn(Mth::DotProduct(dir, GetObject()->m_vel)); // Get gravity force Mth::Vector gravity(0.0f, GetPhysicsFloat(CRCD(0xd1f46992, "Physics_Rail_Gravity")), 0.0f); // Project gravity onto the line we are on gravity.ProjectToNormal(dir); GetObject()->m_vel += gravity * frame_length; // sign is which way we are going along the rail float sign = Mth::Sgn(Mth::DotProduct(dir, GetObject()->m_vel)); // check to see if we are on the last segment of the rail // this logic could be folded into the logic below but it's perhps clearer to have it here bool last_segment = false; if (sign < 0.0f) { if (pStart->GetPrevLink() && pStart->GetPrevLink()->IsActive()) { Mth::Vector v1, v2; v1 = p_rail_man->GetPos(pEnd) - p_rail_man->GetPos(pStart); v2 = p_rail_man->GetPos(pStart) - p_rail_man->GetPos(pStart->GetPrevLink()); v1[Y] = 0.0f; v2[Y] = 0.0f; v1.Normalize(); v2.Normalize(); float dot = Mth::DotProduct(v1, v2); if (dot < cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x76c1da15, "Rail_Corner_Leave_Angle"))))) { // there is more, but angle is too sharp last_segment = true; } else { pOnto = pStart->GetPrevLink(); } } else { last_segment = true; } } else { if (pEnd && pEnd->GetNextLink() && pEnd->GetNextLink()->IsActive()) { Mth::Vector v1,v2; v1 = p_rail_man->GetPos(pStart) - p_rail_man->GetPos(pEnd); v2 = p_rail_man->GetPos(pEnd) - p_rail_man->GetPos(pEnd->GetNextLink()); v1[Y] = 0.0f; v2[Y] = 0.0f; v1.Normalize(); v2.Normalize(); float dot = Mth::DotProduct(v1,v2); if (dot < cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x76c1da15,"Rail_Corner_Leave_Angle"))))) { // there is more, but angle is too sharp last_segment = true; } else { pOnto = pEnd; } } else { last_segment = true; } } // Now we need to see if we have gone beyond the end of the rail segment float extra_dist = 0.0f; float length_along; if (sign < 0.0f) { // going backwards, so it's the distance from the end of the segment length_along = (GetObject()->m_pos - p_rail_man->GetPos(pEnd)).Length(); } else { // going forwards, so it's the distance from the start length_along = (GetObject()->m_pos - p_rail_man->GetPos(pStart)).Length(); } if (length_along > segment_length + 0.1f) // 0.1 inch, so we don't get stuck { // remember this, so we can move along next segment extra_dist = length_along - segment_length; // gone off the end of the segment if (sign < 0.0f) { if (pStart->GetPrevLink() && pStart->GetPrevLink()->IsActive() && Rail_ValidInEditor(p_rail_man->GetPos(pStart), p_rail_man->GetPos(pStart->GetPrevLink()))) { if (!last_segment) { // go onto previous segment GetObject()->m_pos = p_rail_man->GetPos(pStart); start_node = (CRailNode*) pStart->GetPrevLink(); //set_terrain(start_node->GetTerrain()); //maybe_trip_rail_trigger(TRIGGER_SKATE_ONTO); } else { //skate_off_rail(p_rail_man->GetPos(pStart)); on_rail = false; } } else { //skate_off_rail(p_rail_man->GetPos(pStart)); on_rail = false; } } else { if (pEnd->GetNextLink() && pEnd->IsActive() && Rail_ValidInEditor(p_rail_man->GetPos(pEnd), p_rail_man->GetPos(pEnd->GetNextLink()))) { if (!last_segment) { GetObject()->m_pos = p_rail_man->GetPos(pEnd); start_node = (CRailNode*) pEnd; //set_terrain(start_node->GetTerrain()); //maybe_trip_rail_trigger(TRIGGER_SKATE_ONTO); } else { //skate_off_rail(p_rail_man->GetPos(pEnd)); on_rail = false; } } else { //skate_off_rail(p_rail_man->GetPos(pEnd)); on_rail = false; } } } if( on_rail ) // If still on the rail GetState() == RAIL) { // recalculate start, end, dir, as we might be on a new segment const CRailNode* pStart = start_node; const CRailNode* pEnd = pStart->GetNextLink(); Mth::Vector dir = p_rail_man->GetPos(pEnd) - p_rail_man->GetPos(pStart); dir.Normalize(); // sign also may have changed, now that we are auto-linking rail segments // sign is which way we are going along the rail float sign = Mth::Sgn(Mth::DotProduct(dir,GetObject()->m_vel)); //m_rail_time = Tmr::GetTime(); GetObject()->m_vel.RotateToNormal(dir); GetObject()->m_vel *= sign; // sign won't be on a new segment //float facing_sign = mRail_Backwards ? -sign : sign; float facing_sign = sign; // z is forward Mth::Vector target_forward = dir * facing_sign; //m_lerping_display_matrix[Z] = Mth::Lerp(m_lerping_display_matrix[Z], target_forward, 0.3f); //m_lerping_display_matrix[Z].Normalize(); #if 0 // old code // m_lerping_display_matrix[Y].Set(0.0f, 1.0f, 0.0f); // m_lerping_display_matrix[X] = Mth::CrossProduct( // m_lerping_display_matrix[Y], // m_lerping_display_matrix[Z] // ); #else //m_lerping_display_matrix[X].Set( //m_lerping_display_matrix[Z][Z], //0.0f, //-m_lerping_display_matrix[Z][X], //0.0f //); #endif //m_lerping_display_matrix[X].Normalize(); //m_lerping_display_matrix[Y] = Mth::CrossProduct( //m_lerping_display_matrix[Z], //m_lerping_display_matrix[X] //); //m_lerping_display_matrix[Y].Normalize(); // adjust our Z value towards the new value GetObject()->m_matrix[Z] = target_forward; GetObject()->m_matrix[Z].Normalize(); #if 0 // old code // GetObject()->m_matrix[Y].Set(0.0f, 1.0f, 0.0f); // GetObject()->m_matrix[X] = Mth::CrossProduct(GetObject()->m_matrix[Y],GetObject()->m_matrix[Z]); #else GetObject()->m_matrix[X].Set( GetObject()->m_matrix[Z][Z], 0.0f, -GetObject()->m_matrix[Z][X], 0.0f ); #endif GetObject()->m_matrix[X].Normalize(); GetObject()->m_matrix[Y] = Mth::CrossProduct(GetObject()->m_matrix[Z], GetObject()->m_matrix[X]); GetObject()->m_matrix[Y].Normalize(); // This is where we do the actual movement // if this makes us bump into the wall or the ground, then we should leave the rail GetObject()->m_pos += GetObject()->m_vel * frame_length; // current movement GetObject()->m_pos += extra_dist * target_forward; // any extra dist from previous segment } else { return NULL; } return start_node; } /******************************************************************/ /* */ /* */ /******************************************************************/ void CSkaterNonLocalNetLogicComponent::extrapolate_rail_position( void ) { GameNet::Manager* gamenet_man = GameNet::Manager::Instance(); CRailManager* p_rail_man = Mdl::Skate::Instance()->GetRailManager(); CRailNode* start_node; int i, prev_index, most_recent_index, mag_index; SPosEvent* p_pos_history; unsigned int delta_t, net_lag; float coeff, ratio, total_mag, total_ratio, total_coeff, vel_mag, frame_length; GameNet::PlayerInfo* local_player; Net::Client* client; Mth::Vector vel; client = gamenet_man->GetClient( 0 ); p_pos_history = mp_state_history_component->GetPosHistory(); most_recent_index = ( mp_state_history_component->GetNumPosUpdates() - 1 ) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS; prev_index = ( m_last_pos_index + ( CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS - 1 )) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS; delta_t = (int) ( m_frame_length * Tmr::vRESOLUTION ); if( gamenet_man->OnServer()) { GameNet::PlayerInfo* player; Net::Conn* client_conn; player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID()); Dbg_Assert( player ); client_conn = player->m_Conn; net_lag = client_conn->GetAveLatency(); } else { Lst::Search< Net::Conn > sh; Net::Conn* server_conn; server_conn = client->FirstConnection( &sh ); net_lag = server_conn->GetAveLatency(); } // protect against dbz if( delta_t == 0 ) { ratio = 0; } else { ratio = (float) ( net_lag - delta_t ) / (float) delta_t; if( ratio < 0 ) { ratio = 0; } } mag_index = m_num_mags % vMAG_HISTORY_LENGTH; vel = m_interp_pos - m_old_interp_pos; coeff = 0.0f; local_player = gamenet_man->GetLocalPlayer(); if( local_player ) { Mth::Vector my_vel, their_vel; my_vel = local_player->m_Skater->GetVel(); their_vel = vel; my_vel.Normalize(); their_vel.Normalize(); // Get the dot product of our movement vectors coeff = Mth::DotProduct( my_vel, their_vel ); // This will result in a value between -1.0f and 1.0f. Adding one will make that // between 0 and 2 coeff += 1.0f; // We want a value between 0 and 1, so divide by 2 coeff /= 2.0f; } m_mag_history[mag_index] = ( vel.Length() / delta_t ) * 16; m_ratio_history[mag_index] = ratio; m_extrap_coeff_history[mag_index] = coeff; m_num_mags++; total_mag = 0; total_ratio = 0; total_coeff = 0; for( i = 0; i < vMAG_HISTORY_LENGTH; i++ ) { if( i >= m_num_mags ) { break; } total_mag += m_mag_history[i]; total_ratio += m_ratio_history[i]; total_coeff += m_extrap_coeff_history[i]; } vel_mag = total_mag / i; coeff = total_coeff / i; ratio = total_ratio / i; ratio *= coeff; vel.Normalize(); vel *= vel_mag; frame_length = m_frame_length * ratio; // Sanity check to avoid infinite loops if( frame_length > 1.0f ) { frame_length = 1.0f; } start_node = p_rail_man->GetRailNodeByNodeNumber( p_pos_history[m_last_pos_index].RailNode ); if( start_node ) { while( frame_length > 0 ) { if( frame_length > m_frame_length ) { start_node = travel_on_rail( start_node, m_frame_length ); } else { start_node = travel_on_rail( start_node, frame_length ); } // If we grinded off a rail, use standard extrapolation instead. if( start_node == NULL ) { GetObject()->m_pos = m_interp_pos; extrapolate_position(); break; } frame_length -= m_frame_length; } } } /******************************************************************/ /* */ /* */ /******************************************************************/ float CSkaterNonLocalNetLogicComponent::rotate_away_from_wall ( Mth::Vector normal, float &turn_angle ) { float lerp; lerp = 1.0f; // given a wall normal, then calculate the "turn_angle" to rotate the skater by and rotate the matrix and display matrix by this // return the angle between the skater and the wall // note turn_angle is passed by reference, and is altered !!!! // given m_right(dot)normal, we should be able to get a nice angle float dot_right_normal = Mth::DotProduct(GetObject()->m_matrix[X], normal); float angle = acosf(Mth::Clamp(dot_right_normal, -1.0f, 1.0f)); if (angle > Mth::PI / 2.0f) { angle -= Mth::PI; } // angle away from the wall turn_angle = angle * GetPhysicsFloat(CRCD(0xe07ee1a9, "Wall_Bounce_Angle_Multiplier")) * lerp; // Rotate the skater so he is at a slight angle to the wall, especially if we are in a right angled corner, where the skater will bounce out GetObject()->m_vel.RotateY(turn_angle); GetObject()->m_matrix.RotateYLocal(turn_angle); return angle; } /******************************************************************/ /* */ /* */ /******************************************************************/ void CSkaterNonLocalNetLogicComponent::bounce_off_wall ( const Mth::Vector& normal, const Mth::Vector& point ) { // Given the normal of the wall, then bounce off it, turning the skater away from the wall Mth::Vector col_start, col_end; Mth::Vector forward = GetObject()->m_pos - m_old_extrap_pos; Mth::Vector movement = forward; // remember how far we moved CFeeler feeler; forward.Normalize(); Mth::Vector up_offset = GetObject()->m_matrix[Y] * GetPhysicsFloat(CRCD(0xd4205c9b,"Skater_First_Forward_Collision_Height")); float turn_angle; float angle = rotate_away_from_wall(normal, turn_angle); float min = Mth::DegToRad(GetPhysicsFloat(CRCD(0x1483fd01, "Wall_Bounce_Dont_Slow_Angle"))); if (Mth::Abs(angle) > min) { //float old_speed = GetObject()->m_vel.Length(); // The maximum value of Abs(angle) would be PI/2 (90 degrees), so scale the velocity float x = Mth::Abs(angle) - min; x /= (Mth::PI / 2.0f) - min; // get in the range 0 .. 1 x = 1.0f - x; // invert, as we want to stop when angle is 90 GetObject()->m_vel *= x; } // Bit of a patch here to move the skater away from the wall // Not needed so much with new sideways collision checks but we keep it in for low ledges. // Should perhaps standardize the height so collision checks for side and front but we'd probably still have problems. GetObject()->m_pos = point - up_offset; GetObject()->m_pos += normal * 6.0f; // Now the majority of cases have been taken care of; we need to see if the skater is going to get stuck in a corner. float old_speed = movement.Length(); // get how much we moved last time Mth::Vector next_movement = GetObject()->m_vel; // get new direction of velocity forward = GetObject()->m_vel; forward.Normalize(); next_movement = forward * old_speed; // extend by same movment as last time col_start = GetObject()->m_pos + up_offset; col_start += up_offset; col_end = GetObject()->m_pos + up_offset + next_movement + forward * GetPhysicsFloat(CRCD(0x20102726, "Skater_First_Forward_Collision_Length")); // Sanity check to make sure we're not using a line that's a whole level long if(( col_start - col_end ).Length() > FEET( 20 )) { return; } feeler.SetLine( col_start, col_end ); feeler.SetIgnore( mFD_NON_COLLIDABLE, 0 ); if( feeler.GetCollision() && GetSkater()->IsLocalClient()) { // Just rotating the skater will lead to another collision, so try just inverting the skater's velocity from it's original and halving it.... // First reverse the rotation, and rotate 180 degrees GetObject()->m_vel.RotateY(Mth::DegToRad(180.0f) - turn_angle); GetObject()->m_matrix.RotateYLocal(Mth::DegToRad(180.0f) - turn_angle); //ResetLerpingMatrix(); GetObject()->m_vel *= 0.5f; } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CSkaterNonLocalNetLogicComponent::extrapolate_position( void ) { GameNet::Manager* gamenet_man = GameNet::Manager::Instance(); int i, prev_index, most_recent_index, mag_index; SPosEvent* p_pos_history; unsigned int delta_t, net_lag; float coeff, ratio, total_mag, total_ratio, total_coeff, vel_mag; Mth::Vector extrap_pos; GameNet::PlayerInfo* local_player; Mth::Vector vel; Net::Client* client; client = gamenet_man->GetClient( 0 ); p_pos_history = mp_state_history_component->GetPosHistory(); most_recent_index = ( mp_state_history_component->GetNumPosUpdates() - 1 ) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS; prev_index = ( m_last_pos_index + ( CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS - 1 )) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS; delta_t = (int) ( m_frame_length * Tmr::vRESOLUTION ); if( gamenet_man->OnServer()) { GameNet::PlayerInfo* player; Net::Conn* client_conn; player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID()); Dbg_Assert( player ); client_conn = player->m_Conn; net_lag = client_conn->GetAveLatency(); } else { Lst::Search< Net::Conn > sh; Net::Conn* server_conn; server_conn = client->FirstConnection( &sh ); net_lag = server_conn->GetAveLatency(); } // protect against dbz if( delta_t == 0 ) { ratio = 0; } else { ratio = (float) ( net_lag - delta_t ) / (float) delta_t; if( ratio < 0 ) { ratio = 0; } } mag_index = m_num_mags % vMAG_HISTORY_LENGTH; vel = m_interp_pos - m_old_interp_pos; coeff = 0.0f; local_player = gamenet_man->GetLocalPlayer(); if( local_player && local_player->m_Skater ) { Mth::Vector my_vel, their_vel; my_vel = local_player->m_Skater->GetVel(); their_vel = vel; my_vel.Normalize(); their_vel.Normalize(); // Get the dot product of our movement vectors coeff = Mth::DotProduct( my_vel, their_vel ); // This will result in a value between -1.0f and 1.0f. Adding one will make that // between 0 and 2 coeff += 1.0f; // We want a value between 0 and 1, so divide by 2 coeff /= 2.0f; } else { coeff = 0.0f; } m_mag_history[mag_index] = ( vel.Length() / delta_t ) * 16; m_ratio_history[mag_index] = ratio; m_extrap_coeff_history[mag_index] = coeff; m_num_mags++; total_mag = 0; total_ratio = 0; total_coeff = 0; for( i = 0; i < vMAG_HISTORY_LENGTH; i++ ) { if( i >= m_num_mags ) { break; } total_mag += m_mag_history[i]; total_ratio += m_ratio_history[i]; total_coeff += m_extrap_coeff_history[i]; } vel_mag = total_mag / i; coeff = total_coeff / i; ratio = total_ratio / i; ratio *= coeff; vel.Normalize(); vel *= vel_mag; extrap_pos = GetObject()->m_pos + ( vel * ratio ); extrap_pos[Y] = GetObject()->m_pos[Y]; GetObject()->m_pos = extrap_pos; Mth::Vector up_offset = GetObject()->m_matrix[Y] * GetPhysicsFloat(CRCD(0xd4205c9b, "Skater_First_Forward_Collision_Height")); Mth::Vector col_start = m_interp_pos + up_offset; Mth::Vector col_end = GetObject()->m_pos + up_offset; CFeeler feeler; // Sanity check to make sure we're not using a line that's a whole level long if(( col_start - col_end ).Length() > FEET( 10 )) { return; } feeler.SetLine( col_start, col_end ); feeler.SetIgnore( mFD_NON_COLLIDABLE, 0 ); if( feeler.GetCollision()) { bounce_off_wall( feeler.GetNormal(), feeler.GetPoint()); } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CSkaterNonLocalNetLogicComponent::extrapolate_client_position ( ) { if( mp_state_history_component->GetNumPosUpdates() >= CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS ) { if( mp_state_component->GetState() == RAIL ) { extrapolate_rail_position(); } else { extrapolate_position(); snap_to_ground(); } } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CSkaterNonLocalNetLogicComponent::interpolate_client_position ( ) { GameNet::Manager* gamenet_man = GameNet::Manager::Instance(); Net::Client* client; SPosEvent* p_pos_history = mp_state_history_component->GetPosHistory(); if(( client = gamenet_man->GetClient( 0 ))) { // Wait for the object update buffer to fill up if( mp_state_history_component->GetNumPosUpdates() >= CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS ) { unsigned int cur_time; int i, prev_index, start_index, total_t, delta_t; float ratio; Mth::Vector delta_pos; // This will be true only if we've never performed an interpolation if( m_client_initial_update_time == 0 ) { //Dbg_Printf( "Resync'd Skater %d\n", GetID() ); // start off at the latest update and offset ourselves backwards in time from there start_index = ( mp_state_history_component->GetNumPosUpdates() - 1 ) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS; m_client_initial_update_time = client->m_Timestamp; // impose a lag of N frames m_server_initial_update_time = p_pos_history[start_index].GetTime() - Tmr::VBlanksF( vNUM_LAG_FRAMES ); m_last_pos_index = start_index; //Dbg_Printf( "(%d) server initial time = %d\n", nonlocalnet->m_FrameCounter, (int) m_server_initial_update_time ); //Dbg_Printf( "(%d) nonlocalnet initial time = %d\n", nonlocalnet->m_FrameCounter, (int) m_client_initial_update_time ); GetObject()->SetMatrix( p_pos_history[start_index].Matrix ); GetObject()->SetDisplayMatrix( p_pos_history[start_index].Matrix ); GetObject()->SetPos(p_pos_history[start_index].Position); GetObject()->SetTeleported(); GetObject()->m_vel.Set(0.0f, 0.0f, 0.0f); // We don't want to actually render the skaters until we have buffered // up their position updates and are ready to start interpolating. Now we have // that data, so add them to the world if( !GetSkater()->IsInWorld()) { GameNet::PlayerInfo* player; GetSkater()->AddToCurrentWorld(); Script::RunScript( "NetIdle", NULL, GetObject() ); //Hide( false ); player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID() ); Dbg_Assert( player ); // We're just adding them to the world. If they were standing still, // UberFrig would not have been done. So let's do it here setup_brightness_and_shadow(); } } else { Mth::SlerpInterpolator slerp; Mth::Matrix interp_matrix; int most_recent_index, flag; GameNet::PlayerInfo* player; // Maybe a little bit overkill, but this will definitely set skaters that we can // see in the level to be "fully in", which triggers things like their names in // the score list player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID() ); if( !player->IsFullyIn()) { player->MarkAsFullyIn(); } start_index = mp_state_history_component->GetNumPosUpdates() % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS; most_recent_index = ( mp_state_history_component->GetNumPosUpdates() - 1 ) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS; cur_time = m_server_initial_update_time + ( client->m_Timestamp - m_client_initial_update_time ); // If we've gone past the most recent update, slow down a little to allow updates // to catch up //Dbg_Printf( "(%d) NonLocalNet Timestamp %d\n", nonlocalnet->m_FrameCounter, nonlocalnet->m_Timestamp ); if( cur_time > p_pos_history[most_recent_index].GetTime()) { // If we have some major catching up to do (usually at the beginning // when there are long pauses after loading) catch up in one step if(( cur_time - p_pos_history[most_recent_index].GetTime()) > Tmr::Seconds( 1 )) { //Dbg_Printf( "************************ (%d) Major catchup\n", client->m_FrameCounter ); m_server_initial_update_time -= ( cur_time - p_pos_history[most_recent_index].GetTime()); } else { //Dbg_Printf( "************************ (%d) (%d) Slowdown\n", GetObject()->GetID(), client->m_FrameCounter ); m_server_initial_update_time -= Tmr::VBlanks( 1 ); } cur_time = m_server_initial_update_time + ( client->m_Timestamp - m_client_initial_update_time ); //Dbg_Printf( "- Slowdown\n" ); } i = start_index; do { if( cur_time <= p_pos_history[i].GetTime()) { static int lag = 0; m_last_pos_index = i; lag = ( start_index + (CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS - i )) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS; // We probably also should adjust if we get too far behind if( lag >= 10 ) { //Dbg_Printf( "************************ (%d) (%d) +7 Catchup. Lag : %d\n", client->m_FrameCounter, GetObject()->GetID(), lag ); m_server_initial_update_time += Tmr::VBlanks( 10 ); } else if( lag == 0 ) { //Dbg_Printf( "************************ (%d) Resync'ing skater %d\n", client->m_FrameCounter, GetObject()->GetID() ); GetSkater()->Resync(); } prev_index = ( i + ( CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS - 1 )) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS; delta_t = cur_time - p_pos_history[prev_index].GetTime(); total_t = p_pos_history[i].GetTime() - p_pos_history[prev_index].GetTime(); ratio = (float) delta_t / (float) total_t; delta_pos = p_pos_history[i].Position - p_pos_history[prev_index].Position; m_old_interp_pos = m_interp_pos; // Slerp between the two matrices Mth::Matrix t1, t2; Mth::Vector diff; diff = p_pos_history[i].Matrix[Z] - p_pos_history[prev_index].Matrix[Z]; // Don't slerp if the matrices are way different. Just stick with the matrix // we have until the switch is made. if( diff.Length() > 0.75f ) { GetObject()->SetMatrix( p_pos_history[prev_index].Matrix ); GetObject()->SetDisplayMatrix( p_pos_history[prev_index].Matrix ); } else { t1 = p_pos_history[prev_index].Matrix; t2 = p_pos_history[i].Matrix; slerp.setMatrices( &t1, &t2 ); slerp.getMatrix( &interp_matrix, ratio ); GetObject()->SetMatrix( interp_matrix ); GetObject()->SetDisplayMatrix( interp_matrix ); } if( delta_pos.Length() > FEET( 50 )) { GetObject()->m_pos = p_pos_history[i].Position; GetObject()->m_vel = Mth::Vector( 0, 0, 0 ); GetObject()->SetTeleported(); } else { int idx, mag_index; Mth::Vector total_vel; GetObject()->m_pos = p_pos_history[prev_index].Position + ( delta_pos * ratio ); GetObject()->m_vel = ( GetObject()->m_pos - m_old_interp_pos ) / m_frame_length; // do_nonlocalnet_position_update, calculating speed mag_index = m_num_mags % vMAG_HISTORY_LENGTH; // Smooth out changes in velocity m_vel_history[mag_index] = GetObject()->m_vel; for( idx = 0; idx < vMAG_HISTORY_LENGTH; idx++ ) { if( idx >= m_num_mags ) { break; } total_vel += m_vel_history[idx]; } if( idx > 0 ) { GetObject()->m_vel = total_vel / idx; } } m_interp_pos = GetObject()->m_pos; // Apply the state/flag data from this PosEvent mp_state_component->m_state = static_cast< EStateType >( p_pos_history[i].State ); mp_state_component->m_doing_trick = p_pos_history[i].DoingTrick; mp_state_component->m_terrain = p_pos_history[i].Terrain; mp_state_component->m_physics_state = p_pos_history[i].Walking ? WALKING : SKATING; mp_state_component->m_driving = p_pos_history[i].Driving; mp_endrun_component->SetFlags( p_pos_history[i].EndRunFlags ); if( p_pos_history[i].EndRunFlags & CSkaterEndRunComponent::FINISHED_END_OF_RUN ) { //Dbg_Printf( "*** At END OF RUN ***\n" ); } for( flag = 0; flag < NUM_ESKATERFLAGS; flag++ ) { mp_state_component->m_skater_flags[flag].Set(p_pos_history[i].SkaterFlags.Test( flag )); } if( mp_state_component->m_skater_flags[SPINE_PHYSICS].Get()) { mp_state_component->m_spine_vel = GetObject()->m_vel; } else { mp_state_component->m_spine_vel.Set(0.0f, 0.0f, 0.0f); } break; } i = ( i + 1 ) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS; } while( i != start_index ); } } } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CSkaterNonLocalNetLogicComponent::do_client_animation_update( ) { GameNet::Manager* gamenet_man = GameNet::Manager::Instance(); Net::Client* client; Dbg_Assert( mp_animation_component ); if(( client = gamenet_man->GetClient( 0 ))) { // Wait for the object update buffer to fill up if( mp_state_history_component->GetNumPosUpdates() >= CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS ) { unsigned int cur_time; int i, start_index; SAnimEvent* event; int most_recent_index; start_index = mp_state_history_component->GetNumAnimUpdates() % CSkaterStateHistoryComponent::vNUM_ANIM_HISTORY_ELEMENTS; most_recent_index = ( mp_state_history_component->GetNumAnimUpdates() - 1 ) % CSkaterStateHistoryComponent::vNUM_ANIM_HISTORY_ELEMENTS; cur_time = m_server_initial_update_time + ( client->m_Timestamp - m_client_initial_update_time ); i = start_index; bool updated = false; do { event = &mp_state_history_component->GetAnimHistory()[i]; //Dbg_Printf( "(%d) CurTime: %d EventTime[%d]: %d\n", client->m_FrameCounter, cur_time, event->m_MsgId, event->GetTime()); if( ( event->GetTime() > m_last_anm_update_time ) && ( event->GetTime() <= cur_time )) { updated = true; switch( event->m_MsgId ) { case GameNet::MSG_ID_PRIM_ANIM_START: { //Dbg_Printf( "(%d) Playing primary anim: 0x%x Speed: %f Duration: %d Time:%d\n", client->m_Timestamp, event->m_Asset, event->m_Speed, event->m_Duration, event->GetTime()); mp_animation_component->PlayPrimarySequence( event->m_Asset, false, event->m_StartTime, event->m_EndTime, (Gfx::EAnimLoopingType) event->m_LoopingType, event->m_BlendPeriod, event->m_Speed ); break; } case GameNet::MSG_ID_SET_WOBBLE_TARGET: { mp_animation_component->SetWobbleTarget( event->m_Alpha, false ); break; } case GameNet::MSG_ID_ROTATE_SKATEBOARD: { mp_flip_and_rotate_component->RotateSkateboard( event->m_ObjId, event->m_Rotate, 0, false ); break; } case GameNet::MSG_ID_SET_WOBBLE_DETAILS: { Gfx::SWobbleDetails wobbleDetails; wobbleDetails.wobbleAmpA = event->m_WobbleAmpA; wobbleDetails.wobbleAmpB = event->m_WobbleAmpB; wobbleDetails.wobbleK1 = event->m_WobbleK1; wobbleDetails.wobbleK2 = event->m_WobbleK2; wobbleDetails.spazFactor = event->m_SpazFactor; mp_animation_component->SetWobbleDetails( wobbleDetails, false ); break; } case GameNet::MSG_ID_SET_LOOPING_TYPE: { mp_animation_component->SetLoopingType( (Gfx::EAnimLoopingType) event->m_LoopingType, false ); break; } case GameNet::MSG_ID_SET_HIDE_ATOMIC: { mp_model_component->HideGeom( event->m_Asset, event->m_Hide, false ); break; } case GameNet::MSG_ID_SET_ANIM_SPEED: { mp_animation_component->SetAnimSpeed( event->m_Speed, false ); break; } case GameNet::MSG_ID_FLIP_ANIM: { mp_animation_component->FlipAnimation( event->m_ObjId, event->m_Flipped, 0, false ); break; } case GameNet::MSG_ID_ROTATE_DISPLAY: { mp_model_component->m_display_rotation_offset.Set(0,30,0); Tmr::Time start_time=Tmr::ElapsedTime(0); if( event->m_Flags & 1 ) { mp_model_component->mpDisplayRotationInfo[0].SetUp(event->m_Duration, start_time, event->m_StartAngle, event->m_DeltaAngle, event->m_SinePower, event->m_HoldOnLastAngle); } if( event->m_Flags & 2 ) { mp_model_component->mpDisplayRotationInfo[1].SetUp(event->m_Duration, start_time, event->m_StartAngle, event->m_DeltaAngle, event->m_SinePower, event->m_HoldOnLastAngle); } if( event->m_Flags & 4 ) { mp_model_component->mpDisplayRotationInfo[2].SetUp(event->m_Duration, start_time, event->m_StartAngle, event->m_DeltaAngle, event->m_SinePower, event->m_HoldOnLastAngle); } break; } case (char) GameNet::MSG_ID_CLEAR_ROTATE_DISPLAY: { mp_model_component->mpDisplayRotationInfo[0].Clear(); mp_model_component->mpDisplayRotationInfo[1].Clear(); mp_model_component->mpDisplayRotationInfo[2].Clear(); break; } case GameNet::MSG_ID_CREATE_SPECIAL_ITEM: { CSpecialItemComponent* pSpecialItemComponent = GetSpecialItemComponentFromObject(GetObject()); Dbg_MsgAssert( pSpecialItemComponent, ( "No special item component?" ) ); Script::CStruct* pSpecialItemParams = Script::GetStructure( event->m_Asset, Script::ASSERT ); CCompositeObject* pSpecialItemObject = pSpecialItemComponent->CreateSpecialItem( event->m_Index, pSpecialItemParams ); Script::CStruct* pLockParams = new Script::CStruct; // pass along any bone or offset parameters... //pLockParams->AppendStructure( pParams ); pLockParams->AddChecksum( CRCD(0xcab94088,"bone"), event->m_Bone ); pLockParams->AddChecksum( CRCD(0x40c698af,"id"), GetObject()->GetID() ); // component-based CLockObjComponent* pLockObjComponent = GetLockObjComponentFromObject( pSpecialItemObject ); Dbg_MsgAssert( pLockObjComponent, ( "No lockobj component" ) ); pLockObjComponent->InitFromStructure( pLockParams ); delete pLockParams; } break; case GameNet::MSG_ID_DESTROY_SPECIAL_ITEM: { CSpecialItemComponent* pSpecialItemComponent = GetSpecialItemComponentFromObject(GetObject()); pSpecialItemComponent->DestroySpecialItem( event->m_Index ); break; } } } i = ( i + 1 ) % CSkaterStateHistoryComponent::vNUM_ANIM_HISTORY_ELEMENTS; } while( i != start_index ); /*static Tmr::Time s_time = 0; if( !updated ) { if(( Tmr::GetTime() - s_time ) > 1000 ) { i = start_index; do { event = &mp_state_history_component->GetAnimHistory()[i]; Dbg_Printf( "(%d) CurTime: %d EventTime[%d]: %d\n", client->m_FrameCounter, cur_time, event->m_MsgId, event->GetTime()); i = ( i + 1 ) % CSkaterStateHistoryComponent::vNUM_ANIM_HISTORY_ELEMENTS; } while( i != start_index ); s_time = Tmr::GetTime(); } }*/ m_last_anm_update_time = cur_time; } } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CSkaterNonLocalNetLogicComponent::setup_brightness_and_shadow ( ) { // logic extracted from CAdjustComponent::uber_frig which is judged to be required by nonlocal clients CFeeler feeler; feeler.m_start = GetObject()->m_pos; feeler.m_end = GetObject()->m_pos; // Very minor adjustment to move origin away from vert walls feeler.m_start += GetObject()->m_matrix[Y] * 0.001f; feeler.m_start[Y] += 8.0f; feeler.m_end[Y] -= FEET(400); if (!feeler.GetCollision()) return; if (mp_state_component->GetState() != RAIL && feeler.IsBrightnessAvailable()) { Nx::CModelLights *p_model_lights = mp_model_component->GetModel()->GetModelLights(); if (p_model_lights) { p_model_lights->SetBrightness(feeler.GetBrightness()); } else { // Garrett: This should move to CModel eventually Nx::CLightManager::sSetBrightness(feeler.GetBrightness()); } } // Store these values off for the simple shadow calculation. mp_shadow_component->SetShadowPos(feeler.GetPoint()); mp_shadow_component->SetShadowNormal(feeler.GetNormal()); } }