gamnit: cache UICamera anchor points
[nit.git] / lib / gamnit / flat.nit
index f6e0fd2..cafac8a 100644 (file)
 #   devices. It is used to position the sprites in `App::ui_sprites`.
 #
 # See the sample game at `contrib/asteronits/` and the basic project template
-# at `lib/gamnit/examples/template_flat/`.
+# at `lib/gamnit/examples/template/`.
 module flat
 
 import glesv2
 intrude import geometry::points_and_lines # For _x, _y and _z
+intrude import matrix
 import matrix::projection
 import more_collections
 import performance_analysis
 
 import gamnit
-import gamnit::cameras
+import gamnit::cameras_cache
+intrude import gamnit::cameras
 import gamnit::dynamic_resolution
 import gamnit::limit_fps
+import gamnit::camera_control
 
-import android_two_fingers_motion is conditional(android)
-
-# Draw a `texture` at `center`
+# Visible 2D entity in the game world or UI
+#
+# Similar to `gamnit::Actor` which is in 3D.
 #
-# An instance of `Sprite` can only belong to a single `SpriteSet` at
-# a time. The on screen position depends on the camera associated
+# Each sprite associates a `texture` to the position `center`.
+# The appearance is modified by `rotation`, `invert_x`,
+# `scale`, `red`, `green`, `blue` and `alpha`.
+# These values can be changed at any time and will trigger an update
+# of the data on the GPU side, having a small performance cost.
+#
+# For a sprite to be visible, it must be added to either the world `sprites`
+# or the `ui_sprites`.
+# However, an instance of `Sprite` can only belong to a single `SpriteSet`
+# at a time. The final on-screen position depends on the camera associated
 # to the `SpriteSet`.
+#
+# ~~~
+# # Load texture and create sprite
+# var texture = new Texture("path/in/assets.png")
+# var sprite = new Sprite(texture, new Point3d[Float](0.0, 0.0, 0.0))
+#
+# # Add sprite to the visible game world
+# app.sprites.add sprite
+#
+# # Extra configuration of the sprite
+# sprite.rotation = pi/2.0
+# sprite.scale = 2.0
+#
+# # Show only the blue colors
+# sprite.red = 0.0
+# sprite.green = 0.0
+# ~~~
+#
+# To add a sprite to the UI it can be anchored to screen borders
+# with `ui_camera.top_left` and the likes.
+#
+# ~~~nitish
+# # Place it a bit off the top left of the screen
+# var pos = app.ui_camera.top_left.offset(128.0, -128.0, 0)
+#
+# # Load texture and create sprite
+# var texture = new Texture("path/in/assets.png")
+# var sprite = new Sprite(texture, pos)
+#
+# # Add it to the UI (above world sprites)
+# app.ui_sprites.add sprite
+# ~~~
 class Sprite
 
        # Texture drawn to screen
@@ -80,10 +123,10 @@ class Sprite
                center_direct = value
        end
 
-       # Rotation on the Z axis, positive values go counterclockwise
+       # Rotation on the Z axis, positive values turn counterclockwise
        var rotation = 0.0 is writable(rotation_direct=)
 
-       # Rotation on the Z axis, positive values go counterclockwise
+       # Rotation on the Z axis, positive values turn counterclockwise
        fun rotation=(value: Float)
        do
                if isset _rotation and value != rotation then needs_update
@@ -102,10 +145,12 @@ class Sprite
 
        # Scale applied to this sprite
        #
-       # The default size of `self` depends on the size in pixels of `texture`.
+       # The basic size of `self` depends on the size in pixels of `texture`.
        var scale = 1.0 is writable(scale_direct=)
 
-       # Scale applied to this sprite, see `scale`
+       # Scale applied to this sprite
+       #
+       # The basic size of `self` depends on the size in pixels of `texture`.
        fun scale=(value: Float)
        do
                if isset _scale and value != scale then needs_update
@@ -218,25 +263,25 @@ redef class App
 
        # Camera for world `sprites` and `depth::actors` with perspective
        #
-       # By default, the camera is configured to respect the resolution
-       # of the screen in world coordinates at `z == 0.0`.
+       # By default, the camera is configured to a height of 1080 units
+       # of world coordinates at `z == 0.0`.
        var world_camera: EulerCamera is lazy do
                var camera = new EulerCamera(app.display.as(not null))
 
-               # Aim for pixel resolution at level 0
-               camera.reset_height
-               camera.near = 100.0
+               # Aim for full HD pixel resolution at level 0
+               camera.reset_height 1080.0
+               camera.near = 10.0
 
                return camera
        end
 
        # Camera for `ui_sprites` using an orthogonal view
-       var ui_camera: UICamera = new UICamera(app.display.as(not null)) is lazy
+       var ui_camera = new UICamera(app.display.as(not null)) is lazy
 
-       # World sprites to draw as seen by `world_camera`
+       # World sprites drawn as seen by `world_camera`
        var sprites: Set[Sprite] = new SpriteSet
 
-       # UI sprites to draw as seen by `ui_camera`, drawn over world `sprites`
+       # UI sprites drawn as seen by `ui_camera`, over world `sprites`
        var ui_sprites: Set[Sprite] = new SpriteSet
 
        # Main method to refine in clients to update game logic and `sprites`
@@ -249,8 +294,6 @@ redef class App
        do
                texture.load
 
-               ui_camera.reset_height 1080.0
-
                var splash = new Sprite(texture, ui_camera.center)
                ui_sprites.add splash
 
@@ -496,7 +539,7 @@ private class Simple2dProgram
                {
                        if(use_texture) {
                                gl_FragColor = v_color * texture2D(texture0, v_coord);
-                               if (gl_FragColor.a <= 0.1) discard;
+                               if (gl_FragColor.a <= 0.01) discard;
                        } else {
                                gl_FragColor = v_color;
                        }
@@ -541,12 +584,6 @@ private class Simple2dProgram
 end
 
 redef class Point3d[N]
-       # Get a new `Point3d[Float]` with an offset of each axis of `x, y, z`
-       fun offset(x, y, z: Numeric): Point3d[Float]
-       do
-               return new Point3d[Float](self.x.to_f+x.to_f, self.y.to_f+y.to_f, self.z.to_f+z.to_f)
-       end
-
        # ---
        # Associate each point to its sprites
 
@@ -597,12 +634,32 @@ redef class Point3d[N]
        end
 end
 
+redef class OffsetPoint3d
+       redef fun x=(v)
+       do
+               if isset _x and v != x then needs_update
+               super
+       end
+
+       redef fun y=(v)
+       do
+               if isset _y and v != y then needs_update
+               super
+       end
+
+       redef fun z=(v)
+       do
+               if isset _z and v != z then needs_update
+               super
+       end
+end
+
 # Set of sprites sorting them into different `SpriteContext`
 private class SpriteSet
        super HashSet[Sprite]
 
        # Map texture then static vs dynamic to a `SpriteContext`
-       var contexts_map = new HashMap2[GamnitRootTexture, Bool, SpriteContext]
+       var contexts_map = new HashMap2[RootTexture, Bool, SpriteContext]
 
        # Contexts in `contexts_map`
        var contexts_items = new Array[SpriteContext]
@@ -677,6 +734,10 @@ private class SpriteSet
 
        redef fun clear
        do
+               for sprite in self do
+                       sprite.context = null
+                       sprite.sprite_set = null
+               end
                super
                for c in contexts_items do c.destroy
                contexts_map.clear
@@ -693,7 +754,7 @@ private class SpriteContext
        # Context config and state
 
        # Only root texture drawn by this context
-       var texture: nullable GamnitRootTexture
+       var texture: nullable RootTexture
 
        # OpenGL ES usage of `buffer_array` and `buffer_element`
        var usage: GLBufferUsage
@@ -856,7 +917,7 @@ private class SpriteContext
                        else
                                rot = new Matrix.rotation(sprite.rotation, 0.0, 0.0, 1.0)
                        end
-                       data.fill_from(rot.items, o+15)
+                       data.fill_from_matrix(rot, o+15)
 
                        o += float_per_vertex
                end
@@ -1036,6 +1097,8 @@ end
 # The data can be compressed by a call to `defragment`.
 #
 # ~~~
+# intrude import gamnit::flat
+#
 # var array = new GroupedArray[String]
 # assert array.to_s == ""
 #
@@ -1074,7 +1137,7 @@ end
 #
 # array.add "E"
 # assert array.to_s == "[A,B,c,D,E]"
-# assert array.capacity == 5
+# assert array.capacity == 6
 # assert array.length == 5
 #
 # array.remove "A"
@@ -1195,6 +1258,8 @@ private class GroupedArray[E]
        # Returns the elements that moved as a list.
        #
        # ~~~
+       # intrude import gamnit::flat
+       #
        # var array = new GroupedArray[String]
        # array.add "a"
        # array.add "b"
@@ -1254,3 +1319,21 @@ private class GroupedArray[E]
                return ss.join
        end
 end
+
+redef class GLfloatArray
+       private fun fill_from_matrix(matrix: Matrix, dst_offset: nullable Int)
+       do
+               dst_offset = dst_offset or else 0
+               var mat_len = matrix.width*matrix.height
+               assert length >= mat_len + dst_offset
+               native_array.fill_from_matrix_native(matrix.items, dst_offset, mat_len)
+       end
+end
+
+redef class NativeGLfloatArray
+       private fun fill_from_matrix_native(matrix: matrix::NativeDoubleArray, dst_offset, len: Int) `{
+               int i;
+               for (i = 0; i < len; i ++)
+                       self[i+dst_offset] = (GLfloat)matrix[i];
+       `}
+end