2

Currently I building a flight simulator and are considering ways to limit the range of the player's flight path.
(Using collider's capabilities, such as OverlapSphere, makes it simpler, but it cannot be done in this scenario.)

Assuming that there is a defined flight path from point 1 to point 2, the flight limit is set in a certain area between the two points, and when it is out of the range, it will alert and return it to its original position.

example of situation1
example of situation2

Apart from the others, a certain flight-restricted area must be calculated on the path between the two points and made detectable to deviate from that area, but for me, whose mathematical ability is hopeless, I don't know how to deal with this.

I need help with this calculation or opinions on other ideas.

developoma
  • 23
  • 4

3 Answers3

1

I think you would simply divide this into 3 possible cases to test:

  1. plane is within a certain distance to A

    quite trivial

    var planePosition = plane.transform.position;
    planePosition.y = 0;
    var aPosition = A.transform.position;
    aPosition.y = 0;
    
    if(Vector3.Distance(planePosition, aPosition) <= range) { return true; }
    
  2. plane is within a certain distance to B

    basically the same

    var planePosition = plane.transform.position;
    planePosition.y = 0;
    var bPosition = B.transform.position;
    bPosition.y = 0;
    
    if(Vector3.Distance(planePosition, bPosition) <= range) { return true; }
    
  3. plane is within a rectangle between A and B with a certain width (2x range)

    // vector A -> B
    var delta =  bPosition - aPosition;
    var distance = delta.magnitude;
    // direction A -> B with normalized length 1
    var direction = delta.normalized;
    // direction perpendicular to world Up and "direction"
    var cross = Vector3.Cross(direction, Vector3.up).normalized;
    
    // So now we can get our 4 rect points like
    var rectA = aPosition + cross * range;
    var rectB = aPosition - cross * range;
    var rectC = bPosition + cross * range;
    var rectD = bPosition - cross * range;
    
    // and now we can use the area check formular from the link
    var areaRect = cross * 2 * distance;
    
    var areaAPD = Mathf.Abs((planePosition.x * rectA.z - rectA.x * planePosition.z) + (planePosition.x * rectD.z - rectD.x * planePosition.z) + (rectA.x * rectD.z - rectD.x * rectA.z)) / 2f;  
    var areaDPC = Mathf.Abs((planePosition.x * rectD.z - rectD.x * planePosition.z) + (planePosition.x * rectC.z - rectC.x * planePosition.z) + (rectC.x * rectD.z - rectD.x * rectC.z)) / 2f;   
    var areaCPB = Mathf.Abs((planePosition.x * rectC.z - rectC.x * planePosition.z) + (planePosition.x * rectB.z - rectB.x * planePosition.z) + (rectC.x * rectB.z - rectB.x * rectC.z)) / 2f; 
    var areaPBA = Mathf.Abs((planePosition.x * rectB.z - rectB.x * planePosition.z) + (planePosition.x * rectA.z - rectA.x * planePosition.z) + (rectB.x * rectA.z - rectA.x * rectB.z)) / 2f;
    
    return areaAPD + areaDPC + areaCPB + areaPBA <= areaRect;
    

So I think together it could look like e.g.

public static class MathUtils
{
    // little helper extension to get rid of any differences in Y direction 
    public static Vector2 XZ(this Vector3 v) => new Vector2(v.x, v.z);
    
    public static float Area(Vector2 a, Vector2 b, Vector2 c) => Mathf.Abs((a.x * b.y - b.x * a.y) + (b.x * c.y - c.x * b.y) + (c.x * a.y - a.x * c.y)) / 2f;

    public static bool IsWithinRange(this Vector3 p, Vector3 a, Vector3 b, float range)
    {
        var cleanP = p.XZ();
        var cleanA = a.XZ();

        if(Vector2.Distance(cleanP, cleanA) <= range) return true;

        var cleanB = b.XZ();

        if(Vector2.Distance(cleanP, cleanB) <= range) return true;  

        var delta =  cleanB - cleanA;
        var distance = delta.magnitude;
        var direction = delta.normalized;
        var cross = Vector2.Perpendicular(direction).normalized;

        var rectA = cleanA + cross * range;
        var rectB = cleanA - cross * range;
        var rectC = cleanB + cross * range;
        var rectD = cleanB - cross * range;

        var areaRect = cross * 2 * distance;

        var areaAPD = Area(cleanP, rectA, rectD);
        var areaDPC = Area(cleanP, rectC, rectD);
        var areaCPB = Area(cleanP, rectC, rectB);
        var areaPBA = Area(cleanP, rectA, rectB);

        return areaAPD + areaDPC + areaCPB + areaPBA <= areaRect;
    }
}

You will need to test this of course as I am typing this on the phone ;)

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • Since I'm out of my seat, it'll be a little later to go back and test it, but it seems like a good way to solve the problem I'm currently facing. Thank you for a good example. – developoma Oct 06 '22 at 15:21
  • It's been a while, but I have a question as I applied the code. In the above example, float and Vector2 cannot be applied as <= operator in the return part because 'areaRect' is recognized as Vector2. How should I modify it? – developoma Oct 18 '22 at 04:14
1

One way to do it is to use the bounding box between the two points. As stated here:

An axis-aligned bounding box, or AABB for short, is a box aligned with coordinate axes and fully enclosing some object. Because the box is never rotated with respect to the axes, it can be defined by just its center and extents, or alternatively by min and max points.

I have written a simple code for you to show how it works (the gizmos code is from here), if you want more complex shapes you can use multiple bounding boxes (moreover, you can create your path using the probuilder package, disable the shape and just use the collider of it)

    [SerializeField] private Transform startTransform;
    [SerializeField] private Transform endTransform;
    [SerializeField] private Transform plane;
    [SerializeField] private Vector3 size = Vector3.one * 3.5f;
    [SerializeField] private Bounds _bounds;

    private void Start()
    {
        _bounds = new Bounds {center = (startTransform.position + endTransform.position) / 2,extents = size};
    }

    private void Update()
    {
        print(_bounds.Contains(plane.position) ? "yes" : "no");
    }

    private void OnDrawGizmos()
    {
        var xVals = new[]
        {
            _bounds.max.x, _bounds.min.x
        };
        var yVals = new[]
        {
            _bounds.max.y, _bounds.min.y
        };
        var zVals = new[]
        {
            _bounds.max.z, _bounds.min.z
        };

        for (int i = 0; i < xVals.Length; i++)
        {
            var x = xVals[i];
            for (int j = 0; j < yVals.Length; j++)
            {
                var y = yVals[j];
                for (int k = 0; k < zVals.Length; k++)
                {
                    var z = zVals[k];

                    var point = new Vector3(x, y, z);
                    Gizmos.DrawWireCube(point, _bounds.extents);

                    if (i == 0)
                    {
                        Gizmos.DrawLine(point, new Vector3(xVals[1], y, z));
                    }

                    if (j == 0)
                    {
                        Gizmos.DrawLine(point, new Vector3(x, yVals[1], z));
                    }

                    if (k == 0)
                    {
                        Gizmos.DrawLine(point, new Vector3(x, y, zVals[1]));
                    }
                }
            }
        }
    }
Soroush Hosseinpour
  • 539
  • 1
  • 6
  • 25
  • It was an easy-to-access feature, but also help to the problem. It was a feature that I didn't know about Bound at all. In the current project, route control using Collider should be avoided for some reason, which is quite useful for my problem because it is also possible based on Mesh or Render. – developoma Oct 06 '22 at 15:38
0

I have been playing with some ideas and i think the best solution would be to use Point1 (A) and Point2 (B) as points of a line and calculate the shortest distance from the plane (Point3) to the line (AB). You can use something like this as a starting point: Shortest distance between a point and a line segment

A-New-User
  • 61
  • 3
  • I know what it is roughly like to explain, but I'm still confused about the detail of the calculation. I will try it by referring to your link. thanks – developoma Oct 06 '22 at 08:09
  • What you would look for is projection of a point (your plane position) onto a line. Then get the magnitude of that and if greater than X then plane is out of the cylinder. Check the Vector3.Project page where onNormal is the straight path from A to B and the end of vector is the plane position. It will give a point on onNormal. Then you can get the distance with (PtOnNormal - planePosition).magnitude. This will work in 3D as well. https://docs.unity3d.com/ScriptReference/Vector3.Project.html – Everts Oct 06 '22 at 10:43
  • as far as i was able to understand, op is interrested in figuring out if the plane is in the general area (top down perspective, 2d). correct me if i m wrong, but isnt Vector3 a bit overkill then? – A-New-User Oct 06 '22 at 11:50
  • @A-New-User a `Vector2` is basically also just a `Vector3` just stripped off the z axis value. As I understand this it - as you say top down - so you need the `Vector3` x and z components rather than the `Vector2` x and **y** – derHugo Oct 06 '22 at 12:44
  • I hadn't thought of putting this problem on a plane because the current project is 3D, and the plane exists in a free Y-axis path. However, looking at the answers, I don't think my ideas were flexible. It was a good reference. – developoma Oct 06 '22 at 15:28