This solution works in 2D or 3D.
Calculate the angle between the Point A and Point B vectors
var pointAVector = (pointA - centre).normalized;
var pointBVector = (pointB - centre).normalized;
var maxAngle = Vector3.Angle(pointAVector, pointBVector);
Project the current target point onto the plane of the pointA and pointB:
var planeNormal = Vector3.Cross(pointAVector, pointBVector);
var targetVector = (targetPoint - centre);
//Vector of target on the plane of PointA, PointB
var planeVector = targetVector - Vector3.Dot(targetVector, planeNormal) * planeNormal;
//so we can add back on the offset from the plane, store it
var targetOffset = (targetPoint - planeVector);
Now you can calculate the angle between the targetVector and the pointA and pointB vectors, the simple way is to do this:
var angleToA = Vector3.Angle(planeVector, pointAVector);
var angleToB = Vector3.Angle(planeVector, pointBVector);
But if pointA or pointB are spread widely and the tested point can move far beyond the segement in a single frame, it’s best to use a signed angle between the points:
public static float AngleSigned(Vector3 v1, Vector3 v2, Vector3 n = default(Vector3))
{
n = n == default(Vector3) ? Vector3.up : n;
return Mathf.Atan2(Vector3.Dot(n, Vector3.Cross(v1, v2)), Vector3.Dot(v1, v2))*Mathf.Rad2Deg;
}
This lets us work out if either of them is bigger than the maximum:
if(angleToA > 0) planeVector = pointAVector * planeVector.magnitude;
else if(angleToB < 0) planeVector = pointBVector * planeVector.magnitude;
However, the order is important here and the result will quickly snap to the pointAVector if the angle between them is great. To get around that we need to compare the closeness to the two lines and do our test there:
if(Mathf.Abs(angleToA) < Mathf.Abs(angleToB))
{
if(angleToA > 0) planeVector = pointAVector * planeVector.magnitude;
else if(angleToB < 0) planeVector = pointBVector * planeVector.magnitude;
}
else
{
if(angleToB < 0) planeVector = pointBVector * planeVector.magnitude;
else if(angleToA > 0) planeVector = pointAVector * planeVector.magnitude;
}
Then work out the final position
var allowedPosition = planeVector + targetOffset;
If you want the position to be clamped inside the circumference of the points then you need to do this:
var allowedPosition = planeVector.normalized * Mathf.Min(
planeVector.magnitude,
Mathf.Lerp((pointA - centre).magnitude, (pointB - centre).magnitude, angleToA/maxAngle))
+ targetOffset;
Which basically clamps the magnitude to the circumference of an ellipse defined by the centre and the two points.
Here is a script to be attached to the moving object which keeps it between the points. Set the variables for the relevant bits in the inspector. It’s configured to run in edit mode for easy testing:
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class KeepInPoints : MonoBehaviour {
public Transform pointA, pointB, centre;
// Update is called once per frame
void Update () {
Debug.DrawLine(pointA.position, centre.position, Color.white);
Debug.DrawLine(centre.position, pointB.position, Color.white);
var pointAVector = (pointA.position - centre.position).normalized;
var pointBVector = (pointB.position - centre.position).normalized;
var maxAngle = Vector3.Angle(pointAVector, pointBVector);
var planeNormal = Vector3.Cross(pointAVector, pointBVector);
var targetVector = (transform.position - centre.position);
//Vector of target on the plane of PointA, PointB
var planeVector = targetVector - Vector3.Dot(targetVector, planeNormal) * planeNormal;
//so we can add back on the offset from the plane, store it
var targetOffset = (transform.position - planeVector);
var angleToA = AngleSigned(planeVector, pointAVector);
var angleToB = AngleSigned(planeVector, pointBVector);
if(Mathf.Abs(angleToA) < Mathf.Abs(angleToB))
{
if(angleToA > 0) planeVector = pointAVector * planeVector.magnitude;
else if(angleToB < 0) planeVector = pointBVector * planeVector.magnitude;
}
else
{
if(angleToB > 0) planeVector = pointBVector * planeVector.magnitude;
else if(angleToA < 0) planeVector = pointAVector * planeVector.magnitude;
}
transform.position = planeVector.normalized * Mathf.Min(
planeVector.magnitude,
Mathf.Lerp((pointA.position - centre.position).magnitude, (pointB.position - centre.position).magnitude, angleToA/maxAngle))
+ targetOffset;
}
public static float AngleSigned(Vector3 v1, Vector3 v2, Vector3 n = default(Vector3))
{
n = n == default(Vector3) ? Vector3.up : n;
return Mathf.Atan2(Vector3.Dot(n, Vector3.Cross(v1, v2)), Vector3.Dot(v1, v2))*Mathf.Rad2Deg;
}
}