Distance Constraint?

Hey Guys,

Does anyone know if it’s possible to get the behaviour a PxDistanceJoint out of the D6 joint?

UE4 supplies a component and wrapper for the D6. Before I go off and wrap up the distance joint, I wanted to see if anyone has had success with this? I don’t see how it would work, but who knows.

Cheers

Kyle

Did you ever figure this out? I also need the PxDistanceJoint.

I got the goods… Sorta

I did, I just started doing it myself. I have a partial implementation that you can use if you want a jump start. Eventually I may wrap it up and make it a PR, right now I don’t need it anymore so meh.

/ Kyle

Header




#pragma once

#include "DistanceConstraint.generated.h"

struct FBodyInstance;

namespace physx
{
	class PxDistanceJoint;
}

UENUM()
enum class EJointFrame : uint8
{
	Frame1,
	Frame2
};

/**
 * 
 */
USTRUCT()
struct SC_API FDistanceConstraint
{
	GENERATED_USTRUCT_BODY()

	class physx::PxDistanceJoint* ConstraintData;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
	uint32 bMinDistanceEnabled : 1;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
	uint32 bMaxDistanceEnabled : 1;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
	float MinDistance;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
	float MaxDistance;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
	FMatrix RefFrame1;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
	FMatrix RefFrame2;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Spring)
	uint32 bSpringEnabled : 1;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Spring)
	float SpringStiffness;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Spring)
	float SpringDampening;


	FDistanceConstraint();
	~FDistanceConstraint();
	
	void InitConstraint(FBodyInstance* Body1, FBodyInstance* Body2);
	void TermConstraint();

	bool IsValidConstraintInstance() const { return ConstraintData != nullptr; }

	void SetMaxDistance(float NewMaxDistance);
	
	void SetMinDistance(float NewMinDistance);	

	// Pass in reference frame in. If the constraint is currently active, this will set it's active local pose. Otherwise the change will take affect in InitConstraint. 
	void SetRefFrame(EJointFrame Frame, const FMatrix& RefFrame);

	// Pass in reference position in (maintains reference orientation). If the constraint is currently active, this will set its active local pose. Otherwise the change will take affect in InitConstraint.
	void SetRefPosition(EJointFrame Frame, const FVector& RefPosition);

};

CPP




#include "SC.h"
#include "DistanceConstraint.h"

#include "PhysicsPublic.h"
#include "PhysXIncludes.h"

// PhysX
#include "PxJoint.h"
#include "PxDistanceJoint.h"

//
// Hack to avoid having to move PhysXSupprt.h into the Engine/Public/
// Once This joint type is fully implemented, it should probably go back to Epic
//

// #if WITH_PHYSX
/** Convert Unreal FMatrix to PhysX PxTransform */
ENGINE_API PxTransform UMatrix2PTransform(const FMatrix& UTM);
/** Convert Unreal FTransform to PhysX PxTransform */
ENGINE_API PxTransform U2PTransform(const FTransform& UTransform);
/** Convert Unreal FVector to PhysX PxVec3 */
ENGINE_API PxVec3 U2PVector(const FVector& UVec);
/** Convert Unreal FQuat to PhysX PxTransform */
ENGINE_API PxQuat U2PQuat(const FQuat& UQuat);
/** Convert Unreal FMatrix to PhysX PxMat44 */
ENGINE_API PxMat44 U2PMatrix(const FMatrix& UTM);
/** Convert Unreal FPlane to PhysX plane def */
ENGINE_API PxPlane U2PPlane(FPlane& Plane);
/** Convert PhysX PxTransform to Unreal PxTransform */
ENGINE_API FTransform P2UTransform(const PxTransform& PTM);
/** Convert PhysX PxQuat to Unreal FQuat */
ENGINE_API FQuat P2UQuat(const PxQuat& PQuat);
/** Convert PhysX plane def to Unreal FPlane */
ENGINE_API FPlane P2UPlane(PxReal P[4]);
ENGINE_API FPlane P2UPlane(PxPlane& Plane);
/** Convert PhysX PxMat44 to Unreal FMatrix */
ENGINE_API FMatrix P2UMatrix(const PxMat44& PMat);
/** Convert PhysX PxTransform to Unreal FMatrix */
ENGINE_API FMatrix PTransform2UMatrix(const PxTransform& PTM);

FORCEINLINE physx::PxJointActorIndex::Enum U2PJointFrame(EJointFrame Frame)
{
	// Swap frame order, since Unreal reverses physx order
	return (Frame == EJointFrame::Frame1) ? physx::PxJointActorIndex::eACTOR1 : physx::PxJointActorIndex::eACTOR0;
}
//#endif

FDistanceConstraint::FDistanceConstraint()
	: ConstraintData(nullptr)
	, bMinDistanceEnabled(false)
	, bMaxDistanceEnabled(true)
	, MinDistance(-1.0f)
	, MaxDistance(-1.0f)
	, RefFrame1(FMatrix::Identity)
	, RefFrame2(FMatrix::Identity)
	, bSpringEnabled(false)
	, SpringStiffness(0.0f)
	, SpringDampening(0.0f)
{
}

FDistanceConstraint::~FDistanceConstraint()
{
	TermConstraint();
}

void FDistanceConstraint::TermConstraint()
{
#if WITH_PHYSX
	if (!ConstraintData)
	{
		return;
	}
	ConstraintData->release();
	ConstraintData = nullptr;
#endif // WITH_PHYSX
}

void FDistanceConstraint::SetMaxDistance(float NewMaxDistance)
{
	MaxDistance = NewMaxDistance;

#if WITH_PHYSX
	if (ConstraintData)
	{
		ConstraintData->setMaxDistance(NewMaxDistance);
	}
#endif // WITH_PHYSX
}

void FDistanceConstraint::SetRefFrame(EJointFrame Frame, const FMatrix& RefFrame)
{
	if (Frame == EJointFrame::Frame1)
	{
		RefFrame1 = RefFrame;
	}
	else
	{
		RefFrame2 = RefFrame;
	}

#if WITH_PHYSX
	if (ConstraintData)
	{
		physx::PxJointActorIndex::Enum PxFrame = U2PJointFrame(Frame);

		PxTransform PxRefFrame = UMatrix2PTransform(RefFrame);

		ConstraintData->setLocalPose(PxFrame, PxRefFrame);
	}
#endif // WITH_PHYSX
}

// Pass in reference position in (maintains reference orientation). If the constraint is currently active, this will set its active local pose. Otherwise the change will take affect in InitConstraint.
void FDistanceConstraint::SetRefPosition(EJointFrame Frame, const FVector& RefPosition)
{
	if (Frame == EJointFrame::Frame1)
	{
		RefFrame1.SetOrigin(RefPosition);
	}
	else
	{
		RefFrame2.SetOrigin(RefPosition);
	}

#if WITH_PHYSX
	if (ConstraintData)
	{
		physx::PxJointActorIndex::Enum PxFrame = U2PJointFrame(Frame);		

		PxTransform PxRefFrame = ConstraintData->getLocalPose(PxFrame);

		PxRefFrame.p = U2PVector(RefPosition);

		ConstraintData->setLocalPose(PxFrame, PxRefFrame);
	}
#endif // WITH_PHYSX
}

void FDistanceConstraint::InitConstraint(FBodyInstance* Body1, FBodyInstance* Body2)
{
	// if there's already a constraint, get rid of it first
	if (ConstraintData)
	{
		TermConstraint();
	}

	PxRigidActor* PActor1 = Body1 ? Body1->GetPxRigidActor() : nullptr;
	PxRigidActor* PActor2 = Body2 ? Body2->GetPxRigidActor() : nullptr;

	// Do not create joint unless you have two actors
	// Do not create joint unless one of the actors is dynamic
	if ((!PActor1 || !PActor1->isRigidDynamic()) && (!PActor2 || !PActor2->isRigidDynamic()))
	{
		return;
	}

	// Need to worry about the case where one is static and one is dynamic, and make sure the static scene is used which matches the dynamic scene
	if (PActor1 != nullptr && PActor2 != nullptr)
	{
		if (PActor1->isRigidStatic() && PActor2->isRigidDynamic())
		{
			const uint32 SceneType = Body2->RigidActorSync != nullptr ? PST_Sync : PST_Async;
			PActor1 = Body1->GetPxRigidActor(SceneType);
		}
		else if (PActor2->isRigidStatic() && PActor1->isRigidDynamic())
		{
			const uint32 SceneType = Body1->RigidActorSync != nullptr ? PST_Sync : PST_Async;
			PActor2 = Body2->GetPxRigidActor(SceneType);
		}
	}

	// record if actors are asleep before creating joint, so we can sleep them afterwards if so (creating joint wakes them)
	const bool bActor1Asleep = (PActor1 == nullptr || !PActor1->isRigidDynamic() || PActor1->isRigidDynamic()->isSleeping());
	const bool bActor2Asleep = (PActor2 == nullptr || !PActor2->isRigidDynamic() || PActor2->isRigidDynamic()->isSleeping());

	// make sure actors are in same scene
	PxScene* PScene1 = (PActor1 != nullptr) ? PActor1->getScene() : nullptr;
	PxScene* PScene2 = (PActor2 != nullptr) ? PActor2->getScene() : nullptr;

	// make sure actors are in same scene
	if (PScene1 && PScene2 && PScene1 != PScene2)
	{
		UE_LOG(LogPhysics, Log, TEXT("FDistanceConstraint::InitConstraint - Attempting to create a joint between actors in two different scenes. No joint created."));
		return;
	}

	PxScene* PScene = PScene1 ? PScene1 : PScene2;
	check(PScene);

	ConstraintData = nullptr;

	FTransform Transform1 = Body1->GetUnrealWorldTransform();
	FTransform Transform2 = Body2->GetUnrealWorldTransform();

	float BodyDist = FVector::Dist(Transform1.GetLocation(), Transform2.GetLocation());

	PxTransform PxBody1Frame = UMatrix2PTransform(RefFrame1);
	PxTransform PxBody2Frame = UMatrix2PTransform(RefFrame2);

	PxDistanceJoint* DistJoint = PxDistanceJointCreate(*GPhysXSDK, Body2->GetPxRigidActor(), PxBody2Frame, Body1->GetPxRigidActor(), PxBody1Frame);

	if (DistJoint == NULL)
	{
		UE_LOG(LogPhysics, Log, TEXT("FDistanceConstraint::InitConstraint - Invalid Distance Joint"));
		return;
	}

	DistJoint->setConstraintFlag(PxConstraintFlag::eCOLLISION_ENABLED, true);
	DistJoint->setDistanceJointFlag(PxDistanceJointFlag::eMIN_DISTANCE_ENABLED, bMinDistanceEnabled);
	DistJoint->setDistanceJointFlag(PxDistanceJointFlag::eMAX_DISTANCE_ENABLED, bMaxDistanceEnabled);	
	DistJoint->setDistanceJointFlag(PxDistanceJointFlag::eSPRING_ENABLED, bSpringEnabled);
	DistJoint->setMinDistance(MinDistance == -1.0f ? BodyDist : MinDistance);
	DistJoint->setMaxDistance(MinDistance == -1.0f ? BodyDist : MaxDistance);

	if (bSpringEnabled)
	{
		DistJoint->setStiffness(SpringStiffness);
		DistJoint->setDamping(SpringDampening);
	}

	ConstraintData = DistJoint;
}



Nice and thanks for the share. I turns out that I was able to do what I wanted using a linear position drive.

@kylawl your solution worked like a dream for me! It took some minor modifications because IsRigidDynamic() and IsRigidStatic() are deprecated now but you can just cast the PxRigidActor reference to PxRigidDynamic or PxRigidStatic and check if the result is null.

Can anyone confirm that there is still no built-in distance constraint in UE4? It seems pretty essential for something that still requires a custom implementation.

Also if anyone else uses kylawl’s example you should know there is a bug in InitConstraint:

DistJoint->setMaxDistance(MinDistance == -1.0f ? BodyDist : MaxDistance);

should be

DistJoint->setMaxDistance(MaxDistance == -1.0f ? BodyDist : MaxDistance);

otherwise setting MinDistance to something other than -1 makes the joint go haywire.

EDIT: There were some other issues with deprecation and bugs like the physics scene needing to be locked. I’ve added my updated version that I’ve turned into a blueprint spawnable actor component. Just call SetupConstraint() in your BP beginplay and plug in the two components you want to constrain (both have to be mobile/dynamic). I’m using in 4.16. I can’t guarantee it’s perfect but it’s working for me:

DistanceConstraint.h


#pragma once

#include "CoreMinimal.h"
#include "DistanceConstraint.generated.h"


struct FBodyInstance;


namespace physx
{
    class PxDistanceJoint;
}


UENUM()
enum class EJointFrame : uint8
{
    Frame1,
    Frame2
};


/**
*
*/
UCLASS(BlueprintType, ClassGroup = "Physics", Meta = (BlueprintSpawnableComponent))
class [YOURPROJECT]_API UDistanceConstraint : public UActorComponent
{
    GENERATED_BODY()

public:

    class physx::PxDistanceJoint* ConstraintData;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
    uint32 bMinDistanceEnabled : 1;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
    uint32 bMaxDistanceEnabled : 1;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
    float MinDistance;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
    float MaxDistance;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
    uint32 bCollisionEnabled : 1;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
    FMatrix RefFrame1;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
    FMatrix RefFrame2;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Spring)
    uint32 bSpringEnabled : 1;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Spring)
    float SpringStiffness;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Spring)
    float SpringDampening;


    UDistanceConstraint();
    ~UDistanceConstraint();

    UFUNCTION(BlueprintCallable, Category = "Constraint")
    void SetupConstraint(UPrimitiveComponent* ComponentA, UPrimitiveComponent* ComponentB)
    {
        if (ComponentA != nullptr && ComponentB != nullptr) {
            InitConstraint(ComponentA->GetBodyInstance(), ComponentB->GetBodyInstance());
        }
    };

    void InitConstraint(FBodyInstance* Body1, FBodyInstance* Body2);
    void TerminateConstraint();

    UFUNCTION(BlueprintCallable, Category = "Constraint")
    bool IsValidConstraintInstance() const { return ConstraintData != nullptr; }

    UFUNCTION(BlueprintCallable, Category = "Constraint")
    void SetMaxDistance(float NewMaxDistance);

    UFUNCTION(BlueprintCallable, Category = "Constraint")
    void SetMinDistance(float NewMinDistance);

    UFUNCTION(BlueprintCallable, Category = "Constraint")
    void SetDistance(float NewDistance);

    // Pass in reference frame in. If the constraint is currently active, this will set it's active local pose. Otherwise the change will take affect in InitConstraint. 
    void SetRefFrame(EJointFrame Frame, const FMatrix& RefFrame);

    // Pass in reference position in (maintains reference orientation). If the constraint is currently active, this will set its active local pose. Otherwise the change will take affect in InitConstraint.
    void SetRefPosition(EJointFrame Frame, const FVector& RefPosition);

private:

    // The index of the PhysX scene this constraint exists in
    int32 SceneIndex1;

    int32 SceneIndex2;


};

DistanceConstraint.cpp



#include "[YOURPROJECT].h"
#include "DistanceConstraint.h"

#include "PhysicsPublic.h"
#include "PhysXIncludes.h"
#include "Runtime/Engine/Public/PhysXPublic.h"

// PhysX
#include "PxJoint.h"
#include "PxDistanceJoint.h"

FORCEINLINE physx::PxJointActorIndex::Enum U2PJointFrame(EJointFrame Frame)
{
    // Swap frame order, since Unreal reverses physx order
    return (Frame == EJointFrame::Frame1) ? physx::PxJointActorIndex::eACTOR1 : physx::PxJointActorIndex::eACTOR0;
}

UDistanceConstraint::UDistanceConstraint()
    : ConstraintData(nullptr)
    , bMinDistanceEnabled(false)
    , bMaxDistanceEnabled(true)
    , MinDistance(-1.0f)
    , MaxDistance(-1.0f)
    , bCollisionEnabled(true)
    , RefFrame1(FMatrix::Identity)
    , RefFrame2(FMatrix::Identity)
    , bSpringEnabled(false)
    , SpringStiffness(0.0f)
    , SpringDampening(0.0f)
{
    PrimaryComponentTick.bCanEverTick = false;
}

UDistanceConstraint::~UDistanceConstraint()
{
    TerminateConstraint();
}

void UDistanceConstraint::TerminateConstraint()
{
#if WITH_PHYSX
    if (ConstraintData != nullptr)
    {
        PxScene* PScene = ConstraintData->getScene();

        SCOPED_SCENE_WRITE_LOCK(PScene);
        if (PScene != nullptr) {
            ConstraintData->release();
            ConstraintData = nullptr;
        }
    }
#endif // WITH_PHYSX
}

void UDistanceConstraint::SetMaxDistance(float NewMaxDistance)
{
    MaxDistance = NewMaxDistance;

#if WITH_PHYSX
    if (ConstraintData != nullptr)
    {
        ConstraintData->setMaxDistance(NewMaxDistance);
    }
#endif // WITH_PHYSX
}

void UDistanceConstraint::SetMinDistance(float NewMinDistance)
{
    MinDistance = NewMinDistance;

#if WITH_PHYSX
    if (ConstraintData != nullptr)
    {
        ConstraintData->setMinDistance(NewMinDistance);
    }
#endif // WITH_PHYSX
}

void UDistanceConstraint::SetDistance(float NewDistance)
{
    MinDistance = NewDistance;
    MaxDistance = NewDistance;

#if WITH_PHYSX
    if (ConstraintData != nullptr)
    {
        ConstraintData->setMinDistance(NewDistance);
        ConstraintData->setMaxDistance(NewDistance);
    }
#endif // WITH_PHYSX
}

void UDistanceConstraint::SetRefFrame(EJointFrame Frame, const FMatrix& RefFrame)
{
    if (Frame == EJointFrame::Frame1)
    {
        RefFrame1 = RefFrame;
    }
    else
    {
        RefFrame2 = RefFrame;
    }

#if WITH_PHYSX
    if (ConstraintData)
    {
        physx::PxJointActorIndex::Enum PxFrame = U2PJointFrame(Frame);

        PxTransform PxRefFrame = UMatrix2PTransform(RefFrame);

        ConstraintData->setLocalPose(PxFrame, PxRefFrame);
    }
#endif // WITH_PHYSX
}

// Pass in reference position in (maintains reference orientation). If the constraint is currently active, this will set its active local pose. Otherwise the change will take affect in InitConstraint.
void UDistanceConstraint::SetRefPosition(EJointFrame Frame, const FVector& RefPosition)
{
    if (Frame == EJointFrame::Frame1)
    {
        RefFrame1.SetOrigin(RefPosition);
    }
    else
    {
        RefFrame2.SetOrigin(RefPosition);
    }

#if WITH_PHYSX
    if (ConstraintData)
    {
        physx::PxJointActorIndex::Enum PxFrame = U2PJointFrame(Frame);

        PxTransform PxRefFrame = ConstraintData->getLocalPose(PxFrame);

        PxRefFrame.p = U2PVector(RefPosition);

        ConstraintData->setLocalPose(PxFrame, PxRefFrame);
    }
#endif // WITH_PHYSX
}

void UDistanceConstraint::InitConstraint(FBodyInstance* Body1, FBodyInstance* Body2)
{
    // if there's already a constraint, get rid of it first
    if (ConstraintData != nullptr)
    {
        TerminateConstraint();
    }

    FPhysScene* PhysScene = GetWorld()->GetPhysicsScene();
    if (!PhysScene) return;

    SceneIndex1 = Body1->GetSceneIndex();
    SceneIndex2 = Body2->GetSceneIndex();

    PxScene* Body1Scene = GetPhysXSceneFromIndex(SceneIndex1);
    PxScene* Body2Scene = GetPhysXSceneFromIndex(SceneIndex2);

    SCOPED_SCENE_WRITE_LOCK(Body1Scene);
    SCOPED_SCENE_WRITE_LOCK(Body2Scene);

    if (Body1Scene != nullptr && Body2Scene != nullptr) {

        PxRigidActor* PActor1 = Body1 ? Body1->GetPxRigidActor_AssumesLocked() : nullptr;
        PxRigidActor* PActor2 = Body2 ? Body2->GetPxRigidActor_AssumesLocked() : nullptr;

        PxRigidDynamic* PRigidDynamic1 = (physx::PxRigidDynamic*) PActor1;
        PxRigidDynamic* PRigidDynamic2 = (physx::PxRigidDynamic*) PActor2;

        // Do not create joint unless you have two actors and they're both dynamic
        if (PActor1 == nullptr || PActor2 == nullptr || PRigidDynamic1 == nullptr || PRigidDynamic2 == nullptr)
        {
            return;
        }

        // Need to worry about the case where one is static and one is dynamic, and make sure the static scene is used which matches the dynamic scene
        /*if (PActor1 != nullptr && PActor2 != nullptr)
        {
        if (PActor1->isRigidStatic() && PActor2->isRigidDynamic())
        {
        const uint32 SceneType = Body2->RigidActorSync != nullptr ? PST_Sync : PST_Async;
        PActor1 = Body1->GetPxRigidActor(SceneType);
        }
        else if (PActor2->isRigidStatic() && PActor1->isRigidDynamic())
        {
        const uint32 SceneType = Body1->RigidActorSync != nullptr ? PST_Sync : PST_Async;
        PActor2 = Body2->GetPxRigidActor(SceneType);
        }
        }*/

        // record if actors are asleep before creating joint, so we can sleep them afterwards if so (creating joint wakes them)
        const bool bActor1Asleep = PRigidDynamic1->isSleeping();
        const bool bActor2Asleep = PRigidDynamic2->isSleeping();

        FTransform Transform1 = Body1->GetUnrealWorldTransform();
        FTransform Transform2 = Body2->GetUnrealWorldTransform();

        float BodyDist = FVector::Dist(Transform1.GetLocation(), Transform2.GetLocation());
        // Distances will be the initial body distance if they haven't been set
        if (MinDistance == -1.0f) {
            MinDistance = BodyDist;
        }
        if (MaxDistance == -1.0f) {
            MaxDistance = BodyDist;
        }

        PxTransform PxBody1Frame = UMatrix2PTransform(RefFrame1);
        PxTransform PxBody2Frame = UMatrix2PTransform(RefFrame2);

        PxDistanceJoint* DistJoint = PxDistanceJointCreate(*GPhysXSDK, PActor2, PxBody2Frame, PActor1, PxBody1Frame);

        if (DistJoint == nullptr)
        {
            UE_LOG(LogPhysics, Log, TEXT("UDistanceConstraint::InitConstraint - Invalid Distance Joint"));
            return;
        }

        DistJoint->setConstraintFlag(PxConstraintFlag::eCOLLISION_ENABLED, bCollisionEnabled);
        DistJoint->setDistanceJointFlag(PxDistanceJointFlag::eMIN_DISTANCE_ENABLED, bMinDistanceEnabled);
        DistJoint->setDistanceJointFlag(PxDistanceJointFlag::eMAX_DISTANCE_ENABLED, bMaxDistanceEnabled);
        DistJoint->setDistanceJointFlag(PxDistanceJointFlag::eSPRING_ENABLED, bSpringEnabled);
        DistJoint->setMinDistance(MinDistance);
        DistJoint->setMaxDistance(MaxDistance);

        if (bSpringEnabled)
        {
            DistJoint->setStiffness(SpringStiffness);
            DistJoint->setDamping(SpringDampening);
        }

        ConstraintData = DistJoint;
    }
}


@kylawl and @hdelattre Tanks for your solutions. I try configure default UE4 physics constraint (D6 joint) for push and pull object by character and I stuck (I make other question for that at Problem: character controller and physics constraint in push and pull object - Blueprint Visual Scripting - Unreal Engine Forums) so I think your implementations can help me but after I add your codes to my project I have some compiler error and I can’t fix it after some google. compiler errors are:

DistanceConstraint.cpp.obj : error LNK2019: unresolved external symbol “__declspec(dllimport) class physx::PxTransform __cdecl UMatrix2PTransform(struct FMatrix const &)” (_imp?UMatrix2PTransform@@YA?AVPxTransform@physx@@AEBUFMatrix@@@Z) referenced in function “public: void __cdecl UDistanceConstraint::SetRefFrame(enum EJointFrame,struct FMatrix const &)” (?SetRefFrame@UDistanceConstraint@@QEAAXW4EJointFrame@@AEBUFMatrix@@@Z)

DistanceConstraint.cpp.obj : error LNK2019: unresolved external symbol “__declspec(dllimport) struct FThreadSafeStaticStat<struct FStat_STAT_PhysSceneWriteLock> StatPtr_STAT_PhysSceneWriteLock” (_imp?StatPtr_STAT_PhysSceneWriteLock@@3U?$FThreadSafeStaticStat@UFStat_STAT_PhysSceneWriteLock@@@@A) referenced in function “public: __cdecl FPhysXSceneWriteLock::FPhysXSceneWriteLock(class physx::PxScene *,char const *,unsigned int)” (??0FPhysXSceneWriteLock@@QEAA@PEAVPxScene@physx@@PEBDI@Z)

Is anything changed at UE4.24 or I miss something?

@SH_SWAT Make sure your project.build.cs file has “PhysX” and “APEX” in PublicDependencyModuleNames.

Thanks, I missed APEX but after I add it, again it isn’t compile and log same error. this is my project.build.cs:



using UnrealBuildTool;

public class Mechanics : ModuleRules
{
   public Mechanics(ReadOnlyTargetRules Target) : base(Target)
  {
    PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

    PublicDependencyModuleNames.AddRange(new string] { "Core", "CoreUObject", "Engine", "InputCore", "PhysX", "APEX" });

    PrivateDependencyModuleNames.AddRange(new string] { "Core", "CoreUObject", "Engine", "InputCore", "PhysX", "APEX" });

    Definitions.Add("WITH_PHYSX=1");
  }
}


@SH_SWAT I haven’t actually compiled this recently so it’s possible the physx API changed in recent engine versions. I would look through the physx files for suitable replacements for those missing symbols.

Thanks, I think this thread make good point but I’m noob and some thing like Engine\Source\Runtime\Engine\Public\PhysicsPublic.h changed after that answer.

@SH_SWAT

Turns out now you need “PhysicsCore” also added to your PublicDependencyModuleNames now. That should fix your unresolved symbol errors.

Also I guess I actually did update my code for this a little while ago in 4.21. I made one more change just now to get it compatible with 4.25.

This should compile for you:

// DistanceContstraint.h


#pragma once

#include "CoreMinimal.h"
#include "DistanceConstraint.generated.h"

#ifndef USE_RTTI
#define USE_RTTI 0 // for Apex.h
#endif

struct FBodyInstance;

namespace physx
{
class PxDistanceJoint;
}

UENUM()
enum class EJointFrame : uint8
{
Frame1,
Frame2
};

/**
*
*/
UCLASS(BlueprintType, ClassGroup = "Physics", Meta = (BlueprintSpawnableComponent))
class YOURPROJECT_API UDistanceConstraint : public UActorComponent
{
GENERATED_BODY()

public:

virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

public:

class physx::PxDistanceJoint* ConstraintData;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
uint32 bMinDistanceEnabled : 1;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
uint32 bMaxDistanceEnabled : 1;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
float MinDistance;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
float MaxDistance;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
uint32 bCollisionEnabled : 1;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
FMatrix RefFrame1;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Joint)
FMatrix RefFrame2;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Spring)
uint32 bSpringEnabled : 1;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Spring)
float SpringStiffness;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Spring)
float SpringDampening;


UDistanceConstraint();
~UDistanceConstraint();

UFUNCTION(BlueprintCallable, Category = "Constraint")
void SetupConstraint(UPrimitiveComponent* ComponentA, UPrimitiveComponent* ComponentB)
{
if (ComponentA != nullptr && ComponentB != nullptr) {
InitConstraint(ComponentA->GetBodyInstance(), ComponentB->GetBodyInstance());
}
};

void InitConstraint(FBodyInstance* Body1, FBodyInstance* Body2);
void TerminateConstraint();

UFUNCTION(BlueprintCallable, Category = "Constraint")
bool IsValidConstraintInstance() const { return ConstraintData != nullptr; }

UFUNCTION(BlueprintCallable, Category = "Constraint")
void SetMaxDistance(float NewMaxDistance);

UFUNCTION(BlueprintCallable, Category = "Constraint")
void SetMinDistance(float NewMinDistance);

UFUNCTION(BlueprintCallable, Category = "Constraint")
void SetDistance(float NewDistance);

// Pass in reference frame in. If the constraint is currently active, this will set it's active local pose. Otherwise the change will take affect in InitConstraint.
void SetRefFrame(EJointFrame Frame, const FMatrix& RefFrame);

// Pass in reference position in (maintains reference orientation). If the constraint is currently active, this will set its active local pose. Otherwise the change will take affect in InitConstraint.
void SetRefPosition(EJointFrame Frame, const FVector& RefPosition);

};

// DistanceConstraint.cpp


#include "DistanceConstraint.h"

#include "PhysicsPublic.h"
#include "PhysXIncludes.h"
#include "Runtime/Engine/Public/PhysXPublic.h"

// PhysX
#include "PxJoint.h"
#include "PxDistanceJoint.h"

FORCEINLINE physx::PxJointActorIndex::Enum U2PJointFrame(EJointFrame Frame)
{
// Swap frame order, since Unreal reverses physx order
return (Frame == EJointFrame::Frame1) ? physx::PxJointActorIndex::eACTOR1 : physx::PxJointActorIndex::eACTOR0;
}

UDistanceConstraint::UDistanceConstraint()
: ConstraintData(nullptr)
, bMinDistanceEnabled(false)
, bMaxDistanceEnabled(true)
, MinDistance(-1.0f)
, MaxDistance(-1.0f)
, bCollisionEnabled(true)
, RefFrame1(FMatrix::Identity)
, RefFrame2(FMatrix::Identity)
, bSpringEnabled(false)
, SpringStiffness(0.0f)
, SpringDampening(0.0f)
{
PrimaryComponentTick.bCanEverTick = false;
}

UDistanceConstraint::~UDistanceConstraint()
{
TerminateConstraint();
}

void UDistanceConstraint::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
TerminateConstraint();

Super::EndPlay(EndPlayReason);
}

void UDistanceConstraint::TerminateConstraint()
{
#if WITH_PHYSX
if (ConstraintData != nullptr)
{
PxScene* PScene = ConstraintData->getScene();

SCOPED_SCENE_WRITE_LOCK(PScene);
if (PScene != nullptr) {
ConstraintData->release();
ConstraintData = nullptr;
}
}
#endif // WITH_PHYSX
}

void UDistanceConstraint::SetMaxDistance(float NewMaxDistance)
{
MaxDistance = NewMaxDistance;

#if WITH_PHYSX
if (ConstraintData != nullptr)
{
ConstraintData->setMaxDistance(NewMaxDistance);
}
#endif // WITH_PHYSX
}

void UDistanceConstraint::SetMinDistance(float NewMinDistance)
{
MinDistance = NewMinDistance;

#if WITH_PHYSX
if (ConstraintData != nullptr)
{
ConstraintData->setMinDistance(NewMinDistance);
}
#endif // WITH_PHYSX
}

void UDistanceConstraint::SetDistance(float NewDistance)
{
MinDistance = NewDistance;
MaxDistance = NewDistance;

#if WITH_PHYSX
if (ConstraintData != nullptr)
{
ConstraintData->setMinDistance(NewDistance);
ConstraintData->setMaxDistance(NewDistance);
}
#endif // WITH_PHYSX
}

void UDistanceConstraint::SetRefFrame(EJointFrame Frame, const FMatrix& RefFrame)
{
if (Frame == EJointFrame::Frame1)
{
RefFrame1 = RefFrame;
}
else
{
RefFrame2 = RefFrame;
}

#if WITH_PHYSX
if (ConstraintData)
{
physx::PxJointActorIndex::Enum PxFrame = U2PJointFrame(Frame);

PxTransform PxRefFrame = UMatrix2PTransform(RefFrame);

ConstraintData->setLocalPose(PxFrame, PxRefFrame);
}
#endif // WITH_PHYSX
}

// Pass in reference position in (maintains reference orientation). If the constraint is currently active, this will set its active local pose. Otherwise the change will take affect in InitConstraint.
void UDistanceConstraint::SetRefPosition(EJointFrame Frame, const FVector& RefPosition)
{
if (Frame == EJointFrame::Frame1)
{
RefFrame1.SetOrigin(RefPosition);
}
else
{
RefFrame2.SetOrigin(RefPosition);
}

#if WITH_PHYSX
if (ConstraintData)
{
physx::PxJointActorIndex::Enum PxFrame = U2PJointFrame(Frame);

PxTransform PxRefFrame = ConstraintData->getLocalPose(PxFrame);

PxRefFrame.p = U2PVector(RefPosition);

ConstraintData->setLocalPose(PxFrame, PxRefFrame);
}
#endif // WITH_PHYSX
}

void UDistanceConstraint::InitConstraint(FBodyInstance* Body1, FBodyInstance* Body2)
{
// if there's already a constraint, get rid of it first
if (ConstraintData != nullptr)
{
TerminateConstraint();
}

#if 1
FPhysScene* PhysScene = GetWorld()->GetPhysicsScene();
if (!PhysScene) return;

FPhysScene* Scene1 = Body1->GetPhysicsScene();
FPhysScene* Scene2 = Body2->GetPhysicsScene();

PxScene* Body1Scene = Scene1->GetPxScene();
PxScene* Body2Scene = Scene2->GetPxScene();

SCOPED_SCENE_WRITE_LOCK(Body1Scene);
SCOPED_SCENE_WRITE_LOCK(Body2Scene);

if (Body1Scene != nullptr && Body2Scene != nullptr) {

PxRigidActor* PActor1 = Body1 ? FPhysicsInterface::GetPxRigidActor_AssumesLocked(Body1->GetPhysicsActorHandle()) : nullptr;
PxRigidActor* PActor2 = Body2 ? FPhysicsInterface::GetPxRigidActor_AssumesLocked(Body2->GetPhysicsActorHandle()) : nullptr;

PxRigidDynamic* PRigidDynamic1 = (physx::PxRigidDynamic*) PActor1;
PxRigidDynamic* PRigidDynamic2 = (physx::PxRigidDynamic*) PActor2;

// Do not create joint unless you have two actors and they're both dynamic
if (PActor1 == nullptr || PActor2 == nullptr || PRigidDynamic1 == nullptr || PRigidDynamic2 == nullptr)
{
return;
}

// record if actors are asleep before creating joint, so we can sleep them afterwards if so (creating joint wakes them)
const bool bActor1Asleep = PRigidDynamic1->isSleeping();
const bool bActor2Asleep = PRigidDynamic2->isSleeping();

FTransform Transform1 = Body1->GetUnrealWorldTransform();
FTransform Transform2 = Body2->GetUnrealWorldTransform();

float BodyDist = FVector::Dist(Transform1.GetLocation(), Transform2.GetLocation());
// Distances will be the initial body distance if they haven't been set
if (MinDistance == -1.0f) {
MinDistance = BodyDist;
}
if (MaxDistance == -1.0f) {
MaxDistance = BodyDist;
}

PxTransform PxBody1Frame = UMatrix2PTransform(RefFrame1);
PxTransform PxBody2Frame = UMatrix2PTransform(RefFrame2);

PxDistanceJoint* DistJoint = PxDistanceJointCreate(*GPhysXSDK, PActor2, PxBody2Frame, PActor1, PxBody1Frame);

if (DistJoint == nullptr)
{
UE_LOG(LogPhysics, Log, TEXT("UDistanceConstraint::InitConstraint - Invalid Distance Joint"));
return;
}

DistJoint->setConstraintFlag(PxConstraintFlag::eCOLLISION_ENABLED, bCollisionEnabled);
DistJoint->setDistanceJointFlag(PxDistanceJointFlag::eMIN_DISTANCE_ENABLED, bMinDistanceEnabled);
DistJoint->setDistanceJointFlag(PxDistanceJointFlag::eMAX_DISTANCE_ENABLED, bMaxDistanceEnabled);
DistJoint->setDistanceJointFlag(PxDistanceJointFlag::eSPRING_ENABLED, bSpringEnabled);
DistJoint->setMinDistance(MinDistance);
DistJoint->setMaxDistance(MaxDistance);

if (bSpringEnabled)
{
DistJoint->setStiffness(SpringStiffness);
DistJoint->setDamping(SpringDampening);
}

ConstraintData = DistJoint;
}
#endif // WITH_PHYSX
}

@hdelattre

Thank you, I added “PhysicsCore” but again it had some error so I replaced all with your new code 4.24 and I can compile it successfully :slight_smile: