2d display regions and aspect ratios

Hi,
I’ve got a rather straightforward question, how does one set up the aspect ratio on the lens of a camera to a 2d display region, without messing with the position of the elements within it as well as the mouseWatcher assigned to it?
For usual 3d display regions, this of course works fine:

camNP.node().getLens().setAspectRatio(float(dr.getPixelWidth()) / float(dr.getPixelHeight()))

But when applied to 2d display regions, the aforementioned problems arise. If I attempt to modify the aspect ratio of the lens to the camera of a 2d display region, the mouseWatcher works incorrectly and the positions of the gui elements within the display region drastically change. Leaving the aspect ratio untouched however yields no problems at all and everything works fine.

Thanks in advance.

What are you trying to achieve by changing the aspect ratio of a 2D camera? Perhaps there’s some other approach that might serve your purpose without incurring the same issues.

I’m trying to prevent vertical and horizontal stretching, like this:



You can see that the gui-elements in the second image are stretched out, since the aspectRatio wasn’t changed there. Of course, there’s the option of manually scaling and repositioning them, but I feel that somehow modifying the aspectRatio would just be a better way of doing this.

Ah, I see. I’m guessing then that you’re using a custom 2D display region, rather than aspect2d? I don’t have a direct answer offhand, I’m afraid, but perhaps looking at how aspect2d itself handles this might help.

Yeah, like this:

        #First, the 2d display region within which all the content will be displayed:
        self.dr = base.win.makeDisplayRegion(customFrameSize[0],customFrameSize[1],customFrameSize[2],customFrameSize[3])
        self.dr.setSort(20)
        #set the dr color:
        self.dr.setClearColor(Vec4(customCanvasColor[0], customCanvasColor[1],customCanvasColor[2],customCanvasColor[3]))
        self.dr.setClearColorActive(True)
        dr=self.dr
        #create a camera for it:
        myCamera2d = NodePath(Camera('myCam2d'))
        lens = OrthographicLens()
        lens.setFilmSize(2, 2)
        lens.setNearFar(-1000, 1000)
        myCamera2d.node().setLens(lens)
        self.myCamera2d=myCamera2d
        #create a render for it:
        myRender2d = NodePath('myRender2d')
        myRender2d.setDepthTest(False)
        myRender2d.setDepthWrite(False)
        myCamera2d.reparentTo(myRender2d)
        dr.setCamera(myCamera2d)
        
        self.myRender2d=myRender2d
        #create the actual 2d-canvas for it:
        aspectRatio = base.getAspectRatio()
        myAspect2d = myRender2d.attachNewNode(PGTop('myAspect2d'))
        myAspect2d.setScale(1.0 / aspectRatio, 1.0, 1.0)
        myAspect2d.node().setMouseWatcher(base.mouseWatcherNode)
        self.myAspect2d=myAspect2d
        #associate a mouseWatcher with it:
        mw_node = MouseWatcher("my_mouse_watcher")
        mw_node.set_display_region(dr)
        input_ctrl = base.mouseWatcher.parent
        mw = input_ctrl.attach_new_node(mw_node)
        #associate a buttonThrower too:
        bt_node = ButtonThrower("my_btn_thrower")
        mw.attach_new_node(bt_node)
        self.btNode=bt_node
        
        self.myMouseWatcher=mw
        
        bt_node.setThrowButtonsActive(True)
        
        myAspect2d.node().setMouseWatcher(mw_node)
        
        self.myAspect2d=myAspect2d

The manual page referencing 2d-display regions:
2d display regions
That’s where I got the basic information from.

The problems you encounter when changing the aspect ratio of the lens are most likely the same ones reported here. There are apparently some plans to change how all this works in the future.

So for now you’ll probably have to update the aspect ratio of the PGTop node instead. You could do that in a task, but it’s more efficient to do it in a handler for the “window-event” which is generated whenever the window in question (which is passed to the handler) changes in some way (this includes losing or gaining focus).
Now all you need is a DirectObject to listen for this event. Although ShowBase itself derives from DirectObject, it already handles this event to do something similar for the default aspect2d, so unless you explicitly want to override that handler with your own, you should create a new DirectObject for this purpose:

from direct.showbase.DirectObject import DirectObject
...
class run_me(ShowBase):
...
        self._listener = DirectObject()
        self._listener.accept("window-event", self.__update_aspect_ratio)

    def __update_aspect_ratio(self, window):
        aspectRatio = self.getAspectRatio()
        self.myAspect2d.setScale(1.0 / aspectRatio, 1.0, 1.0)

EDIT:
Ah, now I notice the following in ShowBase.adjustWindowAspectRatio (called by the aforementioned ShowBase window-event handler):

            # If anybody needs to update their GUI, put a callback on this event
            messenger.send("aspectRatioChanged")

This aspectRatioChanged event does not seem to be handled by ShowBase itself already, so you could also do:

...
class run_me(ShowBase):
...
        self.accept("aspectRatioChanged", self.__update_aspect_ratio)

    def __update_aspect_ratio(self):
        aspectRatio = self.getAspectRatio()
        self.myAspect2d.setScale(1.0 / aspectRatio, 1.0, 1.0)

This would likely only work for the default window, though.

Hope this helps :slight_smile: .

1 Like

My attempt at implementing that sadly yielded no results…but it’s still alright, since I can manually adjust the scale and position of the gui-elements within the 2d display region, it just means a little more work on my end.

Thanks anyways for the suggestion.

Now that I look at the screenshots in your second post again, I realize that I have indeed overlooked the fact that the aspect ratio of the widgets in your custom DisplayRegion should match that of the widgets attached to the default base.aspect2d node.
This isn’t that hard to fix; just change the code in the __update_aspect_ratio event handler like so:

    def __update_aspect_ratio(self):
        aspectRatio = self.getAspectRatio()
        customFrameSize = self.customFrameSize
        customWidth = customFrameSize[1] - customFrameSize[0]
        customHeight = customFrameSize[3] - customFrameSize[2]
        customAspectRatio = aspectRatio * customWidth / customHeight
        if aspectRatio > 1.:
            s = 1. / customHeight
            self.myAspect2d.setScale(s / customAspectRatio, 1.0, s)
        else:
            s = 1. / customWidth
            self.myAspect2d.setScale(s, 1.0, s * customAspectRatio)

making sure that you define self.customFrameSize in __init__:

        self.customFrameSize = customFrameSize

There was one complication: getting the correct aspect ratio still didn’t make widgets in your custom DisplayRegion the exact same size as similar ones in the default one. This was a bit tricky to get right, since Panda adjusts the aspect ratio (in adjustWindowAspectRatio) based on whether the window is currently taller than it’s wide or vice-versa. The s variable in the new code is used to get the right size.

Thanks as always, that indeed does yield an effect, I’ll work towards adapting it further to my specific situation.