[SOLVED] Problems with resolution independent scaling

Hey fellow Monkeys,
I’ve tried to make my UI resolution independent, by adding an intermediary node with scale (resolution.x, resolution.y, 1), to which I add all Lemur GUI Elements.

While this works fine for positioning, I get problems with components setPreferredSize as they seem to not take into account that huge scaling.

I wanted to add a damage indicator which is basically rotated around the screen center (hence the container) and then moved out of center for 0.25f. The Code to setup looks like this:

@Override
protected void handleAdd(EntityId playerId, IndicatorStruct struct) {
    Container c = new Container(new BorderLayout(), "glass");
    c.setLocalTranslation(new Vector3f(0.5f, 0.5f, 0f)); // Center of screen as rotation pivot.
    Label lbl = new Label("");
    QuadBackgroundComponent qbc = new QuadBackgroundComponent(Main.self.getAssetManager().loadTexture("Interface/DamageIndicator_ColorMask.png"));
    lbl.setBackground(qbc);
    lbl.setLocalTranslation(new Vector3f(0f, 0.25f, 0f));
    lbl.setLocalScale(new Vector3f(64f / 1280f, 64f / 720f, 0f)); // My try to replace the setPreferedSize(64f, 64f, 0f), so I can at least see a part of the qbc   
    c.addChild(lbl, BorderLayout.Position.Center);      
    Main.self.guiAppState.getLemurScreen().attachChild(c); // getLemurScreen is that intermediary node
    System.out.println(lbl.getWorldTransform().toString()); // Debug Info
}

The Debug Info looks like this:

Transform[ 640.0, 540.0, 0.0]
[ 0.0, 0.0, 0.0, 1.0]
[ 64.0 , 64.0, 0.0]

The problem is: This looks fine. 640, 360 is my screen center, rotation none (disabled for bug hunting) and a scale of 64x64 px. Still the Label is so huge, see the screenshot:

So I’d love suggestions how I can fix this. Is there maybe already something built in which I could use to make Lemur’s Size Calculations working again?

Ok.

I don’t understand. Preferred size and positioning will both be done in the space they are in… which is then scaled up. That’s resolution independent.

So if you have scale 2, 2, 1, on your main node then position 5, 5, will really be 10, 10 and size 5, 5, will really be 10, 10.

If you are seeing otherwise then your “seeing” is incorrect.

Well I am talking about stuff like this:
When the QuadBackgroundComponent is fed with a texture of 64x64 pixel, the label is fit to this size.
In my case it might become 64*resolution, because essentially my problem is that I am unable to scale down the label so the texture is of reasonable size. (Actually I am not sure whether a regular quad would be more easy)

Basically you can see the glass style which is unwanted though, I only want to display a Texture

Don’t do this.

As to the rest, I still don’t understand.

The lemur GUI elements, the QBC, the labels, the everything will be in the space of the node they are attached to. So a QBC this is 64, 64 under a root node with doubled scale will look like it’s 128, 128.

What I’m trying to understand is exactly when you don’t want resolution independent scaling and when you do. It’s very confusing trying to sort what you are getting from what you’ve tried from what you want… and I think harder because even you are confused.

Well, in theory I want everything to be resolution independent, as long as I can see the image, which I can’t in this case it seems.

What should not be resolution independent is the auto-sizing of the label probably or say if an image is 64x64 px it would be more predictable if it’s scaled so that it takes actual 64x64 pixels (something like setScale(getImageResolution() / getParent().getScale()); ). But I might be confused about that part.

Anyway, what I wanted to say:

Transform[ 640.0, 540.0, 0.0]
[ 0.0, 0.0, 0.0, 1.0]
[ 64.0 , 64.0, 0.0]

I had adjusted the scale, so that the size is 64x64 (since in the gui node 1 scale = 1 px), yet the label is at least taking up a quarter screen (probably more, if it was placed differently).

Put differently: If I set the preferredSize to 0.125f, the image should only take up an eight of the screen. But even if I additionally set the scale to 0.1f, I still see the picture like above, the only thing which changes is that the two panes are moved a bit apart.

As a comparison the code used for the flame-action bar is nearly the same, but it’s added to a SpringGridLayout and then to the GuiNode instead of directly to the Resolution-Independent Node.
I’ve just tried it: if I switch the ActionBar to the new node, I get the same behavior: I only see the glass style panes.

Show me sample code without strange child scaling. Show me the picture of JUST what you want to show me (and not 50 different screen elements) and then tell me what it should look like.

As it is, it’s too confusing as I don’t know what “damage indicator” is from the 5 things that look like possibly damage indicators.

Okay, now I’m having this problem:
If I start with a clean jme application, this problem does not appear.
In my almost identical code, it does appear.

I’ve removed toneg0ds “Screen”-Control from my GuiNode but the problem still persists.
What I discovered is that if I set the container’s local scale to new Vector3f(0.0025f, 0.0025f, 1f) it starts to become visible.

The World Scale thus is [ 3.1999998 , 1.8, 1.0].
This is where I am clueless. I’ve thought the world scale is basically the size in pixels? Can you maybe give me some pointers how I can troubleshoot my code?

The world scale of what? The guiNode?

The guiNode is 1, 1 = a pixel.

Edit: make sure you aren’t setting the value of some constant Vector3f somewhere or something.

No, the world scale of the Label.
It feels like something is scaling it twice by Resolution (not exaxtly because a world scale of 3 makes half a screen size)

Then you should be able to walk up the scene graph and see.

Lemur is doing no particular magic with local scale.

That’s making me insane, as is still works on the small test case but on my project, everything is failing.
I tried to add another intermediary node, same result.
I confirmed the world transforms of the containers being correct (and actually it’s the containers being too large, one can see the borders/glass style of them).

And when I try to scale another container by setting container.setPreferredSize(new Vector3f(0.75f, 0.5f, 0f));, I even get an Exception:

java.lang.IllegalArgumentException: Size cannot be negative:(0.0, -49.0, 0.0)
        at com.simsilica.lemur.core.GuiControl.setSize(GuiControl.java:242)
        at com.simsilica.lemur.component.BorderLayout.reshape(BorderLayout.java:181)
        at com.simsilica.lemur.core.GuiControl.setSize(GuiControl.java:259)
        at com.simsilica.lemur.core.GuiControl.revalidate(GuiControl.java:351)
        at com.simsilica.lemur.core.GuiControl.controlUpdate(GuiControl.java:318)
        at com.jme3.scene.control.AbstractControl.update(AbstractControl.java:128)
        at com.jme3.scene.Spatial.runControlUpdate(Spatial.java:736)

Now I’ve checked BorderLayout#reshape but I don’t understand Lemur’s Internals, however:
Could it be that the result of calculatePreferredSize() should be used as size in reshape?
I’ve investigated:

NORTH getPreferredSize
(222.0, 24.0, 2.0)
CENTER getPreferredSize
(4.01, 25.0, 0.01)
SOUTH getPreferredSize
(4.01, 25.0, 0.01)
CONTAINER getPreferredSize
(224.0, 76.0, 1.01)

This is partially a problem: North, Center and South don’t take scaling into account (they couldn’t because they aren’t added yet actually), thus being way too large.

Can I do something about that to have the Layout Systems work with that?
Interestingly both my testcase and my real case return the same:

(0.125, 0.125, 0.0)
(2.125, 2.125, 1.0)

So I’ve set the preferredSize of the label to 0.125f but in the test case, the containers size being 2 isn’t scaled so much. But it shows the issue, the layouts are somehow doing pixel calculations

EDIT: HA! I can reproduce the test case if I change the label’s style to glass, but only then!

Random things from reading your post:

Lemur layouts will not take scaling into account. Scale the parent or don’t scale at all, basically.

If you set a preferred size smaller than can possibly be laid out then some GUI elements will try to have negative size.

You probably never want to set a z size of 0 as preferred size.

So before we’re not understanding each other correctly, here is a quick test case:

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.GuiGlobals;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.component.BorderLayout;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import com.simsilica.lemur.style.BaseStyles;

public class LemurScalingTest extends SimpleApplication {
    Container c;
    final boolean working = false;
    final boolean showPanel = true; // whether or not to show the containers background
    @Override
    public void simpleInitApp() {
        // Just because I do all this setup in my main project as well
        GuiGlobals.initialize(this);
        
        // Note: The following three lines DO make a difference....
        GuiGlobals.getInstance().setCursorEventsEnabled(false);
        BaseStyles.loadGlassStyle();
        GuiGlobals.getInstance().getStyles().setDefault("glass");

        // The Scale Node
        Node resolutionNode = new Node();
        resolutionNode.setLocalScale(1280f, 720f, 1f);
        
        // The Container
        if (showPanel) {
            c = new Container(new BorderLayout(), "glass");
        } else {
            c = new Container(new BorderLayout());
        }
        c.setLocalTranslation(new Vector3f(0.25f, 0.5f, 0f)); // Center of screen as rotation pivot.        
        
        Label lbl;
        if (working) {
            lbl = new Label("");
        } else {
            lbl = new Label("", "glass");
        }
        
        QuadBackgroundComponent qbc = new QuadBackgroundComponent(getAssetManager().loadTexture("Common/Textures/MissingTexture.png"));
        lbl.setBackground(qbc);
        lbl.setLocalTranslation(new Vector3f(0f, 0.25f, 0f));        
        lbl.setPreferredSize(new Vector3f(0.125f, 0.125f, 0f));

        c.addChild(lbl, BorderLayout.Position.Center);
        resolutionNode.attachChild(c);
        
        getGuiNode().attachChild(resolutionNode);
    }
    
    public static void main() {
        LemurScalingTest test = new LemurScalingTest();
        test.start();
    }
}

The important part are the three lines of init, I am not sure whether they are useful, needed or not, but fact is:
Without them, working = false does not show the bug. Probably because the glass style is not available?

Not what I think is the issue here:

So I have the intermediary GuiNode which does the scaling. The problem comes from using textures I guess (actually even when using layouts, but not so noticeable): Lemur’s calculatePreferredSize will calculate the size in pixels, which messes everything up.

Imagine I have a Container which contains 10 64x64 textures, the resulting container then has a preferred size of 640x64 (maybe a bit more to have small spacing). This however conflicts with my scaling node, which treats size as relative screen size, not pixels.

So what to do there? I guess I have to set the preferred size manually on every low level element (Label, etc) so that the containers size is halfway correct. Only halfway because if you only have one label in a border layout container you have 1 px border on each side. Which means size is + (2, 2, 1). So in theory I need to set the container’s preferred size on my own?

True, fixed it.

Well that was because I set the preferredSize as I want it in relative-space but the sub containers were in absolute-space.

If you still need me to clarify things, I’m glad to provide information.

I don’t see why this is true. I think you are mixing a bunch of things up… and I say this having already implemented resolution-independent GUIs a bunch of times already.

Labels are not sized based on their QBC unless the QBC includes a margin. They are sized based on the text component. So the size of the QBC’s texture will not matter.

YIKES!

If you do that then all of your sizes must be super-duper teeny tiny. Default margins, default shadow offsets, etc. will be F-ing HUGE.

It’s better to just pick some “natural” screen size for your GUI and then scale that up or down depending on the actual screen size. I generally even just scale based on height and then let the UI layouts stretch naturally as needed in the horizontal.

Ahh, now we’re starting to make progress, yay :slight_smile:
So I only used a Label here to display an image, hence the text is "", which leads to:

(0.01, 21.0, 0.01)
(2.01, 23.0, 1.01)

So 21 pixels height (probably the font height) + margins. So that is that.
And as I was setting the preferredSize of the Label to 0.125f, 0.125f, I still had the margins of 2, which lead to me feeling: “Something looks twice as big as desired”.

Well what I wanted to do is have them relative, like healthBar: position (0.0, 0.1) scale (0.25, 0.1) → Aha, top left corner, quarter width size. But yeah, seems like my problems will be solved, when I just decide on FullHD as reference size and let it scale up- or down. The only thing is that the margins and shadows would also change there, but so small that one doesn’t notice it much.

What would I do to support 4K Monitors where I’d want higher resolution images? Because Scale > 1 would be a loss of quality I guess? Would I then have to change my sizes? So the best idea is to pick the highest resolution possible? (but then I’m wasting VRAM with oversize textures).

Actually maybe just trying to use the Layouts better and get rid of most scaling would be good

Yes, in fact, personally, I only scale for resolutions smaller than some standard size (768 high in my case) so that the UIs are still visible on the lower resolutions. For the higher resolutions, I assume that the user can see everything and so I let the layouts do their thing to just lay things out better.

This has been my experience with many RPGs, etc. in the past that using some larger resolution ends up giving me more view into the world as the HUD elements get smaller relatively speaking.

However, because the scaling is still in place it gives the opportunity to let the user scale up/down if you let them. Which in my case, would be really good for the games I play on my 8 ft screen sitting 12 feet away… where games like Final Fantasy online I have to move my couch right up to the screen to read anything.

2 Likes

Thanks!!
I’ve now managed to rework my GUI within minutes but the improvements are really feelable:

Yes, I’ve failed at the Damage Indicator but Vector Graphics isn’t as simple as one might expect^^

2 Likes

The damage indicator is the red circle segment? Looks okay - player character is being attacked from the front, all clear. :slight_smile:

As for the other things (resolution): I’ve planned to make an “adjustable gui” where the user can decide for the size of UI elements in the “options” of the game menu. Since there is no exact dpi (dots per inch) given for displays, that seemed to be the only way (to me). There are some more advanced ideas for “adjustable gui”, and it will most likely be open sourced to the community when done. Also, @pspeed wouldn’t need to sit too close to his screen then… :wink:

Hey,
I’m sorry that I have to bother you again but I’m having this problem now:
The Bar which is showing the buffs is a container with SpringGridLayout. When no Buff is active, it’s size is basically zero, which changes the layout in it’s parent.
That’s why I am looking for a way to tell the buff container “your minimum height is x pixels”.

I would do that with setPreferredSize however that would also limit the x axis. That’s why I am thinking about extending Container and then just hijack calculatePreferredSize and (if y <= 64) { y = 64; }.

Is that okay?

Limit it to what? If you set the preferred size to 0 then it says “don’t be smaller than 0”… but it can be as big as it needs to be… but that’s depending on how it’s trying to grow.

I basically don’t know enough about how you have it setup… but maybe you can look at the ProgressBar which sounds very similar to what you are trying to achieve. Maybe it will give you ideas.