gamnit: rewrite `Mesh::dimensions & center` and move to its own module
[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, where 0.0 points right and `0.5*pi` points up
56 var rotation = 0.0 is writable
57
58 # Mirror `texture` horizontally, inverting each pixel on the X axis
59 var invert_x = false is writable
60
61 # Scale applied to this sprite
62 var scale = 1.0 is writable
63
64 # Transparency applied to the texture on draw
65 fun alpha: Float do return tint[3]
66
67 # Transparency applied to the texture on draw
68 fun alpha=(value: Float) do tint[3] = value
69
70 # Tint applied to the texture on draw
71 #
72 # Require: `tint.length == 4`
73 var tint: Array[Float] = [1.0, 1.0, 1.0, 1.0] is writable
74
75 private fun draw
76 do
77 var simple_2d_program = app.simple_2d_program
78
79 glActiveTexture gl_TEXTURE0
80 glBindTexture(gl_TEXTURE_2D, texture.root.gl_texture)
81
82 simple_2d_program.translation.array_enabled = false
83 simple_2d_program.color.array_enabled = false
84 simple_2d_program.scale.array_enabled = false
85
86 simple_2d_program.translation.uniform(center.x, center.y, center.z, 0.0)
87 simple_2d_program.color.uniform(tint[0], tint[1], tint[2], tint[3])
88 simple_2d_program.scale.uniform scale
89
90 simple_2d_program.use_texture.uniform true
91 simple_2d_program.texture.uniform 0
92 simple_2d_program.tex_coord.array(
93 if invert_x then
94 texture.texture_coords_invert_x
95 else texture.texture_coords, 2)
96 simple_2d_program.coord.array(texture.vertices, 3)
97
98 simple_2d_program.rotation.uniform new Matrix.rotation(rotation, 0.0, 0.0, -1.0)
99
100 glDrawArrays(gl_TRIANGLE_STRIP, 0, 4)
101 end
102 end
103
104 redef class App
105 # Default graphic program to draw `sprites`
106 var simple_2d_program = new Simple2dProgram is lazy # TODO private
107
108 # Camera for world objects with perspective
109 #
110 # By default, the camera is configured to respect the resolution
111 # of the screen in world coordinates at `z == 0.0`.
112 var world_camera: EulerCamera is lazy do
113 var camera = new EulerCamera(app.display.as(not null))
114
115 # Aim for pixel resolution at level 0
116 camera.reset_height
117 camera.near = 100.0
118
119 return camera
120 end
121
122 # Camera for UI elements using an orthogonal view
123 var ui_camera: UICamera = new UICamera(app.display.as(not null)) is lazy
124
125 # Live sprites to draw in reference to `world_camera`
126 var sprites: Sequence[Sprite] = new List[Sprite]
127
128 # UI sprites to draw in reference to `ui_camera`, over world `sprites`
129 var ui_sprites: Sequence[Sprite] = new List[Sprite]
130
131 private var clock = new Clock is lazy
132
133 redef fun on_create
134 do
135 super
136
137 var display = display
138 assert display != null
139
140 var gl_error = glGetError
141 assert gl_error == gl_NO_ERROR else print gl_error
142
143 # Prepare program
144 var program = simple_2d_program
145 program.compile_and_link
146
147 var gamnit_error = program.error
148 assert gamnit_error == null else print_error gamnit_error
149
150 # Enable blending
151 gl.capabilities.blend.enable
152 glBlendFunc(gl_SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA)
153
154 # Enable depth test
155 gl.capabilities.depth_test.enable
156 glDepthFunc gl_LEQUAL
157 glDepthMask true
158
159 # Prepare viewport and background color
160 glViewport(0, 0, display.width, display.height)
161 glClearColor(0.0, 0.0, 0.0, 1.0)
162
163 gl_error = glGetError
164 assert gl_error == gl_NO_ERROR else print gl_error
165
166 # Prepare to draw
167 for tex in all_root_textures do
168 tex.load
169 gamnit_error = tex.error
170 if gamnit_error != null then print_error gamnit_error
171
172 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
173 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
174 end
175 end
176
177 redef fun frame_core(display)
178 do
179 # Prepare to draw, clear buffers
180 glClear(gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT)
181
182 # Check errors
183 var gl_error = glGetError
184 assert gl_error == gl_NO_ERROR else print gl_error
185
186 # Update game logic and set sprites
187 var dt = clock.lapse.to_f
188 update dt
189
190 # Draw and flip screen
191 frame_core_draw display
192 display.flip
193
194 # Check errors
195 gl_error = glGetError
196 assert gl_error == gl_NO_ERROR else print gl_error
197 end
198
199 # Draw the whole screen, all `glDraw...` calls should be executed here
200 protected fun frame_core_draw(display: GamnitDisplay)
201 do
202 frame_core_world_sprites display
203 frame_core_ui_sprites display
204 end
205
206 # Draw world sprites from `sprites`
207 protected fun frame_core_world_sprites(display: GamnitDisplay)
208 do
209 simple_2d_program.use
210
211 # Set constant configs
212 simple_2d_program.coord.array_enabled = true
213 simple_2d_program.tex_coord.array_enabled = true
214 simple_2d_program.color.array_enabled = false
215
216 # TODO optimize this draw to store constant values on the GPU
217 # World sprites
218 simple_2d_program.mvp.uniform world_camera.mvp_matrix
219 for sprite in sprites do sprite.draw
220 end
221
222 # Draw UI sprites from `ui_sprites`
223 protected fun frame_core_ui_sprites(display: GamnitDisplay)
224 do
225 simple_2d_program.use
226
227 # Set constant configs
228 simple_2d_program.coord.array_enabled = true
229 simple_2d_program.tex_coord.array_enabled = true
230 simple_2d_program.color.array_enabled = false
231
232 # Reset only the depth buffer
233 glClear gl_DEPTH_BUFFER_BIT
234
235 # UI sprites
236 simple_2d_program.mvp.uniform ui_camera.mvp_matrix
237 for sprite in ui_sprites do sprite.draw
238 end
239
240 # Main method to refine in clients to update game logic and `sprites`
241 fun update(dt: Float) do end
242
243 # Display `texture` as a splash screen
244 #
245 # Load `texture` if needed and resets `ui_camera` to 1080 units on the Y axis.
246 fun show_splash_screen(texture: Texture)
247 do
248 texture.load
249
250 ui_camera.reset_height 1080.0
251
252 var splash = new Sprite(texture, ui_camera.center)
253 ui_sprites.add splash
254
255 var display = display
256 assert display != null
257 glClear gl_COLOR_BUFFER_BIT
258 frame_core_ui_sprites display
259 display.flip
260
261 ui_sprites.remove splash
262 end
263
264 redef fun on_stop
265 do
266 # Clean up
267 simple_2d_program.delete
268
269 # Close gamnit
270 var display = display
271 if display != null then display.close
272 end
273 end
274
275 redef class Texture
276
277 # Vertices coordinates of the base geometry
278 private var vertices: Array[Float] is lazy do
279 var mod = 1.0
280 var w = width * mod
281 var h = height * mod
282 var a = [-0.5*w, -0.5*h, 0.0]
283 var b = [ 0.5*w, -0.5*h, 0.0]
284 var c = [-0.5*w, 0.5*h, 0.0]
285 var d = [ 0.5*w, 0.5*h, 0.0]
286
287 var vertices = new Array[Float]
288 for v in [c, d, a, b] do vertices.add_all v
289 return vertices
290 end
291
292 # Coordinates of this texture on the `root` texture, with `[0..1.0]`
293 private var texture_coords: Array[Float] is lazy do
294 var a = [offset_left, offset_bottom]
295 var b = [offset_right, offset_bottom]
296 var c = [offset_left, offset_top]
297 var d = [offset_right, offset_top]
298
299 var texture_coords = new Array[Float]
300 for v in [c, d, a, b] do texture_coords.add_all v
301 return texture_coords
302 end
303
304 # Coordinates of this texture on the `root` texture, with the X axis inverted
305 private var texture_coords_invert_x: Array[Float] is lazy do
306 var a = [offset_left, offset_bottom]
307 var b = [offset_right, offset_bottom]
308 var c = [offset_left, offset_top]
309 var d = [offset_right, offset_top]
310
311 var texture_coords = new Array[Float]
312 for v in [d, c, b, a] do texture_coords.add_all v
313 return texture_coords
314 end
315 end
316
317 # Graphic program to display simple models with a texture, translation, rotation and scale
318 class Simple2dProgram
319 super GamnitProgramFromSource
320
321 redef var vertex_shader_source = """
322 // Vertex coordinates
323 attribute vec4 coord;
324
325 // Vertex color tint
326 attribute vec4 color;
327
328 // Vertex translation
329 attribute vec4 translation;
330
331 // Vertex scaling
332 attribute float scale;
333
334 // Vertex coordinates on textures
335 attribute vec2 tex_coord;
336
337 // Model view projection matrix
338 uniform mat4 mvp;
339
340 // Rotation matrix
341 uniform mat4 rotation;
342
343 // Output for the fragment shader
344 varying vec4 v_color;
345 varying vec2 v_coord;
346
347 void main()
348 {
349 v_color = color;
350 gl_Position = (vec4(coord.xyz * scale, 1.0) * rotation + translation) * mvp;
351 v_coord = tex_coord;
352 }
353 """ @ glsl_vertex_shader
354
355 redef var fragment_shader_source = """
356 precision mediump float;
357
358 // Does this object use a texture?
359 uniform bool use_texture;
360
361 // Texture to apply on this object
362 uniform sampler2D texture;
363
364 // Input from the vertex shader
365 varying vec4 v_color;
366 varying vec2 v_coord;
367
368 void main()
369 {
370 if(use_texture) {
371 gl_FragColor = v_color * texture2D(texture, v_coord);
372 if (gl_FragColor.a == 0.0) discard;
373 } else {
374 gl_FragColor = v_color;
375 }
376 }
377 """ @ glsl_fragment_shader
378
379 # Vertices coordinates
380 var coord = attributes["coord"].as(AttributeVec4) is lazy
381
382 # Should this program use the texture `texture`?
383 var use_texture = uniforms["use_texture"].as(UniformBool) is lazy
384
385 # Visible texture unit
386 var texture = uniforms["texture"].as(UniformSampler2D) is lazy
387
388 # Coordinates on the textures, per vertex
389 var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
390
391 # Color tint per vertex
392 var color = attributes["color"].as(AttributeVec4) is lazy
393
394 # Translation applied to each vertex
395 var translation = attributes["translation"].as(AttributeVec4) is lazy
396
397 # Rotation matrix
398 var rotation = uniforms["rotation"].as(UniformMat4) is lazy
399
400 # Scaling per vertex
401 var scale = attributes["scale"].as(AttributeFloat) is lazy
402
403 # Model view projection matrix
404 var mvp = uniforms["mvp"].as(UniformMat4) is lazy
405 end
406
407 redef class Point3d[N]
408 # Get a new `Point3d[Float]` with an offset of each axis of `x, y, z`
409 fun offset(x, y, z: Numeric): Point3d[Float]
410 do
411 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)
412 end
413 end