From: Alexis Laferrière Date: Sun, 7 Aug 2016 13:59:15 +0000 (-0400) Subject: gamnit: intro two fingers motion service on EulerCamera X-Git-Url: http://nitlanguage.org gamnit: intro two fingers motion service on EulerCamera Signed-off-by: Alexis Laferrière --- diff --git a/lib/gamnit/android_two_fingers_motion.nit b/lib/gamnit/android_two_fingers_motion.nit new file mode 100644 index 0000000..b8329ef --- /dev/null +++ b/lib/gamnit/android_two_fingers_motion.nit @@ -0,0 +1,113 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Two fingers camera manipulation, scroll and pinch to zoom +# +# Provides the main service `EulerCamera::accept_two_fingers_motion`. +module android_two_fingers_motion + +# TODO add an abstraction when another platform supports it + +import gamnit +import cameras +import android + +redef class EulerCamera + # Smoothened history of pointers in the current motion event + private var last_motion_pointers = new HashMap[Int, Point[Float]] is lazy + + # Start time of the current motion event + private var last_motion_start: Int = -1 + + # Move and zoom (set `position`) from a two finger pinch and slide option + # + # Returns `true` if the event is intercepted. + # + # 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_two_fingers_motion(event) then return true + # + # # Handle other events... + # end + # end + # ~~~ + fun accept_two_fingers_motion(event: InputEvent): Bool + 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 +end + +redef class Point[N] + private fun *(scalar: Numeric): Point[N] + do return new Point[N](x.mul(scalar), y.mul(scalar)) + + private fun +(other: Point[N]): Point[N] + do return new Point[N](x.add(other.x), y.add(other.y)) +end