a5a9a6299c90fce50d98a3aa14d25ad969ff8f6a
[nit.git] / contrib / model_viewer / 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 # Intro a custom model, material and graphics program to draw a globe
16 module globe
17
18 intrude import gamnit::depth # To access `Mesh::indices_c`
19
20 # Number of vertices to create parallels, meridians are double of this
21 #
22 # The minimum should be 3 for an octahedron planet.
23 fun n_parallels: Int do return 25
24
25 redef class App
26
27 # Program for the graphic card
28 private var globe_program = new GlobeProgram
29
30 # Texture of the reflexive surface of the earth (with the seas in white)
31 private var texture_seas = new Texture("globe/seas.jpg")
32
33 # Texture of the surface of the earth
34 private var texture_earth = new Texture("globe/earth.jpg")
35
36 # Texture of the lights at night on earth
37 private var texture_night = new Texture("globe/lights.jpg")
38
39 # Elevation map of earth
40 private var texture_elevation = new Texture("globe/elevation.jpg")
41
42 # Texture of the clouds above the earth
43 private var texture_clouds = new Texture("globe/clouds.png")
44
45 redef fun on_create
46 do
47 super
48
49 # Compile globe_program
50 var program = app.globe_program
51 program.compile_and_link
52
53 # Catch any errors
54 var gamnit_error = program.error
55 assert gamnit_error == null else print_error gamnit_error
56 end
57 end
58
59 # Full model of a globe, with a surface, clouds layer and atmosphere
60 class GlobeModel
61 super CompositeModel
62
63 redef fun load
64 do
65 leaves.add new LeafModel(
66 new UVSphere(1.0, 2*n_parallels, n_parallels),
67 new GlobeMaterial.surface)
68 leaves.add new LeafModel(
69 new UVSphere(1.1, 2*n_parallels, n_parallels),
70 new GlobeMaterial.clouds)
71 leaves.add new LeafModel(
72 new UVSphere(1.2, 2*n_parallels, n_parallels),
73 new GlobeMaterial.atmo)
74 end
75 end
76
77 # Parameterizable material to draw the 3 layers of the globe
78 class GlobeMaterial
79 super Material
80
81 # Id of the texture for diffuse colors, if any
82 var texture_id: nullable Int
83
84 # Draw as a surface, using the elevation map and the night lights
85 var is_surface: Bool
86
87 # Ambient color
88 var color: Array[Float]
89
90 # Create and configure a material for the earth surface
91 init surface do init(0, true, [1.0, 1.0, 1.0, 1.0])
92
93 # Create and configure a material for the cloud layer
94 init clouds do init(4, false, [1.0, 1.0, 1.0, 0.5])
95
96 # Create and configure a material for the visible atmosphere
97 init atmo do init(null, false, [0.0, 0.8, 1.0, 0.05])
98
99 redef fun draw(actor, model)
100 do
101 var gl_error = glGetError
102 assert gl_error == gl_NO_ERROR else print gl_error
103
104 var mesh = model.mesh
105
106 var program = app.globe_program
107 program.use
108
109 # Set constant program values
110 program.use
111
112 # Bind textures
113 glActiveTexture gl_TEXTURE0
114 glBindTexture(gl_TEXTURE_2D, app.texture_earth.gl_texture)
115
116 glActiveTexture gl_TEXTURE1
117 glBindTexture(gl_TEXTURE_2D, app.texture_seas.gl_texture)
118
119 glActiveTexture gl_TEXTURE2
120 glBindTexture(gl_TEXTURE_2D, app.texture_night.gl_texture)
121
122 glActiveTexture gl_TEXTURE3
123 glBindTexture(gl_TEXTURE_2D, app.texture_elevation.gl_texture)
124
125 glActiveTexture gl_TEXTURE4
126 glBindTexture(gl_TEXTURE_2D, app.texture_clouds.gl_texture)
127
128 # Set samplers
129 program.tex_specular.uniform 1
130 program.tex_night.uniform 2
131 program.tex_displace.uniform 3
132
133 # Update camera view and light
134 var p = app.world_camera.position
135 program.camera.uniform(p.x, p.y, p.z)
136 program.mvp.uniform app.world_camera.mvp_matrix
137 program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
138
139 # Set attributes
140 program.coord.array_enabled = true
141 program.coord.array(mesh.vertices, 3)
142
143 program.tex_coord.array_enabled = true
144 program.tex_coord.array(mesh.texture_coords, 2)
145
146 program.normal.array_enabled = true
147 program.normal.array(mesh.normals, 3)
148
149 # Set uniforms
150 program.scale.uniform 1.0
151 program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
152 program.translation.uniform(actor.center.x, -actor.center.y, actor.center.z, 0.0)
153 program.color.uniform(color[0], color[1], color[2], color[3])
154 program.is_surface.uniform is_surface
155
156 # Set the color texture?
157 var tex = texture_id
158 if tex == null then
159 program.use_texture.uniform false
160 else
161 program.use_texture.uniform true
162 program.tex.uniform tex
163 end
164
165 # Execute draw, support only meshes with `indices`
166 glDrawElements(gl_TRIANGLES, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
167
168 # Catch any errors
169 gl_error = glGetError
170 assert gl_error == gl_NO_ERROR else print gl_error
171 end
172 end
173
174 # Graphical program to draw a planet with Phong lighting
175 class GlobeProgram
176 super GamnitProgramFromSource
177
178 redef var vertex_shader_source = """
179 // Vertex coordinates
180 attribute vec4 coord;
181
182 // Vertex color tint
183 uniform vec4 color;
184
185 // Vertex translation
186 uniform vec4 translation;
187
188 // Vertex scaling
189 uniform float scale;
190
191 // Vertex coordinates on textures
192 attribute vec2 tex_coord;
193
194 // Vertex normal
195 attribute vec3 normal;
196
197 // Model view projection matrix
198 uniform mat4 mvp;
199
200 // Model rotation
201 uniform mat4 rotation;
202
203 // Lights config
204 uniform vec3 light_center;
205
206 // Texture of surface elevation to displace vertices
207 uniform sampler2D tex_displace;
208
209 // Draw this as a planet surface?
210 uniform bool is_surface;
211
212 // Output for the fragment shader
213 varying vec4 v_color;
214 varying vec2 v_tex_coord;
215 varying vec3 v_normal;
216 varying vec4 v_light_center;
217
218 void main()
219 {
220 v_color = color;
221 v_tex_coord = tex_coord;
222 v_normal = normalize(vec4(normal, 0.0) * rotation * mvp).xyz;
223 v_light_center = vec4(light_center, 0.0) * mvp;
224
225 // Apply displacement map
226 float s = scale;
227 if (is_surface)
228 s += 0.05 * texture2D(tex_displace, tex_coord).r;
229
230 gl_Position = (vec4(coord.xyz * s, 1.0) * rotation + translation) * mvp;
231
232 }
233 """ @ glsl_vertex_shader
234
235 redef var fragment_shader_source = """
236 precision mediump float;
237
238 // Input from the vertex shader
239 varying vec4 v_color;
240 varying vec2 v_tex_coord;
241 varying vec3 v_normal;
242 varying vec4 v_light_center;
243
244 // Coordinates of the camera
245 uniform vec3 camera;
246
247 // Does this object use a texture?
248 uniform bool use_texture;
249
250 // Texture to apply on this object
251 uniform sampler2D tex;
252
253 // Reflection map to apply to the Phong logic
254 uniform sampler2D tex_specular;
255
256 // Texture for the dark side of the earth
257 uniform sampler2D tex_night;
258
259 // Draw this as a planet surface?
260 uniform bool is_surface;
261
262 // Colors config
263 vec4 ambient_color = vec4(0.2, 0.2, 0.2, 1.0);
264 vec4 diffuse_color = vec4(1.0, 1.0, 1.0, 1.0);
265 vec4 specular_color = vec4(1.0, 1.0, 1.0, 1.0);
266
267 void main()
268 {
269 // Lambert diffusion
270 vec3 to_light = normalize(v_light_center.xyz);
271 float lambert = max(dot(to_light, v_normal), 0.0);
272
273 // Phong specular
274 float specular = 0.0;
275 if (lambert > 0.0) {
276 vec3 to_camera = normalize(camera);
277 vec3 normal = normalize(v_normal);
278 vec3 light_reflect = reflect(to_light, normal);
279 float spec_angle = max(dot(light_reflect, to_camera), 0.0);
280 specular = pow(spec_angle, 16.0);
281
282 if (is_surface)
283 specular *= texture2D(tex_specular, v_tex_coord).x;
284 else specular *= 0.2;
285 }
286
287 if(use_texture) {
288 gl_FragColor = v_color * texture2D(tex, v_tex_coord);
289 } else {
290 gl_FragColor = v_color;
291 }
292
293 gl_FragColor *= ambient_color + lambert * diffuse_color;
294 gl_FragColor += specular * specular_color;
295
296 if (is_surface && lambert < 0.2) {
297 // Show city lights at night
298 float p_night = (0.2 - lambert) * 5.0;
299 gl_FragColor += p_night*texture2D(tex_night, v_tex_coord);
300 }
301 }
302 """ @ glsl_fragment_shader
303
304 # Vertices coordinates
305 var coord = attributes["coord"].as(AttributeVec4) is lazy
306
307 # Color tint per vertex
308 var color = uniforms["color"].as(UniformVec4) is lazy
309
310 # Scaling per vertex
311 var scale = uniforms["scale"].as(UniformFloat) is lazy
312
313 # Coordinates on the textures, per vertex
314 var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
315
316 # Normal per vertex
317 var normal = attributes["normal"].as(AttributeVec3) is lazy
318
319 # Model view projection matrix
320 var mvp = uniforms["mvp"].as(UniformMat4) is lazy
321
322 # Should this program use the texture `tex`?
323 var use_texture = uniforms["use_texture"].as(UniformBool) is lazy
324
325 # Main visible texture unit
326 var tex = uniforms["tex"].as(UniformSampler2D) is lazy
327
328 # Texture unit for reflection effect
329 var tex_specular = uniforms["tex_specular"].as(UniformSampler2D) is lazy
330
331 # Texture of the earth at night
332 var tex_night = uniforms["tex_night"].as(UniformSampler2D) is lazy
333
334 # Texture with elevation data
335 var tex_displace = uniforms["tex_displace"].as(UniformSampler2D) is lazy
336
337 # Position of the camera
338 var camera = uniforms["camera"].as(UniformVec3) is lazy
339
340 # Execute this program to draw a planet surface?
341 var is_surface = uniforms["is_surface"].as(UniformBool) is lazy
342
343 # Translation applied to each vertex
344 var translation = uniforms["translation"].as(UniformVec4) is lazy
345
346 # Rotation matrix
347 var rotation = uniforms["rotation"].as(UniformMat4) is lazy
348
349 # Center position of the light
350 var light_center = uniforms["light_center"].as(UniformVec3) is lazy
351 end