Program Listing for include/vehicle2/drivetrain/PxVehicleDrivetrainParams.h

↰ Return to documentation for include/vehicle2/drivetrain/PxVehicleDrivetrainParams.h

// 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-2022 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

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;
    }
};

struct PxVehicleClutchCommandResponseParams
{
    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;
    }
};

struct PxVehicleClutchAccuracyMode
{
    enum Enum
    {
        eESTIMATE = 0,
        eBEST_POSSIBLE
    };
};

struct PxVehicleClutchParams
{
public:

    PxVehicleClutchAccuracyMode::Enum accuracyMode;

    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
{
    enum
    {
        eMAX_NB_ENGINE_TORQUE_CURVE_ENTRIES = 8
    };

    PxVehicleFixedSizeLookupTable<PxReal, eMAX_NB_ENGINE_TORQUE_CURVE_ENTRIES> torqueCurve;

    PxReal moi;

    PxReal peakTorque;

    PxReal idleOmega;

    PxReal maxOmega;

    PxReal dampingRateFullThrottle;

    PxReal dampingRateZeroThrottleClutchEngaged;

    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:

    PxU32 neutralGear;

    enum Enum
    {
        eMAX_NB_GEARS = 32
    };

    PxReal ratios[eMAX_NB_GEARS];

    PxReal finalRatio;

    PxU32 nbRatios;

    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:

    PxReal upRatios[PxVehicleGearboxParams::eMAX_NB_GEARS];

    PxReal downRatios[PxVehicleGearboxParams::eMAX_NB_GEARS];

    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;
    }
};

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
    };

    PxU32 frontWheelIds[2];


    PxU32 rearWheelIds[2];

    PxReal frontRearSplit;

    PxReal frontNegPosSplit;

    PxReal rearNegPosSplit;

    PxReal centerBias;

    PxReal frontBias;

    PxReal rearBias;

    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;
    }
};

struct PxVehicleMultiWheelDriveDifferentialParams
{
    PxReal torqueRatios[PxVehicleLimits::eMAX_NB_WHEELS];

    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;
    }
};

#define PX_VEHICLE_FOUR_WHEEL_DIFFERENTIAL_MAXIMUM_STRENGTH PX_MAX_F32

struct PxVehicleFourWheelDriveDifferentialParams : public PxVehicleMultiWheelDriveDifferentialParams
{
    PxU32 frontWheelIds[2];

    PxU32 rearWheelIds[2];

    PxReal frontBias;

    PxReal frontTarget;

    PxReal rearBias;

    PxReal rearTarget;

    PxReal centerBias;

    PxReal centerTarget;

    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;
    }
};

struct PxVehicleTankDriveDifferentialParams : public PxVehicleMultiWheelDriveDifferentialParams
{
    PX_FORCE_INLINE void setToDefault()
    {
        PxVehicleMultiWheelDriveDifferentialParams::setToDefault();
        nbTracks = 0;
        nbWheelsInTracks = 0;
    }

    void addTankTrack(const PxU32 nbWheelsInTrackToAdd, const PxU32* const wheelIdsInTrackToAdd, const PxU32 thrustControllerIndex)
    {
        PX_ASSERT((nbWheelsInTracks + nbWheelsInTrackToAdd) < PxVehicleLimits::eMAX_NB_WHEELS);
        PX_ASSERT(nbTracks < PxVehicleLimits::eMAX_NB_WHEELS);
        PX_ASSERT(thrustControllerIndex < 2);
        nbWheelsPerTrack[nbTracks] = nbWheelsInTrackToAdd;
        thrustIdPerTrack[nbTracks] = thrustControllerIndex;
        trackToWheelIds[nbTracks] = nbWheelsInTracks;
        for (PxU32 i = 0; i < nbWheelsInTrackToAdd; i++)
        {
            wheelIdsInTrackOrder[nbWheelsInTracks + i] = wheelIdsInTrackToAdd[i];
        }
        nbWheelsInTracks += nbWheelsInTrackToAdd;
        nbTracks++;
    }

    PX_FORCE_INLINE PxU32 getNbTracks() const
    {
        return nbTracks;
    }

    PX_FORCE_INLINE PxU32 getNbWheelsInTrack(const PxU32 i) const
    {
        return nbWheelsPerTrack[i];
    }

    PX_FORCE_INLINE const PxU32* getWheelsInTrack(const PxU32 i) const
    {
        return (wheelIdsInTrackOrder + trackToWheelIds[i]);
    }

    PX_FORCE_INLINE PxU32 getWheelInTrack(const PxU32 j, const PxU32 i) const
    {
        return wheelIdsInTrackOrder[trackToWheelIds[i] + j];
    }

    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);
        PX_CHECK_AND_RETURN_VAL(nbWheelsInTracks <= 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);
            PX_CHECK_AND_RETURN_VAL(getNbWheelsInTrack(i) >= 2, "PxVehicleTankDriveDifferentialParams.nbWheelsPerTrack must be greater than or equal to 2", false);
        }

        for (PxU32 i = 0; i < axleDesc.nbWheels; i++)
        {
            const PxU32 wheelId = axleDesc.wheelIdsInAxleOrder[i];
            PxU32 count = 0;
            for (PxU32 j = 0; j < nbWheelsInTracks; j++)
            {
                if (wheelIdsInTrackOrder[j] == 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;
    PxU32 thrustIdPerTrack[PxVehicleLimits::eMAX_NB_WHEELS];
    PxU32 nbWheelsPerTrack[PxVehicleLimits::eMAX_NB_WHEELS];
    PxU32 trackToWheelIds[PxVehicleLimits::eMAX_NB_WHEELS];

    PxU32 wheelIdsInTrackOrder[PxVehicleLimits::eMAX_NB_WHEELS];
    PxU32 nbWheelsInTracks;
};


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