1
\$\begingroup\$
RaycastHit hit = Interactor.hit;
Transform hitCollider = hit.transform;
    
Vector3 xPoint = Vector3.Scale(hit.point - hitCollider.position, hitCollider.right);
LeftHand.position = hitCollider.position + Vector3.Scale(xPoint, hitCollider.right);

This script runs whenever the camera is pointed at a Wall object and LeftMouseButton is pressed. The hand (sphere) should be placed within the wall, sticking to the location of the RaycastHit. For brevity, I've cached the position of the RaycastHit.point and the transform of the wall from the ray collision. I've also omitted the offsets which move the hand to the corner, since they work fine on their own. For now I'm only concerned with matching the wall's X axis.

Wonky Hand Placement

As you can see, this works fine on walls rotated to 90° angles, but at any other rotation there's some funny business going on. The hand is placed too close to the player, and its position on the wall's X axis is reluctant to stray from the wall transform.

I imagine the issue is either with Vector3.Scale or hitCollider.right. In either case, I'm at a loss for alternatives.

Also, the walls are all rotated so that their forward axis faces away from the player. If this is ever an issue I'll just make one hitbox for each side.

I should also say I can't use the normal of the raycastHit, as sometimes I'll want an oversized hitbox which can be hit from below or above.

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

SOLUTION:

Thanks to DMGregory showing how to use Vector3.Dot(), I was able to fix the solution and easily change the zOffset depending on which side of the wall the player is on. Also thank you to Pikalek for fixing my post formatting.

hit = Interactor.hit;
anchor = hit.transform;

float playerSide = Mathf.Sign(Vector3.Dot(transform.forward, anchor.forward));

Vector3 yOffset = new Vector3(0, anchor.localScale.y / 2, 0);
Vector3 xOffset = Vector3.Dot(hit.point - anchor.position, anchor.right) * anchor.right;
Vector3 zOffset = (anchor.localScale.z / 2) * (anchor.forward * playerSide);

hand.position = anchor.position + (xOffset + yOffset - zOffset);

Correct Hand Placement - 360

Correct Hand Placement - Corners

\$\endgroup\$
1
\$\begingroup\$

Your vector projection expression is incorrect for the general case. It's basically coincidence that it works when all axes but one are zero and the remaining is ±1. What you want is this:

// Project displacement from collider to hit point onto local x-axis.
Vector3 xOffset = Vector3.Dot(hit.point - hitCollider.position, hitCollider.right)
                * hitCollider.right;

// (Note that by construction, this vector must point parallel to hitCollider.right)

// Add projected offset back to collider position to get a point along its local x-axis.
LeftHand.position = hitCollider.position + xOffset;

(I've renamed your variable because it's not a point in space, but a displacement / offset between points - this distinction is important during transformation)

Another way to express this is:

// Convert the position to the collider's local coordinate system.
Vector3 local = hitCollider.transform.InverseTransformPoint(hit.point);

// Zero out the axes we don't want.
local.y = 0;
local.z = 0;

// Convert back to world coordinates.
LeftHand.position = hitCollider.transform.TransformPoint(local);
\$\endgroup\$
1
  • \$\begingroup\$ Works beautifully! I've added my solution to the post. Also thanks for explaining how to convert to local coordinates, I'm sure I'll need that at some point. \$\endgroup\$ Commented Jan 5, 2022 at 17:37

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .