Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Constraints/PathConstraint.cpp
9913 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/Constraints/PathConstraint.h>7#include <Jolt/Physics/Body/Body.h>8#include <Jolt/Core/StringTools.h>9#include <Jolt/ObjectStream/TypeDeclarations.h>10#include <Jolt/Core/StreamIn.h>11#include <Jolt/Core/StreamOut.h>12#ifdef JPH_DEBUG_RENDERER13#include <Jolt/Renderer/DebugRenderer.h>14#endif // JPH_DEBUG_RENDERER1516JPH_NAMESPACE_BEGIN1718JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(PathConstraintSettings)19{20JPH_ADD_BASE_CLASS(PathConstraintSettings, TwoBodyConstraintSettings)2122JPH_ADD_ATTRIBUTE(PathConstraintSettings, mPath)23JPH_ADD_ATTRIBUTE(PathConstraintSettings, mPathPosition)24JPH_ADD_ATTRIBUTE(PathConstraintSettings, mPathRotation)25JPH_ADD_ATTRIBUTE(PathConstraintSettings, mPathFraction)26JPH_ADD_ATTRIBUTE(PathConstraintSettings, mMaxFrictionForce)27JPH_ADD_ATTRIBUTE(PathConstraintSettings, mPositionMotorSettings)28JPH_ADD_ENUM_ATTRIBUTE(PathConstraintSettings, mRotationConstraintType)29}3031void PathConstraintSettings::SaveBinaryState(StreamOut &inStream) const32{33ConstraintSettings::SaveBinaryState(inStream);3435mPath->SaveBinaryState(inStream);36inStream.Write(mPathPosition);37inStream.Write(mPathRotation);38inStream.Write(mPathFraction);39inStream.Write(mMaxFrictionForce);40inStream.Write(mRotationConstraintType);41mPositionMotorSettings.SaveBinaryState(inStream);42}4344void PathConstraintSettings::RestoreBinaryState(StreamIn &inStream)45{46ConstraintSettings::RestoreBinaryState(inStream);4748PathConstraintPath::PathResult result = PathConstraintPath::sRestoreFromBinaryState(inStream);49if (!result.HasError())50mPath = result.Get();51inStream.Read(mPathPosition);52inStream.Read(mPathRotation);53inStream.Read(mPathFraction);54inStream.Read(mMaxFrictionForce);55inStream.Read(mRotationConstraintType);56mPositionMotorSettings.RestoreBinaryState(inStream);57}5859TwoBodyConstraint *PathConstraintSettings::Create(Body &inBody1, Body &inBody2) const60{61return new PathConstraint(inBody1, inBody2, *this);62}6364PathConstraint::PathConstraint(Body &inBody1, Body &inBody2, const PathConstraintSettings &inSettings) :65TwoBodyConstraint(inBody1, inBody2, inSettings),66mRotationConstraintType(inSettings.mRotationConstraintType),67mMaxFrictionForce(inSettings.mMaxFrictionForce),68mPositionMotorSettings(inSettings.mPositionMotorSettings)69{70// Calculate transform that takes us from the path start to center of mass space of body 171mPathToBody1 = Mat44::sRotationTranslation(inSettings.mPathRotation, inSettings.mPathPosition - inBody1.GetShape()->GetCenterOfMass());7273SetPath(inSettings.mPath, inSettings.mPathFraction);74}7576void PathConstraint::NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM)77{78if (mBody1->GetID() == inBodyID)79mPathToBody1.SetTranslation(mPathToBody1.GetTranslation() - inDeltaCOM);80else if (mBody2->GetID() == inBodyID)81mPathToBody2.SetTranslation(mPathToBody2.GetTranslation() - inDeltaCOM);82}8384void PathConstraint::SetPath(const PathConstraintPath *inPath, float inPathFraction)85{86mPath = inPath;87mPathFraction = inPathFraction;8889if (mPath != nullptr)90{91// Get the point on the path for this fraction92Vec3 path_point, path_tangent, path_normal, path_binormal;93mPath->GetPointOnPath(mPathFraction, path_point, path_tangent, path_normal, path_binormal);9495// Construct the matrix that takes us from the closest point on the path to body 2 center of mass space96Mat44 closest_point_to_path(Vec4(path_tangent, 0), Vec4(path_binormal, 0), Vec4(path_normal, 0), Vec4(path_point, 1));97Mat44 cp_to_body1 = mPathToBody1 * closest_point_to_path;98mPathToBody2 = (mBody2->GetInverseCenterOfMassTransform() * mBody1->GetCenterOfMassTransform()).ToMat44() * cp_to_body1;99100// Calculate initial orientation101if (mRotationConstraintType == EPathRotationConstraintType::FullyConstrained)102mInvInitialOrientation = RotationEulerConstraintPart::sGetInvInitialOrientation(*mBody1, *mBody2);103}104}105106void PathConstraint::CalculateConstraintProperties(float inDeltaTime)107{108// Get transforms of body 1 and 2109RMat44 transform1 = mBody1->GetCenterOfMassTransform();110RMat44 transform2 = mBody2->GetCenterOfMassTransform();111112// Get the transform of the path transform as seen from body 1 in world space113RMat44 path_to_world_1 = transform1 * mPathToBody1;114115// Get the transform of from the point on path that body 2 is attached to in world space116RMat44 path_to_world_2 = transform2 * mPathToBody2;117118// Calculate new closest point on path119RVec3 position2 = path_to_world_2.GetTranslation();120Vec3 position2_local_to_path = Vec3(path_to_world_1.InversedRotationTranslation() * position2);121mPathFraction = mPath->GetClosestPoint(position2_local_to_path, mPathFraction);122123// Get the point on the path for this fraction124Vec3 path_point, path_tangent, path_normal, path_binormal;125mPath->GetPointOnPath(mPathFraction, path_point, path_tangent, path_normal, path_binormal);126127// Calculate R1 and R2128RVec3 path_point_ws = path_to_world_1 * path_point;129mR1 = Vec3(path_point_ws - mBody1->GetCenterOfMassPosition());130mR2 = Vec3(position2 - mBody2->GetCenterOfMassPosition());131132// Calculate U = X2 + R2 - X1 - R1133mU = Vec3(position2 - path_point_ws);134135// Calculate world space normals136mPathNormal = path_to_world_1.Multiply3x3(path_normal);137mPathBinormal = path_to_world_1.Multiply3x3(path_binormal);138139// Calculate slide axis140mPathTangent = path_to_world_1.Multiply3x3(path_tangent);141142// Prepare constraint part for position constraint to slide along the path143mPositionConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), mR1 + mU, *mBody2, transform2.GetRotation(), mR2, mPathNormal, mPathBinormal);144145// Check if closest point is on the boundary of the path and if so apply limit146if (!mPath->IsLooping() && (mPathFraction <= 0.0f || mPathFraction >= mPath->GetPathMaxFraction()))147mPositionLimitsConstraintPart.CalculateConstraintProperties(*mBody1, mR1 + mU, *mBody2, mR2, mPathTangent);148else149mPositionLimitsConstraintPart.Deactivate();150151// Prepare rotation constraint part152switch (mRotationConstraintType)153{154case EPathRotationConstraintType::Free:155// No rotational limits156break;157158case EPathRotationConstraintType::ConstrainAroundTangent:159mHingeConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), mPathTangent, *mBody2, transform2.GetRotation(), path_to_world_2.GetAxisX());160break;161162case EPathRotationConstraintType::ConstrainAroundNormal:163mHingeConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), mPathNormal, *mBody2, transform2.GetRotation(), path_to_world_2.GetAxisZ());164break;165166case EPathRotationConstraintType::ConstrainAroundBinormal:167mHingeConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), mPathBinormal, *mBody2, transform2.GetRotation(), path_to_world_2.GetAxisY());168break;169170case EPathRotationConstraintType::ConstrainToPath:171// We need to calculate the inverse of the rotation from body 1 to body 2 for the current path position (see: RotationEulerConstraintPart::sGetInvInitialOrientation)172// RotationBody2 = RotationBody1 * InitialOrientation <=> InitialOrientation^-1 = RotationBody2^-1 * RotationBody1173// We can express RotationBody2 in terms of RotationBody1: RotationBody2 = RotationBody1 * PathToBody1 * RotationClosestPointOnPath * PathToBody2^-1174// Combining these two: InitialOrientation^-1 = PathToBody2 * (PathToBody1 * RotationClosestPointOnPath)^-1175mInvInitialOrientation = mPathToBody2.Multiply3x3RightTransposed(mPathToBody1.Multiply3x3(Mat44(Vec4(path_tangent, 0), Vec4(path_binormal, 0), Vec4(path_normal, 0), Vec4::sZero()))).GetQuaternion();176[[fallthrough]];177178case EPathRotationConstraintType::FullyConstrained:179mRotationConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), *mBody2, transform2.GetRotation());180break;181}182183// Motor properties184switch (mPositionMotorState)185{186case EMotorState::Off:187if (mMaxFrictionForce > 0.0f)188mPositionMotorConstraintPart.CalculateConstraintProperties(*mBody1, mR1 + mU, *mBody2, mR2, mPathTangent);189else190mPositionMotorConstraintPart.Deactivate();191break;192193case EMotorState::Velocity:194mPositionMotorConstraintPart.CalculateConstraintProperties(*mBody1, mR1 + mU, *mBody2, mR2, mPathTangent, -mTargetVelocity);195break;196197case EMotorState::Position:198if (mPositionMotorSettings.mSpringSettings.HasStiffness())199{200// Calculate constraint value to drive to201float c;202if (mPath->IsLooping())203{204float max_fraction = mPath->GetPathMaxFraction();205c = fmod(mPathFraction - mTargetPathFraction, max_fraction);206float half_max_fraction = 0.5f * max_fraction;207if (c > half_max_fraction)208c -= max_fraction;209else if (c < -half_max_fraction)210c += max_fraction;211}212else213c = mPathFraction - mTargetPathFraction;214mPositionMotorConstraintPart.CalculateConstraintPropertiesWithSettings(inDeltaTime, *mBody1, mR1 + mU, *mBody2, mR2, mPathTangent, 0.0f, c, mPositionMotorSettings.mSpringSettings);215}216else217mPositionMotorConstraintPart.Deactivate();218break;219}220}221222void PathConstraint::SetupVelocityConstraint(float inDeltaTime)223{224CalculateConstraintProperties(inDeltaTime);225}226227void PathConstraint::ResetWarmStart()228{229mPositionMotorConstraintPart.Deactivate();230mPositionConstraintPart.Deactivate();231mPositionLimitsConstraintPart.Deactivate();232mHingeConstraintPart.Deactivate();233mRotationConstraintPart.Deactivate();234}235236void PathConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)237{238// Warm starting: Apply previous frame impulse239mPositionMotorConstraintPart.WarmStart(*mBody1, *mBody2, mPathTangent, inWarmStartImpulseRatio);240mPositionConstraintPart.WarmStart(*mBody1, *mBody2, mPathNormal, mPathBinormal, inWarmStartImpulseRatio);241mPositionLimitsConstraintPart.WarmStart(*mBody1, *mBody2, mPathTangent, inWarmStartImpulseRatio);242243switch (mRotationConstraintType)244{245case EPathRotationConstraintType::Free:246// No rotational limits247break;248249case EPathRotationConstraintType::ConstrainAroundTangent:250case EPathRotationConstraintType::ConstrainAroundNormal:251case EPathRotationConstraintType::ConstrainAroundBinormal:252mHingeConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);253break;254255case EPathRotationConstraintType::ConstrainToPath:256case EPathRotationConstraintType::FullyConstrained:257mRotationConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);258break;259}260}261262bool PathConstraint::SolveVelocityConstraint(float inDeltaTime)263{264// Solve motor265bool motor = false;266if (mPositionMotorConstraintPart.IsActive())267{268switch (mPositionMotorState)269{270case EMotorState::Off:271{272float max_lambda = mMaxFrictionForce * inDeltaTime;273motor = mPositionMotorConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, -max_lambda, max_lambda);274break;275}276277case EMotorState::Velocity:278case EMotorState::Position:279motor = mPositionMotorConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, inDeltaTime * mPositionMotorSettings.mMinForceLimit, inDeltaTime * mPositionMotorSettings.mMaxForceLimit);280break;281}282}283284// Solve position constraint along 2 axis285bool pos = mPositionConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathNormal, mPathBinormal);286287// Solve limits along path axis288bool limit = false;289if (mPositionLimitsConstraintPart.IsActive())290{291if (mPathFraction <= 0.0f)292limit = mPositionLimitsConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, 0, FLT_MAX);293else294{295JPH_ASSERT(mPathFraction >= mPath->GetPathMaxFraction());296limit = mPositionLimitsConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, -FLT_MAX, 0);297}298}299300// Solve rotational constraint301// Note, this is not entirely correct, we should apply a velocity constraint so that the body will actually follow the path302// by looking at the derivative of the tangent, normal or binormal but we don't. This means the position constraint solver303// will need to correct the orientation error that builds up, which in turn means that the simulation is not physically correct.304bool rot = false;305switch (mRotationConstraintType)306{307case EPathRotationConstraintType::Free:308// No rotational limits309break;310311case EPathRotationConstraintType::ConstrainAroundTangent:312case EPathRotationConstraintType::ConstrainAroundNormal:313case EPathRotationConstraintType::ConstrainAroundBinormal:314rot = mHingeConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);315break;316317case EPathRotationConstraintType::ConstrainToPath:318case EPathRotationConstraintType::FullyConstrained:319rot = mRotationConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);320break;321}322323return motor || pos || limit || rot;324}325326bool PathConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)327{328// Update constraint properties (bodies may have moved)329CalculateConstraintProperties(inDeltaTime);330331// Solve position constraint along 2 axis332bool pos = mPositionConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mU, mPathNormal, mPathBinormal, inBaumgarte);333334// Solve limits along path axis335bool limit = false;336if (mPositionLimitsConstraintPart.IsActive())337{338if (mPathFraction <= 0.0f)339limit = mPositionLimitsConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mPathTangent, mU.Dot(mPathTangent), inBaumgarte);340else341{342JPH_ASSERT(mPathFraction >= mPath->GetPathMaxFraction());343limit = mPositionLimitsConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mPathTangent, mU.Dot(mPathTangent), inBaumgarte);344}345}346347// Solve rotational constraint348bool rot = false;349switch (mRotationConstraintType)350{351case EPathRotationConstraintType::Free:352// No rotational limits353break;354355case EPathRotationConstraintType::ConstrainAroundTangent:356case EPathRotationConstraintType::ConstrainAroundNormal:357case EPathRotationConstraintType::ConstrainAroundBinormal:358rot = mHingeConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inBaumgarte);359break;360361case EPathRotationConstraintType::ConstrainToPath:362case EPathRotationConstraintType::FullyConstrained:363rot = mRotationConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mInvInitialOrientation, inBaumgarte);364break;365}366367return pos || limit || rot;368}369370#ifdef JPH_DEBUG_RENDERER371void PathConstraint::DrawConstraint(DebugRenderer *inRenderer) const372{373if (mPath != nullptr)374{375// Draw the path in world space376RMat44 path_to_world = mBody1->GetCenterOfMassTransform() * mPathToBody1;377mPath->DrawPath(inRenderer, path_to_world);378379// Draw anchor point of both bodies in world space380RVec3 x1 = mBody1->GetCenterOfMassPosition() + mR1;381RVec3 x2 = mBody2->GetCenterOfMassPosition() + mR2;382inRenderer->DrawMarker(x1, Color::sYellow, 0.1f);383inRenderer->DrawMarker(x2, Color::sYellow, 0.1f);384inRenderer->DrawArrow(x1, x1 + mPathTangent, Color::sBlue, 0.1f);385inRenderer->DrawArrow(x1, x1 + mPathNormal, Color::sRed, 0.1f);386inRenderer->DrawArrow(x1, x1 + mPathBinormal, Color::sGreen, 0.1f);387inRenderer->DrawText3D(x1, StringFormat("%.1f", (double)mPathFraction));388389// Draw motor390switch (mPositionMotorState)391{392case EMotorState::Position:393{394// Draw target marker395Vec3 position, tangent, normal, binormal;396mPath->GetPointOnPath(mTargetPathFraction, position, tangent, normal, binormal);397inRenderer->DrawMarker(path_to_world * position, Color::sYellow, 1.0f);398break;399}400401case EMotorState::Velocity:402{403RVec3 position = mBody2->GetCenterOfMassPosition() + mR2;404inRenderer->DrawArrow(position, position + mPathTangent * mTargetVelocity, Color::sRed, 0.1f);405break;406}407408case EMotorState::Off:409break;410}411}412}413#endif // JPH_DEBUG_RENDERER414415void PathConstraint::SaveState(StateRecorder &inStream) const416{417TwoBodyConstraint::SaveState(inStream);418419mPositionConstraintPart.SaveState(inStream);420mPositionLimitsConstraintPart.SaveState(inStream);421mPositionMotorConstraintPart.SaveState(inStream);422mHingeConstraintPart.SaveState(inStream);423mRotationConstraintPart.SaveState(inStream);424425inStream.Write(mMaxFrictionForce);426inStream.Write(mPositionMotorSettings);427inStream.Write(mPositionMotorState);428inStream.Write(mTargetVelocity);429inStream.Write(mTargetPathFraction);430inStream.Write(mPathFraction);431}432433void PathConstraint::RestoreState(StateRecorder &inStream)434{435TwoBodyConstraint::RestoreState(inStream);436437mPositionConstraintPart.RestoreState(inStream);438mPositionLimitsConstraintPart.RestoreState(inStream);439mPositionMotorConstraintPart.RestoreState(inStream);440mHingeConstraintPart.RestoreState(inStream);441mRotationConstraintPart.RestoreState(inStream);442443inStream.Read(mMaxFrictionForce);444inStream.Read(mPositionMotorSettings);445inStream.Read(mPositionMotorState);446inStream.Read(mTargetVelocity);447inStream.Read(mTargetPathFraction);448inStream.Read(mPathFraction);449}450451Ref<ConstraintSettings> PathConstraint::GetConstraintSettings() const452{453JPH_ASSERT(false); // Not implemented yet454return nullptr;455}456457JPH_NAMESPACE_END458459460