gamnit: intro two fingers motion service on EulerCamera
authorAlexis Laferrière <alexis.laf@xymus.net>
Sun, 7 Aug 2016 13:59:15 +0000 (09:59 -0400)
committerAlexis Laferrière <alexis.laf@xymus.net>
Sun, 7 Aug 2016 23:08:03 +0000 (19:08 -0400)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

lib/gamnit/android_two_fingers_motion.nit [new file with mode: 0644]

diff --git a/lib/gamnit/android_two_fingers_motion.nit b/lib/gamnit/android_two_fingers_motion.nit
new file mode 100644 (file)
index 0000000..b8329ef
--- /dev/null
@@ -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