// SPDX-FileCopyrightText: 2022 Jorrit Rouwe // SPDX-License-Identifier: MIT #include #include #include #include #include #include #ifdef JPH_DEBUG_RENDERER #include #endif // JPH_DEBUG_RENDERER JPH_NAMESPACE_BEGIN JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(PulleyConstraintSettings) { JPH_ADD_BASE_CLASS(PulleyConstraintSettings, TwoBodyConstraintSettings) JPH_ADD_ENUM_ATTRIBUTE(PulleyConstraintSettings, mSpace) JPH_ADD_ATTRIBUTE(PulleyConstraintSettings, mBodyPoint1) JPH_ADD_ATTRIBUTE(PulleyConstraintSettings, mFixedPoint1) JPH_ADD_ATTRIBUTE(PulleyConstraintSettings, mBodyPoint2) JPH_ADD_ATTRIBUTE(PulleyConstraintSettings, mFixedPoint2) JPH_ADD_ATTRIBUTE(PulleyConstraintSettings, mRatio) JPH_ADD_ATTRIBUTE(PulleyConstraintSettings, mMinLength) JPH_ADD_ATTRIBUTE(PulleyConstraintSettings, mMaxLength) } void PulleyConstraintSettings::SaveBinaryState(StreamOut &inStream) const { ConstraintSettings::SaveBinaryState(inStream); inStream.Write(mSpace); inStream.Write(mBodyPoint1); inStream.Write(mFixedPoint1); inStream.Write(mBodyPoint2); inStream.Write(mFixedPoint2); inStream.Write(mRatio); inStream.Write(mMinLength); inStream.Write(mMaxLength); } void PulleyConstraintSettings::RestoreBinaryState(StreamIn &inStream) { ConstraintSettings::RestoreBinaryState(inStream); inStream.Read(mSpace); inStream.Read(mBodyPoint1); inStream.Read(mFixedPoint1); inStream.Read(mBodyPoint2); inStream.Read(mFixedPoint2); inStream.Read(mRatio); inStream.Read(mMinLength); inStream.Read(mMaxLength); } TwoBodyConstraint *PulleyConstraintSettings::Create(Body &inBody1, Body &inBody2) const { return new PulleyConstraint(inBody1, inBody2, *this); } PulleyConstraint::PulleyConstraint(Body &inBody1, Body &inBody2, const PulleyConstraintSettings &inSettings) : TwoBodyConstraint(inBody1, inBody2, inSettings), mLocalSpacePosition1(inSettings.mBodyPoint1), mLocalSpacePosition2(inSettings.mBodyPoint2), mFixedPosition1(inSettings.mFixedPoint1), mFixedPosition2(inSettings.mFixedPoint2), mRatio(inSettings.mRatio), mMinLength(inSettings.mMinLength), mMaxLength(inSettings.mMaxLength), mWorldSpacePosition1(inSettings.mBodyPoint1), mWorldSpacePosition2(inSettings.mBodyPoint2) { if (inSettings.mSpace == EConstraintSpace::WorldSpace) { // If all properties were specified in world space, take them to local space now mLocalSpacePosition1 = inBody1.GetInverseCenterOfMassTransform() * mLocalSpacePosition1; mLocalSpacePosition2 = inBody2.GetInverseCenterOfMassTransform() * mLocalSpacePosition2; } else { // If properties were specified in local space, we need to calculate world space positions mWorldSpacePosition1 = inBody1.GetCenterOfMassTransform() * mWorldSpacePosition1; mWorldSpacePosition2 = inBody2.GetCenterOfMassTransform() * mWorldSpacePosition2; } // Calculate min/max length if it was not provided float current_length = GetCurrentLength(); if (mMinLength < 0.0f) mMinLength = current_length; if (mMaxLength < 0.0f) mMaxLength = current_length; // Initialize the normals to a likely valid axis in case the fixed points overlap with the attachment points (most likely the fixed points are above both bodies) mWorldSpaceNormal1 = mWorldSpaceNormal2 = -Vec3::sAxisY(); } float PulleyConstraint::CalculatePositionsNormalsAndLength() { // Update world space positions (the bodies may have moved) mWorldSpacePosition1 = mBody1->GetCenterOfMassTransform() * mLocalSpacePosition1; mWorldSpacePosition2 = mBody2->GetCenterOfMassTransform() * mLocalSpacePosition2; // Calculate world space normals Vec3 delta1 = mWorldSpacePosition1 - mFixedPosition1; float delta1_len = delta1.Length(); if (delta1_len > 0.0f) mWorldSpaceNormal1 = delta1 / delta1_len; Vec3 delta2 = mWorldSpacePosition2 - mFixedPosition2; float delta2_len = delta2.Length(); if (delta2_len > 0.0f) mWorldSpaceNormal2 = delta2 / delta2_len; // Calculate length return delta1_len + mRatio * delta2_len; } void PulleyConstraint::CalculateConstraintProperties() { // Calculate attachment points relative to COM Vec3 r1 = mWorldSpacePosition1 - mBody1->GetCenterOfMassPosition(); Vec3 r2 = mWorldSpacePosition2 - mBody2->GetCenterOfMassPosition(); mIndependentAxisConstraintPart.CalculateConstraintProperties(*mBody1, *mBody2, r1, mWorldSpaceNormal1, r2, mWorldSpaceNormal2, mRatio); } void PulleyConstraint::SetupVelocityConstraint(float inDeltaTime) { // Determine if the constraint is active float current_length = CalculatePositionsNormalsAndLength(); bool min_length_violation = current_length <= mMinLength; bool max_length_violation = current_length >= mMaxLength; if (min_length_violation || max_length_violation) { // Determine max lambda based on if the length is too big or small mMinLambda = max_length_violation? -FLT_MAX : 0.0f; mMaxLambda = min_length_violation? FLT_MAX : 0.0f; CalculateConstraintProperties(); } else mIndependentAxisConstraintPart.Deactivate(); } void PulleyConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio) { mIndependentAxisConstraintPart.WarmStart(*mBody1, *mBody2, mWorldSpaceNormal1, mWorldSpaceNormal2, mRatio, inWarmStartImpulseRatio); } bool PulleyConstraint::SolveVelocityConstraint(float inDeltaTime) { if (mIndependentAxisConstraintPart.IsActive()) return mIndependentAxisConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mWorldSpaceNormal1, mWorldSpaceNormal2, mRatio, mMinLambda, mMaxLambda); else return false; } bool PulleyConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte) { // Calculate new length (bodies may have changed) float current_length = CalculatePositionsNormalsAndLength(); float position_error = 0.0f; if (current_length < mMinLength) position_error = current_length - mMinLength; else if (current_length > mMaxLength) position_error = current_length - mMaxLength; if (position_error != 0.0f) { // Update constraint properties (bodies may have moved) CalculateConstraintProperties(); return mIndependentAxisConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mWorldSpaceNormal1, mWorldSpaceNormal2, mRatio, position_error, inBaumgarte); } return false; } #ifdef JPH_DEBUG_RENDERER void PulleyConstraint::DrawConstraint(DebugRenderer *inRenderer) const { // Color according to length vs min/max length float current_length = GetCurrentLength(); Color color = Color::sGreen; if (current_length < mMinLength) color = Color::sYellow; else if (current_length > mMaxLength) color = Color::sRed; // Draw constraint inRenderer->DrawLine(mWorldSpacePosition1, mFixedPosition1, color); inRenderer->DrawLine(mFixedPosition1, mFixedPosition2, color); inRenderer->DrawLine(mFixedPosition2, mWorldSpacePosition2, color); // Draw current length inRenderer->DrawText3D(0.5f * (mFixedPosition1 + mFixedPosition2), StringFormat("%.2f", (double)current_length)); } #endif // JPH_DEBUG_RENDERER void PulleyConstraint::SaveState(StateRecorder &inStream) const { TwoBodyConstraint::SaveState(inStream); mIndependentAxisConstraintPart.SaveState(inStream); inStream.Write(mWorldSpaceNormal1); // When distance to fixed point = 0, the normal is used from last frame so we need to store it inStream.Write(mWorldSpaceNormal2); } void PulleyConstraint::RestoreState(StateRecorder &inStream) { TwoBodyConstraint::RestoreState(inStream); mIndependentAxisConstraintPart.RestoreState(inStream); inStream.Read(mWorldSpaceNormal1); inStream.Read(mWorldSpaceNormal2); } Ref PulleyConstraint::GetConstraintSettings() const { PulleyConstraintSettings *settings = new PulleyConstraintSettings; ToConstraintSettings(*settings); settings->mSpace = EConstraintSpace::LocalToBodyCOM; settings->mBodyPoint1 = mLocalSpacePosition1; settings->mFixedPoint1 = mFixedPosition1; settings->mBodyPoint2 = mLocalSpacePosition2; settings->mFixedPoint2 = mFixedPosition2; settings->mRatio = mRatio; settings->mMinLength = mMinLength; settings->mMaxLength = mMaxLength; return settings; } JPH_NAMESPACE_END