Hello,
I am trying to optimize a technical / scientific application that utilizes BabylonJS to draw some stuff, but in contrast to a game, most of the time, the displayed frame stays the same. Only when the user manipulates the scene or changes some inputs using forms, I need to redraw the scene.
The seconds part works fine: I do not install a rendering loop but basically call scene.render()
whenever I change some form value.
However, the user should still be able to rotate and scale the scene. I am using an ArcRotateCamera
to do this. After enabling my optimization, obviously, the scene is no longer redrawn when the camera is moved. When moving the camera and afterwards changing some form value, I do see the effect of the camera manipulation.
How do I get some event from the camera when it is moved to trigger the rendering of the scene? Is this even possible? I saw that there are onViewMatrixChangedObservable
and onProjectionMatrixChangedObservable
, but they do not fire in my case. Actually, they do fire after I call render()
. So maybe these two need a permanent rendering loop? Are there any other sources for events I could use? I’d love to not have to manually listen to the mouse move on the scene.
Best regards,
Axel
Yup camera.update (which is going to fire the onView/ProjectionMatrixObservable) is called during render. I think we have an onInputChanged for the devices, @PolygonalSun do you have any example of using it?
You could always directly access the DeviceSourceManager to access the input device and just update when an input event is received. Example with mouse:
var dsm = new BABYLON.DeviceSourceManager(engine);
var mouse = dsm.getDeviceSource(BABYLON.DeviceType.Mouse);
mouse.onInputChangedObservable.add(eventData => {
scene.render();
});
But dont I get then all the mouse move events, not only the ones actually change the camera? Usually, the camera is only changes while holding the mouse button down.
From the thread linked above I found this bugreport: onViewMatrixChangedObservable not firing with non-standard render loops · Issue #13551 · BabylonJS/Babylon.js · GitHub
Looks like this is addressing a similar configuration. It was fixed in Feb. and I am currently using Babylon 5.55, so I am assuming I should already using a version that has this fixed. So I need to investigate what is different in my case.
So here is my report: It looks like I missed one fundamental part:
engine.runRenderLoop(() => camera.update());
I was a bit surprised that this is needed because I was under the impression that because there is a mouseHandler installed by babylon that this mousehandler would trigger the update of the camera whenever necessary. This approach here feels a bit like “polling”: Although the mouseHandler does something with the camera as well as other stuff, such as picking in my case, I still need a periodic process to process the accumulated camera events.
Maybe it makes more sense to trigger camera.update()
from a custom mouse move handler? This way I could probably save some energy if I assume that my screen stays idle for a while?
Well, one issue that comes to mind with a custom mouse move handler is that if you just update things when you receive a move event that matches your criteria, it will only move when mouse movement is active but ignore any leftover inertia when the move (drag action) is complete. This could cause a minor jump when you perform a new drag. If you’re not using inertia, it isn’t really a problem. If you want to capture any inertial movement, you could try an approach like this:
// Note: this is for an ArcRotateCamera
// For the other cameras, you could refer to the camera.cameraDirection or camera.cameraRotation vectors instead
function render() {
if (camera.inertialAlphaOffset !== 0
|| camera.inertialBetaOffset !== 0
|| camera.inertialPanningX !== 0
|| camera.inertialPanningY !== 0
|| camera.inertialRadiusOffset !== 0) {
scene.render();
}
}
engine.stopRenderLoop();
engine.runRenderLoop(render);
The if statement might be a bit heavy handed but it basically says that if there’s still user-initiated movement left, run the render function. Would something like this work for your use case?
1 Like
As I am doing a technical application, I do not need inertia.
So basically, what my goal is: I want the least amount of code to be run when the scene does not change. So I want to trigger a repaint only:
- If the underlying data model changes
- The user rotates the camera.
Observing my data model works perfectly, even when I need to redraw the scene only due to a hover event to mesh to draw it slightly highlighted. So here I reached my goal.
But for the camera, it feels strange that I cannot get an event whenever the camera changed, ignoring inertia, as it feels so close to the previous case, actually even simpler.
I either need to:
- Install a render loop that basically calls the camera and most of the time, nothing changed
- Manually hook into the event handling and basically reconstruct the event path within the camera to hopefully only redraw when the camera actually changed.
Both methods work, I only tested the first one, but the goal of basically “offloading” the decision when to repaint due to a camera change to babylon is not reached.
I think my best bet so far is to install the render loop on mouse down and uninstall it again on mouse up as the camera can only be changed during this period.