Как нарисовать линию / полилинию на плоскости с помощью ARCore

В примере Java ARCore Hello AR мы можем разместить объект Android на плоскости, нажав на экран, как мы можем использовать эту информацию HitResult, чтобы провести линию между этими объектами?

Спасибо за помощь!


person Alexey Podolian    schedule 12.09.2017    source источник


Ответы (5)


В разделе кода, где вы захватываете якорь, чтобы разместить объект, вы должны проверить, есть ли у вас уже предыдущий якорь. Если у вас есть предыдущая привязка, возьмите worldPosition (как объект Vector3) из предыдущих и текущих привязок, затем вычислите разницу между ними, создайте линию такой длины и прикрепите ее к сцене в средней точке между двумя точки. Наконец, установите previousAnchor на текущий.

Вот код, который я использовал для решения этой проблемы:

// Create the Anchor.
Anchor anchor = hitResult.createAnchor();
AnchorNode anchorNode = new AnchorNode(anchor);

// Code to insert object probably happens here

if (lastAnchorNode != null) {
    Vector3 point1, point2;
    point1 = lastAnchorNode.getWorldPosition();
    point2 = anchorNode.getWorldPosition();
    Node line = new Node();

    /* First, find the vector extending between the two points and define a look rotation in terms of this
        Vector. */

    final Vector3 difference = Vector3.subtract(point1, point2);
    final Vector3 directionFromTopToBottom = difference.normalized();
    final Quaternion rotationFromAToB =
          Quaternion.lookRotation(directionFromTopToBottom, Vector3.up());

    final Renderable[] lineRenderable = new Renderable[1];

    /* Then, create a rectangular prism, using ShapeFactory.makeCube() and use the difference vector
       to extend to the necessary length.  */

    MaterialFactory.makeOpaqueWithColor(this, color)
          .thenAccept(
                  material -> {
                      lineRenderable[0] = ShapeFactory.makeCube(new Vector3(.01f, .01f, difference.length()),
                              Vector3.zero(), material);
              });

    /* Last, set the world rotation of the node to the rotation calculated earlier and set the world position to
       the midpoint between the given points . */
    line.setParent(anchorNode);
    line.setRenderable(lineRenderable[0]);
    line.setWorldPosition(Vector3.add(point1, point2).scaled(.5f));
    line.setWorldRotation(rotationFromAToB);
}

lastAnchorNode = anchorNode;
person Arthulia    schedule 21.08.2018
comment
Что такое lastAnchorNode. Как спасти это setOnTapArPlaneListener. Помогите, пожалуйста - person Shubham Agrawal; 15.10.2018

Anchor будет вам полезен. благодаря этому вы можете отслеживать точки касания и использовать эти координаты между точками. Я сделал что-то подобное, чтобы подсчитать расстояние между двумя точками, которые я коснулся

person Fixus    schedule 13.09.2017
comment
@JagveerSinghRajput это не имеет значения. Он будет работать как со сценой, так и без нее. - person Fixus; 18.07.2018
comment
привет @Fixus, вы смогли нарисовать линию без сцены? если да, то как? - person Yesha Shah; 05.02.2021

Решение от @Arthulia у меня не работает = (

Но я поступил немного иначе. Оно работает:

в моем onCreate:

ArFragment arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.vr_fragment);
arFragment.setOnTapArPlaneListener(
                (HitResult hitResult, Plane plane, MotionEvent motionEvent) -> {
                    addLineBetweenHits(hitResult, plane, motionEvent);
                });

и позже:

private void addLineBetweenHits(HitResult hitResult, Plane plane, MotionEvent motionEvent) {
        Anchor anchor = hitResult.createAnchor();
        AnchorNode anchorNode = new AnchorNode(anchor);

        if (lastAnchorNode != null) {
            anchorNode.setParent(arFragment.getArSceneView().getScene());
            Vector3 point1, point2;
            point1 = lastAnchorNode.getWorldPosition();
            point2 = anchorNode.getWorldPosition();

        /*
            First, find the vector extending between the two points and define a look rotation
            in terms of this Vector.
        */
            final Vector3 difference = Vector3.subtract(point1, point2);
            final Vector3 directionFromTopToBottom = difference.normalized();
            final Quaternion rotationFromAToB =
                    Quaternion.lookRotation(directionFromTopToBottom, Vector3.up());
            MaterialFactory.makeOpaqueWithColor(getApplicationContext(), color)
                    .thenAccept(
                            material -> {
/* Then, create a rectangular prism, using ShapeFactory.makeCube() and use the difference vector
       to extend to the necessary length.  */
                                ModelRenderable model = ShapeFactory.makeCube(
                                        new Vector3(.01f, .01f, difference.length()),
                                        Vector3.zero(), material);
/* Last, set the world rotation of the node to the rotation calculated earlier and set the world position to
       the midpoint between the given points . */
                                Node node = new Node();
                                node.setParent(anchorNode);
                                node.setRenderable(model);
                                node.setWorldPosition(Vector3.add(point1, point2).scaled(.5f));
                                node.setWorldRotation(rotationFromAToB);
                            }
                    );
        lastAnchorNode = anchorNode;
    }
person Andrew Grow    schedule 22.08.2018
comment
Что такое lastAnchorNode. Как спасти это setOnTapArPlaneListener. Помогите, пожалуйста - person Shubham Agrawal; 15.10.2018
comment
@ShubhamAgrawal создайте переменную в своей деятельности: AnchorNode lastAnchorNode; Вы сохраните здесь свой последний узел для будущего использования. - person Andrew Grow; 16.10.2018
comment
Я использовал ваш код, он работал успешно, но когда я перетаскиваю якорь, линия к нему не прикрепляется. это нарушает. помогите мне в этом - person Shubham Agrawal; 16.10.2018
comment
@ShubhamAgrawal Извините, у меня нет ответа на ваш вопрос. Но если найдете решение, пришлите его мне. Спасибо! - person Andrew Grow; 16.10.2018
comment
это сделано с помощью Sceneform? @AndrewGrow - person Yesha Shah; 03.02.2021

  1. Я думаю, что вам следует сохранить всю информацию HitResults, особенно для позиции Pose.

  2. Вы должны создать класс как PlaneRenderer, чтобы он мог рисовать линию между начальной и конечной позицией.

person vvvifdks    schedule 13.09.2017

using System;

namespace GoogleARCore.Examples.HelloAR
{
    using System.Collections.Generic;
    using GoogleARCore;
    using GoogleARCore.Examples.Common;
    using UnityEngine;

    #if UNITY_EDITOR
    // Set up touch input propagation while using Instant Preview in the editor.
    using Input = InstantPreviewInput;
    #endif

    /// <summary>
    /// Controls the HelloAR example.
    /// </summary>
    public class HelloARController : MonoBehaviour
    {
        /// <summary>
        /// The first-person camera being used to render the passthrough camera image (i.e. AR background).
        /// </summary>
        public Camera FirstPersonCamera;

        /// <summary>
        /// A prefab for tracking and visualizing detected planes.
        /// </summary>
        public GameObject DetectedPlanePrefab;

        /// <summary>
        /// A model to place when a raycast from a user touch hits a plane.
        /// </summary>
        public GameObject AndyPlanePrefab;

        /// <summary>
        /// A model to place when a raycast from a user touch hits a feature point.
        /// </summary>
        public GameObject AndyPointPrefab;

        /// <summary>
        /// A gameobject parenting UI for displaying the "searching for planes" snackbar.
        /// </summary>
        public GameObject SearchingForPlaneUI;

        /// <summary>
        /// The rotation in degrees need to apply to model when the Andy model is placed.
        /// </summary>
        private const float k_ModelRotation = 180.0f;

        /// <summary>
        /// A list to hold all planes ARCore is tracking in the current frame. This object is used across
        /// the application to avoid per-frame allocations.
        /// </summary>
        private List<DetectedPlane> m_AllPlanes = new List<DetectedPlane> ();

        /// <summary>
        /// True if the app is in the process of quitting due to an ARCore connection error, otherwise false.
        /// </summary>
        private bool m_IsQuitting = false;

        private int count = 0;
        private Vector3[] pos = new Vector3[4];
        private LineRenderer lineRenderer;

        /// <summary>
        /// The Unity Update() method.
        /// </summary>
        public void Update ()
        {
            _UpdateApplicationLifecycle ();

            // Hide snackbar when currently tracking at least one plane.
            Session.GetTrackables<DetectedPlane> (m_AllPlanes);
            bool showSearchingUI = true;
            for (int i = 0; i < m_AllPlanes.Count; i++) {
                if (m_AllPlanes [i].TrackingState == TrackingState.Tracking) {
                    showSearchingUI = false;
                    break;
                }
            }

            SearchingForPlaneUI.SetActive (showSearchingUI);

            // If the player has not touched the screen, we are done with this update.
            Touch touch;
            if (Input.touchCount < 1 || (touch = Input.GetTouch (0)).phase != TouchPhase.Began) {
                return;
            }

            // Raycast against the location the player touched to search for planes.
            TrackableHit hit;
            TrackableHitFlags raycastFilter = TrackableHitFlags.PlaneWithinPolygon |
                                                       TrackableHitFlags.FeaturePointWithSurfaceNormal;

            if (Frame.Raycast (touch.position.x, touch.position.y, raycastFilter, out hit)) {
                // Use hit pose and camera pose to check if hittest is from the
                // back of the plane, if it is, no need to create the anchor.
                if ((hit.Trackable is DetectedPlane) &&
                                Vector3.Dot (FirstPersonCamera.transform.position - hit.Pose.position,
                                    hit.Pose.rotation * Vector3.up) < 0) {
                    Debug.Log ("Hit at back of the current DetectedPlane");
                } else {
                    // Choose the Andy model for the Trackable that got hit.
                    GameObject prefab;
                    if (hit.Trackable is FeaturePoint) {
                        prefab = AndyPointPrefab;
                    } else {
                        prefab = AndyPlanePrefab;
                    }

                    if (count < 2) {
                        pos [count] = hit.Pose.position;
                        // Instantiate Andy model at the hit pose.
                        var andyObject = Instantiate (prefab, hit.Pose.position, hit.Pose.rotation);

                        // Compensate for the hitPose rotation facing away from the raycast (i.e. camera).
                        andyObject.transform.Rotate (0, k_ModelRotation, 0, Space.Self);

                        // Create an anchor to allow ARCore to track the hitpoint as understanding of the physical
                        // world evolves.
                        var anchor = hit.Trackable.CreateAnchor (hit.Pose);

                        // Make Andy model a child of the anchor.
                        andyObject.transform.parent = anchor.transform;
                        count++;
                    }

// Рисование линий из точек касания с помощью средства рендеринга линий

                  if (count == 2) {

                        //Creating lineRenderer object
                      lineRenderer = new GameObject("Line").AddComponent<LineRenderer>();
                      lineRenderer.startColor = Color.black;
                      lineRenderer.endColor = Color.black;
                      lineRenderer.startWidth = 0.01f;
                      lineRenderer.endWidth = 0.01f;
                      lineRenderer.positionCount = 2;
                      lineRenderer.useWorldSpace = true;    

                      //Drawing line from first touched point to the second one
                      lineRenderer.SetPosition(0,pos[0]);
                      lineRenderer.SetPosition(1,pos[1]);

                  }
                }
            }
        }

        /// <summary>
        /// Check and update the application lifecycle.
        /// </summary>
        private void _UpdateApplicationLifecycle ()
        {
            // Exit the app when the 'back' button is pressed.
            if (Input.GetKey (KeyCode.Escape)) {
                Application.Quit ();
            }

            // Only allow the screen to sleep when not tracking.
            if (Session.Status != SessionStatus.Tracking) {
                const int lostTrackingSleepTimeout = 15;
                Screen.sleepTimeout = lostTrackingSleepTimeout;
            } else {
                Screen.sleepTimeout = SleepTimeout.NeverSleep;
            }

            if (m_IsQuitting) {
                return;
            }

            // Quit if ARCore was unable to connect and give Unity some time for the toast to appear.
            if (Session.Status == SessionStatus.ErrorPermissionNotGranted) {
                _ShowAndroidToastMessage ("Camera permission is needed to run this application.");
                m_IsQuitting = true;
                Invoke ("_DoQuit", 0.5f);
            } else if (Session.Status.IsError ()) {
                _ShowAndroidToastMessage ("ARCore encountered a problem connecting.  Please start the app again.");
                m_IsQuitting = true;
                Invoke ("_DoQuit", 0.5f);
            }
        }

        /// <summary>
        /// Actually quit the application.
        /// </summary>
        private void _DoQuit ()
        {
            Application.Quit ();
        }

        /// <summary>
        /// Show an Android toast message.
        /// </summary>
        /// <param name="message">Message string to show in the toast.</param>
        private void _ShowAndroidToastMessage (string message)
        {
            AndroidJavaClass unityPlayer = new AndroidJavaClass ("com.unity3d.player.UnityPlayer");
            AndroidJavaObject unityActivity = unityPlayer.GetStatic<AndroidJavaObject> ("currentActivity");

            if (unityActivity != null) {
                AndroidJavaClass toastClass = new AndroidJavaClass ("android.widget.Toast");
                unityActivity.Call ("runOnUiThread", new AndroidJavaRunnable (() => {
                    AndroidJavaObject toastObject = toastClass.CallStatic<AndroidJavaObject> ("makeText", unityActivity,
                                                                       message, 0);
                    toastObject.Call ("show");
                }));
            }
        }
    }
}
person Codemaker    schedule 16.01.2020