# 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. # Portable app using gamnit textures and programs with custom calls to OpenGL ES 2.0 module globe is app_name "gamnit Globe" app_namespace "org.nitlanguage.globe" app_version(1, 0, git_revision) android_manifest_activity """android:screenOrientation="portrait"""" android_api_target 15 end import realtime import matrix::projection import gamnit import gamnit::cameras import gamnit::limit_fps private import more_collections # Graphical program to draw a planet with Phong lighting class GlobeProgram 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; // Vertex normal attribute vec3 normal; // Model view projection matrix uniform mat4 mvp; // Texture of surface elevation to displace vertices uniform sampler2D tex_displace; // Draw this as a planet surface? uniform bool is_surface; // Output for the fragment shader varying vec4 v_color; varying vec2 v_tex_coord; varying vec3 v_normal; void main() { // Pass varyings to the fragment shader v_color = color; v_tex_coord = tex_coord; v_normal = normal; // Apply bump map float s = scale; if (is_surface) s += 0.05 * texture2D(tex_displace, tex_coord).r; gl_Position = (vec4(coord.xyz * s, 1.0) + translation) * mvp; } """ @ glsl_vertex_shader redef var fragment_shader_source = """ precision mediump float; // Input from the vertex shader varying vec4 v_color; varying vec2 v_tex_coord; varying vec3 v_normal; // Coordinates of the camera uniform vec3 camera; // Does this object use a texture? uniform bool use_texture; // Texture to apply on this object uniform sampler2D tex; // Reflection map to apply the the phong logic uniform sampler2D tex_shiny; // Texture for the dark side of the earth uniform sampler2D tex_night; // Draw this as a planet surface? uniform bool is_surface; // Lights config // TODO configure from outside the shader vec3 light_dir = normalize(vec3(-1.0, 0.0, -1.0)); vec4 ambient_color = vec4(0.2, 0.2, 0.2, 1.0); vec4 diffuse_color = vec4(1.0, 1.0, 1.0, 1.0); vec4 specular_color = vec4(1.0, 1.0, 1.0, 1.0); void main() { if(use_texture) { gl_FragColor = v_color * texture2D(tex, v_tex_coord); } else { gl_FragColor = v_color; } // Lambert diffusion float lambert = max(dot(light_dir, v_normal), 0.0); // Phong specular float specular = 0.0; if (lambert > 0.0) { vec3 to_camera = normalize(camera); vec3 light_reflect = reflect(light_dir, v_normal); float specularAngle = max(dot(light_reflect, to_camera), 0.0); specular = pow(specularAngle, 16.0); if (is_surface) specular *= texture2D(tex_shiny, v_tex_coord).x; else specular *= 0.2; } gl_FragColor *= ambient_color + lambert * diffuse_color; gl_FragColor += specular * specular_color; if (is_surface && lambert < 0.2) { // Show city lights at night float p_night = (0.2 - lambert) * 5.0; gl_FragColor += p_night*texture2D(tex_night, v_tex_coord); } } """ @ glsl_fragment_shader # Vertices coordinates var coord = attributes["coord"].as(AttributeVec4) is lazy # Color tint per vertex var color = attributes["color"].as(AttributeVec4) is lazy # Scaling per vertex var scale = attributes["scale"].as(AttributeFloat) is lazy # Coordinates on the textures, per vertex var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy # Normal per vertex var normal = attributes["normal"].as(AttributeVec3) is lazy # Model view projection matrix var mvp = uniforms["mvp"].as(UniformMat4) is lazy # Should this program use the texture `tex`? var use_texture = uniforms["use_texture"].as(UniformBool) is lazy # Main visible texture unit var tex = uniforms["tex"].as(UniformSampler2D) is lazy # Texture unit for reflection effect var tex_shiny = uniforms["tex_shiny"].as(UniformSampler2D) is lazy # Texture of the earth at night var tex_night = uniforms["tex_night"].as(UniformSampler2D) is lazy # Texture with elevation data var tex_displace = uniforms["tex_displace"].as(UniformSampler2D) is lazy # Position of the camera var camera = uniforms["camera"].as(UniformVec3) is lazy # Execute this program to draw a planet surface? var is_surface = uniforms["is_surface"].as(UniformBool) is lazy end # Mesh with all information for drawing class Mesh # Vertices coordinates var vertices: Array[Float] # Indices to draw triangles var indices: Array[Int] private var indices_c = new CUInt16Array.from(indices) is lazy # Normals on each vertex var normals: Array[Float] # Coordinates on the texture per vertex var texture_coords: Array[Float] # Create an UV sphere of `radius` with `n_meridians` and `n_parallels` init uv_sphere(radius: Float, n_meridians, n_parallels: Int) do var w = n_meridians var h = n_parallels var vertices = new Array[Float].with_capacity(w*h*3) var texture_coords = new Array[Float].with_capacity(w*h*2) var normals = new Array[Float].with_capacity(w*h*3) # Build vertices for m in [0..w[ do for p in [0..h[ do var u = m.to_f * 2.0 * pi / (w-1).to_f var v = p.to_f * pi / (h-1).to_f vertices.add radius * u.cos * v.sin vertices.add radius * v.cos vertices.add radius * u.sin * v.sin texture_coords.add (1.0 - m.to_f/(w-1).to_f) texture_coords.add(p.to_f/(h-1).to_f) normals.add u.cos * v.sin normals.add v.cos normals.add u.sin * v.sin end end # Build faces var indices = new Array[Int].with_capacity((w-1)*(h-1)*6) for m in [0..w-1[ do for p in [0..h-1[ do var a = m*h + p indices.add a indices.add(a+h) indices.add(a+1) indices.add(a+h) indices.add(a+h+1) indices.add(a+1) end end init(vertices, indices, normals, texture_coords) end end # Number of vertices to create parallels, meridians are double of this # # The minimum should be 3 for an octahedron planet. fun n_parallels: Int do return 25 redef class GamnitAssetTexture # All images are either within the hd or ld folder redef fun path do return app.definition / super end redef class App # Earth var planet = new Mesh.uv_sphere(2.0, 2*n_parallels, n_parallels) # Cloud layer var clouds = new Mesh.uv_sphere(2.08, 2*n_parallels, n_parallels) # Visible atmosphere var atmo = new Mesh.uv_sphere(2.16, 2*n_parallels, n_parallels) # Program for the graphic card var program = new GlobeProgram private var definition: String is lazy do var max_texture_size = glGetIntegerv(gl_MAX_TEXTURE_SIZE) # HD textures max sizes reange from 2048 px to 5400 px if max_texture_size < 5400 then return "ld" else return "hd" end # Texture of the reflexive surface of the earth (with the seas in white) var texture_seas = new Texture("seas.jpg") # Texture of the surface of the earth var texture_earth = new Texture("earth.jpg") # Texture of the lights at night on earth var texture_night = new Texture("lights.jpg") # Elevation map of earth var texture_elevation = new Texture("elevation.jpg") # Texture of the clouds above the earth var texture_clouds = new Texture("clouds.png") # Camera for the only view var camera: EulerCamera is lazy do var camera = new EulerCamera(app.display.as(not null)) camera.move(0.0, 0.1, -5.0) return camera end 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 = program program.compile_and_link var gamnit_error = program.error assert gamnit_error == null else print_error gamnit_error # Blend gl.capabilities.blend.enable glBlendFunc(gl_SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA) gl.capabilities.depth_test.enable glDepthFunc gl_LEQUAL glDepthMask true 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 gl_error = glGetError assert gl_error == gl_NO_ERROR else print gl_error program.use glViewport(0, 0, display.width, display.height) glClearColor(0.0, 0.02, 0.0, 1.0) gl_error = glGetError assert gl_error == gl_NO_ERROR else print gl_error # Assign all textures to a unit glActiveTexture gl_TEXTURE0 glBindTexture(gl_TEXTURE_2D, texture_earth.gl_texture) glActiveTexture gl_TEXTURE1 glBindTexture(gl_TEXTURE_2D, texture_seas.gl_texture) glActiveTexture gl_TEXTURE2 glBindTexture(gl_TEXTURE_2D, texture_night.gl_texture) glActiveTexture gl_TEXTURE3 glBindTexture(gl_TEXTURE_2D, texture_elevation.gl_texture) glActiveTexture gl_TEXTURE4 glBindTexture(gl_TEXTURE_2D, texture_clouds.gl_texture) gl_error = glGetError assert gl_error == gl_NO_ERROR else print gl_error # Constant program values program.coord.array_enabled = true program.tex_coord.array_enabled = true program.normal.array_enabled = true program.scale.uniform 1.0 program.use_texture.uniform true program.tex_shiny.uniform 1 program.tex_night.uniform 2 program.tex_displace.uniform 3 gl_error = glGetError assert gl_error == gl_NO_ERROR else print gl_error end private var clock = new Clock redef fun frame_core do var display = display if display != null then var t = clock.total.to_f/3.0 + 0.75*pi var gl_error = glGetError assert gl_error == gl_NO_ERROR else print gl_error glClear(gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT) program.use # Rotate world # FIXME do this cleanly by moving the camera var mvp = new Matrix.rotation(t, 0.0, 1.0, 0.0) * camera.mvp_matrix program.mvp.uniform mvp program.camera.uniform(-t.sin, -0.8, -t.cos) # Planet program.coord.array(planet.vertices, 3) program.tex_coord.array(planet.texture_coords, 2) program.normal.array(planet.normals, 3) program.tex.uniform 0 program.use_texture.uniform true program.color.uniform(1.0, 1.0, 1.0, 1.0) program.is_surface.uniform true glDrawElements(gl_TRIANGLES, planet.indices.length, gl_UNSIGNED_SHORT, planet.indices_c.native_array) # Clouds program.coord.array(clouds.vertices, 3) program.tex_coord.array(clouds.texture_coords, 2) program.normal.array(clouds.normals, 3) program.tex.uniform 4 program.color.uniform(1.0, 1.0, 1.0, 0.5) # Half transparency program.is_surface.uniform false glDrawElements(gl_TRIANGLES, clouds.indices.length, gl_UNSIGNED_SHORT, clouds.indices_c.native_array) # Atmo program.coord.array(atmo.vertices, 3) program.tex_coord.array(atmo.texture_coords, 2) program.normal.array(atmo.normals, 3) program.color.uniform(0.0, 0.8, 1.0, 0.02) # Blueish program.is_surface.uniform false program.use_texture.uniform false glDrawElements(gl_TRIANGLES, atmo.indices.length, gl_UNSIGNED_SHORT, atmo.indices_c.native_array) assert program.use_texture.is_active display.flip gl_error = glGetError assert gl_error == gl_NO_ERROR else print gl_error end end redef fun on_stop do # Clean up program.delete # Close gamnit var display = display if display != null then display.close end end