24a72c85c7503637cae9e30bc25e5af2130c02d6
[nit.git] / lib / gamnit / flat.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Simple API for 2D games, based around `Sprite` and `App::update`
16 #
17 # Client programs should implement `App::update` to execute game logic and
18 # add instances of `Sprite` to `App::sprites` and `App::ui_sprites`.
19 # At each frame, all sprites are drawn to the screen.
20 #
21 # This system relies on two cameras `App::world_camera` and `App::ui_camera`.
22 #
23 # * `App::world_camera` applies a perspective effect to draw the game world.
24 # This camera is designed to be moved around to see the world as well as to zoom in and out.
25 # It is used to position the sprites in `App::sprites`.
26 #
27 # * `App::ui_camera` is a simple orthogonal camera to display UI objects.
28 # This camera should mostly be still, it can still move for chock effects and the like.
29 # It can be used to standardize the size of the UI across devices.
30 # It is used to position the sprites in `App::ui_sprites`.
31 #
32 # See the sample game at `contrib/asteronits/`.
33 module flat
34
35 import glesv2
36
37 import geometry::points_and_lines
38 import matrix::projection
39 import more_collections
40 import realtime
41
42 import gamnit
43 import gamnit::cameras
44 import gamnit::limit_fps
45
46 # Image to draw on screen
47 class Sprite
48
49 # Texture drawn to screen
50 var texture: Texture is writable
51
52 # Position of this sprite in world coordinates
53 var center: Point3d[Float] is writable
54
55 # Rotation on the Z axis
56 var rotation = 0.0 is writable
57
58 # Scale applied to this sprite
59 var scale = 1.0 is writable
60
61 # Transparency applied to the texture on draw
62 var alpha = 1.0 is writable
63
64 private fun draw
65 do
66 var simple_2d_program = app.simple_2d_program
67
68 glActiveTexture gl_TEXTURE0
69 glBindTexture(gl_TEXTURE_2D, texture.root.gl_texture)
70
71 simple_2d_program.translation.uniform(center.x, -center.y, center.z, 0.0)
72 simple_2d_program.color.uniform(1.0, 1.0, 1.0, alpha)
73 simple_2d_program.scale.uniform scale
74
75 simple_2d_program.use_texture.uniform true
76 simple_2d_program.texture.uniform 0
77 simple_2d_program.tex_coord.array(texture.texture_coords, 2)
78 simple_2d_program.coord.array(texture.vertices, 3)
79
80 simple_2d_program.rotation.uniform new Matrix.rotation(rotation, 0.0, 0.0, 1.0)
81
82 glDrawArrays(gl_TRIANGLE_STRIP, 0, 4)
83 end
84 end
85
86 redef class App
87 # Default graphic program to draw `sprites`
88 var simple_2d_program = new Simple2dProgram is lazy # TODO private
89
90 # Camera for world objects with perspective
91 #
92 # By default, the camera is configured to respect the resolution
93 # of the screen in world coordinates at `z == 0.0`.
94 var world_camera: EulerCamera is lazy do
95 var camera = new EulerCamera(app.display.as(not null))
96
97 # Aim for pixel resolution at level 0
98 camera.reset_height
99 camera.near = 100.0
100
101 return camera
102 end
103
104 # Camera for UI elements using an orthogonal view
105 var ui_camera: UICamera = new UICamera(app.display.as(not null)) is lazy
106
107 # Live sprites to draw in reference to `world_camera`
108 var sprites: Sequence[Sprite] = new List[Sprite]
109
110 # UI sprites to draw in reference to `ui_camera`, over world `sprites`
111 var ui_sprites: Sequence[Sprite] = new List[Sprite]
112
113 private var clock = new Clock
114
115 redef fun on_create
116 do
117 super
118
119 var display = display
120 assert display != null
121
122 var gl_error = glGetError
123 assert gl_error == gl_NO_ERROR else print gl_error
124
125 # Prepare program
126 var program = simple_2d_program
127 program.compile_and_link
128
129 var gamnit_error = program.error
130 assert gamnit_error == null else print_error gamnit_error
131
132 # Enable blending
133 gl.capabilities.blend.enable
134 glBlendFunc(gl_SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA)
135
136 # Enable depth test
137 gl.capabilities.depth_test.enable
138 glDepthFunc gl_LEQUAL
139 glDepthMask true
140
141 # Prepare viewport and background color
142 glViewport(0, 0, display.width, display.height)
143 glClearColor(0.0, 0.0, 0.0, 1.0)
144
145 gl_error = glGetError
146 assert gl_error == gl_NO_ERROR else print gl_error
147
148 # Prepare to draw
149 for tex in all_root_textures do
150 tex.load
151
152 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
153 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
154
155 gamnit_error = tex.error
156 assert gamnit_error == null else print_error gamnit_error
157 end
158
159 # Constant program values
160 program.use
161 program.coord.array_enabled = true
162 program.tex_coord.array_enabled = true
163
164 gl_error = glGetError
165 assert gl_error == gl_NO_ERROR else print gl_error
166 end
167
168 redef fun frame_core(display)
169 do
170 # Prepare to draw, clear buffers
171 glClear(gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT)
172
173 # Check errors
174 var gl_error = glGetError
175 assert gl_error == gl_NO_ERROR else print gl_error
176
177 # Update game logic and set sprites
178 var dt = clock.lapse.to_f
179 update dt
180
181 # Draw and flip screen
182 frame_core_draw display
183 display.flip
184
185 # Check errors
186 gl_error = glGetError
187 assert gl_error == gl_NO_ERROR else print gl_error
188 end
189
190 # Draw sprites in `sprites` and `ui_sprites`
191 protected fun frame_core_draw(display: GamnitDisplay)
192 do
193 # TODO optimize this draw to store constant values on the GPU
194
195 # World sprites
196 simple_2d_program.use
197 simple_2d_program.mvp.uniform world_camera.mvp_matrix
198 for sprite in sprites do sprite.draw
199
200 # Reset only the depth buffer
201 glClear gl_DEPTH_BUFFER_BIT
202
203 # UI sprites
204 simple_2d_program.mvp.uniform ui_camera.mvp_matrix
205 for sprite in ui_sprites do sprite.draw
206 end
207
208 # Main method to refine in clients to update game logic and `sprites`
209 fun update(dt: Float) do end
210
211 redef fun on_stop
212 do
213 # Clean up
214 simple_2d_program.delete
215
216 # Close gamnit
217 var display = display
218 if display != null then display.close
219 end
220 end
221
222 redef class Texture
223
224 # Vertices coordinates of the base geometry
225 private var vertices: Array[Float] is lazy do
226 var mod = 1.0
227 var w = width * mod
228 var h = height * mod
229 var a = [-0.5*w, -0.5*h, 0.0]
230 var b = [ 0.5*w, -0.5*h, 0.0]
231 var c = [-0.5*w, 0.5*h, 0.0]
232 var d = [ 0.5*w, 0.5*h, 0.0]
233
234 var vertices = new Array[Float]
235 for v in [c, d, a, b] do vertices.add_all v
236 return vertices
237 end
238
239 # Coordinates of this texture on the `root` texture, with `[0..1.0]`
240 private var texture_coords: Array[Float] is lazy do
241 var a = [offset_left, offset_bottom]
242 var b = [offset_right, offset_bottom]
243 var c = [offset_left, offset_top]
244 var d = [offset_right, offset_top]
245
246 var texture_coords = new Array[Float]
247 for v in [c, d, a, b] do texture_coords.add_all v
248 return texture_coords
249 end
250 end
251
252 # Graphic program to display simple models with a texture, translation, rotation and scale
253 class Simple2dProgram
254 super GamnitProgramFromSource
255
256 redef var vertex_shader_source = """
257 // Vertex coordinates
258 attribute vec4 coord;
259
260 // Vertex color tint
261 attribute vec4 color;
262
263 // Vertex translation
264 attribute vec4 translation;
265
266 // Vertex scaling
267 attribute float scale;
268
269 // Vertex coordinates on textures
270 attribute vec2 tex_coord;
271
272 // Model view projection matrix
273 uniform mat4 mvp;
274
275 // Rotation matrix
276 uniform mat4 rotation;
277
278 // Output for the fragment shader
279 varying vec4 v_color;
280 varying vec2 v_coord;
281
282 void main()
283 {
284 v_color = color;
285 gl_Position = (vec4(coord.xyz * scale, 1.0) * rotation + translation) * mvp;
286 v_coord = tex_coord;
287 }
288 """ @ glsl_vertex_shader
289
290 redef var fragment_shader_source = """
291 precision mediump float;
292
293 // Does this object use a texture?
294 uniform bool use_texture;
295
296 // Texture to apply on this object
297 uniform sampler2D texture;
298
299 // Input from the vertex shader
300 varying vec4 v_color;
301 varying vec2 v_coord;
302
303 void main()
304 {
305 if(use_texture) {
306 gl_FragColor = v_color * texture2D(texture, v_coord);
307 if (gl_FragColor.a == 0.0) discard;
308 } else {
309 gl_FragColor = v_color;
310 }
311 }
312 """ @ glsl_fragment_shader
313
314 # Vertices coordinates
315 var coord = attributes["coord"].as(AttributeVec4) is lazy
316
317 # Should this program use the texture `texture`?
318 var use_texture = uniforms["use_texture"].as(UniformBool) is lazy
319
320 # Visible texture unit
321 var texture = uniforms["texture"].as(UniformSampler2D) is lazy
322
323 # Coordinates on the textures, per vertex
324 var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
325
326 # Color tint per vertex
327 var color = attributes["color"].as(AttributeVec4) is lazy
328
329 # Translation applied to each vertex
330 var translation = attributes["translation"].as(AttributeVec4) is lazy
331
332 # Rotation matrix
333 var rotation = uniforms["rotation"].as(UniformMat4) is lazy
334
335 # Scaling per vertex
336 var scale = attributes["scale"].as(AttributeFloat) is lazy
337
338 # Model view projection matrix
339 var mvp = uniforms["mvp"].as(UniformMat4) is lazy
340 end
341
342 redef class Point3d[N]
343 # Get a new `Point3d[Float]` with an offset of each axis of `x, y, z`
344 fun offset(x, y, z: Numeric): Point3d[Float]
345 do
346 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)
347 end
348 end