--- /dev/null
+# 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.
+
+# Simple API for 2D games, based around `Sprite` and `App::update`
+#
+# Client programs should implement `App::update` to execute game logic and
+# add instances of `Sprite` to `App::sprites` and `App::ui_sprites`.
+# At each frame, all sprites are drawn to the screen.
+#
+# This system relies on two cameras `App::world_camera` and `App::ui_camera`.
+#
+# * `App::world_camera` applies a perspective effect to draw the game world.
+# This camera is designed to be moved around to see the world as well as to zoom in and out.
+# It is used to position the sprites in `App::sprites`.
+#
+# * `App::ui_camera` is a simple orthogonal camera to display UI objects.
+# This camera should mostly be still, it can still move for chock effects and the like.
+# It can be used to standardize the size of the UI across devices.
+# It is used to position the sprites in `App::ui_sprites`.
+#
+# See the sample game at `examples/asteronits/`.
+module simple_2d
+
+import glesv2
+
+import geometry::points_and_lines
+import matrix::projection
+import more_collections
+import realtime
+
+import gamnit
+import gamnit::cameras
+import gamnit::limit_fps
+
+# Image to draw on screen
+class Sprite
+
+ # Texture drawn to screen
+ var texture: Texture is writable
+
+ # Position of this sprite in world coordinates
+ var center: Point3d[Float] is writable
+
+ # Rotation on the Z axis
+ var rotation = 0.0 is writable
+
+ # Scale applied to this sprite
+ var scale = 1.0 is writable
+
+ # Transparency applied to the texture on draw
+ var alpha = 1.0 is writable
+
+ private fun draw
+ do
+ var simple_2d_program = app.simple_2d_program
+
+ glActiveTexture gl_TEXTURE0
+ glBindTexture(gl_TEXTURE_2D, texture.root.gl_texture)
+
+ simple_2d_program.translation.uniform(center.x, -center.y, center.z, 0.0)
+ simple_2d_program.color.uniform(1.0, 1.0, 1.0, alpha)
+ simple_2d_program.scale.uniform scale
+
+ simple_2d_program.use_texture.uniform true
+ simple_2d_program.texture.uniform 0
+ simple_2d_program.tex_coord.array(texture.texture_coords, 2)
+ simple_2d_program.coord.array(texture.vertices, 3)
+
+ simple_2d_program.rotation.uniform new Matrix.rotation(rotation, 0.0, 0.0, 1.0)
+
+ glDrawArrays(gl_TRIANGLE_STRIP, 0, 4)
+ end
+end
+
+redef class App
+ # Default graphic program to draw `sprites`
+ var simple_2d_program = new Simple2dProgram is lazy # TODO private
+
+ # Camera for world objects with perspective
+ #
+ # By default, the camera is configured to respect the resolution
+ # of the screen in 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
+
+ return camera
+ end
+
+ # Camera for UI elements using an orthogonal view
+ var ui_camera: UICamera = new UICamera(app.display.as(not null)) is lazy
+
+ # Live sprites to draw in reference to `world_camera`
+ var sprites: Sequence[Sprite] = new List[Sprite]
+
+ # UI sprites to draw in reference to `ui_camera`, over world `sprites`
+ var ui_sprites: Sequence[Sprite] = new List[Sprite]
+
+ private var clock = new Clock
+
+ redef fun on_create
+ do
+ super
+
+ var display = display
+ assert display != null
+
+ var gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print gl_error
+
+ # Prepare program
+ var program = simple_2d_program
+ program.compile_and_link
+
+ var gamnit_error = program.error
+ assert gamnit_error == null else print_error gamnit_error
+
+ # Enable blending
+ gl.capabilities.blend.enable
+ glBlendFunc(gl_SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA)
+
+ # Enable depth test
+ gl.capabilities.depth_test.enable
+ glDepthFunc gl_LEQUAL
+ glDepthMask true
+
+ # Prepare viewport and background color
+ glViewport(0, 0, display.width, display.height)
+ glClearColor(0.0, 0.0, 0.0, 1.0)
+
+ gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print gl_error
+
+ # Prepare to draw
+ for tex in all_root_textures do
+ tex.load
+
+ glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
+ glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
+
+ gamnit_error = tex.error
+ assert gamnit_error == null else print_error gamnit_error
+ end
+
+ # Constant program values
+ program.use
+ program.coord.array_enabled = true
+ program.tex_coord.array_enabled = true
+
+ gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print gl_error
+ end
+
+ redef fun frame_core(display)
+ do
+ # Prepare to draw, clear buffers
+ glClear(gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT)
+
+ # Check errors
+ var gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print gl_error
+
+ # Update game logic and set sprites
+ var dt = clock.lapse.to_f
+ update dt
+
+ # Draw and flip screen
+ # TODO optimize this draw to store constant values on the GPU
+
+ ## World sprites
+ simple_2d_program.use
+ simple_2d_program.mvp.uniform world_camera.mvp_matrix
+ for sprite in sprites do sprite.draw
+
+ ## Reset only the depth buffer
+ glClear gl_DEPTH_BUFFER_BIT
+
+ ## UI sprites
+ simple_2d_program.mvp.uniform ui_camera.mvp_matrix
+ for sprite in ui_sprites do sprite.draw
+ display.flip
+
+ # Check errors
+ gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print gl_error
+ end
+
+ # Main method to refine in clients to update game logic and `sprites`
+ fun update(dt: Float) do end
+
+ redef fun on_stop
+ do
+ # Clean up
+ simple_2d_program.delete
+
+ # Close gamnit
+ var display = display
+ if display != null then display.close
+ end
+end
+
+redef class Texture
+
+ # Vertices coordinates of the base geometry
+ private var vertices: Array[Float] is lazy do
+ var mod = 1.0
+ var w = width * mod
+ var h = height * mod
+ var a = [-0.5*w, -0.5*h, 0.0]
+ var b = [ 0.5*w, -0.5*h, 0.0]
+ var c = [-0.5*w, 0.5*h, 0.0]
+ var d = [ 0.5*w, 0.5*h, 0.0]
+
+ var vertices = new Array[Float]
+ for v in [c, d, a, b] do vertices.add_all v
+ return vertices
+ end
+
+ # Coordinates of this texture on the `root` texture, with `[0..1.0]`
+ private var texture_coords: Array[Float] is lazy do
+ var a = [offset_left, offset_bottom]
+ var b = [offset_right, offset_bottom]
+ var c = [offset_left, offset_top]
+ var d = [offset_right, offset_top]
+
+ var texture_coords = new Array[Float]
+ for v in [c, d, a, b] do texture_coords.add_all v
+ return texture_coords
+ end
+end
+
+# Graphic program to display simple models with a texture, translation, rotation and scale
+class Simple2dProgram
+ super GamnitProgramFromSource
+
+ redef var vertex_shader_source = """
+ // Vertex coordinates
+ attribute vec4 coord;
+
+ // Vertex color tint
+ attribute vec4 color;
+
+ // Vertex translation
+ attribute vec4 translation;
+
+ // Vertex scaling
+ attribute float scale;
+
+ // Vertex coordinates on textures
+ attribute vec2 tex_coord;
+
+ // Model view projection matrix
+ uniform mat4 mvp;
+
+ // Rotation matrix
+ uniform mat4 rotation;
+
+ // Output for the fragment shader
+ varying vec4 v_color;
+ varying vec2 v_coord;
+
+ void main()
+ {
+ v_color = color;
+ gl_Position = (vec4(coord.xyz * scale, 1.0) * rotation + translation) * mvp;
+ v_coord = tex_coord;
+ }
+ """ @ glsl_vertex_shader
+
+ redef var fragment_shader_source = """
+ precision mediump float;
+
+ // Does this object use a texture?
+ uniform bool use_texture;
+
+ // Texture to apply on this object
+ uniform sampler2D texture;
+
+ // Input from the vertex shader
+ varying vec4 v_color;
+ varying vec2 v_coord;
+
+ void main()
+ {
+ if(use_texture) {
+ gl_FragColor = v_color * texture2D(texture, v_coord);
+ if (gl_FragColor.a == 0.0) discard;
+ } else {
+ gl_FragColor = v_color;
+ }
+ }
+ """ @ glsl_fragment_shader
+
+ # Vertices coordinates
+ var coord = attributes["coord"].as(AttributeVec4) is lazy
+
+ # Should this program use the texture `texture`?
+ var use_texture = uniforms["use_texture"].as(UniformBool) is lazy
+
+ # Visible texture unit
+ var texture = uniforms["texture"].as(UniformSampler2D) is lazy
+
+ # Coordinates on the textures, per vertex
+ var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
+
+ # Color tint per vertex
+ var color = attributes["color"].as(AttributeVec4) is lazy
+
+ # Translation applied to each vertex
+ var translation = attributes["translation"].as(AttributeVec4) is lazy
+
+ # Rotation matrix
+ var rotation = uniforms["rotation"].as(UniformMat4) is lazy
+
+ # Scaling per vertex
+ var scale = attributes["scale"].as(AttributeFloat) is lazy
+
+ # Model view projection matrix
+ var mvp = uniforms["mvp"].as(UniformMat4) is lazy
+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
+end