Google Maps style zoom to cursor with RenderCam (SOLVED)

I’m trying to implement this in a project. It is where when you zoom in on a map or something your cursor remains at the location of a thing it is hovering. Try Google Maps to remember how that works.

These seem like the steps needed to get the zoom to cursor to work

  • Get rendercam.screen_to_world_2d
  • Adjust camera zoom amount
  • Get rendercam.screen_to_world_2d again
  • Move camera based on the difference of world position of cursor

But if I do that in a frame it doesn’t seem to work. I tried with two cameras as well.

local function rendercam_test()
	go.set_position(vmath.vector3(0,0,0), "camera")
	local position_1 = rendercam.screen_to_world_2d(0, 0, false, nil, false)
	go.set_position(vmath.vector3(100,100,0), "camera")
	local position_2 = rendercam.screen_to_world_2d(0, 0, false, nil, false)

	print(position_1, position_2)
end

local function rendercam_test2()
	rendercam.activate_camera(go.get_id("camera"))
	rendercam.pan(100, 100, go.get_id("camera"))
	local position_1 = rendercam.screen_to_world_2d(10, 10, false, nil, false)
	rendercam.activate_camera(go.get_id("camera_secondary"))
	rendercam.pan(10000, 10000, go.get_id("camera_secondary"))
	local position_2 = rendercam.screen_to_world_2d(10, 10, false, nil, false)

	print("Difference", position_2 - position_1)
	print(position_1, position_2)
	print(go.get_position("camera"), go.get_position("camera_secondary"))

	rendercam.activate_camera(go.get_id("camera"))
end

Should this work? Or is there something simple I’m missing/forgetting?

What do I need to poke in Rendercam to make screen_to_world functions accurate for movements which happen on the same frame?


	print("pre", rendercam.screen_to_world_2d(10, 10, false, nil, false))

	rendercam.pan(100, 100, go.get_id("camera"))

	print("post", rendercam.screen_to_world_2d(10, 10, false, nil, false))

	timer.delay(1, false, function()
		print("delay", rendercam.screen_to_world_2d(10, 10, false, nil, false))
	end)

DEBUG:SCRIPT: pre	vmath.vector3(-0.97916668653488, -0.96875, -1.0002000331879)
DEBUG:SCRIPT: post	vmath.vector3(-0.97916668653488, -0.96875, -1.0002000331879)
DEBUG:SCRIPT: delay	vmath.vector3(109.76202392578, 109.8108215332, -0.057960510253906)

zoom_to_cursor.zip (246.2 KB)

Here’s a base project that has zooming and panning around of an image, but not zoom to cursor yet. I removed my attempts, I’m not sure how to do it immediately on the same frame properly.

Are you just implementing the zoom or the zoom AND a Google maps component? (I’m always lurking for a Defold Google Maps plugin :sweat_smile:: https://forum.defold.com/t/how-would-you-go-about-adding-google-maps/664819

Just the zoom to mouse. It has a variety of uses and making an actual Google Maps viewer would be one of them. :grin:

Depending on your use case, it might be better to implement a Mapbox viewer instead.

If all you’re doing is zooming a 2D camera, you should be able to just scale the difference in coordinates yourself…I think?

The camera View and Projection transforms depend on the ‘go.get_world’ functions, and as far as I know there’s no way to manually trigger a game object’s world transform to update, so during update the camera transforms will always be based on the camera’s position at the start of that frame.

If in your case you can assume that the camera will never have a transformed parent, you could modify it to calculate the View and Projection transforms based on the camera’s local transform instead. Take a look at ‘M.calculate_view()’ and ‘M.calculate_proj()’ in rendercam.lua.

1 Like

That’s something I didn’t realize / remember. Maybe set_position needs an option to force world position update?

print("world a", go.get_world_position("camera"))
print("position a", go.get_position("camera"))
go.set_position(vmath.vector3(99,99,99), "camera")
print("world b", go.get_world_position("camera"))
print("position b", go.get_position("camera"))

DEBUG:SCRIPT: world a vmath.vector3(480, 320, 100)
DEBUG:SCRIPT: position a vmath.vector3(480, 320, 100)
DEBUG:SCRIPT: world b vmath.vector3(480, 320, 100)
DEBUG:SCRIPT: position b vmath.vector3(99, 99, 99)

I’m really not sure how to properly do this with Rendercam. :confused: It seems like it should be simple but I’m failing and getting frustrated.

Camera on a 2d plane of orientation? Yes. No parent? Yes, though I can see use cases where games would want that. Like an RTS.

I thought about having two secondary cameras attached to the main camera as children that are + or - the possible increments of the zoom z wise. Then set those cameras active temporarily to use them for getting the screen to world positions for the cursor when changing zoom. But since world position is not actually updated when changing the position of the main camera on a frame, doing multiple zoom increments in a frame would break. All I want is for the world position at the mouse screen position to not change when zooming.

Maybe the even more janky solution would be to have even more secondary cameras at zoom increments so they are all covered in each possible zoom amount in a frame and then handle all zoom movement adjustments once on update. :rofl:

Mm, yeah, that is a bit more annoying than I thought. Especially since the “ortho scale” in rendercam isn’t the real scale between screen and world space, it’s based on the view area… :expressionless:

This seems to work though:
rcam-zoom-to-cursor.zip (96.5 KB)

3 Likes

Thank you for that sample! Exactly what I needed and will be helpful to more people in the future too.

2 Likes