TCastleViewport.PositionFromWorld utility, to adjust UI position to something in 3D viewport, or measure 3D size on screen

PositionFromWorld demo

New method TCastleViewport.PositionFromWorld allows to map a viewport (3D) position into user interface (2D) position. This allows to:

  1. Place UI element exactly where some 3D thing is.

    The example examples/viewport_and_scenes/position_from_world shows how to position a TCastleLabel so that it always remains at the TCastleSphere middle, regardless of how you move and zoom the camera.

    It comes down to these lines of code:

    { calculate SphereViewportPosition, position of the sphere middle in 2D viewport coordinates. }
    SphereWorldPosition := MainSphere.LocalToWorld(Vector3(0, 0, 0));
    SphereViewportPosition := MainViewport.PositionFromWorld(SphereWorldPosition);
    

    { SphereViewportPosition is immediately useful to determine LabelAttached position }
    LabelAttached.AnchorDelta := SphereViewportPosition;

  2. Measure the (potential) size of 3D element on the screen. In turn, this allows to transform 3D elements to make them look on screen in some desired way. For example, you can adjust 3D scale, to force some 3D object to have certain size on the screen, regardless of how far it is from the camera.

    The same example examples/viewport_and_scenes/position_from_world shows how to position a TCastleText so that it always has the same size, no matter how far away the pivot is (regardless of how much you zoom in/out the camera). Try out the demo, you can rotate and zoom like crazy — the 3D text will rotate, the sphere will get smaller or larger, but the size of the text will remain the same.

    It comes down to these lines of code (note: PositionFromWorld and SphereViewportPosition calculation is the same as in previous use-case):

    { calculate SphereViewportPosition, position of the sphere middle in 2D viewport coordinates. }
    SphereWorldPosition := MainSphere.LocalToWorld(Vector3(0, 0, 0));
    SphereViewportPosition := MainViewport.PositionFromWorld(SphereWorldPosition);
    

    { calculate Sphere1UnitSizeOnScreen, how large is 1 unit in 3D space around sphere, on the screen. }
    SphereWorldPosition1 := SphereWorldPosition + Vector3(0, 1, 0);
    SphereViewportPosition1 := MainViewport.PositionFromWorld(SphereWorldPosition1);
    Sphere1UnitSizeOnScreen := PointsDistance(SphereViewportPosition, SphereViewportPosition1);

    { use Sphere1UnitSizeOnScreen to keep the TextAttached size similar on screen,
    regardless of how far is the scene. }
    TextAttachedScale := 100 / Sphere1UnitSizeOnScreen;
    TextAttached.Scale := Vector3(TextAttachedScale, TextAttachedScale, TextAttachedScale);

1 Like