//=============================================================================
// SVehicle
// Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
//=============================================================================

class SVehicle extends Vehicle
	native(Physics)
	nativereplication
	abstract;


`if(`isdefined(WITH_PHYSX))
/** The main vehicle class pointer */
var native const pointer					PVehicle{class PxVehicleDrive};
/** Its vehicle manager pointer */
var native const pointer					MyVehicleManager{class FPhysXVehicleManager};
`endif 
cpptext
{
	// UObject interface
	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent);

	// Actor interface.
	virtual void physRigidBody(FLOAT DeltaTime);
	virtual void TickSimulated( FLOAT DeltaSeconds );
	virtual void TickAuthoritative( FLOAT DeltaSeconds );
	virtual void setPhysics(BYTE NewPhysics, AActor *NewFloor, FVector NewFloorV);
	virtual void PostNetReceiveLocation();
	INT* GetOptimizedRepList( BYTE* InDefault, FPropertyRetirement* Retire, INT* Ptr, UPackageMap* Map, UActorChannel* Channel );

	// SVehicle interface.
	virtual void VehiclePackRBState();
	virtual void VehicleUnpackRBState();
	virtual FVector GetDampingForce(const FVector& InForce);

#if WITH_NOVODEX

    virtual void OnRigidBodyCollision(const FRigidBodyCollisionInfo& MyInfo, const FRigidBodyCollisionInfo& OtherInfo, const FCollisionImpactData& RigidCollisionData);
	virtual void ModifyNxActorDesc(NxActorDesc& ActorDesc,UPrimitiveComponent* PrimComp, const class NxGroupsMask& GroupsMask, UINT MatIndex);
	virtual void PostInitRigidBody(NxActor* nActor, NxActorDesc& ActorDesc, UPrimitiveComponent* PrimComp);
	virtual void PreTermRigidBody(NxActor* nActor);
	virtual void TermRBPhys(FRBPhysScene* Scene);
#endif

#if WITH_PHYSX
	virtual void OnRigidBodyCollision(const FRigidBodyCollisionInfo& MyInfo, const FRigidBodyCollisionInfo& OtherInfo, const FCollisionImpactData& RigidCollisionData);
	virtual void PostInitRigidBody(PxActor* nActor, UPrimitiveComponent* PrimComp);
	virtual void TermRBPhys(FRBPhysScene* Scene);

	/** Create vehicle util function. */
	void CreateVehicleUtil();

	void DrawWheelsDebug(BOOL bDrawForcePoints);

	/** process vehicle control inputs */
	void ProcessVehicleInputs(FLOAT DeltaTime);	
#endif

	virtual void UpdateVehicle(ASVehicle* Vehicle, FLOAT DeltaTime) {}

	/** Set any params on wheel particle effect */
	virtual void SetWheelEffectParams(USVehicleWheel* VW, FLOAT SlipVel);
}


/** Chassis mass */
var(Vehicle) float ChassisMass;

/** If this vehicle is a tank */
var(Vehicle) BOOL bIsTank;

/** Enable debug rendering for this vehicle */
var(Vehicle) bool bEnableDebugRendering;

/** Current debug graph channels for wheel and engine */ 
var			INT DebugRenderActiveGraphChannelWheel;
var			INT DebugRenderActiveGraphChannelEngine;

/** Whether the vehicle is in reverse mode */
var			bool bInReverseMode;

/** Object containing the actual vehicle simulation paramters and code. Allows you to 'plug' different vehicle types in. */
var() noclear const			SVehicleSimBase			SimObj;

/** Data for each wheel. Very important to list in this order FrontLeft, FrontRight, RearLeft, RearRight. */
var() editinline export		array<SVehicleWheel>	Wheels;

/** Center of mass location of the vehicle, in local space. */
var()						vector					COMOffset;

/** Inertia Tensor Multipler - allows you to scale the components of the pre-calculated inertia tensor */
var()						vector					InertiaTensorMultiplier;

/** Use the stay-upright world constraint */
var(UprightConstraint)      bool                    bStayUpright;

/** Angle at which the vehicle will resist rolling */
var(UprightConstraint)      float                   StayUprightRollResistAngle;

/** Angle at which the vehicle will resist pitching */
var(UprightConstraint)      float                   StayUprightPitchResistAngle;

/** Amount of spring past the limit angles */
var(UprightConstraint)      float                   StayUprightStiffness;

/** Amount of dampening past the limit angles */
var(UprightConstraint)      float                   StayUprightDamping;

var editinline export	    RB_StayUprightSetup     StayUprightConstraintSetup;
var editinline export	    RB_ConstraintInstance   StayUprightConstraintInstance;

var                         bool                    bUseSuspensionAxis;

/** set this flag to cause wheel shape parameters to be updated.  Cleared after update occurs. */
var							bool					bUpdateWheelShapes;

/** Percent the Suspension must want to move in one go for the heavy shift function to be called*/
var							float					HeavySuspensionShiftPercent;

/** Vehicle total speed will be capped to this value */
var()						float					MaxSpeed;

/** Vehicle angular velocity will be capped to this value */
var()						float					MaxAngularVelocity;

/** OUTPUT: True if _any_ SVehicleWheel is currently touching the ground (ignores contacts with chassis etc) */
var	const bool			bVehicleOnGround;

/** OUTPUT: Time that bVehicleOnGround has been false for. */
var const float			TimeOffGround;

/** OUTPUT: True if _any_ SVehicleWheel is currently touching water */
var	const bool			bVehicleOnWater;

/** OUTPUT: True if vehicle is mostly upside down. */
var	const bool			bIsInverted;

/** OUTPUT: True if there are any contacts between the chassis of the vehicle and the ground. */
var	const bool			bChassisTouchingGround;

/** OUTPUT: True if there were any contacts between the chassis of the vehicle and the ground last tick */
var	const bool			bWasChassisTouchingGroundLastTick;

/** @name Vehicle uprighting */
//@{

/** true if vehicle can be uprighted by player */
var				bool		bCanFlip;
/** Scales the lifting force applied to the vehicle during uprighting.	*/
var(Uprighting)	float		UprightLiftStrength;
/** Scales the torque applied to the vehicle during uprighting.			*/
var(Uprighting)	float		UprightTorqueStrength;
/** Time in seconds to apply uprighting force/torque.					*/
var(Uprighting)	float		UprightTime;

/** Changes the direction the car gets flipped (used to prevent flipping car onto player	*/
var				bool		bFlipRight;
/** Internal variable.  True while uprighting forces are being applied.	*/
var				bool		bIsUprighting;

/** Internal variable.  Marks the time that uprighting began.			*/
var				float		UprightStartTime;
//@}

/** @name Sounds													*/
//@{
/** Ambient engine-running sound.  Pitch modulated based on RPMS.	*/
var(Sounds) editconst const AudioComponent EngineSound;

/** Volume-modulated wheel squeeling.								*/
var(Sounds) editconst const AudioComponent SquealSound;

/** Played when the vehicle slams into things.						*/
var(Sounds) SoundCue CollisionSound;

/** Engine startup sound played upon entering the vehicle.			*/
var(Sounds) SoundCue EnterVehicleSound;

/** Engine switch-off sound played upon exiting the vhicle.			*/
var(Sounds) SoundCue ExitVehicleSound;

/** Minimum time passed between the triggering collision sounds; generally set to longest collision sound. */
var(Sounds) float CollisionIntervalSecs;

/** Slip velocity cuttoff below which no wheel squealing is heard. */
var(Sounds) const float	SquealThreshold;

/** Lateral Slip velocity cut off below which no wheel squealing is heard */
var(Sounds) const float SquealLatThreshold;

/** multiplier for volume level of Lateral squeals relative to straight slip squeals. */
var(Sounds) const float LatAngleVolumeMult;

/** Time delay between the engine startup sound and the engine idling sound.						*/
var(Sounds) const float	EngineStartOffsetSecs;
/** Time delay between the engine shutdown sound and the deactivation of the engine idling sound.	*/
var(Sounds) const float	EngineStopOffsetSecs;

/** Internal variable; prevents collision sounds from being triggered too frequently.	*/
var float LastCollisionSoundTime;
//@}

// Internal
var		float				OutputBrake;
var		float				OutputGas;
var		float				OutputSteering;
var		float				OutputRise;
var		bool				bOutputHandbrake;
var     bool                bHoldingDownHandbrake;

var		float				ForwardVel;
var		int					NumPoweredWheels;

// camera
var()	vector				BaseOffset;
var()	float				CamDist;

var		int					DriverViewPitch;      // The driver's view pitch
var		int					DriverViewYaw;        // The driver's view yaw

// replication

struct native VehicleState
{
	var	RigidBodyState		RBState;

	var byte				ServerBrake;
	var byte				ServerGas;
	var	byte				ServerSteering;
	var byte				ServerRise;
	var bool				bServerHandbrake;
	var int					ServerView; // Pitch and Yaw - 16 bits each
};

var native const VehicleState	VState;
var	native const float			AngErrorAccumulator;

/** Hacky - used to scale hurtradius applied impulses */
var float RadialImpulseScaling;

replication
{
	if (Physics == PHYS_RigidBody)
		VState, MaxSpeed;
}

// Physics interface
native function AddForce(Vector Force);
native function AddImpulse(Vector Impulse);
native function AddTorque(Vector Torque);
native function bool IsSleeping();

`if(`isdefined(WITH_PHYSX))
/** Actual draw call for vehicle debug rendering */
// native function to be called from C++

native function DrawGraphsAndPrintTireSurfaceTypes(HUD HUD, FLOAT YL, FLOAT YPos);
`endif 

/** turns on or off a wheel's collision
 * @param WheelNum the index of the wheel in the Wheels array to change
 * @param bCollision whether to turn collision on or off
 */
native final function SetWheelCollision(int WheelNum, bool bCollision);

/**
 * Called when gameplay begins. Used to make sure the engine sound audio component
 * has the right properties set to ensure it gets restarted if it has been cut out
 * due to the audio code running out of HW channels to play the sound in.
 */
simulated event PostBeginPlay()
{
	Super.PostBeginPlay();
	if( EngineSound != None )
	{
		EngineSound.bShouldRemainActiveIfDropped = TRUE;
	}
	if (CollisionSound != None && CollisionIntervalSecs <= 0.0)
	{
		CollisionIntervalSecs = CollisionSound.GetCueDuration() / WorldInfo.TimeDilation;
	}
}

/** Store pointer to each wheel's SkelControlWheel. */
simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
{
	local int WheelIndex;
	local SVehicleWheel Wheel;

	Super.PostInitAnimTree(SkelComp);

	if (SkelComp == Mesh)
	{
		for(WheelIndex = 0; WheelIndex<Wheels.length; WheelIndex++)
		{
			Wheel = Wheels[WheelIndex];
			Wheel.WheelControl = SkelControlWheel( Mesh.FindSkelControl(Wheel.SkelControlName) );
		}
	}
}

simulated event Destroyed()
{
	Super.Destroyed();
	StopVehicleSounds();
}

/** TurnOff()
Freeze pawn - stop sounds, animations, physics, weapon firing
*/
simulated function TurnOff()
{
	Super.TurnOff();
	StopVehicleSounds();
}

simulated function StopVehicleSounds()
{
	if ( EngineSound != None )
	{
		EngineSound.Stop();
	}

	if ( SquealSound != None )
	{
		SquealSound.Stop();
	}
}

/**
 * Take Radius Damage
 * by default scales damage based on distance from HurtOrigin to Actor's location.
 * This can be overriden by the actor receiving the damage for special conditions (see KAsset.uc).
 *
 * @param	InstigatedBy, instigator of the damage
 * @param	Base Damage
 * @param	Damage Radius (from Origin)
 * @param	DamageType class
 * @param	Momentum (float)
 * @param	HurtOrigin, origin of the damage radius.
 * @param	bFullDamage, if true, damage not scaled based on distance HurtOrigin
 * @param DamageCauser the Actor that directly caused the damage (i.e. the Projectile that exploded, the Weapon that fired, etc)
 */
simulated function TakeRadiusDamage
(
	Controller			InstigatedBy,
	float				BaseDamage,
	float				DamageRadius,
	class<DamageType>	DamageType,
	float				Momentum,
	vector				HurtOrigin,
	bool				bFullDamage,
	Actor               DamageCauser,
	optional float      DamageFalloffExponent=1.f
)
{
	local vector HitLocation, Dir, NewDir;
	local float Dist, DamageScale;
	local TraceHitInfo HitInfo;

	if ( Role < ROLE_Authority )
		return;

	// calculate actual hit position on mesh, rather than approximating with cylinder
	HitLocation = Location;
	Dir = Location - HurtOrigin;

	CheckHitInfo( HitInfo, Mesh, Dir, HitLocation );
	NewDir = HitLocation - HurtOrigin;
	Dist = VSize(NewDir);

	if ( bFullDamage )
	{
		DamageScale = 1.f;
	}
	else if ( dist > DamageRadius )
		return;
	else
	{
		DamageScale = FMax(0,1 - Dist/DamageRadius);
		DamageScale = DamageScale ** DamageFalloffExponent;
	}

	RadialImpulseScaling = DamageScale;

	TakeDamage
	(
		BaseDamage * DamageScale,
		InstigatedBy,
		HitLocation,
		(DamageScale * Momentum * Normal(dir)),
		DamageType,
		HitInfo,
		DamageCauser
	);
	RadialImpulseScaling = 1.0;
	if (Health > 0)
	{
		DriverRadiusDamage(BaseDamage, DamageRadius, InstigatedBy, DamageType, Momentum, HurtOrigin, DamageCauser);
	}
}

/**
 *	Utility for switching the vehicle from a single body to an articulated ragdoll-like one given a new mesh and physics asset.
 *	ActorMove is an extra translation applied to Actor during the transition, which can be useful to avoid ragdoll mesh penetrating into the ground.
 */
native function InitVehicleRagdoll( SkeletalMesh RagdollMesh, PhysicsAsset RagdollPhysAsset, vector ActorMove, bool bClearAnimTree );

function AddVelocity( vector NewVelocity, vector HitLocation,class<DamageType> DamageType, optional TraceHitInfo HitInfo )
{
	if (!IsZero(NewVelocity))
	{
		NewVelocity = RadialImpulseScaling * MomentumMult * DamageType.Default.VehicleMomentumScaling * DamageType.Default.KDamageImpulse * Normal(NewVelocity);
		if (!bIgnoreForces && !IsZero(NewVelocity))
		{
			if (Location.Z > WorldInfo.StallZ)
			{
				NewVelocity.Z = FMin(NewVelocity.Z, 0);
			}
			if (InGodMode())
			{
				NewVelocity *= 0.25;
			}
			Mesh.AddImpulse(NewVelocity, HitLocation);
		}
	}
	RadialImpulseScaling = 1.0;
}

function bool Died(Controller Killer, class<DamageType> DamageType, vector HitLocation)
{
	if ( Super.Died(Killer, DamageType, HitLocation) )
	{
		bDriving = false;
		AddVelocity(TearOffMomentum, HitLocation, DamageType);
		return true;
	}
	return false;
}

/**
 *	Calculate camera view point, when viewing this actor.
 *
 * @param	fDeltaTime	delta time seconds since last update
 * @param	out_CamLoc	Camera Location
 * @param	out_CamRot	Camera Rotation
 * @param	out_FOV		Field of View
 *
 * @return	true if Pawn should provide the camera point of view.
 */
simulated function bool CalcCamera( float fDeltaTime, out vector out_CamLoc, out rotator out_CamRot, out float out_FOV )
{
	local vector	Pos, HitLocation, HitNormal;

	// Simple third person view implementation
	GetActorEyesViewPoint( out_CamLoc, out_CamRot );

	out_CamLoc += BaseOffset;
	Pos = out_CamLoc - Vector(out_CamRot) * CamDist;
	if( Trace(HitLocation, HitNormal, Pos, out_CamLoc, false, vect(0,0,0)) != None )
	{
		out_CamLoc = HitLocation + HitNormal*2;
	}
	else
	{
		out_CamLoc = Pos;
	}

	return true;
}

simulated function name GetDefaultCameraMode( PlayerController RequestedBy )
{
	return 'Default';
}

function bool TryToDrive(Pawn P)
{
	// Does the vehicle need to be uprighted?
	if ( bIsInverted && !bVehicleOnGround && VSize(Velocity) <= 0.1f )
	{
		if ( bCanFlip )
		{
			bIsUprighting = true;
			UprightStartTime = WorldInfo.TimeSeconds;
		}
		return false;
	}

	return Super.TryToDrive(P);
}

/** HasWheelsOnGround()
returns true if any of vehicles wheels are currently in contact with the ground (wheel has bWheelOnGround==true)
*/
simulated native function bool HasWheelsOnGround();

/** turns on the engine sound */
simulated function StartEngineSound()
{
	if (EngineSound != None)
	{
		EngineSound.Play();
	}
	ClearTimer('StartEngineSound');
	ClearTimer('StopEngineSound');
}

/** starts a timer of EngineStartOffsetSecs duration to turn on the engine sound */
simulated function StartEngineSoundTimed()
{
	if (EngineStartOffsetSecs > 0.f)
	{
		ClearTimer('StopEngineSound');
		SetTimer( EngineStartOffsetSecs, false, nameof(StartEngineSound) );
	}
	else
	{
		StartEngineSound();
	}
}

/** turns off the engine sound */
simulated function StopEngineSound()
{
	if (EngineSound != None)
	{
		EngineSound.Stop();
	}
	ClearTimer('StartEngineSound');
	ClearTimer('StopEngineSound');
}

/** starts a timer of EngineStopOffsetSecs duration to turn off the engine sound */
simulated function StopEngineSoundTimed()
{
	if (EngineStopOffsetSecs > 0.f)
	{
		ClearTimer('StartEngineSound');
		SetTimer( EngineStopOffsetSecs, false, nameof(StopEngineSound) );
	}
	else
	{
		StopEngineSound();
	}
}

simulated function VehiclePlayEnterSound()
{
	// For efficiency we're removing this fix and replacing the sounds with cues instead of components.
	// Deactivate any engine stopping sounds that were happening.

	// Trigger the engine starting sound.
	if (EnterVehicleSound != None)
	{
		PlaySound(EnterVehicleSound);
	}
	StartEngineSoundTimed();

}

simulated function VehiclePlayExitSound()
{
	// For efficiency we're removing this fix and replacing the sounds with cues instead of components.
	// Deactivate any engine starting sounds that were happening.

	// Trigger the engine stopping sound.
	if (ExitVehicleSound != None)
	{
		PlaySound(ExitVehicleSound);
	}
	StopEngineSoundTimed(); 
}

simulated function DrivingStatusChanged()
{
	// turn parking friction on or off
	bUpdateWheelShapes = true;

	if ( bDriving )
	{
		VehiclePlayEnterSound();
	}
	else if ( Health > 0 )
	{
		VehiclePlayExitSound();
	}
}

simulated event RigidBodyCollision( PrimitiveComponent HitComponent, PrimitiveComponent OtherComponent,
					const out CollisionImpactData RigidCollisionData, int ContactIndex )
{
	if( CollisionSound != None && WorldInfo.TimeSeconds - LastCollisionSoundTime > CollisionIntervalSecs )
	{
		PlaySound(CollisionSound, true);
		LastCollisionSoundTime = WorldInfo.TimeSeconds;
	}
}

/** called when the suspension moves a large amount, passes the delta*/
simulated event SuspensionHeavyShift(float Delta);

function PostTeleport(Teleporter OutTeleporter)
{
	Mesh.SetRBPosition(Location);
}

simulated function DisplayDebug(HUD HUD, out float out_YL, out float out_YPos)
{
	local Array<String>	DebugInfo;
	local int			i;

	super.DisplayDebug(HUD, out_YL, out_YPOS);

	GetSVehicleDebug( DebugInfo );

	Hud.Canvas.SetDrawColor(0,255,0);
	for (i=0;i<DebugInfo.Length;i++)
	{
		Hud.Canvas.DrawText( "  " @ DebugInfo[i] );
		out_YPos += out_YL;
		Hud.Canvas.SetPos(4, out_YPos);
	}

    // Uncomment to see detailed per-wheel debug info
	//PX3_TODO REMOVE?
	// LOC_MOD
	DisplayWheelsDebug(HUD, out_YL);

`if(`isdefined(WITH_PHYSX))
	// Draw telemetry data
	if(bEnableDebugRendering)
	{
		DrawGraphsAndPrintTireSurfaceTypes(HUD, out_YL, out_YPos);
	}
`endif
}

/**
 * Special debug information for the wheels that is displayed at each wheel's location
 */
simulated function DisplayWheelsDebug(HUD HUD, float YL)
{
    local int i, j;
    local vector WorldLoc, ScreenLoc, X, Y, Z, EndPoint, ScreenEndPoint;
    local Color SaveColor;
    local float LastForceValue;
    local float GraphScale;
    local float ForceValue;
    local vector ForceValueLoc;

    if (SimObj == None)
	{
		return;
	}

    GraphScale = 100.0f;
    SaveColor = HUD.Canvas.DrawColor;

	for (i=0; i<Wheels.Length; i++)
	{
    	GetAxes(Rotation, X, Y, Z);
    	WorldLoc =  Location + (Wheels[i].WheelPosition >> Rotation);
    	ScreenLoc = HUD.Canvas.Project(WorldLoc);
    	
		if( ScreenLoc.X >= 0 &&	ScreenLoc.X < HUD.Canvas.ClipX && ScreenLoc.Y >= 0 && ScreenLoc.Y < HUD.Canvas.ClipY )
		{
			// Draw Text
			HUD.Canvas.DrawColor = MakeColor(255,255,0,255);
			HUD.Canvas.SetPos(ScreenLoc.X, ScreenLoc.Y);
			HUD.Canvas.DrawText("Force "$Wheels[i].ContactForce);
			HUD.Canvas.SetPos(ScreenLoc.X, ScreenLoc.Y - (1 * YL));
			HUD.Canvas.DrawText("SR "$Wheels[i].LongSlipRatio);
			HUD.Canvas.SetPos(ScreenLoc.X, ScreenLoc.Y - (2 * YL));
			HUD.Canvas.DrawText("SA "$Wheels[i].LatSlipAngle * RadToDeg$" ("$Wheels[i].LatSlipAngle$")");
   			HUD.Canvas.SetPos(ScreenLoc.X, ScreenLoc.Y - (3 * YL));
  			HUD.Canvas.DrawText("Torque "$Wheels[i].MotorTorque);
			HUD.Canvas.SetPos(ScreenLoc.X, ScreenLoc.Y - (4 * YL));
			HUD.Canvas.DrawText("SpinVel "$Wheels[i].SpinVel);

			// Draw Lines
			HUD.Canvas.DrawColor = HUD.RedColor;
			EndPoint = WorldLoc + (Wheels[i].LongImpulse * 100 * Wheels[i].LongDirection) - (Wheels[i].WheelRadius * Z);
			ScreenEndPoint = HUD.Canvas.Project(EndPoint);
			DrawDebugLine(WorldLoc - (Wheels[i].WheelRadius * Z), EndPoint, 255, 0, 0);
			HUD.Canvas.SetPos(ScreenEndPoint.X, ScreenEndPoint.Y);
			HUD.Canvas.DrawText(Wheels[i].LongImpulse);

			HUD.Canvas.DrawColor = HUD.GreenColor;
			EndPoint = WorldLoc + (Wheels[i].LatImpulse * 100 * Wheels[i].LatDirection) - (Wheels[i].WheelRadius * Z);
			ScreenEndPoint = HUD.Canvas.Project(EndPoint);
			DrawDebugLine(WorldLoc - (Wheels[i].WheelRadius * Z), EndPoint, 0, 255, 0);
			HUD.Canvas.SetPos(ScreenEndPoint.X, ScreenEndPoint.Y);
			HUD.Canvas.DrawText(Wheels[i].LatImpulse);
			HUD.Canvas.SetPos(ScreenLoc.X, ScreenLoc.Y + YL);
			HUD.Canvas.DrawText(Wheels[i].LatImpulse);

			// Draw Axes
			HUD.Canvas.DrawColor = MakeColor(255,255,255,255);
			HUD.Draw2DLine(ScreenLoc.X, ScreenLoc.Y, ScreenLoc.X + GraphScale, ScreenLoc.Y, MakeColor(0,0,255,255));
			HUD.Canvas.SetPos(ScreenLoc.X + GraphScale, ScreenLoc.Y);
			HUD.Canvas.DrawText(PI * 0.5f);
			HUD.Draw2DLine(ScreenLoc.X, ScreenLoc.Y, ScreenLoc.X, ScreenLoc.Y - GraphScale, MakeColor(0,0,255,255));
			HUD.Canvas.SetPos(ScreenLoc.X, ScreenLoc.Y - GraphScale);
			HUD.Canvas.DrawText(SimObj.WheelLatExtremumValue);

			// Draw Graph
			LastForceValue = 0.0f;
			for (j=0; j<=GraphScale; j++)
			{
				ForceValue = HermiteEval(j * ((PI * 0.5f) / GraphScale));
				ForceValue = (ForceValue / SimObj.WheelLatExtremumValue) * GraphScale;
				HUD.Draw2DLine(ScreenLoc.X + (j - 1), ScreenLoc.Y - LastForceValue, ScreenLoc.X + j, ScreenLoc.Y - ForceValue, MakeColor(0,255,0,255));
				LastForceValue = ForceValue;
			}

			// Draw Force Value
			ForceValue = HermiteEval(Abs(Wheels[i].LatSlipAngle));
			ForceValueLoc.X = ScreenLoc.X + (Abs(Wheels[i].LatSlipAngle) / (PI * 0.5f)) * GraphScale;
			ForceValueLoc.Y = ScreenLoc.Y - (ForceValue / SimObj.WheelLatExtremumValue) * GraphScale;
			HUD.Draw2DLine(ForceValueLoc.X - 5, ForceValueLoc.Y, ForceValueLoc.X + 5, ForceValueLoc.Y, MakeColor(255,0,0,255));
			HUD.Draw2DLine(ForceValueLoc.X, ForceValueLoc.Y - 5, ForceValueLoc.X, ForceValueLoc.Y + 5, MakeColor(255,0,0,255));
			HUD.Canvas.SetPos(ScreenLoc.X, ForceValueLoc.Y);
			HUD.Canvas.DrawText(ForceValue);
			HUD.Canvas.SetPos(ForceValueLoc.X, ScreenLoc.Y + YL);
			HUD.Canvas.DrawText(Wheels[i].LatSlipAngle);
		}
	}

    HUD.Canvas.DrawColor = SaveColor;
}

/**
 *  Hermite spline evaluation for use with the slip curve debug display
 */
simulated function float HermiteEval(float Slip)
{
    local float LatExtremumSlip;
    local float LatExtremumValue;
    local float LatAsymptoteSlip;
    local float LatAsymptoteValue;
    local float SlipSquared, SlipCubed;
    local float C0, C1, C3;

    LatExtremumSlip = SimObj.WheelLatExtremumSlip;
    LatExtremumValue = SimObj.WheelLatExtremumValue;
    LatAsymptoteSlip = SimObj.WheelLatAsymptoteSlip;
    LatAsymptoteValue = SimObj.WheelLatAsymptoteValue;

    if (Slip < LatExtremumSlip)
    {
	Slip /= LatExtremumSlip;
	SlipSquared = Slip * Slip;
	SlipCubed = SlipSquared * Slip;
	C3 = -2.0f * SlipCubed + 3.0f * SlipSquared;
	C1 = SlipCubed - 2.0f * SlipSquared + Slip;
	return ((C1 + C3) * LatExtremumValue);
    }
    else if (Slip > LatAsymptoteSlip)
    {
	return (LatAsymptoteValue);
    }
    else
    {
	Slip /= (LatAsymptoteSlip - LatExtremumSlip);
	Slip -= LatExtremumSlip;
	SlipSquared = Slip * Slip;
	SlipCubed = SlipSquared * Slip;
	C3 = -2.0f * SlipCubed + 3.0f * SlipSquared;
	C0 = 2.0f * SlipCubed - 3.0f * SlipSquared + 1.0f;
	return (C0 * LatExtremumValue + C3 * LatAsymptoteValue);
    }
}

/**
 * Retrieves important SVehicle debug information as an array of strings. That can then be dumped or displayed on HUD.
 */
simulated function GetSVehicleDebug( out Array<String> DebugInfo )
{
	DebugInfo[DebugInfo.Length] = "----Vehicle----: ";
	DebugInfo[DebugInfo.Length] = "Speed: "$VSize(Velocity)$" Unreal -- "$VSize(Velocity) * 0.0426125$" MPH";
	if (Wheels.length > 0)
	{
		DebugInfo[DebugInfo.Length] = "MotorTorque: "$Wheels[0].MotorTorque;
	}
	DebugInfo[DebugInfo.Length] = "Throttle: "$OutputGas;
	DebugInfo[DebugInfo.Length] = "Brake: "$OutputBrake;
}

defaultproperties
{
	TickGroup=TG_PostAsyncWork

	Begin Object Class=SkeletalMeshComponent Name=SVehicleMesh
		RBChannel=RBCC_Vehicle
		RBCollideWithChannels=(Default=TRUE,BlockingVolume=TRUE,GameplayPhysics=TRUE,EffectPhysics=TRUE,Vehicle=TRUE)
		BlockActors=true
		BlockZeroExtent=true
		BlockRigidBody=true
		BlockNonzeroExtent=true
		CollideActors=true
		bForceDiscardRootMotion=true
		bUseSingleBodyPhysics=1
		bNotifyRigidBodyCollision=true
		ScriptRigidBodyCollisionThreshold=250.0
	End Object
	CollisionComponent=SVehicleMesh
	Mesh=SVehicleMesh
	Components.Add(SVehicleMesh)

	BaseOffset=(Z=128)
	CamDist=512

	Physics=PHYS_RigidBody
	bEdShouldSnap=true
	bStatic=false
	bCollideActors=true
	bCollideWorld=true
	bProjTarget=true
	bBlockActors=true
	bWorldGeometry=false
	bCanBeBaseForPawns=true
	bAlwaysRelevant=false
	RemoteRole=ROLE_SimulatedProxy
	bNetInitialRotation=true
	bBlocksTeleport=TRUE

	// Stay-upright constraint
	Begin Object Class=RB_StayUprightSetup Name=MyStayUprightSetup
	End Object
	StayUprightConstraintSetup=MyStayUprightSetup

	Begin Object Class=RB_ConstraintInstance Name=MyStayUprightConstraintInstance
	End Object
	StayUprightConstraintInstance=MyStayUprightConstraintInstance

	// Absolute max physics speed
	MaxSpeed=2500

	// Absolute max physics angular velocity (Unreal angular units)
	MaxAngularVelocity=75000.0

	// Inertia Tensor
	InertiaTensorMultiplier=(x=1.0,y=1.0,z=1.0)

	// Initialize uprighting parameters.
	bCanFlip=true
	UprightLiftStrength = 225.0;
	UprightTorqueStrength = 50.0;
	UprightTime = 1.5;
	bIsUprighting = false;

	// Initialize sound members.
	SquealThreshold = 250.0;
	SquealLatThreshold=250.0f;
	LatAngleVolumeMult=1.0f;
	EngineStartOffsetSecs = 2.0;
	EngineStopOffsetSecs = 1.0;
	HeavySuspensionShiftPercent=0.5f;

	RadialImpulseScaling=1.0

	bInReverseMode=false
	bEnableDebugRendering=true
	
	ChassisMass=1500
	
	bIsTank=FALSE
}
