Move an object inside a specific angle between two other objects?

Hi,

I wrote a script that moves an object when the mouse is moved, relative to the player’s position, but I cannot find out how to restrict it’s movement to only inside a specific angle. The attached image should explain things better:

![1]

Thanks in advance!

I’m assuming the problem is 2D. If not, you would need to project “Game Object” onto the plane formed by the three points. Let’s call the pivot point P.

One solution is to fine the vector that bisects Angle AB and also the angle of APB. Then you can test whether a point is within the angle by seeing if the point is within 1/2 of the angle APB of the vector that bisects the angle. Example script:

function IsBetween() : boolean {
	var v3One = pointA.position - pivot.position;
	var v3Two = pointB.position - pivot.position;
	var v3Test = transform.position - pivot.position;
	
	var angle = Vector3.Angle(v3One, v3Two);
	var halfVector = (v3One.normalized + v3Two.normalized);
	
	return Vector3.Angle(halfVector, v3Test) < angle / 2.0;
}

Note I’m assuming this script is on “Game Object” so that is why 3vTest uses ‘tranform.position’. Also the routine above calculates the various vectors (v3One, v3Two, v3Test) each time it is called. If A, B and Pivot are static, these vectors could be calculated once in Start().

This solution will only work for angles less than 180 degrees. If you need a solution that handles larger angles, then you can define the two points in terms of left and right. Make PA the left vector, and make PB the right vector. You can calculate whether a test vector (Game Object-Pivot) is to the left or right of any vectors. It will be in between if it is to the right of the left vector and to the left of the right vector.

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;
    }
}