From d7b8c0ede13a88b4994fbd39d614810ed4c47f34 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Alexis=20Laferri=C3=A8re?= Date: Fri, 20 May 2016 14:57:40 -0400 Subject: [PATCH] gamnit: intro selection services, `visible_at` and `visible_in_center` MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Alexis Laferrière --- lib/gamnit/depth/depth.nit | 3 +- lib/gamnit/depth/selection.nit | 321 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 lib/gamnit/depth/selection.nit diff --git a/lib/gamnit/depth/depth.nit b/lib/gamnit/depth/depth.nit index 12d0f15..9707779 100644 --- a/lib/gamnit/depth/depth.nit +++ b/lib/gamnit/depth/depth.nit @@ -18,6 +18,7 @@ module depth intrude import more_materials import more_models import particles +import selection redef class App @@ -30,7 +31,7 @@ redef class App world_camera.near = 0.1 # Prepare programs - var programs = [versatile_program, normals_program, explosion_program, smoke_program, static_program: GamnitProgram] + var programs = [versatile_program, normals_program, explosion_program, smoke_program, static_program, selection_program: GamnitProgram] for program in programs do program.compile_and_link var gamnit_error = program.error diff --git a/lib/gamnit/depth/selection.nit b/lib/gamnit/depth/selection.nit new file mode 100644 index 0000000..18744ee --- /dev/null +++ b/lib/gamnit/depth/selection.nit @@ -0,0 +1,321 @@ +# 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. + +# Select `Actor` from a screen coordinate +# +# The two main services are `App::visible_at` and ; App::visible_in_center`. +# +# This is implemented with simple pixel picking. +# This algorithm draws each actor in a unique color to the display buffer, +# using the color as an ID to detect which actor is visible at each pixel. +# +# It is implemented at the level of the material, +# so it can be applied to any _gamnit_ programs. +# However it is not optimal performance wise, +# so client programs should implement a more efficient algorithm. +# +# By default, the actors are drawn as opaque objects. +# This behavior can be refined, as does `TexturedMaterial` to use its +# `diffuse_texture` for partial opacity. +module selection + +# TODO support `sprites` and `ui_sprites` + +import more_materials +intrude import depth_core + +redef class App + + # Which `Actor` is at the center of the screen? + fun visible_in_center: nullable Actor + do + var display = display + assert display != null + return visible_at(display.width/2, display.height/2) + end + + # Which `Actor` is on screen at `x, y`? + fun visible_at(x, y: Numeric): nullable Actor + do + var display = display + assert display != null + + if not selection_calculated then draw_selection_screen + + x = x.to_i + y = y.to_i + y = display.height - y + + # Read selection values + var data = once new NativeCByteArray(4) + glReadPixels(x, y, 1, 1, gl_RGBA, gl_UNSIGNED_BYTE, data) + assert_no_gl_error + + var r = display.red_bits + var g = display.green_bits + var b = display.blue_bits + + # Rebuild ID from pixel color + var rv = data[0].to_i >> (8-r) + var gv = data[1].to_i >> (8-g) << (r) + var bv = data[2].to_i >> (8-b) << (r+g) + if data[0].to_i & (2**(8-r)-1) > (2**(8-r-1)) then rv += 1 + if data[1].to_i & (2**(8-g)-1) > (2**(8-g-1)) then gv += 1 << r + if data[2].to_i & (2**(8-b)-1) > (2**(8-b-1)) then bv += 1 << (r+g) + var id = rv + gv + bv + + # ID 0 is the background + if id == 0 then return null + + # Wrongful selection? This should not happen. + if not selection_map.keys.has(id) then + print_error "Gamnit Warning: Invalid selection {id}" + return null + end + + return selection_map[id] + end + + # Program drawing selection values to the buffer + var selection_program = new SelectionProgram + + # Map IDs to actors + private var selection_map = new Map[Int, Actor] + + # Is there a valid selection draw in the buffer? + private var selection_calculated = false + + # Draw the selection values to the buffer + private fun draw_selection_screen + do + selection_calculated = true + + app.selection_program.use + app.selection_program.mvp.uniform app.world_camera.mvp_matrix + + # Set aside previous buffer clear color + var user_r = glGetFloatv(gl_COLOR_CLEAR_VALUE, 0) + var user_g = glGetFloatv(gl_COLOR_CLEAR_VALUE, 1) + var user_b = glGetFloatv(gl_COLOR_CLEAR_VALUE, 2) + var user_a = glGetFloatv(gl_COLOR_CLEAR_VALUE, 3) + + glClearColor(0.0, 0.0, 0.0, 1.0) + glClear(gl_DEPTH_BUFFER_BIT | gl_COLOR_BUFFER_BIT) + + # TODO restrict the list of actors with a valid ID, maybe with an `active_actors` list? + + var id = 1 + for actor in actors do + selection_map[id] = actor + for leaf in actor.model.leaves do + leaf.material.draw_selection(actor, leaf, id) + end + + id += 1 + #id += 100 # Debug + end + + # Debug, show the selection values for half a second + #display.flip + #0.5.sleep + + glClearColor(user_r, user_g, user_b, user_a) + end + + redef fun frame_core(display) + do + super + + # Invalidate the selection values + selection_calculated = false + end +end + +redef class Material + + # Draw `actor` to selection values + protected fun draw_selection(actor: Actor, model: LeafModel, id: Int) + do + var program = app.selection_program + var mesh = model.mesh + + draw_selection_texture(actor, model) + + program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0) + program.scale.uniform actor.scale + + program.coord.array_enabled = true + program.coord.array(mesh.vertices, 3) + program.rotation.uniform new Matrix.rotation(actor.rotation, 0.0, 1.0, 0.0) + + var display = app.display + assert display != null + var r = display.red_bits + var g = display.green_bits + var b = display.blue_bits + + # Build ID as a color + var p1 = id & ((2**r)-1) + var p2 = id >> r & ((2**g)-1) + var p3 = id >> (r+g) & ((2**b)-1) + program.color_id.uniform( + p1.to_f/((2**r)-1).to_f, + p2.to_f/((2**g)-1).to_f, + p3.to_f/((2**b)-1).to_f, 1.0) + + if mesh.indices.is_empty then + glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3) + else + glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array) + end + end + + private fun draw_selection_texture(actor: Actor, model: LeafModel) + do + var program = app.selection_program + program.use_map_diffuse.uniform false + end +end + +redef class TexturedMaterial + redef fun draw_selection_texture(actor, model) + do + var program = app.selection_program + var mesh = model.mesh + + # One of the textures used, if any + var sample_used_texture = null + var texture = diffuse_texture + if texture != null then + glActiveTexture gl_TEXTURE1 + glBindTexture(gl_TEXTURE_2D, texture.gl_texture) + program.use_map_diffuse.uniform true + program.map_diffuse.uniform 1 + sample_used_texture = texture + else + program.use_map_diffuse.uniform false + end + + # If using a texture, set `texture_coords` + program.tex_coord.array_enabled = sample_used_texture != null + if sample_used_texture != null then + if sample_used_texture isa GamnitRootTexture then + # Coordinates are directly valid + program.tex_coord.array(mesh.texture_coords, 2) + else + # Correlate texture coordinates from the subtexture sand the mesh. + # This is slow, but should be cached on the GPU. + var xa = sample_used_texture.offset_left + var xd = sample_used_texture.offset_right - xa + var ya = sample_used_texture.offset_top + var yd = sample_used_texture.offset_bottom - ya + + var tex_coords = new Array[Float].with_capacity(mesh.texture_coords.length) + for i in [0..mesh.texture_coords.length/2[ do + tex_coords[i*2] = xa + xd * mesh.texture_coords[i*2] + tex_coords[i*2+1] = ya + yd * mesh.texture_coords[i*2+1] + end + + program.tex_coord.array(tex_coords, 2) + end + end + end +end + +# Program to draw selection values +class SelectionProgram + super GamnitProgramFromSource + + redef var vertex_shader_source = """ + // Vertex coordinates + attribute vec4 coord; + + // Vertex translation + uniform vec4 translation; + + // Vertex scaling + uniform float scale; + + // Vertex coordinates on textures + attribute vec2 tex_coord; + + // Model view projection matrix + uniform mat4 mvp; + + // Model rotation + uniform mat4 rotation; + + // Output for the fragment shader + varying vec2 v_tex_coord; + + void main() + { + v_tex_coord = vec2(tex_coord.x, 1.0 - tex_coord.y); + + gl_Position = (vec4(coord.xyz * scale, 1.0) * rotation + translation) * mvp; + } + """ @ glsl_vertex_shader + + # + redef var fragment_shader_source = """ + precision highp float; + + varying vec2 v_tex_coord; + + // Map used as reference for opacity + uniform sampler2D map_diffuse; + + // Should `map_diffuse` be used? + uniform bool use_map_diffuse; + + // Color ID + uniform vec4 color; + + void main() + { + gl_FragColor = vec4(color.rgb, 1.0); + + if (use_map_diffuse && texture2D(map_diffuse, v_tex_coord).a < 0.1) { + gl_FragColor.a = 0.0; + } + } + """ @ glsl_fragment_shader + + # Vertices coordinates + var coord = attributes["coord"].as(AttributeVec4) is lazy + + # Should this program use the texture `map_diffuse`? + var use_map_diffuse = uniforms["use_map_diffuse"].as(UniformBool) is lazy + + # Diffuse texture unit + var map_diffuse = uniforms["map_diffuse"].as(UniformSampler2D) is lazy + + # Coordinates on the textures, per vertex + var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy + + # Translation applied to each vertex + var translation = uniforms["translation"].as(UniformVec4) is lazy + + # Rotation matrix + var rotation = uniforms["rotation"].as(UniformMat4) is lazy + + # Scaling per vertex + var scale = uniforms["scale"].as(UniformFloat) is lazy + + # Model view projection matrix + var mvp = uniforms["mvp"].as(UniformMat4) is lazy + + # ID as a color + var color_id = uniforms["color"].as(UniformVec4) is lazy +end -- 1.7.9.5