Hi BJS community !
This is my first post, seeking for help.
I’m quite new at using BabylonJS.
First of all, here is a playground to illustrate my problem: https://playground.babylonjs.com/#V181I2#34
On my scene I’m trying to replicate the camera’s behavior we have with paraview.
- left drag → rotation around some point. OK
- right drag → panning of the camera, but without changing the rotation center. I managed to do that using camera.targetScreenOffset thanks to a Playground I found here (https://playground.babylonjs.com/#EBPQH9#43 → many thanks to the author !)
- Now on double click (for example) on a mesh, I would like to change the center of rotation to the center of the mesh, but again without having the targeted mesh centered on the screen, without having the current view changed the slightest.
I’ve been searching a lot how to do that without success.
In particular I’ve been looking for the right way to project a distance computed in the world coordinate system (between 2 successive camera targets) to coordinates for the targetScreenOffset property, without success. I don’t even understand the units used by that property.
And I don’t feel qualified enough to write the camera’s behavior from scratch (at least for now)
would anyone be able to help me or point me in the right direction ?
Thank you for your help !
to complete my previous post.
From my understanding, in order for the target mesh not to be automatically centered:
- modifying the camera position is useless. It would only change the perspective.
- setting the right values for the targetScreenOffset would work. But I don’t know how to determine them. Do you ?
- there must be some other ways ?
Thank you for your help !
@PolygonalSun might be able to help with this one ?
I’ve been thinking about the best approach to this and it’s a bit tricky. I’d imagine that your total camera movement would be some amount of x and y offset (relative to the viewport), combined with an adjustment to the radius. You’d also have to do it in a way that would preserve the alpha and beta angles. You could try starting with recording the old alpha/beta, setting the target, and then restoring those angles. That would effectively make your view parallel to your original view.
Next, is panning your camera back into its original place. Panning is effectively moving across a plane. If you think of your viewport as this plane, we would need to get your camera’s original position to be a point on that “plane”. This would require adjusting the radius to pull/push the camera into the right position. Finally, you would set the offsets based on the distance of your original position to the previous one. I know this isn’t necessarily an answer so much as an approach but this would be where I’d start.
Another idea would be to try using using
Vector3.Project to figure out where your desired mesh is, with respect to screen space and then work from there:
let meshPosInScreenSpace = BABYLON.Vector3.Project(
If you then use
camera.panningInertia set to zero) to move your target into place, set your radius as the distance between the camera and the mesh, and set your offsets to match your inertialPanning values. That might work.
I can’t really think of anything concrete right now but if something else comes to mind, I’ll reply here. Hopefully, these leads give you some inspiration and if you do come up with a solution, please reply with what you’ve found.
Thank you @PolygonalSun for taking the time.
@jcoop @bghgary please allow me to add you to this discussion as you seem to have been working with the targetScreenOffset property.
I get the idea but I still don’t understand how to convert between world space coordinates and values for the targetScreenOffset camera property.
Because the unit used by that property does not seem to be the same as the one used when doing a projection with BABYLON.Vector3.Project. Indeed I also tried using BABYLON.Vector3.Project without success. Also that unit does not seem to correspond to the ones of the various event properties we get when listening to a POINTERDOWN event.
I’ll keep trying. Don’t hesitate to share any other idea or code sample.
Of course if I find a solution, I’ll share it here.
I tried taking a look at this and I think I can help out a bit (and also learn more about the Babylon cameras on the way!). I initially also got a bit confused about the targetScreenOffset property, but taking a look into the ArcRotateCamera code elucidates things quite a bit. Turns out these targetScreenOffset values are added into the view matrix’s 12th and 13th elements: Babylon.js/arcRotateCamera.ts at 344bfa4dd8d6f8689c157faf31f883e5cf75f4d0 · BabylonJS/Babylon.js · GitHub
What this effectively means is that the targetScreenOffset translates the camera target, but in world space. So if you set a targetScreenOffset.x of 3, you’re moving the target by 3 units in the world x axis.
Knowing this, we can start to work on something. When the double click event is received, we get the current “effective” camera target in world space, i.e, the actual camera target property plus the targetScreenOffset. We also know the new target for the camera. So the new targetScreenOffset should be set to the difference between the old “effective” target and the new actual target. This works fine for the box that has no Z-coordinate, however it doesn’t work as well for the other one, since the difference in this case has a Z-coordinate and we don’t have a way to add this to the view matrix like the x and y. I’m pretty sure there’s a way around that, I’ll just have to think a little bit more
Here’s the playground with the idea, I hope it can help: https://playground.babylonjs.com/#V181I2#85
(P.S: And if you have any questions please let me know! )
Nice, I’ve been tinkering with this one one too, on and off, but haven’t come up with much, or anything that survives a few rotations.
I’d almost decided that even subclassing ArcRotateCamera and separating the target from the center of rotation might be easier, after going in circles trying to do it with ArcRotateCamera.
But that seems like a last resort, and it still seems like maybe there’s an elegant, fairly straightforward solution hiding beneath the surface LOL.
Edit: PS, I think this was the closest playground I came up with, by just setting targetOffset.x to target.x and targetOffset.y to target.z, but of course it doesn’t work once they’re rotated much…
Tried taking a look at it again, I have an idea but I’m a bit stuck…
My previous approach was missing a way to correct any differences in the camera’s forward direction, so I needed a way to solve that. I had the notion that I just needed to compute the rotation between the previous forward direction and the current one (the one after setting the new target), then applying this rotation to the camera. However, if I try to set the rotationQuaternion property of the camera, it doesn’t seem to do anything.
@PolygonalSun is there a way to manually modify the rotation on the ArcRotateCamera or is it only through the alpha and beta properties?
I did not have time yet to look in details at your answers but I will surely do it very soon !
For now I just wanted say how grateful I am for you taking some time on that issue.
Thanks a lot !
AFAIK, the only way to rotate the ArcRotateCamera is via the alpha and beta values. I don’t believe that it supports any changes to either rotation or rotationQuaternion.
So I just wanted to reply on this thread that I was able to make a PG that should do the desired behavior. It’s pretty big so check out the setCameraOffset and resetCameraTarget functions for the actual camera movement logic: ArcRotateCamera Offset Demo | Babylon.js Playground (babylonjs.com)
@PolygonalSun this is awesome ! Thanks a lot !
I’ll analyze your code soon.
Yet I notice that if you rotate the camzera before the panning stabilizes (inertia), the target mesh moves on the screen.
Is there a way to prevent it ?
I guess I would stop the panning as soon as the rotation starts…
I had a hack for handling it in the code. If you check lines 302 to 309 in that PG, I wait for the inertialPanning values for the camera to be 0 and if they are, we set the target to the original active mesh again.