- Home /
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:
Thanks in advance!
Answer by whydoidoit · Sep 27, 2013 at 04:58 AM
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;
}
}
The allows the point to be at any height above or below the plane defined by pointA and pointB - if you just want it to be on that plane, don't add the targetOffset.
If you want it to be in the segment of the ellipse described by pointA and pointB (as in it can't lie outside the perimiter) then:
var allowedPosition = planeVector.normalized * $$anonymous$$athf.$$anonymous$$in(
planeVector.magnitude,
$$anonymous$$athf.Lerp((pointA - centre).magnitude, (pointB - centre).magnitude, angleToA/maxAngle))
+ targetOffset;
Thank you for taking the time to write this answer. I have a few questions regarding the code. Is the targetVector
variable the position of the object I want to move? Following the instructions, I get the allowedPosition
, but what would I need to do to actually keep the object inside the plane area? (What do I set the object's position to?)
The variables going into the code are:
pointA, pointB, centre and targetPoint
You set the position of the thing to allowedPosition after the end of the code I posted.
targetVector is targetPoint - centre (so the vector from the centre to the target point)
The routine uses this as the representation of the target so that afterwards it can use the clamping on that, before turning it back into a simple position.
O$$anonymous$$, I have set the targetPoint
variable to the object I am moving, then after doing the moving part, I have put the code from the answer. Then I set the objects position to the allowedPosition
. When I check the scene view, I see the object in the wrong position. Firstly, it is outside the angle, and secondly, the object doesn't move when I move the mouse. Is there something I am missing?
Answer by robertbu · Sep 27, 2013 at 04:50 AM
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.
Your answer
![](https://koobas.hobune.stream/wayback/20220613114904im_/https://answers.unity.com/themes/thub/images/avi.jpg)
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
rotate to specific coordinate c# 1 Answer
Flip over an object (smooth transition) 3 Answers