Pan & Zoom on an image within the BabylonJS Framework

Hello,
i am using BabylonJS in a presentation which displays gltf models with markers and labels to visualize information on an item.
Now I was asked to also include a 2d image ( jpg / png ) and display it with the usual pan/zoom functionality that users are used to ( e.g.: Drag To Move & Mouse Wheel Example )
I did find a way to load and display the images using planes.
However since the usual cameras and the general concepts of BJS are geared toward 3d worlds, it feels like a lot of shoehorning has to be done to make this work properly.
I also saw the Layer Object in the API but it seems to be more appropriate for a static background.
Would anyone here an idea on how to integrate a simple 2d pan & zoom mode into a bJS instance?
The markers and labels logic already exists in BJS so it would have to be recreated if I would have to use something else then BJS.
Thanks for all suggestions.

1 Like

For planes I would use PointerDragBehavior | Babylon.js Documentation and usual mouse wheel zoom (default in ArcRotate camera).

1 Like

Hey there @EvilTwinsNiceBrother,

Here is a PG example where I have a 2D image displayed on a plane and a camera that supports zooming in and out: 2D image viewer example | Babylon.js Playground (babylonjs.com)

In order to implement panning, you would just want to add a pointer observable and move the camera position when the user drags the mouse.

Let me know if you have any difficulty implementing it :slight_smile:

1 Like

Thanks a lot!
I had basically displayes the image the same way like you only I did not use CreateGround but CreatePlane.
I simplified the scene and added a PointerDragBehaviour.
https://playground.babylonjs.com/#JZITG9
With this, dragging works fine, but the mouse zoom is not working anymore. Is this somehow overwriten with the PointerDragBehaviour?
Also I would need to calculate the bounds of the image so I can drag only when zoomed in.
Would that be in the camera settings or in the DragBehaviour?
Thanks again.

1 Like

Seems that in order for zoom controls to work you need to add camera.attachControl(canvas.true);
Zoom by default exists for ArcRotateCamera.
Camera zoom doesn’t work in your example because you use FreeCamera.
Here - https://playground.babylonjs.com/#JZITG9#1 - you may zoom FreeCamera with up/down cursor keys.

1 Like

Hm, I see. Thanks.
I need to make it work with the mouse exclusively though.
So I guess I’ll have to write my own MouseHandler, it seems?

Here are some hints - Customizing Camera Inputs | Babylon.js Documentation

1 Like

So,
i have been playing with various options and came up with this solution:
https://playground.babylonjs.com/#7ARY1R
The only problem is that the dragging of the camera should follow the mouse as it would in a mesh drag operation.
I guess that would also depend on the zoom level of the camera?
Thanks!

I found a couple of issues in your code:

  • On line 37 you were computing the delta of the mouse position using “lastPos”, but you probably wanted “this.lastPos”, since lastPos was undefined
  • You were only updating this.lastPos once, onPointerDown, but you probably want to update it every frame so that you can see in what direction the user has moved their mouse since the last frame (rather than since the mouse was first pushed)
  • You were using Math.sign(lastPos.x-pos.x), but this meant that any mouse movement would move the camera the same amount, whether it was large or small. This makes the camera movement feel disconnected from the user input. I think you should care about the magnitude of the movement.
  • You may also want to flip the x direction or the y direction, depending on how you want panning to work. This is up to you.

I made some changes let me know if this is closer to what you are looking for: Pan & Zoom | Babylon.js Playground (babylonjs.com)

You could also scale the amount of camera movement by the zoom level. I think this would be a good idea. I would recommend multiplying the speed of movement by the z of the camera. If you have trouble getting this to feel right, let me know!

2 Likes

Thank you. I got a bit confused with my playgrounds ( i am testing mostly locally ) and hadn’t updated my final version which also had the Math.sign removed and the lastPos updated in the POINTERMOVE handler.
I updated the playground now. It is basically similar to what you changed.
However I am not happy with the inertia the dragging has.
Also the dragging feels different depending on how much the camera is zoomed.
I guess that I have to consider that zoom factor as well.

Thanks again for all help.

1 Like

Well, the current code that you have is basically giving the camera an acceleration, and then letting the speed decay. This is why there’s a feeling of inertia. To remove this, you can simply adjust the camera’s position directly in your pointer observable; then the camera movement will be instantaneous.

For the scaling, just scale the pan speed by the z of the camera :slight_smile:

2 Likes

Thanks a lot.
I had been trying to set the camera position directly,
However I dont know how to apply the scene.pointerX scene.pointerY to the cameraPosition.
It seems these values need to be translated in a way?
Thanks

@DarraghBurke will be able to help with that one :slight_smile:

1 Like

I summon the mighty @DarraghBurke :slight_smile:

2 Likes

What you’re looking for here is the position property of your camera object, see here: Camera | Babylon.js Documentation (babylonjs.com)

You will want to adjust the camera’s position.x by the delta of the pointer’s x position, and the camera’s position.y by the delta of the pointer’s y position.

You may want to multiply the delta for either x or y by -1, depending on how you want panning to feel. This will determine whether you pan with the pointer move or against it.

I might make a fool of myself but what are you referring to when you mean “delta” of the pointer’s x position? My math classes are a looooong way back in time.
Thanks

When I say “delta”, I just mean the change in value since the last frame. So, pointerPos.x - lastPointerPos.x would be the delta of the pointer’s x coordinate. Does that make sense?

ah!
thanks!

1 Like

So, thanks to all your help I managed to achieve what I needed:
https://playground.babylonjs.com/#7ARY1R#5

One last question I have is:
How can I restrict the camera not to fly “through” the object?
With the ArcRotateCamera I have upper and lower limits to do that.
Would I need to check the collisions with the free camera, or is there a more direct way?
I would basically want to set the min and max zoom levels.
Thanks again!

1 Like

Well, the "zoom level’ corresponds to the z value of the camera’s position. (Closer to 0 = object looks larger, further from 0 = object looks smaller.) When the z value crosses into negative values, you pass through the object. So to prevent this, you can just ensure the camera’s z never goes below a certain fixed positive value.

1 Like