Actor
from a screen coordinateThe 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.
gamnit :: selection $ TexturedMaterial
Material with potentialdiffuse_texture
and specular_texture
gamnit :: selection $ TexturedMaterial
Material with potentialdiffuse_texture
and specular_texture
accept_scroll_and_zoom
Serializable::inspect
to show more useful information
more_collections :: more_collections
Highly specific, but useful, collections-related classes.performance_analysis :: performance_analysis
Services to gather information on the performance of events by categoriesserialization :: serialization_core
Abstract services to serialize Nit objects to different formatscore :: union_find
union–find algorithm using an efficient disjoint-set data structureEulerCamera
and App::frame_core_draw
to get a stereoscopic view
# 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.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
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 RootTexture 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
lib/gamnit/depth/selection.nit:15,1--321,3