AR Foundation 6 - check Raycast hit place if the plane is vertical or horizontal

Hi

I’m doing a raycast hit test against a trackable type of plane, however I would also like to see if the plane is vertical or horizontal as I want to do different things based on if the plane is vertical or horizontal, but trackable types doesnt seem to make this destinction…

        if (aRRaycastManager.Raycast(new Vector2(Screen.width / 2, Screen.height / 2), hits, TrackableType.PlaneWithinPolygon))
        {

        }

Could somebody kindly point me to the relevant section in the docs for vertical vs horizontal planes…
All I found is this, but it doesnt help with what I’m trying to achive.
https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@5.1/api/UnityEngine.XR.ARSubsystems.TrackableType.html?q=TrackableType

If there is no way of doing this with trackbletype, how else would I check if the plane is vertical vs horizontal?

Alternatively, how can I do an if statement based on plane classification for example, floor vs wall?

Or could I simply switch the AR Plane Manager’s detection mode at runtime between horizontal and vertical or is that a bad idea?

Both ARCore:

and also ARKit:

…seem to have a way to detect if a plane is vertical or horizontal.

I’m just not sure how to get this info in AR Foundation…

If you have a reference to the ARPlane, you can check the alignment with the ARPlane.alignment property.

To get a reference to the ARPlane from the raycast, you can look at the TrackableType to know what kind of trackable you hit, then ask the corresponding manager for that trackable passing in the ARRaycastHit.trackableId.

For example, if the TrackableType was PlaneWithinBounds, you could call ARPlaneManager.GetPlane(ARRaycastHit.trackableId).

For classification you can do something like the following:

// plane is a reference to the ARPlane
if ((plane.classifications & PlaneClassifications.WallFace) != 0)
{
    // the plane is a wall face
}

You could swap between alignments at runtime but it has different behavior depending on the platform. All it guarantees is that the manager will stop detecting any new planes that do not match the set alignment. It doesn’t mean that any already discovered planes that don’t match will be removed.

2 Likes

I have this in my Update function, but it seems to never print my messages of Wall or Floor to the console so I’m not sure things are working as I expect:

    void Update()
    {
        if (aRRaycastManager.Raycast(new Vector2(Screen.width / 2, Screen.height / 2), hits, TrackableType.PlaneWithinPolygon))
        {
            TrackableId _planeID = hits[0].trackableId;
            ARPlane _arPlane = arPlaneManager.GetPlane(_planeID);

            // plane is a reference to the ARPlane
            if ((_arPlane.classifications & PlaneClassifications.Floor) != 0)
            {
                // the plane is a wall face
                Debug.Log("Floor!!!");
            }

            // plane is a reference to the ARPlane
            if ((_arPlane.classifications & PlaneClassifications.WallFace) != 0)
            {
                // the plane is a wall face
                Debug.Log("Wall!!!");
            }
        }
    }

Something is not right here and I dont know if its just because I’m running the test in the XR Simulation environment or what, but the plane clasification is always “NONE” on any plane of trackabletype PlaneWithinPolygon

I dont get it, what am I doing wrong?

I now added this to various variables:

public bool supportsClassification { get; }

and added this to my Update function:

Debug.Log(supportsClassification);

and that ALWAYS results in false. So cant I get plane classification in the XR Simulator???

I couldnt get the classifications to work for unknown reasons. Would be great if somebody could help me out with that.

In the meantime, the plane’s normal seems to do the job:

                if (_arPlane.normal.y == 1)
                {
                    // the plane is a Floor face
                    Debug.Log("Floor!!!");
                }

                if (_arPlane.normal.x == -1 || _arPlane.normal.x == 1 || _arPlane.normal.z == -1 || _arPlane.normal.z == 1)
                {
                    // the plane is a Wall face
                    Debug.Log("Wall!!!");
                }

This is driving me BONKERS!!!

So now this line ONLY fires:

Debug.Log("Wall!!!");

…if the normal.z == 1

if normal.z == -1 or normal.x == -1 or normal.x == 1
…then it doesnt enter the if statement!!!

In this screenshot you can clearly see normal.z == -1, however the “Wall” never fires:

and here you can see when normal.z == 1 and “Wall” fires fine!

Any idea WHAT is going on here???

No. XR Simulation does not support plane classification. Only Meta Quest and iOS support plane classification, as documented here: Plane detection platform support | AR Foundation | 6.0.2.

You shouldn’t use the == operator to compare floats for equality. Due to the nature of floating point precision, the resulting behavior is often not what you intend. Use Mathf.Approxmiately instead: Unity - Scripting API: Mathf.Approximately

2 Likes

Thanks, that seems to work great for detecting walls in the XR simulator if I have my if statement modified like so:

if (Mathf.Approximately(arPlane.normal.x, -1) || Mathf.Approximately(arPlane.normal.x, 1) || Mathf.Approximately(arPlane.normal.z, -1) || Mathf.Approximately(arPlane.normal.z, 1))
{
}

…however, even though this now finally works in the XR simulator, it doesnt seem to work at all in the real world if I build to my iOS device, it never detects any walls with that if statement…

ah yes I see now what is going on
since the XR simulation has all the walls to exact 90 degree angles to the parallel to the grid, then the code in my if statement works perfectly fine.

However in the real world, you’r hardly ever standing exactly parralel to a wall etc, so instead of the normal reading that “1.00, 0.00, 1.0f” value, it’s more some odd value depending on the angle like for example “0.67, 0,00 -0.23” etc

so I modified my if statement to:

if ((arPlane.normal.x <= -0.05) || (arPlane.normal.x >= 0.05) || (arPlane.normal.z <= -0.05) || (arPlane.normal.z >= 1))
{
}

…that seems to work fine

But still, I wish there was a simpler way to do this!

There is, if you know some math :). I asked Chat GPT to write some sample code for you.

using UnityEngine;

public class VectorUtility : MonoBehaviour
{
    /// <summary>
    /// Checks if the given normalized vector is within 1 degree of being parallel to the ground.
    /// </summary>
    /// <param name="normalizedVector">The normalized vector to check.</param>
    /// <returns>True if the vector is within 1 degree of being parallel to the ground, false otherwise.</returns>
    public bool IsVectorParallelToGround(Vector3 normalizedVector)
    {
        // Ensure the vector is normalized
        if (normalizedVector.magnitude != 1.0f)
        {
            Debug.LogError("The input vector is not normalized.");
            return false;
        }

        // Vector parallel to the ground will have a Y component close to 0
        float yComponent = Mathf.Abs(normalizedVector.y);

        // Calculate the angle in degrees between the vector and the XZ plane
        float angle = Mathf.Asin(yComponent) * Mathf.Rad2Deg;

        // Check if the angle is within 1 degree
        return angle <= 1.0f;
    }
}

Explanation:

  • Normalization Check: Ensure the vector is normalized by checking if its magnitude is 1.0. If it’s not normalized, it logs an error and returns false.
  • Y Component Calculation: The absolute value of the Y component of the vector is taken because we are only interested in the magnitude of the deviation from the horizontal plane, not the direction (above or below the plane).
  • Angle Calculation: The angle between the vector and the horizontal plane (XZ plane) is calculated using Mathf.Asin(yComponent). Since Mathf.Asin returns the result in radians, it is converted to degrees by multiplying by Mathf.Rad2Deg.
  • Angle Check: The method checks if this angle is less than or equal to 1 degree. If it is, the vector is considered to be within 1 degree of being parallel to the ground.

Usage:
You can use this method by attaching the VectorUtility script to a GameObject in your Unity scene and calling the IsVectorParallelToGround method with the normalized vector you want to check.

If a plane’s normal vector is parallel to the ground, then the plane is vertically aligned.

Most platforms do this calculation for you though. Why not just read ARPlane.alignment to see if the plane is vertically aligned?

1 Like