// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  * Neither the name of NVIDIA CORPORATION nor the names of its
//    contributors may be used to endorse or promote products derived
//    from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.  

#pragma once


#include "foundation/PxFoundation.h"
#include "common/PxCoreUtilityTypes.h"

#include "vehicle2/PxVehicleParams.h"

#include "vehicle2/commands/PxVehicleCommandParams.h"

#if !PX_DOXYGEN
namespace physx
{
namespace vehicle2
{
#endif

/**
\brief Distribute a throttle response to the wheels of a direct drive vehicle.
\note The drive torque applied to each wheel on the ith axle is throttleCommand * maxResponse * wheelResponseMultipliers[i].
\note A typical use case is to set maxResponse to be the vehicle's maximum achievable drive torque
that occurs when the steer command is equal to 1.0. The array wheelResponseMultipliers[i] would then be used
to specify the maximum achievable drive torque per wheel as a fractional multiplier of the vehicle's maximum achievable steer angle.
*/
struct PxVehicleDirectDriveThrottleCommandResponseParams : public PxVehicleCommandResponseParams
{
	PX_FORCE_INLINE PxVehicleDirectDriveThrottleCommandResponseParams transformAndScale(
		const PxVehicleFrame& srcFrame, const PxVehicleFrame& trgFrame, const PxVehicleScale& srcScale, const PxVehicleScale& trgScale) const
	{
		PX_UNUSED(srcFrame);
		PX_UNUSED(trgFrame);
		PxVehicleDirectDriveThrottleCommandResponseParams r = *this;
		const PxReal scale = trgScale.scale/srcScale.scale;
		r.maxResponse *= (scale*scale);	//Max response is a torque!
		return r;
	}

	PX_FORCE_INLINE bool isValid(const PxVehicleAxleDescription& axleDesc) const
	{
		PX_CHECK_AND_RETURN_VAL(PxIsFinite(maxResponse), "PxVehicleDirectDriveThrottleCommandResponseParams.maxResponse must be a finite value", false);
		for (PxU32 i = 0; i < axleDesc.nbWheels; i++)
		{
			PX_CHECK_AND_RETURN_VAL(PxIsFinite(wheelResponseMultipliers[axleDesc.wheelIdsInAxleOrder[i]]), "PxVehicleDirectDriveThrottleCommandResponseParams.wheelResponseMultipliers[i] must be a finite value", false);
		}
		return true;
	}
};

/**
\brief Specifies the maximum clutch strength that occurs when the clutch pedal is fully disengaged and the clutch is fully engaged.
*/
struct PxVehicleClutchCommandResponseParams
{
	/**
	\brief Strength of clutch.

	\note The clutch is the mechanism that couples the engine to the wheels.
	A stronger clutch more strongly couples the engine to the wheels, while a
	clutch of strength zero completely decouples the engine from the wheels.
	Stronger clutches more quickly bring the wheels and engine into equilibrium, while weaker
	clutches take longer, resulting in periods of clutch slip and delays in power transmission
	from the engine to the wheels.
	The torque generated by the clutch is proportional to the clutch strength and
	the velocity difference between the engine's rotational speed and the rotational speed of the
	driven wheels after accounting for the gear ratio.
	The torque at the clutch is applied negatively to the engine and positively to the driven wheels.

	<b>Range:</b> [0,inf)<br>
	<b>Unit:</b> torque * time = mass * (length^2) / time
	*/
	PxReal maxResponse;

	PX_FORCE_INLINE PxVehicleClutchCommandResponseParams transformAndScale(
		const PxVehicleFrame& srcFrame, const PxVehicleFrame& trgFrame, const PxVehicleScale& srcScale, const PxVehicleScale& trgScale) const
	{
		PX_UNUSED(srcFrame);
		PX_UNUSED(trgFrame);
		const PxReal scale = trgScale.scale/srcScale.scale;
		PxVehicleClutchCommandResponseParams r = *this;
		r.maxResponse *= (scale*scale);
		return r;
	}

	PX_FORCE_INLINE bool isValid() const
	{
		PX_CHECK_AND_RETURN_VAL(maxResponse >= 0.0f, "PxVehicleClutchCommandResponseParams.maxResponse must be greater than or equal to zero", false);
		return true;
	}
};

/**
\brief Choose between a potentially more expensive but more accurate solution to the clutch model or a potentially cheaper but less accurate solution.
\see PxVehicleClutchParams
*/
struct PxVehicleClutchAccuracyMode
{
	enum Enum
	{
		eESTIMATE = 0,
		eBEST_POSSIBLE
	};
};

/**
\brief The clutch connects two plates together. One plate rotates with the speed of the engine. The second plate rotates with a weighted average of all
wheels connected to the differential after accounting for the gear ratio. The difference in rotation speeds generates a restoring torque applied to engine and wheels
that aims to reduce the difference in rotational speed. The restoring torque is proportional to the clutch strength and the clutch pedal position.
*/
struct PxVehicleClutchParams
{
public:

	/**
	\brief The engine and wheel rotation speeds that are coupled through the clutch can be updated by choosing
	one of two modes: eESTIMATE and eBEST_POSSIBLE.

	\note If eESTIMATE is chosen the vehicle sdk will update the wheel and engine rotation speeds
	with estimated values to the implemented clutch model.

	\note If eBEST_POSSIBLE is chosen the vehicle sdk will compute the best possible
	solution (within floating point tolerance) to the implemented clutch model.
	This is the recommended mode.

	\note The clutch model remains the same if either eESTIMATE or eBEST_POSSIBLE is chosen but the accuracy and
	computational cost of the solution to the model can be tuned as required.
	*/
	PxVehicleClutchAccuracyMode::Enum accuracyMode;

	/**
	\brief Tune the mathematical accuracy and computational cost of the computed estimate to the wheel and
	engine rotation speeds if eESTIMATE is chosen.

	\note As estimateIterations increases the computational cost of the clutch also increases and the solution
	approaches the solution that would be computed if eBEST_POSSIBLE was chosen instead.

	\note This has no effect if eBEST_POSSIBLE is chosen as the accuracy mode.

	\note A value of zero is not allowed if eESTIMATE is chosen as the accuracy mode.
	*/
	PxU32 estimateIterations;

	PX_FORCE_INLINE PxVehicleClutchParams transformAndScale(
		const PxVehicleFrame& srcFrame, const PxVehicleFrame& trgFrame, const PxVehicleScale& srcScale, const PxVehicleScale& trgScale) const
	{
		PX_UNUSED(srcFrame);
		PX_UNUSED(trgFrame);
		PX_UNUSED(srcScale);
		PX_UNUSED(trgScale);
		return *this;
	}

	PX_FORCE_INLINE bool isValid() const
	{
		PX_CHECK_AND_RETURN_VAL((PxVehicleClutchAccuracyMode::eBEST_POSSIBLE == accuracyMode) || ((PxVehicleClutchAccuracyMode::eESTIMATE == accuracyMode) && (estimateIterations > 0)), "PxVehicleClutchParams.estimateIterations must be greater than zero if PxVehicleClutchAccuracyMode::eESTIMATE is selected", false);
		return true;
	}
};


struct PxVehicleEngineParams
{
	/**
	\brief Maximum supported number of points in the #torqueCurve graph.
	*/
	enum
	{
		eMAX_NB_ENGINE_TORQUE_CURVE_ENTRIES = 8
	};

	/**
	\brief Graph of normalized torque (torque/#peakTorque) against normalized engine speed ( engineRotationSpeed / #maxOmega ).

	\note The normalized engine speed is the x-axis of the graph, while the normalized torque is the y-axis of the graph.
	*/
	PxVehicleFixedSizeLookupTable<PxReal, eMAX_NB_ENGINE_TORQUE_CURVE_ENTRIES> torqueCurve;

	/**
	\brief Moment of inertia of the engine around the axis of rotation.

	<b>Range:</b> (0, inf)<br>
	<b>Unit:</b> mass * (length^2)
	*/
	PxReal moi;

	/**
	\brief Maximum torque available to apply to the engine when the accelerator pedal is at maximum.

	\note The torque available is the value of the accelerator pedal (in range [0, 1]) multiplied by the normalized torque as computed from #torqueCurve multiplied by #peakTorque.

	<b>Range:</b> [0, inf)<br>
	<b>Unit:</b> mass * (length^2) / (time^2)
	*/
	PxReal peakTorque;

	/**
	\brief Minimum rotation speed of the engine.

	<b>Range:</b> [0, inf)<br>
	<b>Unit:</b> radians / time
	*/
	PxReal idleOmega;

	/**
	\brief Maximum rotation speed of the engine.

	<b>Range:</b> [0, inf)<br>
	<b>Unit:</b> radians / time
	*/
	PxReal maxOmega;

	/**
	\brief Damping rate of engine when full throttle is applied.

	\note If the clutch is engaged (any gear except neutral) then the damping rate applied at run-time is an interpolation
	between #dampingRateZeroThrottleClutchEngaged and #dampingRateFullThrottle:
	#dampingRateZeroThrottleClutchEngaged + (#dampingRateFullThrottle-#dampingRateZeroThrottleClutchEngaged)*acceleratorPedal;

	\note If the clutch is disengaged (in neutral gear) the damping rate applied at run-time is an interpolation
	between #dampingRateZeroThrottleClutchDisengaged and #dampingRateFullThrottle:
	#dampingRateZeroThrottleClutchDisengaged + (#dampingRateFullThrottle-#dampingRateZeroThrottleClutchDisengaged)*acceleratorPedal;

	<b>Range:</b> [0, inf)<br>
	<b>Unit:</b> torque * time = mass * (length^2) / time
	*/
	PxReal dampingRateFullThrottle;

	/**
	\brief Damping rate of engine when no throttle is applied and with engaged clutch.

	\note If the clutch is engaged (any gear except neutral) then the damping rate applied at run-time is an interpolation
	between #dampingRateZeroThrottleClutchEngaged and #dampingRateFullThrottle:
	#dampingRateZeroThrottleClutchEngaged + (#dampingRateFullThrottle-#dampingRateZeroThrottleClutchEngaged)*acceleratorPedal;

	\note If the clutch is disengaged (in neutral gear) the damping rate applied at run-time is an interpolation
	between #dampingRateZeroThrottleClutchDisengaged and #dampingRateFullThrottle:
	#dampingRateZeroThrottleClutchDisengaged + (#dampingRateFullThrottle-#dampingRateZeroThrottleClutchDisengaged)*acceleratorPedal;

	<b>Range:</b> [0, inf)<br>
	<b>Unit:</b> torque * time = mass * (length^2) / time
	*/
	PxReal dampingRateZeroThrottleClutchEngaged;

	/**
	\brief Damping rate of engine when no throttle is applied and with disengaged clutch.

	\note If the clutch is engaged (any gear except neutral) then the damping rate applied at run-time is an interpolation
	between #dampingRateZeroThrottleClutchEngaged and #dampingRateFullThrottle:
	#dampingRateZeroThrottleClutchEngaged + (#dampingRateFullThrottle-#dampingRateZeroThrottleClutchEngaged)*acceleratorPedal;

	\note If the clutch is disengaged (in neutral gear) the damping rate applied at run-time is an interpolation
	between #dampingRateZeroThrottleClutchDisengaged and #dampingRateFullThrottle:
	#dampingRateZeroThrottleClutchDisengaged + (#dampingRateFullThrottle-#dampingRateZeroThrottleClutchDisengaged)*acceleratorPedal;

	<b>Range:</b> [0, inf)<br>
	<b>Unit:</b> torque * time = mass * (length^2) / time
	*/
	PxReal dampingRateZeroThrottleClutchDisengaged;

	PX_FORCE_INLINE PxVehicleEngineParams transformAndScale(
		const PxVehicleFrame& srcFrame, const PxVehicleFrame& trgFrame, const PxVehicleScale& srcScale, const PxVehicleScale& trgScale) const
	{
		PX_UNUSED(srcFrame);
		PX_UNUSED(trgFrame);
		PxVehicleEngineParams r = *this;
		const PxReal scale = trgScale.scale/srcScale.scale;
		r.moi *= (scale*scale);
		r.peakTorque *= (scale*scale);
		r.dampingRateFullThrottle *= (scale*scale);
		r.dampingRateZeroThrottleClutchDisengaged *= (scale*scale);
		r.dampingRateZeroThrottleClutchEngaged *= (scale*scale);
		return r;
	}

	PX_FORCE_INLINE bool isValid() const
	{
		PX_CHECK_AND_RETURN_VAL(moi > 0.0f, "PxVehicleEngineParams.moi must be greater than zero", false);
		PX_CHECK_AND_RETURN_VAL(peakTorque >= 0.0f, "PxVehicleEngineParams.peakTorque must be greater than or equal to zero", false);
		PX_CHECK_AND_RETURN_VAL(torqueCurve.isValid(), "PxVehicleEngineParams.torqueCurve is invalid", false);
		PX_CHECK_AND_RETURN_VAL(maxOmega >= 0.0f, "PxVehicleEngineParams.maxOmega must be greater than or equal to zero", false);
		PX_CHECK_AND_RETURN_VAL(idleOmega >= 0.0f, "PxVehicleEngineParams.idleOmega must be greater or equal to zero", false);
		PX_CHECK_AND_RETURN_VAL(dampingRateFullThrottle >= 0.0f, "PxVehicleEngineParams.dampingRateFullThrottle must be greater than or equal to zero", false);
		PX_CHECK_AND_RETURN_VAL(dampingRateZeroThrottleClutchEngaged >= 0.0f, "PxVehicleEngineParams.dampingRateZeroThrottleClutchEngaged must be greater than or equal to zero", false);
		PX_CHECK_AND_RETURN_VAL(dampingRateZeroThrottleClutchDisengaged >= 0.0f, "PxVehicleEngineParams.dampingRateZeroThrottleClutchDisengaged must be greater than or equal to zero", false)
		return true;
	}
};

struct PxVehicleGearboxParams
{
public:

	/**
	\brief The gear that denotes neutral gear
	*/
	PxU32 neutralGear;

	/**
	\brief Maximum supported number of gears, including reverse and neutral.
	*/
	enum Enum
	{
		eMAX_NB_GEARS = 32
	};

	/**
	\brief Gear ratios

	The ratio for reverse gears must be negative, the ratio for the neutral gear
	has to be 0 and the ratio for forward gears must be positive.
	*/
	PxReal ratios[eMAX_NB_GEARS];

	/**
	\brief Gear ratio applied is #ratios[currentGear]*#finalRatio

	<b>Range:</b> (0, inf)<br>
	*/
	PxReal finalRatio;

	/**
	\brief Number of gears (including reverse and neutral).

	<b>Range:</b> [1, eMAX_NB_GEARS]<br>
	*/
	PxU32 nbRatios;

	/**
	\brief Time it takes to switch gear.

	<b>Range:</b> [0, inf)<br>
	<b>Unit:</b> time
	*/
	PxReal switchTime;

	PX_FORCE_INLINE PxVehicleGearboxParams transformAndScale(
		const PxVehicleFrame& srcFrame, const PxVehicleFrame& trgFrame, const PxVehicleScale& srcScale, const PxVehicleScale& trgScale) const
	{
		PX_UNUSED(srcFrame);
		PX_UNUSED(trgFrame);
		PX_UNUSED(srcScale);
		PX_UNUSED(trgScale);
		return *this;
	}

	PX_FORCE_INLINE bool isValid() const
	{
		PX_CHECK_AND_RETURN_VAL(finalRatio > 0, "PxVehicleGearboxParams.finalRatio must be greater than zero", false);
		PX_CHECK_AND_RETURN_VAL(nbRatios >= 1, "PxVehicleGearboxParams.nbRatios must be greater than zero", false);
		PX_CHECK_AND_RETURN_VAL(nbRatios <= eMAX_NB_GEARS, "PxVehicleGearboxParams.nbRatios must be less than or equal to eMAX_NB_GEARS", false);
		PX_CHECK_AND_RETURN_VAL(neutralGear < nbRatios, "PxVehicleGearboxParams.neutralGear must be less than PxVehicleGearboxParams.nbRatios", false);
		PX_CHECK_AND_RETURN_VAL(switchTime >= 0.0f, "PxVehicleGearboxParams.switchTime must be greater than or equal to zero", false);
		for(PxU32 i = 0; i < neutralGear; i++)
		{
			PX_CHECK_AND_RETURN_VAL(ratios[i] < 0.0f, "Reverse gear ratios must be less than zero", false);
		}
		{
			PX_CHECK_AND_RETURN_VAL(ratios[neutralGear] == 0.0f, "Neutral gear ratio must be equal to zero", false);
		}
		for (PxU32 i = neutralGear + 1; i < nbRatios; i++)
		{
			PX_CHECK_AND_RETURN_VAL(ratios[i] > 0.0f, "Forward gear ratios must be greater than zero", false);
		}
		for (PxU32 i = neutralGear + 2; i < nbRatios; i++)
		{
			PX_CHECK_AND_RETURN_VAL(ratios[i] < ratios[i - 1], "Forward gear ratios must be a descending sequence of gear ratios", false);
		}
		return true;
	}
};


struct PxVehicleAutoboxParams
{
public:

	/**
	\brief Value of ( engineRotationSpeed /maxEngineRevs ) that is high enough to increment gear.

	\note When ( engineRotationSpeed / #PxVehicleEngineParams::maxOmega ) > upRatios[currentGear] the autobox will begin
	a transition to currentGear+1 unless currentGear is the highest possible gear or neutral or reverse.

	<b>Range:</b> [0, 1]<br>
	*/
	PxReal upRatios[PxVehicleGearboxParams::eMAX_NB_GEARS];

	/**
	\brief Value of engineRevs/maxEngineRevs that is low enough to decrement gear.

	\note When ( engineRotationSpeed / #PxVehicleEngineParams::maxOmega) < downRatios[currentGear] the autobox will begin
	a transition to currentGear-1 unless currentGear is first gear or neutral or reverse.

	<b>Range:</b> [0, 1]<br>
	*/
	PxReal downRatios[PxVehicleGearboxParams::eMAX_NB_GEARS];

	/**
	\brief Set the latency time of the autobox.

	\note Latency time is the minimum time that must pass between each gear change that is initiated by the autobox.
	The auto-box will only attempt to initiate another gear change up or down if the simulation time that has passed since the most recent
	automated gear change is greater than the specified latency.

	<b>Range:</b> [0, inf)<br>
	<b>Unit:</b> time
	*/
	PxReal latency;

	PX_FORCE_INLINE PxVehicleAutoboxParams transformAndScale(
		const PxVehicleFrame& srcFrame, const PxVehicleFrame& trgFrame, const PxVehicleScale& srcScale, const PxVehicleScale& trgScale) const
	{
		PX_UNUSED(srcFrame);
		PX_UNUSED(trgFrame);
		PX_UNUSED(srcScale);
		PX_UNUSED(trgScale);
		return *this;
	}

	PX_FORCE_INLINE bool isValid(const PxVehicleGearboxParams& gearboxParams) const
	{
		if (!gearboxParams.isValid())
			return false;

		for (PxU32 i = gearboxParams.neutralGear + 1; i < gearboxParams.nbRatios-1; i++)
		{
			PX_CHECK_AND_RETURN_VAL(upRatios[i] >= 0.0f, "PxVehicleAutoboxParams.upRatios must be greater than or equal to zero", false);
		}
		for (PxU32 i = gearboxParams.neutralGear + 2; i < gearboxParams.nbRatios; i++)
		{
			PX_CHECK_AND_RETURN_VAL(downRatios[i] >= 0.0f, "PxVehicleAutoboxParams.downRatios must be greater than or equal to zero", false);
		}
		PX_CHECK_AND_RETURN_VAL(latency >= 0.0f, "PxVehicleAutoboxParams.latency must be greater than or equal to zero", false);
		return true;
	}
};

/**
\deprecated This API was introduced with the new Vehicle API for transition purposes but will be removed in a future version.
*/
struct PX_DEPRECATED PxVehicleFourWheelDriveDifferentialLegacyParams
{
public:

	enum Enum
	{
		eDIFF_TYPE_LS_4WD,			//limited slip differential for car with 4 driven wheels
		eDIFF_TYPE_LS_FRONTWD,		//limited slip differential for car with front-wheel drive
		eDIFF_TYPE_LS_REARWD,		//limited slip differential for car with rear-wheel drive
		eMAX_NB_DIFF_TYPES
	};

	/**
	\brief The two front wheels are specified by the array frontWheelIds.
	\note The states eDIFF_TYPE_LS_4WD, eDIFF_TYPE_LS_FRONTWD require knowledge of the front wheels
	to allow torque splits between front and rear axles as well as torque splits across the front axle.
	\note frontWheelIds[0] should specify the wheel on the front axle that is negatively placed on the lateral axis.
	\note frontWheelIds[1] should specify the wheel on the front axle that is positively placed on the lateral axis.
	\note If #frontNegPosSplit > 0.5, more torque will be delivered to the wheel specified by frontWheelIds[0] than to the wheel
	specified by frontWheelIds[1]. The converse is true if #frontNegPosSplit < 0.5.
	*/
	PxU32 frontWheelIds[2];


	/**
	\brief The two rear wheels are specified by the array rearWheelIds.
	\note The states eDIFF_TYPE_LS_4WD, eDIFF_TYPE_LS_REARWD require knowledge of the rear wheels
	to allow torque splits between front and rear axles as well as torque splits across the rear axle.
	\note rearWheelIds[0] should specify the wheel on the rear axle that is negatively placed on the lateral axis.
	\note rearWheelIds[1] should specify the wheel on the rear axle that is positively placed on the lateral axis.
	\note If #rearNegPosSplit > 0.5, more torque will be delivered to the wheel specified by rearWheelIds[0] than to the wheel
	specified by rearWheelIds[1]. The converse is true if #rearNegPosSplit < 0.5.
	*/
	PxU32 rearWheelIds[2];

	/**
	\brief Ratio of torque split between front and rear wheels (>0.5 means more front, <0.5 means more to rear).

	\note Only applied to DIFF_TYPE_LS_4WD

	<b>Range:</b> [0, 1]<br>
	*/
	PxReal frontRearSplit;

	/**
	\brief Ratio of torque split between front-negative and front-positive wheels (>0.5 means more to #frontWheelIds[0], <0.5 means more to #frontWheelIds[1]).

	\note Only applied to DIFF_TYPE_LS_4WD and DIFF_TYPE_LS_FRONTWD

	<b>Range:</b> [0, 1]<br>
	*/
	PxReal frontNegPosSplit;

	/**
	\brief Ratio of torque split between rear-negative wheel and rear-positive wheels (>0.5 means more to #rearWheelIds[0], <0.5 means more to #rearWheelIds[1]).

	\note Only applied to DIFF_TYPE_LS_4WD and eDIFF_TYPE_LS_REARWD

	<b>Range:</b> [0, 1]<br>
	*/
	PxReal rearNegPosSplit;

	/**
	\brief Maximum allowed ratio of average front wheel rotation speed and rear wheel rotation speeds.
	The differential will divert more torque to the slower wheels when the bias is exceeded.

	\note Only applied to DIFF_TYPE_LS_4WD

	<b>Range:</b> [1, inf)<br>
	*/
	PxReal centerBias;

	/**
	\brief Maximum allowed ratio of front-left and front-right wheel rotation speeds.
	The differential will divert more torque to the slower wheel when the bias is exceeded.

	\note Only applied to DIFF_TYPE_LS_4WD and DIFF_TYPE_LS_FRONTWD

	<b>Range:</b> [1, inf)<br>
	*/
	PxReal frontBias;

	/**
	\brief Maximum allowed ratio of rear-left and rear-right wheel rotation speeds.
	The differential will divert more torque to the slower wheel when the bias is exceeded.

	\note Only applied to DIFF_TYPE_LS_4WD and DIFF_TYPE_LS_REARWD

	<b>Range:</b> [1, inf)<br>
	*/
	PxReal rearBias;

	/**
	\brief Type of differential.

	<b>Range:</b> [DIFF_TYPE_LS_4WD, eDIFF_TYPE_LS_REARWD]<br>
	*/
	Enum type;

	PX_FORCE_INLINE PxVehicleFourWheelDriveDifferentialLegacyParams transformAndScale(
		const PxVehicleFrame& srcFrame, const PxVehicleFrame& trgFrame, const PxVehicleScale& srcScale, const PxVehicleScale& trgScale) const
	{
		PX_UNUSED(srcFrame);
		PX_UNUSED(trgFrame);
		PX_UNUSED(srcScale);
		PX_UNUSED(trgScale);
		return *this;
	}

	PX_FORCE_INLINE bool isValid(const PxVehicleAxleDescription& axleDesc) const
	{
		PX_UNUSED(axleDesc);
		PX_CHECK_AND_RETURN_VAL((axleDesc.getAxle(frontWheelIds[0]) != 0xffffffff) && (axleDesc.getAxle(frontWheelIds[0])== axleDesc.getAxle(frontWheelIds[1])), "frontWheelIds[0] and frontWheelIds[1] must reference wheels on the same axle", false);
		PX_CHECK_AND_RETURN_VAL((axleDesc.getAxle(rearWheelIds[0]) != 0xffffffff) && (axleDesc.getAxle(rearWheelIds[0]) == axleDesc.getAxle(rearWheelIds[1])), "rearWheelIds[0] and rearWheelIds[1] must reference wheels on the same axle", false);
		PX_CHECK_AND_RETURN_VAL(frontRearSplit <= 1.0f && frontRearSplit >= 0.0f, "PxVehicleLegacyFourWheelDriveDifferentialParams.frontRearSplit must be in range [0,1]", false);
		PX_CHECK_AND_RETURN_VAL(frontNegPosSplit <= 1.0f && frontNegPosSplit >= 0.0f, "PxVehicleLegacyFourWheelDriveDifferentialParams.frontNegPosSplit must be in range [0,1]", false);
		PX_CHECK_AND_RETURN_VAL(rearNegPosSplit <= 1.0f && rearNegPosSplit >= 0.0f, "PxVehicleLegacyFourWheelDriveDifferentialParams.rearNegPosSplit must be in range [0,1]", false);
		PX_CHECK_AND_RETURN_VAL(centerBias >= 1.0f, "PxVehicleLegacyFourWheelDriveDifferentialParams.centerBias must be greater than or equal to 1.0f", false);
		PX_CHECK_AND_RETURN_VAL(frontBias >= 1.0f, "PxVehicleLegacyFourWheelDriveDifferentialParams.frontBias must be greater than or equal to 1.0f", false);
		PX_CHECK_AND_RETURN_VAL(rearBias >= 1.0f, "PxVehicleLegacyFourWheelDriveDifferentialParams.rearBias must be greater than or equal to 1.0f", false);
		PX_CHECK_AND_RETURN_VAL(type < PxVehicleFourWheelDriveDifferentialLegacyParams::eMAX_NB_DIFF_TYPES, "PxVehicleLegacyFourWheelDriveDifferentialParams.type has illegal value", false);
		return true;
	}
};

/**
\brief PxVehicleMultiWheelDriveDifferentialParams specifies the wheels that are to receive drive torque from the differential 
and the division of torque between the wheels that are connected to the differential. 
*/
struct PxVehicleMultiWheelDriveDifferentialParams
{	 
	/**
	\brief torqueRatios describes the fraction of torque delivered to each wheel through the differential.
	\note Wheels not connected to the differential must receive zero torque.
	\note Wheels connected to the differential may receive a non-zero torque.
	\note The sum of the absolute of the ratios of all wheels must equal to 1.0.
	\note A negative torque ratio simulates a wheel with negative gearing applied.

	<b>Range:</b> [1, -1]<br>
	*/
	PxReal torqueRatios[PxVehicleLimits::eMAX_NB_WHEELS];

	/**
	\brief aveWheelSpeedRatios describes the contribution of each wheel to the average wheel speed measured at the clutch.
	\note Wheels not connected to the differential do not contribute to the average wheel speed measured at the clutch.
	\note Wheels connected to the differential may delivere a non-zero contribution to the average wheel speed measured at the clutch.
	\note The sum of all ratios of all wheels must equal to 1.0.

	<b>Range:</b> [0, 1]<br>
	*/
	PxReal aveWheelSpeedRatios[PxVehicleLimits::eMAX_NB_WHEELS];


	PX_FORCE_INLINE void setToDefault()
	{
		PxMemZero(this, sizeof(PxVehicleMultiWheelDriveDifferentialParams));
	}

	PX_FORCE_INLINE PxVehicleMultiWheelDriveDifferentialParams transformAndScale(
		const PxVehicleFrame& srcFrame, const PxVehicleFrame& trgFrame, const PxVehicleScale& srcScale, const PxVehicleScale& trgScale) const
	{
		PX_UNUSED(srcFrame);
		PX_UNUSED(trgFrame);
		PX_UNUSED(srcScale);
		PX_UNUSED(trgScale);
		return *this;
	}

	PX_FORCE_INLINE bool isValid(const PxVehicleAxleDescription& axleDesc) const
	{
		if (!axleDesc.isValid())
			return false;
		PxReal aveWheelSpeedSum = 0.0f;
		PxReal torqueRatioSum = 0.0f;
		for (PxU32 i = 0; i < axleDesc.nbWheels; i++)
		{
			const PxU32 wheelId = axleDesc.wheelIdsInAxleOrder[i];
			PX_CHECK_AND_RETURN_VAL(PxAbs(torqueRatios[wheelId]) <= 1.0f, "PxVehicleMultiWheelDriveDifferentialParams.torqueRatios[i] must be in range [-1, 1] for all wheels connected to the differential", false);
			PX_CHECK_AND_RETURN_VAL(aveWheelSpeedRatios[wheelId] >= 0.0f && aveWheelSpeedRatios[wheelId] <= 1.0f, "PxVehicleMultiWheelDriveDifferentialParams.aveWheelSpeedRatios[i] must be in range [0, 1] for all wheels connected to the differential", false);
				aveWheelSpeedSum += aveWheelSpeedRatios[wheelId];
				torqueRatioSum += PxAbs(torqueRatios[wheelId]);
		}
		PX_CHECK_AND_RETURN_VAL(aveWheelSpeedSum >= 0.99f && aveWheelSpeedSum <= 1.01f, "Sum of PxVehicleMultiWheelDriveDifferentialParams.aveWheelSpeedRatios[i] must be 1.0.", false);
		PX_CHECK_AND_RETURN_VAL(torqueRatioSum >= 0.99f && torqueRatioSum <= 1.01f, "Sum of PxVehicleMultiWheelDriveDifferentialParams.torqueRatios[i] must be 1.0.", false);
		PX_UNUSED(aveWheelSpeedSum);
		PX_UNUSED(torqueRatioSum);
		return true;
	}
};

/**
\brief A special value that indicates instantaneous resolution of a slip that exceeds a differential bias.
*/
#define PX_VEHICLE_FOUR_WHEEL_DIFFERENTIAL_MAXIMUM_STRENGTH PX_MAX_F32

/**
\brief PxVehicleFourWheelDriveDifferentialParams specifies the wheels that are to receive drive torque from the differential 
and the division of torque between the wheels that are connected to the differential.  Additionally, it specifies the biases
and strength of a limited slip differential that operates on two wheels specified as front wheels and two wheels specified
as rear wheels.  
*/
struct PxVehicleFourWheelDriveDifferentialParams : public PxVehicleMultiWheelDriveDifferentialParams
{
	/**
	\brief The ids of the two wheels considered by the differential to be the front pair.
	\note When the ratio of the rotational speeds of the front wheels exceeds frontBias, drive torque is diverted so 
	that the ratio approaches the value specified by frontTarget.  When the bias is not breached, the drive torque 
	is specified by the corresponding entries in PxVehicleMultiWheelDriveDifferentialParams::torqueRatios.
	*/
	PxU32 frontWheelIds[2];

	/**
	\brief The ids of the two wheels considered by the differential to be the rear pair.
	\note When the ratio of the rotational speeds of the rear wheels exceeds rearBias, drive torque is diverted  so
	that the ratio approaches the value specified by rearTarget. When the bias is not breached, the drive torque 
	is specified by the corresponding entries in PxVehicleMultiWheelDriveDifferentialParams::torqueRatios.
	*/
	PxU32 rearWheelIds[2];

	/**
	\brief The parameter frontBias specifies the maximum angular speed ratio of the two front wheels specified by frontWheelIds[2].
	\note If frontBias has value 0.0, the differential will not try to enforce any relationship between the rotational speeds of the two front wheels.
	\note If frontBias has value 0.0, the torque split between the front wheels is specified by the array torqueRatios.
	\note frontBias must have value 0.0 (deactivated limited slip) or be greater than 1.0 (activated limited slip) or be equal to 1.0 (locked axle).
	\note If frontBias has value greater than or equal to 1.0 then the array frontWheelIds must be specified and the corresponding entries of 
	the isConnected[] array must be set true.
	\note If frontBias has value greater than or equal to 1.0 then frontTarget must also be specified. 
	\note A locked axle may be achieved by setting frontBias and frontTarget to 1.0.
	\note A limited slip differential may be achieved by setting frontBias > 1.0 and frontBias > frontTarget > 1.0. 
	*/
	PxReal frontBias;

	/**
	\brief The parameter frontTarget specifies the target rotational speed ratio of the two front wheels in the event that the ratio exceeds frontBias and frontBias
	is configured for an activated limited slip or locked axle.
	\note frontTarget must be less than frontBias and greater than 1.0 to implement a limited slip differential.
	\note Set frontTarget and frontBias to 1.0 to implement a locked axle.
	*/
	PxReal frontTarget;

	/**
	\brief The parameter rearBias specifies the maximum angular speed ratio of the two rear wheels specified by rearWheelIds[2].
	\note If rearBias has value 0.0, the differential will not try to enforce any relationship between the rotational speeds of the two rear wheels.
	\note If rearBias has value 0.0, the torque split between the rear wheels is specified by the array torqueRatios.
	\note rearBias must have value 0.0 (deactivated limited slip) or be greater than 1.0 (activated limited slip) or be equal to 1.0 (locked axle).
	\note If rearBias has value greater than or equal to 1.0 then the array rearWheelIds must be specified and the corresponding entries of
	the isConnected[] array must be set true.
	\note If rearBias has value greater than or equal to 1.0 then rearTarget must also be specified.  
	\note A locked axle may be achieved by setting rearBias and rearTarget to 1.0.
	\note A limited slip differential may be achieved by setting rearBias > 1.0 and rearBias > rearTarget > 1.0
	*/
	PxReal rearBias;

	/**
	\brief The parameter rearTarget specifies the target rotational speed ratio of the two rear wheels in the event that the ratio exceeds rearBias and rearBias
	is configured for an activated limited slip or locked axle.
	\note rearTarget must be less than rearBias and greater than 1.0 to implement a limited slip differential.
	\note Set rearTarget and rearBias to 1.0 to implement a locked axle.
	*/
	PxReal rearTarget;

	/**
	\brief The parameter centerBias specifies the maximum angular speed ratio of the sum of the two front wheels and the sum of the two rear wheels,
	as specified by frontWheelIds[2] and rearWheelIds[2].
	\note If centerBias has value 0.0, the differential will not try to enforce any relationship between the rotational speeds of the front and rear wheels.
	\note If centerBias has value 0.0, the torque split between the front and rear rear wheels is specified by the array torqueRatios.
	\note centerBias must have value 0.0 (deactivated limited slip) or be greater than 1.0 (activated limited slip) or be equal to 1.0 (locked).
	\note If centerBias has value greater than or equal to 1.0 then the arrays frontWheelIds and rearWheelIds must be specified and the corresponding entries of 
	the isConnected[] array must be set true.
	\note If centerBias has value greater than or equal to 1.0 then centerTarget must also be specified.  
	\note A locked front/rear differential may be achieved by setting centerBias and centerTarget to 1.0.
	\note A limited slip differential may be achieved by setting centerBias > 1.0 and centerBias > centerTarget > 1.0
	*/
	PxReal centerBias;

	/**
	\brief The parameter centerTarget specifies the target rotational speed ratio of the sum of the two front wheels and the sum of the two rear wheels 
	in the event that the ratio exceeds centerBias and centerBias is configured for an activated limited slip.
	\note centerTarget must be less than centerBias and greater than 1.0 to implement a limited slip differential.
	\note Set centerTarget and centerBias to 1.0 to implement a locked differential.
	*/
	PxReal centerTarget;

	/**
	\brief The parameter rate specifies how quickly the ratio of rotational speeds approaches the target rotational speed ratio.
	\note strength must be in range [0,PX_VEHICLE_FOUR_WHEEL_DIFF_MAXIMUM_STRENGTH]. 
	\note The ratio of rotational speeds is decremented each update by rate*dt until the ratio is equal to the target ratio.
	\note A value of 0 will result in a deactivated limited slip.
	\note A value of PX_VEHICLE_FOUR_WHEEL_DIFF_MAXIMUM_STRENGTH will result in instantaneous correction of the rotational speed ratios.
	*/
	PxReal rate;


	PX_FORCE_INLINE void setToDefault()
	{
		PxVehicleMultiWheelDriveDifferentialParams::setToDefault();
		frontBias = 0.0f;
		rearBias = 0.0f;
		centerBias = 0.0f;
	}

	PX_FORCE_INLINE bool isValid(const PxVehicleAxleDescription& axleDesc) const
	{
		if (!PxVehicleMultiWheelDriveDifferentialParams::isValid(axleDesc))
			return false;

		PX_CHECK_AND_RETURN_VAL((0.0f == centerBias) ||
			(centerBias > 1.0f &&
				frontWheelIds[0] < axleDesc.nbWheels && frontWheelIds[1] < axleDesc.nbWheels &&
				rearWheelIds[0] < axleDesc.nbWheels && rearWheelIds[1] < axleDesc.nbWheels), 
			"PxVehicleFourWheelDriveDifferentialParams:: centreBias is enabled but the frontWheelIds and rearWheelIds are not configured correctly.", false);
		PX_CHECK_AND_RETURN_VAL((0.0f == centerBias) ||
			(centerBias > 1.0f &&
				frontWheelIds[0] != frontWheelIds[1] && frontWheelIds[0] != rearWheelIds[0] && frontWheelIds[0] != rearWheelIds[1] &&
				frontWheelIds[1] != rearWheelIds[0] && frontWheelIds[1] != rearWheelIds[1] &&
				rearWheelIds[0] != rearWheelIds[1]),
			"PxVehicleFourWheelDriveDifferentialParams:: centreBias is enabled but the frontWheelIds and rearWheelIds are not configured correctly.", false);
		PX_CHECK_AND_RETURN_VAL((0.0f == centerBias) ||
			(centerBias > 1.0f &&
				torqueRatios[frontWheelIds[0]] != 0.0f && torqueRatios[frontWheelIds[1]] != 0.0f && torqueRatios[rearWheelIds[0]] != 0.0f && torqueRatios[rearWheelIds[1]] != 0.0f),
			"PxVehicleFourWheelDriveDifferentialParams:: centreBias is enabled but the front and rear wheels are not connected.", false);

		PX_CHECK_AND_RETURN_VAL((0.0f == rearBias) ||
			(rearBias > 1.0f && rearWheelIds[0] < axleDesc.nbWheels && rearWheelIds[1] < axleDesc.nbWheels),
			"PxVehicleFourWheelDriveDifferentialParams:: rearBias is enabled but the rearWheelIds are not configured correctly.", false);
		PX_CHECK_AND_RETURN_VAL((0.0f == rearBias) ||
			(rearBias > 1.0f && rearWheelIds[0] != rearWheelIds[1]),
			"PxVehicleFourWheelDriveDifferentialParams:: rearBias is enabled but the rearWheelIds are not configured correctly.", false);
		PX_CHECK_AND_RETURN_VAL((0.0f == centerBias) ||
			(rearBias > 1.0f && torqueRatios[rearWheelIds[0]] != 0.0f && torqueRatios[rearWheelIds[1]] != 0.0f),
			"PxVehicleFourWheelDriveDifferentialParams:: rearBias is enabled but the rear wheels are not connected.", false);

		PX_CHECK_AND_RETURN_VAL((0.0f == frontBias) ||
			(frontBias > 1.0f && frontWheelIds[0] < axleDesc.nbWheels && frontWheelIds[1] < axleDesc.nbWheels),
			"PxVehicleFourWheelDriveDifferentialParams:: frontBias is enabled but the frontWheelIds are not configured correctly.", false);
		PX_CHECK_AND_RETURN_VAL((0.0f == frontBias) ||
			(frontBias > 1.0f && frontWheelIds[0] != frontWheelIds[1]),
			"PxVehicleFourWheelDriveDifferentialParams:: frontBias is enabled but the frontWheelIds are not configured correctly.", false);
		PX_CHECK_AND_RETURN_VAL((0.0f == frontBias) ||
			(frontBias > 1.0f && torqueRatios[frontWheelIds[0]] != 0.0f && frontWheelIds[frontWheelIds[1]]),
			"PxVehicleFourWheelDriveDifferentialParams:: frontBias is enabled but the front wheels are not connected.", false);

		PX_CHECK_AND_RETURN_VAL(((0.0f == frontBias) && (0.0f == rearBias) && (0.0f == centerBias)) || (rate >= 0.0f),
			"PxVehicleFourWheelDriveDifferentialParams:: strength must be greater than or equal to zero", false);

		PX_CHECK_AND_RETURN_VAL((0.0f == frontBias) || ((1.0f == frontTarget && 1.0f == rearTarget) || (frontTarget > 1.0f && frontTarget < frontBias)),
			"PxVehicleFourWheelDriveDifferentialParams: frontBias  is enabled but frontTarget not in range (1.0f, frontBias)", false);
		PX_CHECK_AND_RETURN_VAL((0.0f == rearBias) || ((1.0f == rearTarget && 1.0f == rearBias) || (rearTarget > 1.0f && rearTarget < rearBias)),
			"PxVehicleFourWheelDriveDifferentialParams: rearBias  is enabled but rearTarget not in range (1.0f, rearBias)", false);
		PX_CHECK_AND_RETURN_VAL((0.0f == centerBias) || ((1.0f == centerTarget && 1.0f == centerBias) || (centerTarget > 1.0f && centerTarget < centerBias)),
			"PxVehicleFourWheelDriveDifferentialParams: centerBias  is enabled but centerTarget not in range (1.0f, centerBias)", false);

		return true;
	}

	PX_FORCE_INLINE PxVehicleFourWheelDriveDifferentialParams transformAndScale(
		const PxVehicleFrame& srcFrame, const PxVehicleFrame& trgFrame, const PxVehicleScale& srcScale, const PxVehicleScale& trgScale) const
	{
		PX_UNUSED(srcFrame);
		PX_UNUSED(trgFrame);
		PX_UNUSED(srcScale);
		PX_UNUSED(trgScale);
		PxVehicleFourWheelDriveDifferentialParams r = *this;
		static_cast<PxVehicleMultiWheelDriveDifferentialParams&>(r) = PxVehicleMultiWheelDriveDifferentialParams::transformAndScale(srcFrame, trgFrame, srcScale, trgScale);
		return r;
	}
};

/**
\brief A description of a tank differential.
\note The wheels on a tank may be connected to the differential or not connected to the differential.
\note The wheels on a tank track may be connected to a tank track or not connected to a tank track.
\note Wheels connected to the differential but not to a tank track receive the torque split specified by the
corresponding elements of PxVehicleMultiWheelDriveDifferentialParams::torqueRatios[].
\note Wheels connected to a tank track but not to the differential receive no torque from the engine.
\note Wheels connected to a tank track and to the differential receive the torque split specified by the 
corresponding elements of PxVehicleMultiWheelDriveDifferentialParams::torqueRatios[] multiplied by the corresponding
thrust controller value. If the thrust controller has a negative value, the wheels will receive a torque that is negative
with respect to the gearing ratio.
\note The wheels in each tank track have a constraint applied to them to enforce the rule that they all have the same longitudinal speed
at the contact point between the wheel and the tank track.
*/
struct PxVehicleTankDriveDifferentialParams : public PxVehicleMultiWheelDriveDifferentialParams
{
	PX_FORCE_INLINE void setToDefault()
	{
		PxVehicleMultiWheelDriveDifferentialParams::setToDefault();
		nbTracks = 0;
	}

	/**
	\brief Add a tank track by specifying the number of wheels along the track and an array of wheel ids specifying each wheel in the tank track.
	\param[in] nbWheelsInTrackToAdd is the number of wheels in the track to be added.
	\param[in] wheelIdsInTrackToAdd is an array of wheel ids specifying all the wheels in the track to be added.
	\param[in] thrustControllerIndex specifies the index of the thrust controller that will be used to control the tank track.
	*/
	void addTankTrack(const PxU32 nbWheelsInTrackToAdd, const PxU32* const wheelIdsInTrackToAdd, const PxU32 thrustControllerIndex)
	{
		PxU32 trackToWheelIdsOffset = nbTracks ? trackToWheelIds[nbTracks - 1] + nbWheelsPerTrack[nbTracks - 1] : 0;
		PX_ASSERT((trackToWheelIdsOffset + nbWheelsInTrackToAdd) <= PxVehicleLimits::eMAX_NB_WHEELS);
		PX_ASSERT(nbTracks < PxVehicleLimits::eMAX_NB_WHEELS);
		PX_ASSERT(thrustControllerIndex < 2);
		nbWheelsPerTrack[nbTracks] = nbWheelsInTrackToAdd;
		thrustIdPerTrack[nbTracks] = thrustControllerIndex;
		trackToWheelIds[nbTracks] = trackToWheelIdsOffset;
		for (PxU32 i = 0; i < nbWheelsInTrackToAdd; i++)
		{
			wheelIdsInTrackOrder[trackToWheelIdsOffset + i] = wheelIdsInTrackToAdd[i];
		}
		nbTracks++;
	}

	/**
	\brief Return the number of tracks.
	\return The number of tracks.
	\see getNbWheelsInTrack()
	*/
	PX_FORCE_INLINE PxU32 getNbTracks() const
	{
		return nbTracks;
	}

	/**
	\brief Return the number of wheels in the ith track.
	\param[in] i specifies the track to be queried for its wheel count.
	\return The number of wheels in the specified track.
	\see getWheelInTrack()
	*/
	PX_FORCE_INLINE PxU32 getNbWheelsInTrack(const PxU32 i) const
	{
		return nbWheelsPerTrack[i];
	}

	/**
	\brief Return the array of all wheels in the ith track.
	\param[in] i specifies the track to be queried for its wheels.
	\return The array of wheels in the specified track.
	*/
	PX_FORCE_INLINE const PxU32* getWheelsInTrack(const PxU32 i) const
	{
		return (wheelIdsInTrackOrder + trackToWheelIds[i]);
	}

	/**
	\brief Return the wheel id of the jth wheel in the ith track.
	\param[in] j specifies that the wheel id to be returned is the jth wheel in the list of wheels on the specified track.
	\param[in] i specifies the track to be queried.
	\return The wheel id of the jth wheel in the ith track.
	\see getNbWheelsInTrack()
	*/
	PX_FORCE_INLINE PxU32 getWheelInTrack(const PxU32 j, const PxU32 i) const
	{
		return wheelIdsInTrackOrder[trackToWheelIds[i] + j];
	}

	/**
	\brief Return the index of the thrust controller that will control a specified track.
	\param[in] i specifies the track to be queried for its thrust controller index
	\return The index of the thrust controller that will control the ith track.
	*/
	PX_FORCE_INLINE PxU32 getThrustControllerIndex(const PxU32 i) const
	{
		return thrustIdPerTrack[i];
	}

	PX_FORCE_INLINE PxVehicleTankDriveDifferentialParams transformAndScale(
		const PxVehicleFrame& srcFrame, const PxVehicleFrame& trgFrame, const PxVehicleScale& srcScale, const PxVehicleScale& trgScale) const
	{
		PX_UNUSED(srcFrame);
		PX_UNUSED(trgFrame);
		PX_UNUSED(srcScale);
		PX_UNUSED(trgScale);
		PxVehicleTankDriveDifferentialParams r = *this;
		static_cast<PxVehicleMultiWheelDriveDifferentialParams&>(r) = PxVehicleMultiWheelDriveDifferentialParams::transformAndScale(srcFrame, trgFrame, srcScale, trgScale);
		return r;
	}

	PX_FORCE_INLINE bool isValid(const PxVehicleAxleDescription& axleDesc) const
	{
		if (!PxVehicleMultiWheelDriveDifferentialParams::isValid(axleDesc))
			return false;

		PX_CHECK_AND_RETURN_VAL(nbTracks <= PxVehicleLimits::eMAX_NB_WHEELS, "PxVehicleTankDriveDifferentialParams.nbTracks must not exceed PxVehicleLimits::eMAX_NB_WHEELS", false);
		
		const PxU32 nbWheelsInTracksSize = nbTracks ? trackToWheelIds[nbTracks - 1] + nbWheelsPerTrack[nbTracks - 1] : 0;
		PX_UNUSED(nbWheelsInTracksSize);
		PX_CHECK_AND_RETURN_VAL(nbWheelsInTracksSize <= PxVehicleLimits::eMAX_NB_WHEELS, "PxVehicleTankDriveDifferentialParams.nbWheelsInTracks must not exceed PxVehicleLimits::eMAX_NB_WHEELS", false);
		for (PxU32 i = 0; i < nbTracks; i++)
		{
			PX_CHECK_AND_RETURN_VAL(thrustIdPerTrack[i] < 2, "PxVehicleTankDriveDifferentialParams.thrustId must be less than 2", false);
		}

		for (PxU32 i = 0; i < axleDesc.nbWheels; i++)
		{
			const PxU32 wheelId = axleDesc.wheelIdsInAxleOrder[i];
			PxU32 count = 0;
			for (PxU32 j = 0; j < nbTracks; j++)
			{
				const PxU32 nbWheels = getNbWheelsInTrack(j);
				const PxU32* wheelIds = getWheelsInTrack(j);

				for (PxU32 k = 0; k < nbWheels; k++)
				{
					if (wheelIds[k] == wheelId)
						count++;
				}
			}
			PX_CHECK_AND_RETURN_VAL(count <= 1, "PxVehicleTankDriveDifferentialParams - a wheel cannot be in more than one tank track", false);
		}
		return true;
	}

	PxU32 nbTracks;												//!< The number of tracks
	PxU32 thrustIdPerTrack[PxVehicleLimits::eMAX_NB_WHEELS];	//!< The id of the thrust that will  control the track. Must have value 0 or 1.
	PxU32 nbWheelsPerTrack[PxVehicleLimits::eMAX_NB_WHEELS];	//!< The number of wheels in each track
	PxU32 trackToWheelIds[PxVehicleLimits::eMAX_NB_WHEELS];		//!< The list of wheel ids for the ith tank track begins at wheelIdsInTrackOrder[trackToWheelIds[i]]

	PxU32 wheelIdsInTrackOrder[PxVehicleLimits::eMAX_NB_WHEELS];//!< The list of all wheel ids in all tracks
};


#if !PX_DOXYGEN
} // namespace vehicle2
} // namespace physx
#endif


