Correctly positioning an actor at a socket

The player has a socket called “RightHand”.
The item has a socket called “AttachOrigin”.

I want to attach the item to the player’s right hand via the attach origin–eg. not the origin of the item’s mesh. Crude drawing to clarify:

70173-crapart.png

Most likely just a dumb math problem but I can’t get it working. I’m using the following code to attempt to correct the positioning:

item->AttachRootComponentTo(GetMesh(), "RightHand", EAttachLocation::SnapToTarget, true);
FVector socketOffset = item->GetMesh()->GetSocketLocation("AttachOrigin") - item->GetActorLocation();
item->SetActorRelativeLocation(-socketOffset);

What is the correct way to do this?

First, you do want to calculate socketOffset before attaching. After attaching, “item->GetActorLocation()” will give you non-world location.

The line:

FVector socketOffset = item->GetMesh()->GetSocketLocation("AttachOrigin") - item->GetActorLocation();

would only work if the ‘item’ has no rotation to begin with. If you give it some random rotations and try it again, you’ll see it’ll be off.

You do want to find an offset from your item origin to the socket, but you need to find them in local space.

If you want to do it a quick-and-dirty way you can do:

item->SetActorRotation(FRotator(0.0f, 0.0f, 0.0f));
FVector socketOffset = item->GetMesh()->GetSocketLocation("AttachOrigin") - item->GetActorLocation();

The “AttachRootComponentTo” will overwrite the item’s rotation anyway, so zeroing it out before the socketOffset calculation is not too much of a hack.

Now, if you rotate the item, you’ll notice it will act weird and not rotate around the socket. This is because you need to rotate the item in local space. The problem is that “SetActorRelativeRotation” and “GetActorRotation” work in different coordinate spaces. “SetActorRelativeRotation” works in the new local space, child of “RightHand”, whereas “GetActorRotation” returns values in the space of “RightHand” itself. So doing a get with one and set with the other will be screwed up.

This will work:

FRotator Rot(item->GetRootComponent()->GetRelativeTransform().GetRotation());
Rot.Yaw += degree;
FVector NewOfs = Rot.RotateVector(socketOffset);
item->SetActorRelativeRotation(Rot);
item->SetActorRelativeLocation(NewOfs);

Here I’m rotating by ‘degree’ in Yaw, but it can be anything.

“socketOffset” you calculate when you attach the object, but keep it as a class variable rather than a local variable.

item->SetActorRotation(FRotator(0.0f, 0.0f, 0.0f));
socketOffset = item->GetMesh()->GetSocketLocation("AttachOrigin")
item->AttachRootComponentTo(GetMesh(), "RightHand", EAttachLocation::SnapToTarget, true);
item->SetActorRelativeLocation(MyActorOfs);

This code worked for me and tested it in UE 4,9.3

Unfortunately SetRelativeLocation isn’t a function in AActor.

Sorry about the mixup:

item->GetRootComponent()->SetRelativeLocation(-socketOffset);

I fixed it in the original reply as well.

Thanks, it doesn’t seem to help though. Apparently SetActorRelativeLocation just calls exactly RootComponent->SetRelativeLocation().

Strangely, I find that getting socketOffset before attaching it to the player’s socket makes it work. It does however mess up if I attempt to rotate the actor.

Hello sgp,

For the sake of the time it would take to reproduce this in C++, I just put together something similar in blueprint just to work with it. It seems like you’re close with your equation but you may be getting a mix of World and Local locations. From what I see, there doesn’t seem to be a way to get a Socket’s Local location since GetSocketLocation returns a World location value. Heres a screenshot of the blueprint I put together for it. It was able to emulate attaching the two sockets together for me. Please let me know if you have any questions or need help translating it over to C++.

Ok, I felt bad I phoned it in on my first answer and screwed it up to boot, so I prepared a thorough answer. I fixed it in the original response for others to see first, and because I ran out of characters here.

In short, attach like this:

item->SetActorRotation(FRotator(0.0f, 0.0f, 0.0f));
 socketOffset = item->GetMesh()->GetSocketLocation("AttachOrigin")
 item->AttachRootComponentTo(GetMesh(), "RightHand", EAttachLocation::SnapToTarget, true);
 item->SetActorRelativeLocation(MyActorOfs);

… and rotate like this:

FRotator Rot(item->GetRootComponent()->GetRelativeTransform().GetRotation());
 Rot.Yaw += degree;
 FVector NewOfs = Rot.RotateVector(socketOffset);
 item->SetActorRelativeRotation(Rot);
 item->SetActorRelativeLocation(NewOfs);

So keep socketOffset as a class variable instead of a local variable.

Tested this in UE4.9.3

Thanks, this reliably works. However, is there any way to make it so that the orientation of the item is taken into account? For example, maybe the player is holding a bowl with things in it that shouldn’t be tipped over. Thus I want him to hold it properly (eg. for the bowl’s world rotation to be zero). As soon as that’s in there however, everything goes out of place again. Is there any way to fix this?

I’m rotating the actor so that it’s world orientation is zero. (Doing this before or after attachment both fails).

Actually the whole ‘get offset before attaching’ thing confuses me. No matter where the object is (and how it’s orientated), the offset of a socket should be the same, shouldn’t it (assuming no animation)? It should always be the same distance away from the origin. Or am I failing elementary math right now because vectors?

you need a tick function for that. This works:

void YourCharacter::Tick(float Dt)
{
	FRotator Rot(GetMesh()->GetSocketTransform("RightHand").Inverse().GetRotation());
	// socketOffset from when you attached
	FVector NewOfs = Rot.RotateVector(socketOffset);
	item->SetActorRelativeRotation(Rot);
	item->SetActorRelativeLocation(NewOfs);
}

I’m sure can come up with a blueprint version of this.