Unity-Technologies / arfoundation-samples

Example content for Unity projects based on AR Foundation
Other
3.03k stars 1.13k forks source link

Create square planes #665

Closed asteurba closed 3 years ago

asteurba commented 3 years ago

Hi, I'm pretty new to ARFoundation and Unity/C# in general, so this might be easier than I think.

Anyway, for a project I trying to catch the first plane that the ARPlaneManager detects, only show it when it reaches a certain size (2 sqm fe) and not show the rest. This I have managed so far by adding an extra script to my Session Origin that subscribes to ARPlanesChangedEventArgs, grabs the first added plane, storing it and then subscribing to the ARPlaneBoundaryChangedEventArgs of that plane to see whether it's large enough.

The making visible of that specific plane based on size and the rendering invisible of all the subsequent planes I did by making a custom ARPlaneMeshVisualizer that holds an extra visiblePlane bool which is standard set to false (and basically prevents any plane from rendering unless I set it to true).

What I'm trying to do now is instead of having the polygon shape of the plane, I'd like to have a rectangle that is basically a bounding box around the polygon. I thought I'd be able to do this by creating a NativeArray that basically holds a square and pass that to OnBoundaryChanged in the custom ARPlaneMeshVisualizer, instead of the boundary of the plane, but so far no luck. Am I on the right path or making this too complicated for myself?

If any more clarification is needed, or I have to adjust my post, let me know! Thanks in advance!

Script to catch first plane and check for size:

using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using UnityEngine;

public class ARCreateSinglePlane : MonoBehaviour
{
  [SerializeField]
  private GameObject planePrefab;
  [SerializeField]
  private ARSession aRSession;
  private GameObject placedPlane;
  private ARPlane firstPlacedPlane;
  private ARPlaneManager aRPlaneManager;
  // Start is called before the first frame update
  void Awake()
  {
    aRPlaneManager = GetComponent<ARPlaneManager>();
    aRPlaneManager.planesChanged += OnPlanesChanged;
  }

  void OnEnable()
  {
    aRPlaneManager.planesChanged += OnPlanesChanged;
    if (firstPlacedPlane != null)
    {
      firstPlacedPlane.boundaryChanged += OnBoundaryChanged;
    }
  }
  void OnDisable()
  {
    aRPlaneManager.planesChanged -= OnPlanesChanged;
    if (firstPlacedPlane != null)
    {
      firstPlacedPlane.boundaryChanged -= OnBoundaryChanged;
    }
  }

  void OnPlaneAddedSingle(ARPlanesChangedEventArgs eventArgs)
  {
    if (eventArgs.added != null && placedPlane == null)
    {
      placedPlane = Instantiate(planePrefab, eventArgs.added[0].transform.position, Quaternion.identity);
      aRPlaneManager.requestedDetectionMode = PlaneDetectionMode.None;
    }

  }
  void OnPlanesChanged(ARPlanesChangedEventArgs eventArgs)
  {
    if (eventArgs.added != null && firstPlacedPlane == null)
    {
      firstPlacedPlane = eventArgs.added[0];
      firstPlacedPlane.boundaryChanged += OnBoundaryChanged;
    }
    if (eventArgs.added != null)
    {
      foreach (ARPlane plane in eventArgs.added)
      {
        if (plane.trackableId != firstPlacedPlane.trackableId)
        {
          plane.gameObject.SetActive(false);
        }
      }
    }
  }
  void OnBoundaryChanged(ARPlaneBoundaryChangedEventArgs eventArgs)
  {
    if (firstPlacedPlane.size.x * firstPlacedPlane.size.y >= 2f)
    {
        firstPlacedPlane.GetComponent<ARCustomPlaneMeshVisualizer>().MakePlaneVisible(true);
    }
  }

  public void ResetSession()
  {
    aRSession.Reset();
  }
}

Custom ARPlaneMeshVisualizer:

using System;
using System.Collections.Generic;
using UnityEngine.XR.ARSubsystems;
using Unity.Collections;

namespace UnityEngine.XR.ARFoundation
{
  /// <summary>
  /// Generates a mesh for an <see cref="ARPlane"/>.
  /// </summary>
  /// <remarks>
  /// If this <c>GameObject</c> has a <c>MeshFilter</c> and/or <c>MeshCollider</c>,
  /// this component will generate a mesh from the underlying <c>BoundedPlane</c>.
  ///
  /// It will also update a <c>LineRenderer</c> with the boundary points, if present.
  /// </remarks>
  [RequireComponent(typeof(ARPlane))]
  public sealed class ARCustomPlaneMeshVisualizer : MonoBehaviour
  {
    /// <summary>
    /// Get the <c>Mesh</c> that this visualizer creates and manages.
    /// </summary>
    public Mesh mesh { get; private set; }
    private Boolean visiblePlane = false;

    void OnBoundaryChanged(ARPlaneBoundaryChangedEventArgs eventArgs)
    {
      var boundary = m_Plane.boundary;
      NativeArray<Vector2> square = CreateSquareBoundaries();
      if (!ARPlaneMeshGenerators.GenerateMesh(mesh, new Pose(transform.localPosition, transform.localRotation), square))
        return;

      var lineRenderer = GetComponent<LineRenderer>();
      if (lineRenderer != null)
      {
        lineRenderer.positionCount = square.Length;
        for (int i = 0; i < square.Length; ++i)
        {
          var point2 = square[i];
          lineRenderer.SetPosition(i, new Vector3(point2.x, 0, point2.y));
        }
      }

      var meshFilter = GetComponent<MeshFilter>();
      if (meshFilter != null)
        meshFilter.sharedMesh = mesh;

      var meshCollider = GetComponent<MeshCollider>();
      if (meshCollider != null)
        meshCollider.sharedMesh = mesh;
    }
    /// Here I try to create new boundaries based on center and extend, but not sure if this is the best way to do it?
    NativeArray<Vector2> CreateSquareBoundaries()
    {
      Vector2[] boundariesVector = new Vector2[4];
      Vector2 center = m_Plane.centerInPlaneSpace;
      Vector2 extents = m_Plane.extents;
      boundariesVector[0] = new Vector2(center.x + extents.x, center.y - extents.y);
      boundariesVector[1] = new Vector2(center.x + extents.x, center.y + extents.y);
      boundariesVector[2] = new Vector2(center.x - extents.x, center.y + extents.y);
      boundariesVector[3] = new Vector2(center.x - extents.x, center.y - extents.y);
      NativeArray<Vector2> boundaries = new NativeArray<Vector2>(boundariesVector, Allocator.Temp);

      return boundaries;
    }
    void DisableComponents()
    {
      enabled = false;

      var meshCollider = GetComponent<MeshCollider>();
      if (meshCollider != null)
        meshCollider.enabled = false;

      UpdateVisibility();
    }

    public void MakePlaneVisible(bool visible)
    {
      visiblePlane = visible;
    }
    void SetVisible(bool visible)
    {
      var meshRenderer = GetComponent<MeshRenderer>();
      if (meshRenderer != null)
        meshRenderer.enabled = visible;

      var lineRenderer = GetComponent<LineRenderer>();
      if (lineRenderer != null)
        lineRenderer.enabled = visible;
    }

    void UpdateVisibility()
    {
      var visible = enabled &&
          (m_Plane.trackingState != TrackingState.None) &&
          (ARSession.state > ARSessionState.Ready) &&
          (m_Plane.subsumedBy == null) && visiblePlane;

      SetVisible(visible);
    }

    void Awake()
    {
      mesh = new Mesh();
      m_Plane = GetComponent<ARPlane>();
    }

    void OnEnable()
    {
      m_Plane.boundaryChanged += OnBoundaryChanged;
      UpdateVisibility();
      OnBoundaryChanged(default(ARPlaneBoundaryChangedEventArgs));
    }

    void OnDisable()
    {
      m_Plane.boundaryChanged -= OnBoundaryChanged;
      UpdateVisibility();
    }

    void Update()
    {
      if (transform.hasChanged)
      {
        var lineRenderer = GetComponent<LineRenderer>();
        if (lineRenderer != null)
        {
          if (!m_InitialLineWidthMultiplier.HasValue)
            m_InitialLineWidthMultiplier = lineRenderer.widthMultiplier;

          lineRenderer.widthMultiplier = m_InitialLineWidthMultiplier.Value * transform.lossyScale.x;
        }
        else
        {
          m_InitialLineWidthMultiplier = null;
        }

        transform.hasChanged = false;
      }

      if (m_Plane.subsumedBy != null)
      {
        DisableComponents();
      }
      else
      {
        UpdateVisibility();
      }
    }

    float? m_InitialLineWidthMultiplier;

    ARPlane m_Plane;
  }
}
tdmowrer commented 3 years ago

What I'm trying to do now is instead of having the polygon shape of the plane, I'd like to have a rectangle that is basically a bounding box around the polygon.

You could use a planar mesh (GameObject > 3D Object > Plane) for your plane prefab and adjust its transform's localScale accordingly.

Screen Shot 2020-11-14 at 1 22 09 PM

This is effectively what we did in very early versions, before boundary geometry was available. Note that the planar mesh is 10m x 10m, but the ARPlane's size is provided in meters, so you'll need to divide by a factor of 10 when you scale it.

Screen Shot 2020-11-14 at 1 23 05 PM

stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

asteurba commented 3 years ago

Hi, thanks a lot for the fast response and apologies for my late one! Thanks for the pointers, got a semi working version going now, needs a bit of tweaking but I'll get there!