// 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. #include "ExtRackAndPinionJoint.h" #include "ExtConstraintHelper.h" #include "extensions/PxRevoluteJoint.h" #include "extensions/PxPrismaticJoint.h" #include "PxArticulationJointReducedCoordinate.h" //#include #include "omnipvd/ExtOmniPvdSetData.h" using namespace physx; using namespace Ext; PX_IMPLEMENT_OUTPUT_ERROR RackAndPinionJoint::RackAndPinionJoint(const PxTolerancesScale& /*scale*/, PxRigidActor* actor0, const PxTransform& localFrame0, PxRigidActor* actor1, const PxTransform& localFrame1) : RackAndPinionJointT(PxJointConcreteType::eRACK_AND_PINION, actor0, localFrame0, actor1, localFrame1, "RackAndPinionJointData") { RackAndPinionJointData* data = static_cast(mData); data->hingeJoint = NULL; data->prismaticJoint = NULL; data->ratio = 1.0f; data->px = 0.0f; data->vangle = 0.0f; resetError(); } void RackAndPinionJoint::setRatio(float ratio) { RackAndPinionJointData* data = reinterpret_cast(mData); data->ratio = ratio; resetError(); markDirty(); OMNI_PVD_SET(OMNI_PVD_CONTEXT_HANDLE, PxRackAndPinionJoint, ratio, static_cast(*this), ratio) } float RackAndPinionJoint::getRatio() const { RackAndPinionJointData* data = reinterpret_cast(mData); return data->ratio; } bool RackAndPinionJoint::setData(PxU32 nbRackTeeth, PxU32 nbPinionTeeth, float rackLength) { if(!nbRackTeeth) return outputError(__LINE__, "PxRackAndPinionJoint::setData: nbRackTeeth cannot be zero."); if(!nbPinionTeeth) return outputError(__LINE__, "PxRackAndPinionJoint::setData: nbPinionTeeth cannot be zero."); if(rackLength<=0.0f) return outputError(__LINE__, "PxRackAndPinionJoint::setData: rackLength must be positive."); RackAndPinionJointData* data = reinterpret_cast(mData); data->ratio = (PxTwoPi*nbRackTeeth)/(rackLength*nbPinionTeeth); resetError(); markDirty(); return true; } bool RackAndPinionJoint::setJoints(const PxBase* hinge, const PxBase* prismatic) { RackAndPinionJointData* data = static_cast(mData); if(hinge) { const PxType type0 = hinge->getConcreteType(); if(type0 == PxConcreteType::eARTICULATION_JOINT_REDUCED_COORDINATE) { const PxArticulationJointReducedCoordinate* joint0 = static_cast(hinge); const PxArticulationJointType::Enum artiJointType0 = joint0->getJointType(); if(artiJointType0 != PxArticulationJointType::eREVOLUTE && artiJointType0 != PxArticulationJointType::eREVOLUTE_UNWRAPPED) return outputError(__LINE__, "PxRackAndPinionJoint::setJoints: passed joint must be a revolute joint."); } else { if(type0 != PxJointConcreteType::eREVOLUTE && type0 != PxJointConcreteType::eD6) return outputError(__LINE__, "PxRackAndPinionJoint::setJoints: passed hinge joint must be either a revolute joint or a D6 joint."); } } if(prismatic) { const PxType type1 = prismatic->getConcreteType(); if(type1 == PxConcreteType::eARTICULATION_JOINT_REDUCED_COORDINATE) { const PxArticulationJointReducedCoordinate* joint1 = static_cast(prismatic); const PxArticulationJointType::Enum artiJointType1 = joint1->getJointType(); if(artiJointType1 != PxArticulationJointType::ePRISMATIC) return outputError(__LINE__, "PxRackAndPinionJoint::setJoints: passed joint must be a prismatic joint."); } else { if(type1 != PxJointConcreteType::ePRISMATIC && type1 != PxJointConcreteType::eD6) return outputError(__LINE__, "PxRackAndPinionJoint::setJoints: passed prismatic joint must be either a prismatic joint or a D6 joint."); } } data->hingeJoint = hinge; data->prismaticJoint = prismatic; resetError(); markDirty(); #if PX_SUPPORT_OMNI_PVD const PxBase* joints[] = { hinge, prismatic }; PxU32 jointCount = sizeof(joints) / sizeof(joints[0]); OMNI_PVD_SET_ARRAY(OMNI_PVD_CONTEXT_HANDLE, PxRackAndPinionJoint, joints, static_cast(*this), joints, jointCount) #endif return true; } void RackAndPinionJoint::getJoints(const PxBase*& hinge, const PxBase*& prismatic) const { const RackAndPinionJointData* data = static_cast(mData); hinge = data->hingeJoint; prismatic = data->prismaticJoint; } static float angleDiff(float angle0, float angle1) { const float diff = fmodf( angle1 - angle0 + PxPi, PxTwoPi) - PxPi; return diff < -PxPi ? diff + PxTwoPi : diff; } void RackAndPinionJoint::updateError() { RackAndPinionJointData* data = static_cast(mData); if(!data->hingeJoint || !data->prismaticJoint) return; PxRigidActor* rackActor0; PxRigidActor* rackActor1; getActors(rackActor0, rackActor1); float Angle0 = 0.0f; float Sign0 = 0.0f; { PxRigidActor* hingeActor0; PxRigidActor* hingeActor1; const PxType type = data->hingeJoint->getConcreteType(); if(type == PxConcreteType::eARTICULATION_JOINT_REDUCED_COORDINATE) { const PxArticulationJointReducedCoordinate* artiHingeJoint = static_cast(data->hingeJoint); hingeActor0 = &artiHingeJoint->getParentArticulationLink(); hingeActor1 = &artiHingeJoint->getChildArticulationLink(); Angle0 = artiHingeJoint->getJointPosition(PxArticulationAxis::eTWIST); } else { const PxJoint* hingeJoint = static_cast(data->hingeJoint); hingeJoint->getActors(hingeActor0, hingeActor1); if(type == PxJointConcreteType::eREVOLUTE) Angle0 = static_cast(hingeJoint)->getAngle(); else if (type == PxJointConcreteType::eD6) Angle0 = static_cast(hingeJoint)->getTwistAngle(); } if(rackActor0 == hingeActor0 || rackActor1 == hingeActor0) Sign0 = -1.0f; else if (rackActor0 == hingeActor1 || rackActor1 == hingeActor1) Sign0 = 1.0f; else PX_ASSERT(0); } if(!mInitDone) { mInitDone = true; mPersistentAngle0 = Angle0; } const float travelThisFrame0 = angleDiff(Angle0, mPersistentAngle0); mVirtualAngle0 += travelThisFrame0; // printf("mVirtualAngle0: %f\n", mVirtualAngle0); mPersistentAngle0 = Angle0; float px = 0.0f; float Sign1 = 0.0f; { PxRigidActor* prismaticActor0; PxRigidActor* prismaticActor1; const PxType type = data->prismaticJoint->getConcreteType(); if(type == PxConcreteType::eARTICULATION_JOINT_REDUCED_COORDINATE) { const PxArticulationJointReducedCoordinate* artiPrismaticJoint = static_cast(data->prismaticJoint); prismaticActor0 = &artiPrismaticJoint->getParentArticulationLink(); prismaticActor1 = &artiPrismaticJoint->getChildArticulationLink(); px = artiPrismaticJoint->getJointPosition(PxArticulationAxis::eX); } else { const PxJoint* prismaticJoint = static_cast(data->prismaticJoint); prismaticJoint->getActors(prismaticActor0, prismaticActor1); if(type==PxJointConcreteType::ePRISMATIC) px = static_cast(prismaticJoint)->getPosition(); else if(type==PxJointConcreteType::eD6) px = prismaticJoint->getRelativeTransform().p.x; } if(rackActor0 == prismaticActor0 || rackActor1 == prismaticActor0) Sign1 = -1.0f; else if(rackActor0 == prismaticActor1 || rackActor1 == prismaticActor1) Sign1 = 1.0f; else PX_ASSERT(0); } // printf("px: %f\n", px); data->px = Sign1*px; data->vangle = Sign0*mVirtualAngle0; markDirty(); } void RackAndPinionJoint::resetError() { mVirtualAngle0 = 0.0f; mPersistentAngle0 = 0.0f; mInitDone = false; } static void RackAndPinionJointVisualize(PxConstraintVisualizer& viz, const void* constantBlock, const PxTransform& body0Transform, const PxTransform& body1Transform, PxU32 flags) { if(flags & PxConstraintVisualizationFlag::eLOCAL_FRAMES) { const RackAndPinionJointData& data = *reinterpret_cast(constantBlock); // Visualize joint frames PxTransform32 cA2w, cB2w; joint::computeJointFrames(cA2w, cB2w, data, body0Transform, body1Transform); viz.visualizeJointFrames(cA2w, cB2w); } if(0) { const RackAndPinionJointData& data = *reinterpret_cast(constantBlock); if(0) { PxTransform32 cA2w, cB2w; joint::computeJointFrames(cA2w, cB2w, data, body0Transform, body1Transform); const PxVec3 gearAxis0 = cA2w.q.getBasisVector0(); const PxVec3 rackPrismaticAxis = cB2w.q.getBasisVector0(); viz.visualizeLine(cA2w.p, cA2w.p + gearAxis0, 0xff0000ff); viz.visualizeLine(cB2w.p, cB2w.p + rackPrismaticAxis, 0xff0000ff); } } } //TAG:solverprepshader static PxU32 RackAndPinionJointSolverPrep(Px1DConstraint* constraints, PxVec3p& body0WorldOffset, PxU32 /*maxConstraints*/, PxConstraintInvMassScale& invMassScale, const void* constantBlock, const PxTransform& bA2w, const PxTransform& bB2w, bool /*useExtendedLimits*/, PxVec3p& cA2wOut, PxVec3p& cB2wOut) { const RackAndPinionJointData& data = *reinterpret_cast(constantBlock); PxTransform32 cA2w, cB2w; joint::ConstraintHelper ch(constraints, invMassScale, cA2w, cB2w, body0WorldOffset, data, bA2w, bB2w); cA2wOut = cB2w.p; cB2wOut = cB2w.p; const PxVec3 gearAxis = cA2w.q.getBasisVector0(); const PxVec3 rackPrismaticAxis = cB2w.q.getBasisVector0(); // PT: this optional bit of code tries to fix the ratio for cases where the "same" rack is moved e.g. above or below a gear. // In that case the rack would move in one direction or another depending on its position compared to the gear, and to avoid // having to use a negative ratio in one of these cases this code tries to compute the proper sign and handle both cases the // same way from the user's perspective. This created unexpected issues in ill-defined cases where e.g. the gear and the rack // completely overlap, and we end up with a +0 or -0 for "dp" in the code below. So now this code disables itself in these // cases but it would probably be better to disable it entirely. We don't do it though since it could break existing scenes. // We might want to revisit these decisions at some point. float Coeff = 1.0f; const float epsilon = 0.001f; const PxVec3 delta = cB2w.p - cA2w.p; if(delta.magnitudeSquared()>epsilon*epsilon) { const PxVec3 velocity = gearAxis.cross(delta); if(velocity.magnitudeSquared()>epsilon*epsilon) { const float dp = velocity.dot(rackPrismaticAxis); Coeff = fabsf(dp)>epsilon ? PxSign(dp) : 1.0f; } } Px1DConstraint& con = constraints[0]; con.linear0 = PxVec3(0.0f); con.linear1 = rackPrismaticAxis * data.ratio*Coeff; con.angular0 = gearAxis; con.angular1 = PxVec3(0.0f); con.geometricError = -Coeff*data.px*data.ratio - data.vangle; con.minImpulse = -PX_MAX_F32; con.maxImpulse = PX_MAX_F32; con.velocityTarget = 0.f; con.solveHint = 0; con.flags = Px1DConstraintFlag::eOUTPUT_FORCE|Px1DConstraintFlag::eANGULAR_CONSTRAINT; con.mods.bounce.restitution = 0.0f; con.mods.bounce.velocityThreshold = 0.0f; return 1; } /////////////////////////////////////////////////////////////////////////////// static PxConstraintShaderTable gRackAndPinionJointShaders = { RackAndPinionJointSolverPrep, RackAndPinionJointVisualize, PxConstraintFlag::eALWAYS_UPDATE }; PxConstraintSolverPrep RackAndPinionJoint::getPrep() const { return gRackAndPinionJointShaders.solverPrep; } PxRackAndPinionJoint* physx::PxRackAndPinionJointCreate(PxPhysics& physics, PxRigidActor* actor0, const PxTransform& localFrame0, PxRigidActor* actor1, const PxTransform& localFrame1) { PX_CHECK_AND_RETURN_NULL(localFrame0.isSane(), "PxRackAndPinionJointCreate: local frame 0 is not a valid transform"); PX_CHECK_AND_RETURN_NULL(localFrame1.isSane(), "PxRackAndPinionJointCreate: local frame 1 is not a valid transform"); PX_CHECK_AND_RETURN_NULL((actor0 && actor0->is()) || (actor1 && actor1->is()), "PxRackAndPinionJointCreate: at least one actor must be dynamic"); PX_CHECK_AND_RETURN_NULL(actor0 != actor1, "PxRackAndPinionJointCreate: actors must be different"); return createJointT(physics, actor0, localFrame0, actor1, localFrame1, gRackAndPinionJointShaders); } // PX_SERIALIZATION void RackAndPinionJoint::resolveReferences(PxDeserializationContext& context) { mPxConstraint = resolveConstraintPtr(context, mPxConstraint, this, gRackAndPinionJointShaders); RackAndPinionJointData* data = static_cast(mData); context.translatePxBase(data->hingeJoint); context.translatePxBase(data->prismaticJoint); } //~PX_SERIALIZATION #if PX_SUPPORT_OMNI_PVD template<> void physx::Ext::omniPvdInitJoint(RackAndPinionJoint& joint) { OMNI_PVD_WRITE_SCOPE_BEGIN(pvdWriter, pvdRegData) PxRackAndPinionJoint& j = static_cast(joint); OMNI_PVD_CREATE_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxRackAndPinionJoint, j); omniPvdSetBaseJointParams(static_cast(joint), PxJointConcreteType::eRACK_AND_PINION); OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxRackAndPinionJoint, ratio, j, joint.getRatio()) OMNI_PVD_WRITE_SCOPE_END } #endif