Revision control

++ // Copyright (C) 2026 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include "qd6joint_p.h"
++
++ #include "physxnode/qphysxactorbody_p.h"
++ #include "qabstractphysicsnode_p.h"
++ #include "qstaticphysxobjects_p.h"
++
++ #include <extensions/PxD6Joint.h>
++ #include <PxPhysics.h>
++
++ QT_BEGIN_NAMESPACE
++
++
++ /*!
++ \qmltype D6Joint
++ \inqmlmodule QtQuick3D.Physics
++ \since 6.13
++ \brief A configurable joint with up to 6 degrees of freedom (3 linear, 3 angular).
++
++ The D6Joint allows custom configuration for each axis of movement and rotation.
++ Each degree of freedom can be set to \c Locked, \c Limited, or \c Free using the \l Motion enum.
++ Soft limits with spring stiffness and damping can be configured for linear and angular constraints.
++
++ \sa {DistanceJoint}
++ \sa {FixedJoint}
++ \sa {PrismaticJoint}
++ \sa {RevoluteJoint}
++ \sa {SphericalJoint}
++ */
++
++ /*!
++ \qmlproperty enumeration D6Joint::xMotion
++ \since 6.13
++ \default D6Joint.Locked
++
++ Available options:
++
++ \value D6Joint.Locked
++ The degree of freedom is completely restricted and cannot move.
++ \value D6Joint.Limited
++ The degree of freedom is constrained within specified lower and upper limits.
++ \value D6Joint.Free
++ The degree of freedom is unconstrained and moves freely.
++
++ This property holds the motion constraint along the X axis.
++ */
++
++ /*!
++ \qmlproperty enumeration D6Joint::yMotion
++ \since 6.13
++ \default D6Joint.Locked
++
++ Available options:
++
++ \value D6Joint.Locked
++ The degree of freedom is completely restricted and cannot move.
++ \value D6Joint.Limited
++ The degree of freedom is constrained within specified lower and upper limits.
++ \value D6Joint.Free
++ The degree of freedom is unconstrained and moves freely.
++
++ This property holds the motion constraint along the Y axis.
++ */
++
++ /*!
++ \qmlproperty enumeration D6Joint::zMotion
++ \since 6.13
++ \default D6Joint.Locked
++
++ Available options:
++
++ \value D6Joint.Locked
++ The degree of freedom is completely restricted and cannot move.
++ \value D6Joint.Limited
++ The degree of freedom is constrained within specified lower and upper limits.
++ \value D6Joint.Free
++ The degree of freedom is unconstrained and moves freely.
++
++ This property holds the motion constraint along the Z axis.
++ */
++
++ /*!
++ \qmlproperty enumeration D6Joint::twistMotion
++ \since 6.13
++ \default D6Joint.Locked
++
++ Available options:
++
++ \value D6Joint.Locked
++ The degree of freedom is completely restricted and cannot move.
++ \value D6Joint.Limited
++ The degree of freedom is constrained within specified lower and upper limits.
++ \value D6Joint.Free
++ The degree of freedom is unconstrained and moves freely.
++
++ This property holds the rotational constraint around the X axis (twist).
++ */
++
++ /*!
++ \qmlproperty enumeration D6Joint::swingMotionY
++ \since 6.13
++ \default D6Joint.Locked
++
++ Available options:
++
++ \value D6Joint.Locked
++ The degree of freedom is completely restricted and cannot move.
++ \value D6Joint.Limited
++ The degree of freedom is constrained within specified lower and upper limits.
++ \value D6Joint.Free
++ The degree of freedom is unconstrained and moves freely.
++
++ This property holds the rotational constraint around the Y axis (swing Y).
++ */
++
++ /*!
++ \qmlproperty enumeration D6Joint::swingMotionZ
++ \since 6.13
++ \default D6Joint.Locked
++
++ Available options:
++
++ \value D6Joint.Locked
++ The degree of freedom is completely restricted and cannot move.
++ \value D6Joint.Limited
++ The degree of freedom is constrained within specified lower and upper limits.
++ \value D6Joint.Free
++ The degree of freedom is unconstrained and moves freely.
++
++ This property holds the rotational constraint around the Z axis (swing Z).
++ */
++
++ /*!
++ \qmlproperty real D6Joint::linearLimitXLower
++ \since 6.13
++ \default -1.0
++
++ This property holds the lower limit of movement along the X axis.
++ The value must be less than or equal to \l linearLimitXUpper.
++
++ Range: \c{(-\inf, \inf)}
++ */
++
++ /*!
++ \qmlproperty real D6Joint::linearLimitXUpper
++ \since 6.13
++ \default 1.0
++
++ This property holds the upper limit of movement along the X axis.
++ The value must be greater than or equal to \l linearLimitXLower.
++
++ Range: \c{(-\inf, \inf)}
++ */
++
++ /*!
++ \qmlproperty real D6Joint::linearLimitYLower
++ \since 6.13
++ \default -1.0
++
++ This property holds the lower limit of movement along the Y axis.
++ The value must be less than or equal to \l linearLimitYUpper.
++
++ Range: \c{(-\inf, \inf)}
++ */
++
++ /*!
++ \qmlproperty real D6Joint::linearLimitYUpper
++ \since 6.13
++ \default 1.0
++
++ This property holds the upper limit of movement along the Y axis.
++ The value must be greater than or equal to \l linearLimitYLower.
++
++ Range: \c{(-\inf, \inf)}
++ */
++
++ /*!
++ \qmlproperty real D6Joint::linearLimitZLower
++ \since 6.13
++ \default -1.0
++
++ This property holds the lower limit of movement along the Z axis.
++ The value must be less than or equal to \l linearLimitZUpper.
++
++ Range: \c{(-\inf, \inf)}
++ */
++
++ /*!
++ \qmlproperty real D6Joint::linearLimitZUpper
++ \since 6.13
++ \default 1.0
++
++ This property holds the upper limit of movement along the Z axis.
++ The value must be greater than or equal to \l linearLimitZLower.
++
++ Range: \c{(-\inf, \inf)}
++ */
++
++ /*!
++ \qmlproperty real D6Joint::twistLimitLower
++ \since 6.13
++ \default -pi/4
++
++ This property holds the lower angular limit (in radians) around the X axis.
++ The value must be less than or equal to \l twistLimitUpper.
++
++ Range: \c{(-2pi, 2pi)}
++ */
++
++ /*!
++ \qmlproperty real D6Joint::twistLimitUpper
++ \since 6.13
++ \default pi/4
++
++ This property holds the upper angular limit (in radians) around the X axis.
++ The value must be greater than or equal to \l twistLimitLower.
++
++ Range: \c{(-2pi, 2pi)}
++ */
++
++ /*!
++ \qmlproperty real D6Joint::swingLimitAngleY
++ \since 6.13
++ \default pi/4
++
++ This property holds the maximum swing angle limit (in radians) around the Y axis.
++
++ Range: \c{(0, pi)}
++ */
++
++ /*!
++ \qmlproperty real D6Joint::swingLimitAngleZ
++ \since 6.13
++ \default pi/4
++
++ This property holds the maximum swing angle limit (in radians) around the Z axis.
++
++ Range: \c{(0, pi)}
++ */
++
++ /*!
++ \qmlproperty real D6Joint::linearStiffness
++ \since 6.13
++ \default 0.0
++
++ This property holds the spring stiffness for linear motion constraints.
++ When set to a value greater than 0, linear limits act as soft constraints (springs).
++
++ Range: \c{[0, \inf)}
++ */
++
++ /*!
++ \qmlproperty real D6Joint::linearDamping
++ \since 6.13
++ \default 0.0
++
++ This property holds the spring damping for linear motion constraints.
++
++ Range: \c{[0, \inf)}
++ */
++
++ /*!
++ \qmlproperty real D6Joint::angularStiffness
++ \since 6.13
++ \default 0.0
++
++ This property holds the spring stiffness for angular motion constraints.
++ When set to a value greater than 0, angular limits act as soft constraints (springs).
++
++ Range: \c{[0, \inf)}
++ */
++
++ /*!
++ \qmlproperty real D6Joint::angularDamping
++ \since 6.13
++ \default 0.0
++
++ This property holds the spring damping for angular motion constraints.
++
++ Range: \c{[0, \inf)}
++ */
++
++ namespace {
++ physx::PxD6Motion::Enum toPxEnum(QD6Joint::Motion val)
++ {
++ switch (val) {
++ case QD6Joint::Motion::Locked: return physx::PxD6Motion::eLOCKED;
++ case QD6Joint::Motion::Limited: return physx::PxD6Motion::eLIMITED;
++ case QD6Joint::Motion::Free: return physx::PxD6Motion::eFREE;
++ }
++
++ Q_UNREACHABLE_RETURN(physx::PxD6Motion::eLOCKED);
++ }
++ }
++
++ QD6Joint::QD6Joint() = default;
++
++ QD6Joint::Motion QD6Joint::xMotion() const
++ {
++ return m_xMotion;
++ }
++
++ void QD6Joint::setXMotion(Motion motion)
++ {
++ if (m_xMotion == motion)
++ return;
++ m_xMotion = motion;
++ m_dirtyProperties = true;
++ emit xMotionChanged();
++ }
++
++ QD6Joint::Motion QD6Joint::yMotion() const
++ {
++ return m_yMotion;
++ }
++
++ void QD6Joint::setYMotion(Motion motion)
++ {
++ if (m_yMotion == motion)
++ return;
++ m_yMotion = motion;
++ m_dirtyProperties = true;
++ emit yMotionChanged();
++ }
++
++ QD6Joint::Motion QD6Joint::zMotion() const
++ {
++ return m_zMotion;
++ }
++
++ void QD6Joint::setZMotion(Motion motion)
++ {
++ if (m_zMotion == motion)
++ return;
++ m_zMotion = motion;
++ m_dirtyProperties = true;
++ emit zMotionChanged();
++ }
++
++ QD6Joint::Motion QD6Joint::twistMotion() const
++ {
++ return m_twistMotion;
++ }
++
++ void QD6Joint::setTwistMotion(Motion motion)
++ {
++ if (m_twistMotion == motion)
++ return;
++ m_twistMotion = motion;
++ m_dirtyProperties = true;
++ emit twistMotionChanged();
++ }
++
++ float QD6Joint::linearLimitXLower() const
++ {
++ return m_linearLimitXLower;
++ }
++
++ void QD6Joint::setLinearLimitXLower(float limit)
++ {
++ if (qFuzzyCompare(m_linearLimitXLower, limit))
++ return;
++ m_linearLimitXLower = limit;
++ m_dirtyProperties = true;
++ emit linearLimitXLowerChanged();
++ }
++
++ float QD6Joint::linearLimitXUpper() const
++ {
++ return m_linearLimitXUpper;
++ }
++
++ void QD6Joint::setLinearLimitXUpper(float limit)
++ {
++ if (qFuzzyCompare(m_linearLimitXUpper, limit))
++ return;
++ m_linearLimitXUpper = limit;
++ m_dirtyProperties = true;
++ emit linearLimitXUpperChanged();
++ }
++
++ float QD6Joint::linearLimitYLower() const
++ {
++ return m_linearLimitYLower;
++ }
++
++ void QD6Joint::setLinearLimitYLower(float limit)
++ {
++ if (qFuzzyCompare(m_linearLimitYLower, limit))
++ return;
++ m_linearLimitYLower = limit;
++ m_dirtyProperties = true;
++ emit linearLimitYLowerChanged();
++ }
++
++ float QD6Joint::linearLimitYUpper() const
++ {
++ return m_linearLimitYUpper;
++ }
++
++ void QD6Joint::setLinearLimitYUpper(float limit)
++ {
++ if (qFuzzyCompare(m_linearLimitYUpper, limit))
++ return;
++ m_linearLimitYUpper = limit;
++ m_dirtyProperties = true;
++ emit linearLimitYUpperChanged();
++ }
++
++ float QD6Joint::linearLimitZLower() const
++ {
++ return m_linearLimitZLower;
++ }
++
++ void QD6Joint::setLinearLimitZLower(float limit)
++ {
++ if (qFuzzyCompare(m_linearLimitZLower, limit))
++ return;
++ m_linearLimitZLower = limit;
++ m_dirtyProperties = true;
++ emit linearLimitZLowerChanged();
++ }
++
++ float QD6Joint::linearLimitZUpper() const
++ {
++ return m_linearLimitZUpper;
++ }
++
++ void QD6Joint::setLinearLimitZUpper(float limit)
++ {
++ if (qFuzzyCompare(m_linearLimitZUpper, limit))
++ return;
++ m_linearLimitZUpper = limit;
++ m_dirtyProperties = true;
++ emit linearLimitZUpperChanged();
++ }
++
++ float QD6Joint::twistLimitLower() const
++ {
++ return m_twistLimitLower;
++ }
++
++ void QD6Joint::setTwistLimitLower(float limit)
++ {
++ // PhysX requires the twist limit to be strictly within (-2*Pi, 2*Pi)
++ limit = qBound(-float(M_PI * 2) + 0.0001f, limit, float(M_PI * 2) - 0.0001f);
++
++ if (qFuzzyCompare(m_twistLimitLower, limit))
++ return;
++ m_twistLimitLower = limit;
++ m_dirtyProperties = true;
++ emit twistLimitLowerChanged();
++ }
++
++ float QD6Joint::twistLimitUpper() const
++ {
++ return m_twistLimitUpper;
++ }
++
++ void QD6Joint::setTwistLimitUpper(float limit)
++ {
++ // PhysX requires the twist limit to be strictly within (-2*Pi, 2*Pi)
++ limit = qBound(-float(M_PI * 2) + 0.0001f, limit, float(M_PI * 2) - 0.0001f);
++
++ if (qFuzzyCompare(m_twistLimitUpper, limit))
++ return;
++ m_twistLimitUpper = limit;
++ m_dirtyProperties = true;
++ emit twistLimitUpperChanged();
++ }
++
++ float QD6Joint::linearStiffness() const
++ {
++ return m_linearStiffness;
++ }
++
++ void QD6Joint::setLinearStiffness(float stiffness)
++ {
++ stiffness = qMax(0.f, stiffness);
++
++ if (qFuzzyCompare(m_linearStiffness, stiffness))
++ return;
++ m_linearStiffness = stiffness;
++ m_dirtyProperties = true;
++ emit linearStiffnessChanged();
++ }
++
++ float QD6Joint::linearDamping() const
++ {
++ return m_linearDamping;
++ }
++
++ void QD6Joint::setLinearDamping(float damping)
++ {
++ damping = qMax(0.f, damping);
++
++ if (qFuzzyCompare(m_linearDamping, damping))
++ return;
++ m_linearDamping = damping;
++ m_dirtyProperties = true;
++ emit linearDampingChanged();
++ }
++
++ float QD6Joint::angularStiffness() const
++ {
++ return m_angularStiffness;
++ }
++
++ void QD6Joint::setAngularStiffness(float stiffness)
++ {
++ stiffness = qMax(0.f, stiffness);
++ if (qFuzzyCompare(m_angularStiffness, stiffness))
++ return;
++ m_angularStiffness = stiffness;
++ m_dirtyProperties = true;
++ emit angularStiffnessChanged();
++ }
++
++ float QD6Joint::angularDamping() const
++ {
++ return m_angularDamping;
++ }
++
++ void QD6Joint::setAngularDamping(float damping)
++ {
++ damping = qMax(0.f, damping);
++ if (qFuzzyCompare(m_angularDamping, damping))
++ return;
++ m_angularDamping = damping;
++ m_dirtyProperties = true;
++ emit angularDampingChanged();
++ }
++
++
++ QD6Joint::Motion QD6Joint::swingMotionY() const
++ {
++ return m_swingMotionY;
++ }
++
++ void QD6Joint::setSwingMotionY(Motion newSwingMotionY)
++ {
++ if (m_swingMotionY == newSwingMotionY)
++ return;
++ m_swingMotionY = newSwingMotionY;
++ m_dirtyProperties = true;
++ emit swingMotionYChanged();
++ }
++
++ QD6Joint::Motion QD6Joint::swingMotionZ() const
++ {
++ return m_swingMotionZ;
++ }
++
++ void QD6Joint::setSwingMotionZ(Motion newSwingMotionZ)
++ {
++ if (m_swingMotionZ == newSwingMotionZ)
++ return;
++ m_swingMotionZ = newSwingMotionZ;
++ m_dirtyProperties = true;
++ emit swingMotionZChanged();
++ }
++
++ float QD6Joint::swingLimitAngleY() const
++ {
++ return m_swingLimitAngleY;
++ }
++
++ void QD6Joint::setSwingLimitAngleY(float newSwingLimitAngleY)
++ {
++ // Clamp swing angle between Math standard (0, Pi)
++ newSwingLimitAngleY = qBound(0.0001f, newSwingLimitAngleY, float(M_PI) - 0.0001f);
++
++ if (qFuzzyCompare(m_swingLimitAngleY, newSwingLimitAngleY))
++ return;
++ m_swingLimitAngleY = newSwingLimitAngleY;
++ m_dirtyProperties = true;
++ emit swingLimitAngleYChanged();
++ }
++
++ float QD6Joint::swingLimitAngleZ() const
++ {
++ return m_swingLimitAngleZ;
++ }
++
++ void QD6Joint::setSwingLimitAngleZ(float newSwingLimitAngleZ)
++ {
++ // Clamp swing angle between Math standard (0, Pi)
++ newSwingLimitAngleZ = qBound(0.0001f, newSwingLimitAngleZ, float(M_PI) - 0.0001f);
++
++ if (qFuzzyCompare(m_swingLimitAngleZ, newSwingLimitAngleZ))
++ return;
++ m_swingLimitAngleZ = newSwingLimitAngleZ;
++ m_dirtyProperties = true;
++ emit swingLimitAngleZChanged();
++ }
++
++ physx::PxJoint *QD6Joint::createPhysxJoint(physx::PxRigidActor *actorA,
++ physx::PxRigidActor *actorB,
++ const physx::PxTransform &trfA,
++ const physx::PxTransform &trfB)
++ {
++ return physx::PxD6JointCreate(*StaticPhysXObjects::getReference().physics,
++ actorA, trfA, actorB, trfB);
++ }
++
++ void QD6Joint::setJointProperties()
++ {
++ auto *d6Joint = static_cast<physx::PxD6Joint *>(m_joint);
++ if (!d6Joint)
++ return;
++
++ const physx::PxTolerancesScale scale =
++ StaticPhysXObjects::getReference().physics->getTolerancesScale();
++
++ d6Joint->setMotion(physx::PxD6Axis::eX, toPxEnum(m_xMotion));
++ d6Joint->setMotion(physx::PxD6Axis::eY, toPxEnum(m_yMotion));
++ d6Joint->setMotion(physx::PxD6Axis::eZ, toPxEnum(m_zMotion));
++ d6Joint->setMotion(physx::PxD6Axis::eTWIST, toPxEnum(m_twistMotion));
++ d6Joint->setMotion(physx::PxD6Axis::eSWING1, toPxEnum(m_swingMotionY));
++ d6Joint->setMotion(physx::PxD6Axis::eSWING2, toPxEnum(m_swingMotionZ));
++
++ if (m_xMotion == Motion::Limited) {
++ physx::PxJointLinearLimitPair limit(scale, qMin(m_linearLimitXLower, m_linearLimitXUpper),
++ qMax(m_linearLimitXLower, m_linearLimitXUpper));
++ limit.stiffness = m_linearStiffness; // Use linear stiffness for linear limits
++ limit.damping = m_linearDamping; // Use linear damping for linear limits
++ d6Joint->setLinearLimit(physx::PxD6Axis::eX, limit);
++ }
++
++ if (m_yMotion == Motion::Limited) {
++ physx::PxJointLinearLimitPair limit(scale, qMin(m_linearLimitYLower, m_linearLimitYUpper),
++ qMax(m_linearLimitYLower, m_linearLimitYUpper));
++ limit.stiffness = m_linearStiffness; // Use linear stiffness for linear limits
++ limit.damping = m_linearDamping; // Use linear damping for linear limits
++ d6Joint->setLinearLimit(physx::PxD6Axis::eY, limit);
++ }
++
++ if (m_zMotion == Motion::Limited) {
++ physx::PxJointLinearLimitPair limit(scale, qMin(m_linearLimitZLower, m_linearLimitZUpper),
++ qMax(m_linearLimitZLower, m_linearLimitZUpper));
++ limit.stiffness = m_linearStiffness; // Use linear stiffness for linear limits
++ limit.damping = m_linearDamping; // Use linear damping for linear limits
++ d6Joint->setLinearLimit(physx::PxD6Axis::eZ, limit);
++ }
++
++ if (m_twistMotion == Motion::Limited) {
++ physx::PxJointAngularLimitPair limit(qMin(m_twistLimitLower, m_twistLimitUpper),
++ qMax(m_twistLimitLower, m_twistLimitUpper));
++ limit.stiffness = m_angularStiffness; // Use angular stiffness for twist limit
++ limit.damping = m_angularDamping; // Use angular damping for twist limit
++ d6Joint->setTwistLimit(limit);
++ }
++
++ if (m_swingMotionY == Motion::Limited || m_swingMotionZ == Motion::Limited) {
++ // PhysX requires cone angles to be strictly within (0, Pi) range
++ const float angleY = qBound(0.0001f, m_swingLimitAngleY, float(M_PI) - 0.0001f);
++ const float angleZ = qBound(0.0001f, m_swingLimitAngleZ, float(M_PI) - 0.0001f);
++
++ physx::PxJointLimitCone limit(angleY, angleZ);
++ limit.stiffness = m_angularStiffness;
++ limit.damping = m_angularDamping;
++ d6Joint->setSwingLimit(limit);
++ }
++ }
++
++ QT_END_NAMESPACE
++