Merge: A few game related services and tweaks
authorJean Privat <jean@pryen.org>
Mon, 23 May 2016 02:28:26 +0000 (22:28 -0400)
committerJean Privat <jean@pryen.org>
Mon, 23 May 2016 02:28:26 +0000 (22:28 -0400)
Once again, here are some details on less intuitive commits:

* `AMotionEventAction::down_time` is used to better understand and respond to complex touch gestures.
* Serialization caches are accessed in the game WBTW to selectively delete data from the memory.
* `ThinGame::tick` is basically a counter of the current game logic frame. This value must be modified externally in mutliplayer games or sometimes when deserializing a game.
* `ImprovedNoise` can be useful in the lib, from experience it gives a better result that our current noise algorithms and it is in 3D. However it has less customization options.
* `EulerCamera::camera_to_world` is used to translate mouse position to in-world 3D coordinates in simple games viewed from directly above or from the side, like SDO and Action Nitro.

Pull-Request: #2119
Reviewed-by: Jean Privat <jean@pryen.org>

examples/rosettacode/perlin_noise.nit
lib/a_star.nit
lib/android/input_events.nit
lib/bucketed_game.nit
lib/gamnit/cameras.nit
lib/noise.nit
lib/serialization/caching.nit

index 0733bbf..a5cd314 100644 (file)
@@ -8,69 +8,7 @@
 # See: <http://rosettacode.org/wiki/Perlin_noise>
 module perlin_noise
 
-redef universal Float
-       # Smoothened `self`
-       fun fade: Float do return self*self*self*(self*(self*6.0-15.0)+10.0)
-end
-
-# Improved noise
-class ImprovedNoise
-       # Permutations
-       var p: Array[Int] = [151,160,137,91,90,15,
-               131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
-               190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
-               88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
-               77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
-               102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
-               135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
-               5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
-               223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
-               129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
-               251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
-               49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
-               138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180]
-
-       # Noise value in [-1..1] at 3d coordinates `x, y, z`
-       fun noise(x, y, z: Float): Float
-       do
-               var xx = x.to_i & 255
-               var yy = y.to_i & 255
-               var zz = z.to_i & 255
-
-               x -= x.floor
-               y -= y.floor
-               z -= z.floor
-
-               var u = x.fade
-               var v = y.fade
-               var w = z.fade
-
-               var a  = p[xx  ] + yy
-               var aa = p[a   ] + zz
-               var ab = p[a+1 ] + zz
-               var b  = p[xx+1] + yy
-               var ba = p[b   ] + zz
-               var bb = p[b+1 ] + zz
-
-               return w.lerp(v.lerp(u.lerp(grad(p[aa  ], x,     y,     z    ),
-                                           grad(p[ba  ], x-1.0, y,     z    )),
-                                    u.lerp(grad(p[ab  ], x,     y-1.0, z    ),
-                                           grad(p[bb  ], x-1.0, y-1.0, z    ))),
-                      v.lerp(u.lerp(grad(p[aa+1], x,     y,     z-1.0),
-                                           grad(p[ba+1], x-1.0, y,     z-1.0)),
-                                    u.lerp(grad(p[ab+1], x,     y-1.0, z-1.0),
-                                           grad(p[bb+1], x-1.0, y-1.0, z-1.0))))
-       end
-
-       # Value at a corner of the grid
-       fun grad(hash: Int, x, y, z: Float): Float
-       do
-               var h = hash & 15
-               var u = if h < 8 then x else y
-               var v = if h < 4 then y else if h == 12 or h == 14 then x else z
-               return (if h.is_even then u else -u) + (if h & 2 == 0 then v else -v)
-       end
-end
+import noise
 
 var map = new ImprovedNoise
 print map.noise(3.14, 42.0, 7.0).to_precision(17)
index 7b7ce02..cc0cba3 100644 (file)
@@ -188,6 +188,14 @@ class Node
                end
        end
 
+       # Find the closest node accepted by `cond` under `max_cost`
+       fun find_closest(max_cost: Int, context: PathContext, cond: nullable TargetCondition[N]): nullable N
+       do
+               var path = path_to_alts(null, max_cost, context, cond)
+               if path == null then return null
+               return path.nodes.last
+       end
+
        # We customize the serialization process to avoid problems with recursive
        # serialization engines. These engines, such as `JsonSerializer`,
        # are at danger to serialize the graph as a very deep tree.
index 8aea378..5ee9d87 100644 (file)
@@ -70,6 +70,8 @@ private extern class NativeAndroidMotionEvent `{AInputEvent *`}
        `}
 
        fun action: AMotionEventAction `{ return AMotionEvent_getAction(self); `}
+
+       fun native_down_time: Int `{ return AMotionEvent_getDownTime(self); `}
 end
 
 private extern class AMotionEventAction `{ int32_t `}
@@ -149,6 +151,11 @@ class AndroidMotionEvent
                        return null
                end
        end
+
+       # Time when the user originally pressed down to start a stream of position events
+       #
+       # The return value is in the `java.lang.System.nanoTime()` time base.
+       fun down_time: Int do return native.native_down_time
 end
 
 # A pointer event
index b5542e4..28129a5 100644 (file)
@@ -26,6 +26,7 @@
 module bucketed_game is serialize
 
 import serialization
+import counter
 
 # Something acting on the game
 abstract class Turnable[G: Game]
@@ -65,6 +66,9 @@ class Buckets[G: Game]
        private var buckets: Array[BUCKET] =
                [for b in n_buckets.times do new HashSet[Bucketable[G]]] is lazy
 
+       # Stats on delays asked when adding an event with `act_in` and `act_next`
+       private var delays = new Counter[Int]
+
        # Add the Bucketable event `e` at `at_tick`.
        fun add_at(e: Bucketable[G], at_tick: Int)
        do
@@ -106,6 +110,26 @@ class Buckets[G: Game]
                        end
                end
        end
+
+       # Get some statistics on both the current held events and historic expired events
+       fun stats: String
+       do
+               var entries = 0
+               var instances = new HashSet[Bucketable[G]]
+               var max = 0
+               var min = 100000
+               for bucket in buckets do
+                       var len = bucket.length
+                       entries += len
+                       instances.add_all bucket
+                       min = min.min(len)
+                       max = max.max(len)
+               end
+               var avg = entries.to_f / buckets.length.to_f
+
+               return "{buckets.length} buckets; uniq/tot:{instances.length}/{entries}, avg:{avg.to_precision(1)}, min:{min}, max:{max}\n" +
+                       "history:{delays.sum}, avg:{delays.avg}, min:{delays[delays.min.as(not null)]}, max:{delays[delays.max.as(not null)]}"
+       end
 end
 
 # Game related event
@@ -123,16 +147,16 @@ end
 # Game logic on the client
 class ThinGame
 
-       # Game tick when `self` should act.
+       # Current game tick
        #
        # Default is 0.
-       var tick: Int = 0 is protected writable
+       var tick: Int = 0 is writable
 end
 
 # Game turn on the client
 class ThinGameTurn[G: ThinGame]
 
-       # Game tick when `self` should act.
+       # Game tick when happened this turn
        var tick: Int is protected writable
 
        # Game events occurred for `self`.
@@ -153,10 +177,18 @@ class GameTurn[G: Game]
        end
 
        # Insert the Bucketable event `e` to be executed at next tick.
-       fun act_next(e: Bucketable[G]) do game.buckets.add_at(e, tick + 1)
+       fun act_next(e: Bucketable[G])
+       do
+               game.buckets.add_at(e, tick + 1)
+               game.buckets.delays.inc(1)
+       end
 
        # Insert the Bucketable event `e` to be executed at tick `t`.
-       fun act_in(e: Bucketable[G], t: Int) do game.buckets.add_at(e, tick + t)
+       fun act_in(e: Bucketable[G], t: Int)
+       do
+               game.buckets.add_at(e, tick + t)
+               game.buckets.delays.inc(t)
+       end
 
        # Add and `apply` a game `event`.
        fun add_event( event : GameEvent )
index a1eed2f..245d071 100644 (file)
@@ -162,6 +162,35 @@ class EulerCamera
                yaw = 0.0
                roll = 0.0
        end
+
+       # Convert the position `x, y` on screen, to world coordinates on the plane at `target_z`
+       #
+       # `target_z` defaults to `0.0` and specifies the Z coordinates of the plane
+       # on which to project the screen position `x, y`.
+       #
+       # This method assumes that the camera is looking along the Z axis towards higher values.
+       # Using it in a different orientation can be useful, but won't result in valid
+       # world coordinates.
+       fun camera_to_world(x, y: Numeric, target_z: nullable Float): Point[Float]
+       do
+               # TODO, this method could be tweaked to support projecting the 2D point,
+               # on the near plane (x,y) onto a given distance no matter to orientation
+               # of the camera.
+
+               target_z = target_z or else 0.0
+
+               # Convert from pixel units / window resolution to
+               # units on the near clipping wall to
+               # units on the target wall at Z = 0
+               var near_height = (field_of_view_y/2.0).tan * near
+               var cross_screen_to_near = near_height / (display.height.to_f/2.0)
+               var cross_near_to_target = (position.z - target_z) / near
+               var mod = cross_screen_to_near * cross_near_to_target * 1.72 # FIXME drop the magic number
+
+               var wx = position.x + (x.to_f-display.width.to_f/2.0) * mod
+               var wy = position.y - (y.to_f-display.height.to_f/2.0) * mod
+               return new Point[Float](wx, wy)
+       end
 end
 
 # Orthogonal camera to draw UI objects with services to work with screens of different sizes
index ba09bc1..cf4d0fa 100644 (file)
@@ -359,3 +359,72 @@ redef universal Int
                return self & 0x3FFF_FFFF
        end
 end
+
+redef universal Float
+       # Smoothened `self`, used by `ImprovedNoise`
+       private fun fade: Float do return self*self*self*(self*(self*6.0-15.0)+10.0)
+end
+
+# Direct translation of Ken Perlin's improved noise Java implementation
+#
+# This implementation differs from `PerlinNoise` on two main points.
+# This noise is calculated for a 3D point, vs 2D in `PerlinNoise`.
+# `PerlinNoise` is based off a customizable seed, while this noise has a static data source.
+class ImprovedNoise
+
+       # Permutations
+       private var p: Array[Int] = [151,160,137,91,90,15,
+               131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
+               190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
+               88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
+               77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
+               102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
+               135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
+               5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
+               223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
+               129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
+               251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
+               49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
+               138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180] * 2
+
+       # Noise value in [-1..1] at 3D coordinates `x, y, z`
+       fun noise(x, y, z: Float): Float
+       do
+               var xx = x.floor.to_i & 255
+               var yy = y.floor.to_i & 255
+               var zz = z.floor.to_i & 255
+
+               x -= x.floor
+               y -= y.floor
+               z -= z.floor
+
+               var u = x.fade
+               var v = y.fade
+               var w = z.fade
+
+               var a  = p[xx  ] + yy
+               var aa = p[a   ] + zz
+               var ab = p[a+1 ] + zz
+               var b  = p[xx+1] + yy
+               var ba = p[b   ] + zz
+               var bb = p[b+1 ] + zz
+
+               return w.lerp(v.lerp(u.lerp(grad(p[aa  ], x,     y,     z    ),
+                                           grad(p[ba  ], x-1.0, y,     z    )),
+                                    u.lerp(grad(p[ab  ], x,     y-1.0, z    ),
+                                           grad(p[bb  ], x-1.0, y-1.0, z    ))),
+                      v.lerp(u.lerp(grad(p[aa+1], x,     y,     z-1.0),
+                                           grad(p[ba+1], x-1.0, y,     z-1.0)),
+                                    u.lerp(grad(p[ab+1], x,     y-1.0, z-1.0),
+                                           grad(p[bb+1], x-1.0, y-1.0, z-1.0))))
+       end
+
+       # Value at a corner of the grid
+       private fun grad(hash: Int, x, y, z: Float): Float
+       do
+               var h = hash & 15
+               var u = if h < 8 then x else y
+               var v = if h < 4 then y else if h == 12 or h == 14 then x else z
+               return (if h.is_even then u else -u) + (if h & 2 == 0 then v else -v)
+       end
+end
index 4541428..3554d22 100644 (file)
@@ -55,7 +55,7 @@ end
 # then using a reference.
 class SerializerCache
        # Map of already serialized objects to the reference id
-       private var sent: Map[Serializable, Int] = new StrictHashMap[Serializable, Int]
+       protected var sent: Map[Serializable, Int] = new StrictHashMap[Serializable, Int]
 
        # Is `object` known?
        fun has_object(object: Serializable): Bool do return sent.keys.has(object)
@@ -88,7 +88,7 @@ end
 # Used by `Deserializer` to find already deserialized objects by their reference.
 class DeserializerCache
        # Map of references to already deserialized objects.
-       private var received: Map[Int, Object] = new StrictHashMap[Int, Object]
+       protected var received: Map[Int, Object] = new StrictHashMap[Int, Object]
 
        # Is there an object associated to `id`?
        fun has_id(id: Int): Bool do return received.keys.has(id)