GUI related: how to position a button in one of the corners of a given screen? (SOLVED)

Hello Babylon.js Fellas!

How are you?

Guys, could you help me?

I’m trying to change the position of a button. It would be nice if the button could apper on one of the corners of a given screen. At the moment it is appering over the airplane model.

Here is the playground in question:

var createScene = function () {

    // Low Poly Character with Blender Tutorial of Grant Abbitt: https://www.youtube.com/user/mediagabbitt

    engine.enableOfflineSupport = false;

    // Scene and Camera
    var scene = new BABYLON.Scene(engine);

    var camera1 = new BABYLON.ArcRotateCamera("camera1", Math.PI / 2, Math.PI / 4, 10, new BABYLON.Vector3(0, -5, 0), scene);
    scene.activeCamera = camera1;
    scene.activeCamera.attachControl(canvas, true);
    camera1.lowerRadiusLimit = 2;
    camera1.upperRadiusLimit = 10;
    camera1.wheelDeltaPercentage = 0.01;
    camera1.pinchPrecision=100

    // Lights
    var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 3, -3), scene);
    light.intensity = 2.5;
    light.specular = BABYLON.Color3.Black();

    var light2 = new BABYLON.DirectionalLight("dir01", new BABYLON.Vector3(0, -0.5, -1.0), scene);
    light2.position = new BABYLON.Vector3(0, 5, 5);

    // Skybox
    var skybox = BABYLON.MeshBuilder.CreateBox("skyBox", { size: 1000.0 }, scene);
    var skyboxMaterial = new BABYLON.StandardMaterial("skyBox", scene);
    skyboxMaterial.backFaceCulling = false;
    //skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("textures/skybox2", scene);
    //skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
    skyboxMaterial.diffuseColor = new BABYLON.Color3(0, 0, 0);
    //skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
    skybox.material = skyboxMaterial;

    // Ground
    //var ground = BABYLON.MeshBuilder.CreateGround("ground", { height: 50, width: 50, subdivisions: 4 }, scene);
    //var groundMaterial = new BABYLON.StandardMaterial("groundMaterial", scene);
    //groundMaterial.diffuseTexture = new BABYLON.Texture("textures/wood.jpg", scene);
    //groundMaterial.diffuseTexture.uScale = 30;
    //groundMaterial.diffuseTexture.vScale = 30;
    //groundMaterial.specularColor = new BABYLON.Color3(.1, .1, .1);
    //ground.material = groundMaterial;


    // Load hero character and play animation
    BABYLON.SceneLoader.ImportMesh("", "https://raw.githubusercontent.com/chicortiz/glb/main/", "demoi12.glb", scene, function (newMeshes, particleSystems, skeletons, animationGroups) {
        var hero = newMeshes[0];

        //Scale the model down        
        hero.scaling.scaleInPlace(0.1);

        //Lock camera on the character 
        camera1.target = hero;

        //Get the Samba animation Group
        //const sambaAnim = scene.getAnimationGroupByName("Samba");

        //Play the Samba animation  
        //sambaAnim.start(true, 1.0, sambaAnim.from, sambaAnim.to, false);
        
            // GUI
    var advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");

    var button = BABYLON.GUI.Button.CreateImageButton("but", "", "https://raw.githubusercontent.com/chicortiz/svg/main/sd.svg");
    // ("but", "Click Me",
    button.width = 0.2;
    button.height = "40px";
    button.color = "transparent";
    button.background = "transparent";
    advancedTexture.addControl(button);
    advancedTexture.addControl(target);
    target.linkWithMesh(demoi);
    demoi.linkOffsetY = -150; 

    });

    return scene;
};

Here is the result: https://prototipe.com.br/html_3d/demoibabylon.html

Maybe mix my actual playground with this other one?

https://playground.babylonjs.com/#XCPP9Y#39

I mean something like this:

 advancedTexture.addControl(button);
    advancedTexture.addControl(target);
    target.linkWithMesh(demoi);
    demoi.linkOffsetY = -150; 

I also don’t know if I’m accessing the console in the right way to check what is going wrong. Any help is appreciated.

Thank you very much for your time,

Greetings,

Ortiz

Hi Ortiz,

I’m not sure I understand the question fully. The function linkWithMesh() is meant to position a GUI element relative to a mesh in the world, but I see that you want to position an element on the corner of the screen. If that’s the case, just remove the linkWithMesh() to put the GUI back into screen space and then position accordingly.

You can use the Inspector tool to debug UI issues and play around with different UI properties to find what works for you. On the playground, you can find it here:
image

1 Like

Hi Alexchuber!
Thank you very much for your reply! :smiley:

Initially I was tinkering with this playground to addapt it to my needs:

    // GUI
    var advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");

    var button = BABYLON.GUI.Button.CreateImageButton("but", "Click Me", "textures/grass.png");
    button.width = 0.2;
    button.height = "40px";
    button.color = "white";
    button.background = "green";
    advancedTexture.addControl(button);

It’s possible to change the button’s width and height but how to change it’s position so it does not overlaps the 3D model? :thinking: I don’t know… Maybe something like “button.position”…

I understand that depending on the size of the screen and zoom, overlapping will be inevitable but in some situations maybe the button can appear in a more convenient place? :upside_down_face:

Other interesting information is that for the airplane scene there is no “pan” movement for the camera. The camera is looking towards the center of mass of the airplane instead (pretty much like the samba animation playground example).

To move GUI elements, you have the top, left, topInPixels and leftInPixels properties.

top and left requires a string, such as “50%”. Whereas topInPixels and leftInPixels takes a number for the number of pixels.

The position is also affected by horizontalAlignment and verticalAlignment. Each of them allows you to align content based on the center, top, bottom, left or right inside their own container.

For example:

rect1.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
rect1.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;

Will make the element align with the top left corner.

For figuring out the ideal layout for the button, at a given frame, you can use the following code:

scene.onBeforeRenderObservable.add(() => {
        let worldPosition = sphere.absolutePosition;

        // Convert the world position to screen coordinates
        let screenPosition = BABYLON.Vector3.Project(
            worldPosition,
            BABYLON.Matrix.Identity(),
            scene.getTransformMatrix(),
            camera.viewport.toGlobal(
                scene.getEngine().getRenderWidth(),
                scene.getEngine().getRenderHeight()
            )
        );
    });

This calculates the screen position of the sphere. You can then take this position and compare it to the render width and height, to determine the ideal position for the button.

1 Like

Thank you very much @RandomlyFish!

That’s what just what I need:

    var button = BABYLON.GUI.Button.CreateImageButton("but", "", "https://raw.githubusercontent.com/chicortiz/svg/main/sd.svg");
    // ("but", "Click Me",
    button.width = 0.4;
    button.height = "80px";
    button.color = "transparent";
    button.background = "transparent";
    advancedTexture.addControl(button);
    button.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
    button.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;

I guess the other solution you just pointed out is too advanced for me… :sweat_smile:
I need to study more!

P.S.

Updating: https://prototipe.com.br/html_3d/demoibabylon.html

Playground: https://playground.babylonjs.com/#32FNVD

1 Like

I completely understand that feeling @chicortiz :smile:

The example I shared looks quite complex, due to the matrix math. However, if you look past that detail, the rest is quite a bit easier to understand once it’s broken down.

First, we listen for every time a new frame is about to render:

scene.onBeforeRenderObservable.add(() => {
        // Code in here will trigger before rendering a new frame
});

Each frame we get the sphere’s world position:

let worldPosition = sphere.absolutePosition;

I specifically used absolutePosition instead of position, in case the object is a child of another object. In other words, if you have assigned a parent for the sphere.

When moving a parent, each of the children gets moved with it. Same with rotation and scale. When the parent moves, the object’s position property will not change, as the position refers to the position within its parent. However, the object’s absolutePosition will change, as it refers to the object’s world position.

To use a real world example, you can think of this as a banana in a food container. If you pick up the container and place it in another room. The banana is still positioned inside the container, but at the same time, the banana is in fact now in a different room, hence why there is a distinction between position and absolutePosition.

Of course, in your code, you did not assign a parent to the sphere. But it’s a good practice to try to make your code adapt to different situations when possible. That way, you don’t have to worry as much about functionality breaking, due to other changes.

Finally, we get the screen position, based on the world position:

let screenPosition = BABYLON.Vector3.Project( ... );

I woudn’t be able to provide a good explanation on all the math here, but the important part is that the name of the variable explains what is being calculated by the math.

Now if you want to check if the sphere is within the top left 50x50 pixels on the screen, you would simply check:

if (screenPosition.x <= 50 && screenPosition.y <= 50) {
        // Do something if the sphere is close to the top left corner
}

Or to be more specific, if the sphere is on the left side of the screen:

if (screenPosition.x < scene.getEngine().getRenderWidth() / 2) {
        // Do something if the sphere is anywhere on the left of the screen
}
2 Likes

Whoa!
My goodness!

Thank you for the Masterclass @RandomlyFish !

Let me see if I can do the homework! :sweat_smile: :nerd_face:

1 Like