contrib/model_viewer: add missing texture assets
[nit.git] / lib / gamnit / examples / globe / src / globe.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 # Portable app using gamnit textures and programs with custom calls to OpenGL ES 2.0
16 module globe is
17 app_name "gamnit Globe"
18 app_namespace "org.nitlanguage.globe"
19 app_version(1, 0, git_revision)
20
21 android_manifest_activity """android:screenOrientation="portrait""""
22 android_api_target 15
23 end
24
25 import realtime
26 import matrix::projection
27
28 import gamnit
29 import gamnit::cameras
30 import gamnit::limit_fps
31
32 private import more_collections
33
34 # Graphical program to draw a planet with Phong lighting
35 class GlobeProgram
36 super GamnitProgramFromSource
37
38 redef var vertex_shader_source = """
39 // Vertex coordinates
40 attribute vec4 coord;
41
42 // Vertex color tint
43 attribute vec4 color;
44
45 // Vertex translation
46 attribute vec4 translation;
47
48 // Vertex scaling
49 attribute float scale;
50
51 // Vertex coordinates on textures
52 attribute vec2 tex_coord;
53
54 // Vertex normal
55 attribute vec3 normal;
56
57 // Model view projection matrix
58 uniform mat4 mvp;
59
60 // Texture of surface elevation to displace vertices
61 uniform sampler2D tex_displace;
62
63 // Draw this as a planet surface?
64 uniform bool is_surface;
65
66 // Output for the fragment shader
67 varying vec4 v_color;
68 varying vec2 v_tex_coord;
69 varying vec3 v_normal;
70
71 void main()
72 {
73 // Pass varyings to the fragment shader
74 v_color = color;
75 v_tex_coord = tex_coord;
76 v_normal = normal;
77
78 // Apply bump map
79 float s = scale;
80 if (is_surface)
81 s += 0.05 * texture2D(tex_displace, tex_coord).r;
82
83 gl_Position = (vec4(coord.xyz * s, 1.0) + translation) * mvp;
84 }
85 """ @ glsl_vertex_shader
86
87 redef var fragment_shader_source = """
88 precision mediump float;
89
90 // Input from the vertex shader
91 varying vec4 v_color;
92 varying vec2 v_tex_coord;
93 varying vec3 v_normal;
94
95 // Coordinates of the camera
96 uniform vec3 camera;
97
98 // Does this object use a texture?
99 uniform bool use_texture;
100
101 // Texture to apply on this object
102 uniform sampler2D tex;
103
104 // Reflection map to apply the the phong logic
105 uniform sampler2D tex_shiny;
106
107 // Texture for the dark side of the earth
108 uniform sampler2D tex_night;
109
110 // Draw this as a planet surface?
111 uniform bool is_surface;
112
113 // Lights config
114 // TODO configure from outside the shader
115 vec3 light_dir = normalize(vec3(-1.0, 0.0, -1.0));
116 vec4 ambient_color = vec4(0.2, 0.2, 0.2, 1.0);
117 vec4 diffuse_color = vec4(1.0, 1.0, 1.0, 1.0);
118 vec4 specular_color = vec4(1.0, 1.0, 1.0, 1.0);
119
120 void main()
121 {
122 if(use_texture) {
123 gl_FragColor = v_color * texture2D(tex, v_tex_coord);
124 } else {
125 gl_FragColor = v_color;
126 }
127
128 // Lambert diffusion
129 float lambert = max(dot(light_dir, v_normal), 0.0);
130
131 // Phong specular
132 float specular = 0.0;
133 if (lambert > 0.0) {
134 vec3 to_camera = normalize(camera);
135 vec3 light_reflect = reflect(light_dir, v_normal);
136 float specularAngle = max(dot(light_reflect, to_camera), 0.0);
137 specular = pow(specularAngle, 16.0);
138
139 if (is_surface)
140 specular *= texture2D(tex_shiny, v_tex_coord).x;
141 else specular *= 0.2;
142 }
143
144 gl_FragColor *= ambient_color + lambert * diffuse_color;
145 gl_FragColor += specular * specular_color;
146
147 if (is_surface && lambert < 0.2) {
148 // Show city lights at night
149 float p_night = (0.2 - lambert) * 5.0;
150 gl_FragColor += p_night*texture2D(tex_night, v_tex_coord);
151 }
152 }
153 """ @ glsl_fragment_shader
154
155 # Vertices coordinates
156 var coord = attributes["coord"].as(AttributeVec4) is lazy
157
158 # Color tint per vertex
159 var color = attributes["color"].as(AttributeVec4) is lazy
160
161 # Scaling per vertex
162 var scale = attributes["scale"].as(AttributeFloat) is lazy
163
164 # Coordinates on the textures, per vertex
165 var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
166
167 # Normal per vertex
168 var normal = attributes["normal"].as(AttributeVec3) is lazy
169
170 # Model view projection matrix
171 var mvp = uniforms["mvp"].as(UniformMat4) is lazy
172
173 # Should this program use the texture `tex`?
174 var use_texture = uniforms["use_texture"].as(UniformBool) is lazy
175
176 # Main visible texture unit
177 var tex = uniforms["tex"].as(UniformSampler2D) is lazy
178
179 # Texture unit for reflection effect
180 var tex_shiny = uniforms["tex_shiny"].as(UniformSampler2D) is lazy
181
182 # Texture of the earth at night
183 var tex_night = uniforms["tex_night"].as(UniformSampler2D) is lazy
184
185 # Texture with elevation data
186 var tex_displace = uniforms["tex_displace"].as(UniformSampler2D) is lazy
187
188 # Position of the camera
189 var camera = uniforms["camera"].as(UniformVec3) is lazy
190
191 # Execute this program to draw a planet surface?
192 var is_surface = uniforms["is_surface"].as(UniformBool) is lazy
193 end
194
195 # Mesh with all information for drawing
196 class Mesh
197
198 # Vertices coordinates
199 var vertices: Array[Float]
200
201 # Indices to draw triangles
202 var indices: Array[Int]
203
204 private var indices_c = new CUInt16Array.from(indices) is lazy
205
206 # Normals on each vertex
207 var normals: Array[Float]
208
209 # Coordinates on the texture per vertex
210 var texture_coords: Array[Float]
211
212 # Create an UV sphere of `radius` with `n_meridians` and `n_parallels`
213 init uv_sphere(radius: Float, n_meridians, n_parallels: Int)
214 do
215 var w = n_meridians
216 var h = n_parallels
217
218 var vertices = new Array[Float].with_capacity(w*h*3)
219 var texture_coords = new Array[Float].with_capacity(w*h*2)
220 var normals = new Array[Float].with_capacity(w*h*3)
221
222 # Build vertices
223 for m in [0..w[ do
224 for p in [0..h[ do
225 var u = m.to_f * 2.0 * pi / (w-1).to_f
226 var v = p.to_f * pi / (h-1).to_f
227
228 vertices.add radius * u.cos * v.sin
229 vertices.add radius * v.cos
230 vertices.add radius * u.sin * v.sin
231
232 texture_coords.add (1.0 - m.to_f/(w-1).to_f)
233 texture_coords.add(p.to_f/(h-1).to_f)
234
235 normals.add u.cos * v.sin
236 normals.add v.cos
237 normals.add u.sin * v.sin
238 end
239 end
240
241 # Build faces
242 var indices = new Array[Int].with_capacity((w-1)*(h-1)*6)
243 for m in [0..w-1[ do
244 for p in [0..h-1[ do
245 var a = m*h + p
246
247 indices.add a
248 indices.add(a+h)
249 indices.add(a+1)
250
251 indices.add(a+h)
252 indices.add(a+h+1)
253 indices.add(a+1)
254 end
255 end
256
257 init(vertices, indices, normals, texture_coords)
258 end
259 end
260
261 # Number of vertices to create parallels, meridians are double of this
262 #
263 # The minimum should be 3 for an octahedron planet.
264 fun n_parallels: Int do return 25
265
266 redef class GamnitAssetTexture
267 # All images are either within the hd or ld folder
268 redef fun path do return app.definition / super
269 end
270
271 redef class App
272
273 # Earth
274 var planet = new Mesh.uv_sphere(2.0, 2*n_parallels, n_parallels)
275
276 # Cloud layer
277 var clouds = new Mesh.uv_sphere(2.08, 2*n_parallels, n_parallels)
278
279 # Visible atmosphere
280 var atmo = new Mesh.uv_sphere(2.16, 2*n_parallels, n_parallels)
281
282 # Program for the graphic card
283 var program = new GlobeProgram
284
285 private var definition: String is lazy do
286 var max_texture_size = glGetIntegerv(gl_MAX_TEXTURE_SIZE)
287
288 # HD textures max sizes reange from 2048 px to 5400 px
289 if max_texture_size < 5400 then
290 return "ld"
291 else return "hd"
292 end
293
294 # Texture of the reflexive surface of the earth (with the seas in white)
295 var texture_seas = new Texture("seas.jpg")
296
297 # Texture of the surface of the earth
298 var texture_earth = new Texture("earth.jpg")
299
300 # Texture of the lights at night on earth
301 var texture_night = new Texture("lights.jpg")
302
303 # Elevation map of earth
304 var texture_elevation = new Texture("elevation.jpg")
305
306 # Texture of the clouds above the earth
307 var texture_clouds = new Texture("clouds.png")
308
309 # Camera for the only view
310 var camera: EulerCamera is lazy do
311 var camera = new EulerCamera(app.display.as(not null))
312 camera.move(0.0, 0.1, -5.0)
313 return camera
314 end
315
316 redef fun on_create
317 do
318 super
319
320 var display = display
321 assert display != null
322
323 var gl_error = glGetError
324 assert gl_error == gl_NO_ERROR else print gl_error
325
326 # Prepare program
327 var program = program
328 program.compile_and_link
329
330 var gamnit_error = program.error
331 assert gamnit_error == null else print_error gamnit_error
332
333 # Blend
334 gl.capabilities.blend.enable
335 glBlendFunc(gl_SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA)
336
337 gl.capabilities.depth_test.enable
338 glDepthFunc gl_LEQUAL
339 glDepthMask true
340
341 gl_error = glGetError
342 assert gl_error == gl_NO_ERROR else print gl_error
343
344 # Prepare to draw
345 for tex in all_root_textures do
346 tex.load
347 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
348 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
349
350 gamnit_error = tex.error
351 assert gamnit_error == null else print_error gamnit_error
352 end
353
354 gl_error = glGetError
355 assert gl_error == gl_NO_ERROR else print gl_error
356
357 program.use
358
359 glViewport(0, 0, display.width, display.height)
360 glClearColor(0.0, 0.02, 0.0, 1.0)
361
362 gl_error = glGetError
363 assert gl_error == gl_NO_ERROR else print gl_error
364
365 # Assign all textures to a unit
366 glActiveTexture gl_TEXTURE0
367 glBindTexture(gl_TEXTURE_2D, texture_earth.gl_texture)
368
369 glActiveTexture gl_TEXTURE1
370 glBindTexture(gl_TEXTURE_2D, texture_seas.gl_texture)
371
372 glActiveTexture gl_TEXTURE2
373 glBindTexture(gl_TEXTURE_2D, texture_night.gl_texture)
374
375 glActiveTexture gl_TEXTURE3
376 glBindTexture(gl_TEXTURE_2D, texture_elevation.gl_texture)
377
378 glActiveTexture gl_TEXTURE4
379 glBindTexture(gl_TEXTURE_2D, texture_clouds.gl_texture)
380
381 gl_error = glGetError
382 assert gl_error == gl_NO_ERROR else print gl_error
383
384 # Constant program values
385 program.coord.array_enabled = true
386 program.tex_coord.array_enabled = true
387 program.normal.array_enabled = true
388
389 program.scale.uniform 1.0
390
391 program.use_texture.uniform true
392
393 program.tex_shiny.uniform 1
394 program.tex_night.uniform 2
395 program.tex_displace.uniform 3
396
397 gl_error = glGetError
398 assert gl_error == gl_NO_ERROR else print gl_error
399 end
400
401 private var clock = new Clock
402
403 redef fun frame_core(display)
404 do
405 var t = clock.total.to_f/3.0 + 0.75*pi
406
407 var gl_error = glGetError
408 assert gl_error == gl_NO_ERROR else print gl_error
409
410 glClear(gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT)
411 program.use
412
413 # Rotate world
414 # FIXME do this cleanly by moving the camera
415 var mvp = new Matrix.rotation(t, 0.0, 1.0, 0.0) * camera.mvp_matrix
416 program.mvp.uniform mvp
417 program.camera.uniform(-t.sin, -0.8, -t.cos)
418
419 # Planet
420 program.coord.array(planet.vertices, 3)
421 program.tex_coord.array(planet.texture_coords, 2)
422 program.normal.array(planet.normals, 3)
423 program.tex.uniform 0
424 program.use_texture.uniform true
425 program.color.uniform(1.0, 1.0, 1.0, 1.0)
426 program.is_surface.uniform true
427 glDrawElements(gl_TRIANGLES, planet.indices.length, gl_UNSIGNED_SHORT, planet.indices_c.native_array)
428
429 # Clouds
430 program.coord.array(clouds.vertices, 3)
431 program.tex_coord.array(clouds.texture_coords, 2)
432 program.normal.array(clouds.normals, 3)
433 program.tex.uniform 4
434 program.color.uniform(1.0, 1.0, 1.0, 0.5) # Half transparency
435 program.is_surface.uniform false
436 glDrawElements(gl_TRIANGLES, clouds.indices.length, gl_UNSIGNED_SHORT, clouds.indices_c.native_array)
437
438 # Atmo
439 program.coord.array(atmo.vertices, 3)
440 program.tex_coord.array(atmo.texture_coords, 2)
441 program.normal.array(atmo.normals, 3)
442 program.color.uniform(0.0, 0.8, 1.0, 0.02) # Blueish
443 program.is_surface.uniform false
444 program.use_texture.uniform false
445 glDrawElements(gl_TRIANGLES, atmo.indices.length, gl_UNSIGNED_SHORT, atmo.indices_c.native_array)
446 assert program.use_texture.is_active
447
448 display.flip
449
450 gl_error = glGetError
451 assert gl_error == gl_NO_ERROR else print gl_error
452 end
453
454 redef fun on_stop
455 do
456 # Clean up
457 program.delete
458
459 # Close gamnit
460 var display = display
461 if display != null then display.close
462 end
463 end