UIDocument face the camera

I want my world space document to face the camera.
But I have to say I got some problem obtaining center position of my document that is needed for this calculations.

This is my attempt that is not exactly working.

            Vector2 panelCenter = document.rootVisualElement.worldBound.center;
            Vector3 localCenter = new Vector3(panelCenter.x, panelCenter.y, 0f);
            Vector3 documentCenterWorldSpace = document.transform.TransformPoint(localCenter);

If you document pivot is centered, would this work for you?

Transform t = document.transform;
Vector3 dir = t.position - cam.transform.position;
t.rotation = Quaternion.LookRotation(dir, Vector3.up);

It would because I already had such a solution.

But it is not enough because sometimes I need this pivot to be somewhere else.
I also would like to use this boundary information of UIDocument for other purposes, like aligning UIDocuments to some edges or to each-other. But right now proper facing the camera is a priority for me.

Is it a “standalone” UIDocument or does it have nested UIDocuments, or is it, itself, a nested UIDocument?

It is standalone document.
It is basically UIDocument on GameObject no nested UIDocument.

I’m still not able to resolve it, any news ?
I went to the source and there are some information needed for this calculations.
But damn, everything is internal.

I don’t think you need any internal API for this. This seems to work:

var doc = GetComponent<UIDocument>();
var root = doc.rootVisualElement;
var documentCenter = transform.TransformPoint(root.worldBound.center);
var lookVector = documentCenter - cam.transform.position;
transform.rotation = Quaternion.LookRotation(lookVector, cam.transform.up);

LookAtCamera

Then it’s up to you to adjust the arguments of Quaternion.LookRotation depending if you want it to face the camera or the camera plane, and how you want it to act vertically.

I start to understand what is going on, basically system is starting to return proper value at some point. This is why it works for you in Update method.

Take a look at this script, it will log the position of the UIDocument center and direction from center of the document to point (0,0,0).

 public class UIDocumentTestComponent : MonoBehaviour
    {

        [SerializeField] private UIDocument document;

        private void Awake()
        {
            LogPositionAndDirection(nameof(Awake));
            document.rootVisualElement.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
        }

        private void OnEnable()
        {
            LogPositionAndDirection(nameof(OnEnable));
        }

        private void Update()
        {
            LogPositionAndDirection(nameof(Update));
        }

        private void OnGeometryChanged(GeometryChangedEvent evt)
        {
            LogPositionAndDirection(nameof(OnGeometryChanged));
        }

        private void LogPositionAndDirection(string label)
        {
            var point = Vector3.zero; 
            
            var documentCenter = transform.TransformPoint(document.rootVisualElement.worldBound.center);
            var direction = (point - documentCenter).normalized;

            Debug.Log($"{label}: {documentCenter} : {direction}");
        }
    }

For my simple case this is the result:

As we can see Awake and OnEnable returns NaN, but also first Update returns NaN.
After this we got OnGeometryChanged that returns something that is not right.
And finally we got stream of Update that started to return proper values.

I seriously don’t want to use Update method for this.
Best would be OnGemoetryChanged, but the one that is actually returning proper values.
Or something that lets me force building uidocument so I can obtain proper values immediately in Awake.

Try using LateUpdate() since Layout is updated between Update and LateUpdate

I can always hack it like this, but I see it problematic that we do not have a proper way to know when document is finally ready. Now when using LateUpdate or Update I will have to play with enable of my component during runtime so it is not called each frame. And I’m not even going deeper into situations when there are multiple documents whose positions/ dimensions depends on each-other.

And results returned by OnGeometryChanged - looks like a bug to me.

Layout happens all in one stage: UnityCsReference/Runtime/Export/PlayerLoop/PlayerLoop.bindings.cs at master · Unity-Technologies/UnityCsReference · GitHub

So even if you have multiple documents, they will all be calculated by the time you reach LateUpdate().