Search code examples
c#unity-game-enginevuforiahololens

Issue with Positioning Sphere Relative to Vuforia Image Target in Unity and HoloLens


I'm developing an AR app using Unity, Vuforia, and HoloLens. My goal is to create a sphere at a specific position relative to a Vuforia image target (e.g., (1, 2, 3) relative to the image target's origin).

What I've Tried:

Manually: I created a sphere at (1, 2, 3) in the Unity Inspector as a child of the Vuforia image target, and it appeared at the correct position. Via Script: However, when creating the sphere through a script, its position is not as expected.

Code: In my script, I attempt to position the sphere as follows:

sphere.transform.position = targetTransform.position + localPosition;

Here, targetTransform is the Transform of the ObserverBehaviour (Vuforia's target). Despite this, the sphere appears in a different location compared to the manually placed sphere.

Full Script:

Here’s the relevant part of my code for creating and positioning the sphere:

public class SphereManager
{
    private GameObject sphere;
    private Vector3 localPosition;

    public SphereManager(Vector3 position)
    {
        localPosition = position;
    }

    public void CreateSphere(Transform targetTransform)
    {
        if (sphere == null)
        {
            sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            sphere.transform.SetParent(targetTransform, false);
            sphere.transform.position = targetTransform.position + localPosition;
            sphere.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
        }
    }

    public void DestroySphere()
    {
        if (sphere != null)
        {
            GameObject.Destroy(sphere);
            sphere = null;
        }
    }

    public bool IsSphereActive() => sphere != null;
}

And the script handling the Vuforia target detection:

public class SphereCreator : MonoBehaviour
{
    public Vector3 pose = Vector3.zero;
    private SphereManager sphereManager;
    private ObserverBehaviour observerBehaviour;

    void Start()
    {
        sphereManager = new SphereManager(pose);
        observerBehaviour = GetComponent<ObserverBehaviour>() ?? GetComponentInChildren<ObserverBehaviour>() ?? GetComponentInParent<ObserverBehaviour>();

        if (observerBehaviour)
        {
            observerBehaviour.OnTargetStatusChanged += OnTargetStatusChanged;
        }
        else
        {
            Debug.LogWarning("ObserverBehaviour component not found.");
        }
    }

    private void OnTargetStatusChanged(ObserverBehaviour behaviour, TargetStatus targetStatus)
    {
        if (targetStatus.Status == Status.TRACKED || targetStatus.Status == Status.EXTENDED_TRACKED)
        {
            sphereManager.CreateSphere(behaviour.transform);
        }
        else
        {
            sphereManager.DestroySphere();
        }
    }

    private void OnDestroy()
    {
        if (observerBehaviour != null)
        {
            observerBehaviour.OnTargetStatusChanged -= OnTargetStatusChanged;
        }
    }
}

Problem:

The sphere created via the script doesn't align with the one placed manually in the Inspector. How should I set the sphere's position in the script to ensure it matches the manually placed sphere?


Solution

  • The position you see and configure in the Inspector is the Local Position relative to the parent!

    So if you want to set the same via script and it is a child of the target all you need to do is

    sphere.transform.localPosition = localPosition;
    

    Assuming that localPosition here matches your expected value of 1,2,3


    The reason it doesn't match when using the global positioning

    sphere.transform.position = targetTransform.position + localPosition;
    

    Is that here you don't take into account any scaling or rotation of the parent object target.

    If you really wanted to do it in world space - for instance if you want to achieve the same but without making the sphere a child - you could achieve this going through TransformPosition

    sphere.transform.position = targetTransform.TransformPoint(localPosition);
    

    This converts the local position into world space taking into account any parent translation, rotation and scale.

    Though as the sphere is a child of the parent anyway I would strongly prefer the above for simplicity ;)