Zoom and scroll this camera from user input

Scrolling is accomplished by moving the camera on the XY plane and zooming by moving it on the Z axis.

This method has distinct implementations per platform. On desktop computers, the mouse wheel changes the zoom level, and holding down the middle mouse button scrolls the camera. On Android, a two finger pinch and slide gesture zoom and scroll.

Returns true if the event is used.

Should be called from App::accept_event before accepting pointer events:

redef class App
    redef fun accept_event(event)
    do
        if world_camera.accept_scroll_and_zoom(event) then return true

        # Handle other events...
        return false
    end
end

Property definitions

gamnit :: camera_control $ EulerCamera :: accept_scroll_and_zoom
	# Zoom and scroll this camera from user input
	#
	# Scrolling is accomplished by moving the camera on the XY plane and
	# zooming by moving it on the Z axis.
	#
	# This method has distinct implementations per platform.
	# On desktop computers, the mouse wheel changes the zoom level, and
	# holding down the middle mouse button scrolls the camera.
	# On Android, a two finger pinch and slide gesture zoom and scroll.
	#
	# Returns `true` if the event is used.
	#
	# Should be called from `App::accept_event` before accepting pointer events:
	#
	# ~~~nitish
	# redef class App
	#     redef fun accept_event(event)
	#     do
	#         if world_camera.accept_scroll_and_zoom(event) then return true
	#
	#         # Handle other events...
	#         return false
	#     end
	# end
	# ~~~
	fun accept_scroll_and_zoom(event: InputEvent): Bool do return false
lib/gamnit/camera_control.nit:25,2--50,68

gamnit :: camera_control_linux $ EulerCamera :: accept_scroll_and_zoom
	redef fun accept_scroll_and_zoom(event)
	do
		# Zoom
		if event isa GamnitMouseWheelEvent then
			var dy = event.y
			var mod = camera_zoom_mod
			if dy > 0.0 then
				# Zoom in when moving the wheel up
				mod = 1.0/mod
			else dy = -dy

			position.z *= dy * mod
			return true
		end

		# Scroll
		var but_mask = camera_pan_mask
		if but_mask != 0 and event isa GamnitPointerEvent then
			var native = event.native
			if native isa SDLMouseMotionEvent and native.state & but_mask == but_mask then
				var dx = native.xrel.to_f
				var dy = native.yrel.to_f

				var world_height = field_of_view_y.sin * position.z
				var mod = app.display.as(not null).height.to_f / world_height
				position.x -= dx / mod
				position.y += dy / mod # Y is inverted between the input and output
				return true
			end
		end

		return false
	end
lib/gamnit/camera_control_linux.nit:31,2--63,4

gamnit :: camera_control_android $ EulerCamera :: accept_scroll_and_zoom
	redef fun accept_scroll_and_zoom(event)
	do
		if not event isa AndroidMotionEvent then return false

		if event.pointers.length < 2 then
			# Intercept leftovers of the last motion
			return event.down_time == last_motion_start
		end

		# Collect active pointer and their world position
		var new_motion_pointers = new HashMap[Int, Point[Float]]
		var ids = new Array[Int]
		for pointer in event.pointers do
			var id = pointer.pointer_id
			ids.add id
			new_motion_pointers[id] = camera_to_world(pointer.x, pointer.y)
		end

		var last_motion_pointers = last_motion_pointers
		if last_motion_start == event.down_time and
		   last_motion_pointers.keys.has(ids[0]) and last_motion_pointers.keys.has(ids[1]) then
			# Continued motion event

			# Get new and old position for 2 fingers
			var new_motion_a = new_motion_pointers[ids[0]]
			var new_motion_b = new_motion_pointers[ids[1]]
			var prev_pos_a = last_motion_pointers[ids[0]]
			var prev_pos_b = last_motion_pointers[ids[1]]

			# Move camera
			var prev_middle_pos = prev_pos_a.lerp(prev_pos_b, 0.5)
			var new_middle_pos = new_motion_a.lerp(new_motion_b, 0.5)
			position.x -= new_middle_pos.x - prev_middle_pos.x
			position.y -= new_middle_pos.y - prev_middle_pos.y

			# Zoom camera
			var prev_dist = prev_pos_a.dist(prev_pos_b)
			var new_dist = new_motion_a.dist(new_motion_b)

			position.z = prev_dist * position.z / new_dist
		else
			# Prepare for a new motion event
			last_motion_pointers.clear
			last_motion_start = event.down_time
		end

		# Keep a smooth history
		for i in [0..1] do
			if last_motion_pointers.keys.has(ids[i]) then
				last_motion_pointers[ids[i]] = last_motion_pointers[ids[i]]*0.5 +
				                                new_motion_pointers[ids[i]]*0.5
			else last_motion_pointers[ids[i]] = new_motion_pointers[ids[i]]
		end

		return true
	end
lib/gamnit/camera_control_android.nit:28,2--83,4