I called .setFrameFullscreenQuad() on CardMaker, created a card and attached it to aspect2d. However the card does not cover the full screen, but is a square matching the screen height. How do I make the card to match the screen width as well?
You just need to scale the card from the desired side using the screen aspect ratio for example:
from direct.showbase.ShowBase import ShowBase
from panda3d.core import CardMaker, NodePath
class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
cm = CardMaker('logo')
cm.set_frame_fullscreen_quad()
facecard = NodePath(cm.generate())
facecard.reparent_to(aspect2d)
# calc aspect ratio
facecard.set_sx(base.win.get_x_size()/base.win.get_y_size())
#facecard.set_sx(base.get_aspect_ratio())
app = MyApp()
app.run()
Or parent it not to “aspect2d”, but instead to “render2d”, if I’m not much mistaken.
OK, so parenting to render2d works indeed but stretches text.
How to manage changes in aspect ratio, i.e. when the user resizes the window? Is there an event that fires when aspect ratio changes?
Alternatively, is there a way to un-stretch text on render2d?
Don’t rely on built-in events, rather use your own.
from direct.showbase.ShowBase import ShowBase
from panda3d.core import LVecBase2i
class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
taskMgr.add(self.my_event_resize, 'my_event_resize')
self.old_win_size = LVecBase2i(base.win.get_size())
def my_event_resize(self, task):
if base.win.get_size() != self.old_win_size:
self.old_win_size = LVecBase2i(base.win.get_size())
print("Window change event")
return task.cont
app = MyApp()
app.run()
I don’t know, I’ve had no real problem with the use of the built-in events.
Here’s a simple test-program to demonstrate:
from direct.showbase.ShowBase import ShowBase
from direct.showbase.DirectObject import DirectObject
from direct.gui.OnscreenText import OnscreenText
class Game(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.directObjectForWindow = DirectObject()
self.directObjectForWindow.accept("window-event", self.windowChanged)
# Note! I recommend >not< calling your event-method "windowEvent",
# as this may clash with an internal method of that name.
# It can work, I believe, but it makes life harder than
# it could be.
self.textObject = OnscreenText("Kittens", parent = render2d)
def windowChanged(self, graphicsWindow):
aspectRatio = self.getAspectRatio()
self.textObject.setSx(1/aspectRatio)
app = Game()
app.run()
If you run the above, you should see that the text in the centre of the screen retains its aspect-ratio even if you adjust the aspect ratio of the screen!
The problem will arise if you need to bind another action when the window is resized.
OK, I just noticed that the whole 3d scene is stretched as I resize the window. Is it the expected behaviour? I want the viewport to change instead, so objects in the scene are not resized. It looks to me that the camera is initialized to aspect ratio 4:3.
Is there some aspect-preserving alternative to base.render, just like there is aspect2d and render2d?
…
And this effect is triggered by binding to ‘window-event’! You were right, @serega-kkz . Handling window events is a bad idea.
…
The usual behaviour is restored by setting camLens.setAspectRatio, but I wonder if there is some default event handler for ‘window-event’ that could be called from overloaded method?
You need to create a separate node for your purposes and scale it as you see fit.
Just include it in the callback function.
That is, if there are two things that you want done when the window is resized, just do them both in a single callback.
Or, if you really need them to be separate, I imagine that having separate DirectObjects for each should do the trick.
That is odd!
I’m not seeing this effect when I try it on my end: the aspect ratio of the 3D scene appears to be preserved, even when I bind a window-event.
Could you please show the code that you’re using? I suspect that the issue may lie there…
Perhaps, for example, you’ve named your callback-method “windowEvent”?
If so, this is due to your version of “windowEvent” overriding the version in ShowBase. The solution is simply to call the ShowBase version, as with any overridden method. Like so:
def windowEvent(self, graphicsWindow)
ShowBase.windowEvent(self, graphicsWindow)
# Your logic here...
You have a problem, the function is being called multiple times. Even if you touch the border once.
from direct.showbase.ShowBase import ShowBase
from direct.showbase.DirectObject import DirectObject
class Game(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.directObjectForWindow = DirectObject()
self.directObjectForWindow.accept("window-event", self.windowChanged)
def windowChanged(self, graphicsWindow):
print("Window change event")
app = Game()
app.run()
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
Window change event
For example, you won’t be able to track a change in the starting point of a window using standard events.
from direct.showbase.ShowBase import ShowBase
from panda3d.core import LVecBase2i
class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
taskMgr.add(self.my_event_resize, 'my_event_reposition')
self.old_win_pos = LVecBase2i(base.win.get_properties().get_origin())
def my_event_resize(self, task):
if base.win.get_properties().get_origin() != self.old_win_pos:
self.old_win_pos = LVecBase2i(base.win.get_properties().get_origin())
print("Window change position")
return task.cont
app = MyApp()
app.run()
Therefore, use your own logic, this will make your game modules independent of others, meaning you don’t need pointless callbacks from another module’s other space.
I mean, your approach calls its function repeatedly, even when there is no event, since it’s polling.
But sure, just run your window-size logic in the callback to determine whether the callback is related to a change in the size of the window.
Just check whether the new window-position has changed, and only update the stored window-position if here has been a change.
Basically, the logic works in pretty much the same way as yours–but it’s only fired when an actual change has been made to the window, rather than every task-update.
I’m not sure of what you mean here: both of our approaches use Panda-internal elements.
Yours uses the task manager, and mine uses a callback.
I kind of pointed out the difference, I’m pretty sure Panda3D uses the same polling under the hood in the callback case. As I reported above, you get n number of phantom calls as a replacement. To determine what exactly happened, you need to filter out the events, which creates more code.
I don’t see any benefit, you are also using DirectObject, which binds you to ShowBase, this will not work in C++, because sooner or later it may be needed.
The window-event mechanism is actually implemented in C++. No polling is used.
You’ll be getting more phantom calls, essentially: you’ll be getting your task run once per frame, regardless of whether a window-event has occurred.
If we add a statement to print “task called” to the beginning of your task, we get endless printouts–so many that I wasn’t able to catch the “window change” printout in a screenshot.
I mean, the code is little different to what you’re already doing in your task: check, for example, whether the window’s position has moved, and respond accordingly. There’s no real difference there, I argue.
I’m pretty sure that you can use DirectObject without ShowBase. (Well, without the ShowBase class. It is in the ShowBase module.)
It might, but I’ve been working with Panda for quite a while now, and as best I recall have never found myself needing to switch to primarily using C++ for a project.
So while a move to C++ might be called for, it may well not be called for.
Choosing an approach based on what’s feasible in C++ is thus, I feel, a premature optimisation–it’s choosing an approach based on a “maybe, possibly”.
I think the code should be unified, no matter python or C++, so using an approach that works in both cases is more correct than any confusion.
Even if it works without an instance of ShowBase, this does not mean that the Direct module can be excluded from use, you will still depend on this module, even if you program in python and you do not need the Direct module.
And yes, although the task is called every frame, it does not affect performance, since checking for the identity of the window dimensions discards all logic.
In your version, you actually need to use essentially the same code on top of the callback (which, by the way, is not very convenient architecturally). Which only provides bulkiness and has no advantages.
