//-----------------------------------------------------------------------------
// Purpose: Initialize the rays to be cast from the vehicle wheel positions to
//          the "ground."
//-----------------------------------------------------------------------------
void IVP_Controller_Raycast_Airboat::PreRaycasts( IVP_Ray_Solver_Template *pRaySolverTemplates, 
												  const IVP_U_Matrix *matWorldFromCore, 
												  IVP_Raycast_Airboat_Pontoon_Temp *pTempPontoons )
{
	int nPontoonPoints = n_wheels;
	for ( int iPoint = 0; iPoint < nPontoonPoints; ++iPoint )
	{
		IVP_Raycast_Airboat_Wheel *pPontoonPoint = get_wheel( IVP_POS_WHEEL( iPoint ) );
		if ( pPontoonPoint )
		{
			// Fill the in the ray solver template for the current wheel.
			IVP_Ray_Solver_Template &raySolverTemplate = pRaySolverTemplates[iPoint];

			// Transform the wheel "start" position from vehicle core-space to world-space.  This is
			// the raycast starting position.
			matWorldFromCore->vmult4( &pPontoonPoint->raycast_start_cs, &raySolverTemplate.ray_start_point );

			// Transform the shock (spring) direction from vehicle core-space to world-space.  This is
			// the raycast direction.
			matWorldFromCore->vmult3( &pPontoonPoint->raycast_dir_cs, &pTempPontoons[iPoint].raycast_dir_ws );
			raySolverTemplate.ray_normized_direction.set( &pTempPontoons[iPoint].raycast_dir_ws );

			// Set the length of the ray cast.
			raySolverTemplate.ray_length = AIRBOAT_RAYCAST_DIST;	

			// Set the ray solver template flags.  This defines wish objects you wish to
			// collide against in the physics environment.
			raySolverTemplate.ray_flags = IVP_RAY_SOLVER_ALL;
		}
	}
}
void IVP_Controller_Raycast_Airboat::change_wheel_torque(IVP_POS_WHEEL pos, IVP_FLOAT torque)
{
    IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
    wheel->torque = torque;

	// Wake the physics object if need be!
	m_pAirboatBody->get_environment()->get_controller_manager()->ensure_controller_in_simulation( this );
}
void IVP_Controller_Raycast_Airboat::do_steering_wheel(IVP_POS_WHEEL wheel_nr, IVP_FLOAT s_angle)
{
    IVP_Raycast_Airboat_Wheel *wheel = get_wheel(wheel_nr);
	
    wheel->axis_direction_cs.set_to_zero();
    wheel->axis_direction_cs.k[ index_x ] = 1.0f;
    wheel->axis_direction_cs.rotate( IVP_COORDINATE_INDEX(index_y), s_angle);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void IVP_Controller_Raycast_Airboat::DoSimulationPontoons( IVP_Raycast_Airboat_Pontoon_Temp *pTempPontoons,
													       IVP_Raycast_Airboat_Impact *pImpacts, IVP_Event_Sim *pEventSim )
{
	for ( int iPoint = 0; iPoint < n_wheels; ++iPoint )
	{
		IVP_Raycast_Airboat_Wheel *pPontoonPoint = get_wheel( IVP_POS_WHEEL( iPoint ) );
		if ( !pPontoonPoint )
			continue;

		if ( pImpacts[iPoint].bImpact )
			DoSimulationPontoonsGround( pPontoonPoint, &pTempPontoons[iPoint], &pImpacts[iPoint], pEventSim );
		else if ( pImpacts[iPoint].bInWater )
			DoSimulationPontoonsWater( &pTempPontoons[iPoint], &pImpacts[iPoint], pEventSim );
	}
}
void CPhysics_Car_System_Raycast_Wheels::update_wheel_positions( void )
{
	// Get the car body object.
    IVP_Cache_Object *pCacheObject = car_body->get_cache_object();

	// Get the core (vehicle) matrix.
    IVP_U_Matrix m_core_f_object;    
	car_body->calc_m_core_f_object( &m_core_f_object );
    
    for ( int iWheel = 0; iWheel < n_wheels; ++iWheel )
	{
		// Get the current raycast wheel.
		IVP_Raycast_Car_Wheel *pRaycastWheel = get_wheel( IVP_POS_WHEEL( iWheel ) );

		// Get the position of the wheel in vehicle core space.
		IVP_U_Float_Point hp_cs; 
		hp_cs.add_multiple( &pRaycastWheel->hp_cs, &pRaycastWheel->spring_direction_cs, pRaycastWheel->raycast_dist - pRaycastWheel->wheel_radius );

		// Get the position on vehicle object space (inverse transform).
		IVP_U_Float_Point hp_os; 
		m_core_f_object.vimult4( &hp_cs, &hp_os );

		// Transform the wheel position from object space into world space.
		IVP_U_Point hp_ws; 
		pCacheObject->transform_position_to_world_coords( &hp_os, &hp_ws );
		
		// Apply rotational component.
		IVP_U_Point wheel_cs( &pRaycastWheel->axis_direction_cs );
		IVP_U_Point wheel2_cs( 0 ,0 ,0 ); 
		wheel2_cs.k[index_y] = -1.0;
		wheel2_cs.rotate( IVP_COORDINATE_INDEX( index_x ), pRaycastWheel->angle_wheel );
		
		IVP_U_Matrix3 m_core_f_wheel;
		m_core_f_wheel.init_normized3_col( &wheel_cs, IVP_COORDINATE_INDEX( index_x ), &wheel2_cs );
		
		IVP_U_Matrix3 m_world_f_wheel;
		pCacheObject->m_world_f_object.mmult3( &m_core_f_wheel, &m_world_f_wheel ); // bid hack, assumes cs = os (for rotation);
		
		IVP_U_Quat rot_ws; 
		rot_ws.set_quaternion( &m_world_f_wheel );
		m_pWheels[iWheel]->beam_object_to_new_position( &rot_ws, &hp_ws );
    }

    pCacheObject->remove_reference();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void IVP_Controller_Raycast_Airboat::InitRaycastCarWheels( const IVP_Template_Car_System *pCarSystemTemplate )
{
    IVP_U_Matrix m_core_f_object;
    m_pAirboatBody->calc_m_core_f_object( &m_core_f_object );

	// Initialize the car wheel system.
    for ( int iWheel = 0; iWheel < n_wheels; iWheel++ )
	{
		// Get and clear out memory for the current raycast wheel.
		IVP_Raycast_Airboat_Wheel *pRaycastWheel = get_wheel( IVP_POS_WHEEL( iWheel ) );
		P_MEM_CLEAR( pRaycastWheel );

		// Put the wheel in car space.
		m_core_f_object.vmult4( &pCarSystemTemplate->wheel_pos_Bos[iWheel], &pRaycastWheel->hp_cs );
		m_core_f_object.vmult4( &pCarSystemTemplate->trace_pos_Bos[iWheel], &pRaycastWheel->raycast_start_cs );
		
		// Add in the raycast start offset.
		pRaycastWheel->raycast_length = AIRBOAT_RAYCAST_DIST;
		pRaycastWheel->raycast_dir_cs.set_to_zero();
		pRaycastWheel->raycast_dir_cs.k[index_y] = gravity_y_direction;

		// Spring (Shocks) data.
		pRaycastWheel->spring_len = -pCarSystemTemplate->spring_pre_tension[iWheel];

		pRaycastWheel->spring_direction_cs.set_to_zero();
		pRaycastWheel->spring_direction_cs.k[index_y] = gravity_y_direction;
		
		pRaycastWheel->spring_constant = pCarSystemTemplate->spring_constant[iWheel];
		pRaycastWheel->spring_damp_relax = pCarSystemTemplate->spring_dampening[iWheel];
		pRaycastWheel->spring_damp_compress = pCarSystemTemplate->spring_dampening_compression[iWheel];

		// Wheel data.
		pRaycastWheel->friction_of_wheel = 1.0f;//pCarSystemTemplate->friction_of_wheel[iWheel];
		pRaycastWheel->wheel_radius = pCarSystemTemplate->wheel_radius[iWheel];
		pRaycastWheel->inv_wheel_radius = 1.0f / pCarSystemTemplate->wheel_radius[iWheel];

		do_steering_wheel( IVP_POS_WHEEL( iWheel ), 0.0f );
		
		pRaycastWheel->wheel_is_fixed = IVP_FALSE;	
		pRaycastWheel->max_rotation_speed = pCarSystemTemplate->wheel_max_rotation_speed[iWheel>>1];

		pRaycastWheel->wheel_is_fixed = IVP_TRUE;
    }
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void IVP_Controller_Raycast_Airboat::DoSimulationSteering( IVP_Raycast_Airboat_Pontoon_Temp *pTempPontoons,
													       IVP_Core *pAirboatCore, IVP_Event_Sim *pEventSim )
{
	IVP_U_Float_Point vecRightWS; 
    const IVP_U_Matrix *matWorldFromCore = pAirboatCore->get_m_world_f_core_PSI();
	matWorldFromCore->get_col( IVP_COORDINATE_INDEX( index_x ), &vecRightWS );

	IVP_FLOAT flForceRotational = 0.0f;

	// Check for rotation.
	if ( fabsf( m_SteeringAngle ) > 0.01 )
	{
		// Get the steering sign.
		IVP_FLOAT flSteeringSign = m_SteeringAngle < 0.0f ? -1.0f : 1.0f;
		flForceRotational = IVP_RAYCAST_AIRBOAT_STEERING_RATE * pAirboatCore->get_mass() * pEventSim->i_delta_time;
		flForceRotational *= flSteeringSign;
	}

	// General force that will help get us back to zero rotation.
	IVP_FLOAT flRotSpeedSign = pAirboatCore->rot_speed.k[1] < 0.0f ? -1.0f : 1.0f;
	IVP_FLOAT flRotationalDrag = -0.5f * IVP_RAYCAST_AIRBOAT_STEERING_RATE * pAirboatCore->get_mass() * pEventSim->i_delta_time;
	flRotationalDrag *= flRotSpeedSign;
	flForceRotational += flRotationalDrag;
	
	IVP_U_Float_Point vecImpulse; 
	for ( int iPoint = 0; iPoint < 4; ++iPoint )
	{
		IVP_Raycast_Airboat_Wheel *pPontoonPoint = get_wheel( IVP_POS_WHEEL( iPoint ) );
		IVP_Raycast_Airboat_Pontoon_Temp *pTempPontoonPoint = &pTempPontoons[iPoint];

		IVP_FLOAT flPontoonSign = iPoint >= 2 ? -1.0f : 1.0f;
		IVP_FLOAT flForceRot = flForceRotational * flPontoonSign;

		vecImpulse.set_multiple( &vecRightWS, flForceRot * pEventSim->delta_time );

		IVP_U_Float_Point vecPointPosCS, vecPointPosWS;
		vecPointPosCS = pPontoonPoint->raycast_start_cs;
		matWorldFromCore->vmult3( &vecPointPosCS, &vecPointPosWS );
		IVP_U_Point vecPointPositionWS( vecPointPosWS );
		pAirboatCore->push_core_ws( &vecPointPositionWS, &vecImpulse );		
	}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void IVP_Controller_Raycast_Airboat::DoSimulationPontoons( IVP_Raycast_Airboat_Pontoon_Temp *pTempPontoons,
													       IVP_Raycast_Airboat_Impact *pImpacts, IVP_Event_Sim *pEventSim,
													       IVP_Core *pAirboatCore )
{
	int nPontoonPoints = n_wheels;
	for ( int iPoint = 0; iPoint < nPontoonPoints; ++iPoint )
	{
		IVP_Raycast_Airboat_Wheel *pPontoonPoint = get_wheel( IVP_POS_WHEEL( iPoint ) );
		if ( !pPontoonPoint )
			continue;

		if ( pImpacts[iPoint].bImpact )
		{
			DoSimulationPontoonsGround( pPontoonPoint, &pTempPontoons[iPoint], &pImpacts[iPoint], pEventSim, pAirboatCore );
		}
		else if ( pImpacts[iPoint].bInWater )
		{
			IVP_BOOL bFront = ( iPoint < 2 ) ? IVP_TRUE : IVP_FALSE;
			DoSimulationPontoonsWater( pPontoonPoint, &pTempPontoons[iPoint], &pImpacts[iPoint], pEventSim, pAirboatCore, bFront );
		}
	}
}
IVP_DOUBLE IVP_Controller_Raycast_Airboat::get_wheel_angular_velocity(IVP_POS_WHEEL pos)
{
    IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
    return wheel->wheel_angular_velocity;
}
void IVP_Controller_Raycast_Airboat::change_friction_of_wheel( IVP_POS_WHEEL pos, IVP_FLOAT friction )
{
	IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
	wheel->friction_of_wheel = friction;
}
void IVP_Controller_Raycast_Airboat::fix_wheel(IVP_POS_WHEEL pos, IVP_BOOL stop_wheel)
{
    IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
    wheel->wheel_is_fixed = stop_wheel;
}
void IVP_Controller_Raycast_Airboat::change_spring_length(IVP_POS_WHEEL pos, IVP_FLOAT spring_length)
{
    IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
    wheel->spring_len = spring_length;
}
void IVP_Controller_Raycast_Airboat::change_spring_pre_tension(IVP_POS_WHEEL pos, IVP_FLOAT pre_tension_length)
{
    IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
    wheel->spring_len = gravity_y_direction * (wheel->distance_orig_hp_to_hp - pre_tension_length);
}
void IVP_Controller_Raycast_Airboat::change_spring_dampening_compression(IVP_POS_WHEEL pos, IVP_FLOAT spring_dampening)
{
    IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
    wheel->spring_damp_compress = spring_dampening;
}
void IVP_Controller_Raycast_Airboat::change_spring_constant(IVP_POS_WHEEL pos, IVP_FLOAT spring_constant)
{
    IVP_Raycast_Airboat_Wheel *wheel = get_wheel(pos);
    wheel->spring_constant = spring_constant;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool IVP_Controller_Raycast_Airboat::PostRaycasts( IVP_Ray_Solver_Template *pRaySolverTemplates, const IVP_U_Matrix *matWorldFromCore, IVP_Raycast_Airboat_Pontoon_Temp *pTempPontoons, 
												   IVP_Raycast_Airboat_Impact *pImpacts )
{
	int nPontoonPoints = n_wheels;
	for( int iPoint = 0; iPoint < nPontoonPoints; ++iPoint )
	{
		// Get data at raycast position.
		IVP_Raycast_Airboat_Wheel *pPontoonPoint = get_wheel( IVP_POS_WHEEL( iPoint ) );
		IVP_Raycast_Airboat_Pontoon_Temp *pTempPontoonPoint = &pTempPontoons[iPoint];
		IVP_Raycast_Airboat_Impact *pImpact = &pImpacts[iPoint];
		IVP_Ray_Solver_Template *pRaySolver = &pRaySolverTemplates[iPoint];
		if ( !pPontoonPoint || !pTempPontoonPoint || !pImpact || !pRaySolver )
			continue;

		// Copy the ray length back, it may have changed.
		pPontoonPoint->raycast_length = pRaySolver->ray_length;

		// Test for inverted raycast direction.
		if ( pImpact->bInWater )
		{
			pTempPontoonPoint->raycast_dir_ws.set_multiple( &pTempPontoonPoint->raycast_dir_ws, -1 );
		}

		// Impact.
		if ( pImpact->bImpact )
		{
			// Save impact normal.
			pTempPontoonPoint->ground_normal_ws = pImpact->vecImpactNormalWS;
	
			// Save impact distance.
			IVP_U_Point vecDelta;
			vecDelta.subtract( &pImpact->vecImpactPointWS, &pRaySolver->ray_start_point );
			pPontoonPoint->raycast_dist = vecDelta.real_length();

			// Get the inverse portion of the surface normal in the direction of the ray cast (shock - used in the shock simulation code for the sign
			// and percentage of force applied to the shock).
			pTempPontoonPoint->inv_normal_dot_dir = 1.1f / ( IVP_Inline_Math::fabsd( pTempPontoonPoint->raycast_dir_ws.dot_product( &pTempPontoonPoint->ground_normal_ws ) ) + 0.1f );

			// Set the wheel friciton - ground friction (if any) + wheel friction.
			pTempPontoonPoint->friction_value = pImpact->flFriction * pPontoonPoint->friction_of_wheel;
		}
		// No impact.
		else
		{
			pPontoonPoint->raycast_dist = pPontoonPoint->raycast_length;

			pTempPontoonPoint->inv_normal_dot_dir = 1.0f;	    
			pTempPontoonPoint->moveable_object_hit_by_ray = NULL;
			pTempPontoonPoint->ground_normal_ws.set_multiple( &pTempPontoonPoint->raycast_dir_ws, -1 );
			pTempPontoonPoint->friction_value = 1.0f;
		}

		// Set the new wheel position (the impact point or the full ray distance).  Make this from the wheel not the ray trace position.
		pTempPontoonPoint->ground_hit_ws.add_multiple( &pRaySolver->ray_start_point, &pTempPontoonPoint->raycast_dir_ws, pPontoonPoint->raycast_dist );

		// Get the speed (velocity) at the impact point.
		m_pAirboatBody->get_core()->get_surface_speed_ws( &pTempPontoonPoint->ground_hit_ws, &pTempPontoonPoint->surface_speed_wheel_ws );
		pTempPontoonPoint->projected_surface_speed_wheel_ws.set_orthogonal_part( &pTempPontoonPoint->surface_speed_wheel_ws, &pTempPontoonPoint->ground_normal_ws );

		matWorldFromCore->vmult3( &pPontoonPoint->axis_direction_cs, &pTempPontoonPoint->axis_direction_ws );
		pTempPontoonPoint->projected_axis_direction_ws.set_orthogonal_part( &pTempPontoonPoint->axis_direction_ws, &pTempPontoonPoint->ground_normal_ws );
		if ( pTempPontoonPoint->projected_axis_direction_ws.normize() == IVP_FAULT )
		{
			printf( "IVP_Controller_Raycast_Airboat::do_simulation_controller projected_axis_direction_ws.normize failed\n" );
			return false;
		}
	}

	return true;
}