gamnit: remove `Gamnit` from `GamnitRootTexture` and `GamnitSubtexture`
[nit.git] / lib / gamnit / depth / selection.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 # Select `Actor` from a screen coordinate
16 #
17 # The two main services are `App::visible_at` and ; App::visible_in_center`.
18 #
19 # This is implemented with simple pixel picking.
20 # This algorithm draws each actor in a unique color to the display buffer,
21 # using the color as an ID to detect which actor is visible at each pixel.
22 #
23 # It is implemented at the level of the material,
24 # so it can be applied to any _gamnit_ programs.
25 # However it is not optimal performance wise,
26 # so client programs should implement a more efficient algorithm.
27 #
28 # By default, the actors are drawn as opaque objects.
29 # This behavior can be refined, as does `TexturedMaterial` to use its
30 # `diffuse_texture` for partial opacity.
31 module selection
32
33 # TODO support `sprites` and `ui_sprites`
34
35 import more_materials
36 intrude import depth_core
37
38 redef class App
39
40 # Which `Actor` is at the center of the screen?
41 fun visible_in_center: nullable Actor
42 do
43 var display = display
44 assert display != null
45 return visible_at(display.width/2, display.height/2)
46 end
47
48 # Which `Actor` is on screen at `x, y`?
49 fun visible_at(x, y: Numeric): nullable Actor
50 do
51 var display = display
52 assert display != null
53
54 if not selection_calculated then draw_selection_screen
55
56 x = x.to_i
57 y = y.to_i
58 y = display.height - y
59
60 # Read selection values
61 var data = once new NativeCByteArray(4)
62 glReadPixels(x, y, 1, 1, gl_RGBA, gl_UNSIGNED_BYTE, data)
63 assert_no_gl_error
64
65 var r = display.red_bits
66 var g = display.green_bits
67 var b = display.blue_bits
68
69 # Rebuild ID from pixel color
70 var rv = data[0].to_i >> (8-r)
71 var gv = data[1].to_i >> (8-g) << (r)
72 var bv = data[2].to_i >> (8-b) << (r+g)
73 if data[0].to_i & (2**(8-r)-1) > (2**(8-r-1)) then rv += 1
74 if data[1].to_i & (2**(8-g)-1) > (2**(8-g-1)) then gv += 1 << r
75 if data[2].to_i & (2**(8-b)-1) > (2**(8-b-1)) then bv += 1 << (r+g)
76 var id = rv + gv + bv
77
78 # ID 0 is the background
79 if id == 0 then return null
80
81 # Wrongful selection? This should not happen.
82 if not selection_map.keys.has(id) then
83 print_error "Gamnit Warning: Invalid selection {id}"
84 return null
85 end
86
87 return selection_map[id]
88 end
89
90 # Program drawing selection values to the buffer
91 var selection_program = new SelectionProgram
92
93 # Map IDs to actors
94 private var selection_map = new Map[Int, Actor]
95
96 # Is there a valid selection draw in the buffer?
97 private var selection_calculated = false
98
99 # Draw the selection values to the buffer
100 private fun draw_selection_screen
101 do
102 selection_calculated = true
103
104 app.selection_program.use
105 app.selection_program.mvp.uniform app.world_camera.mvp_matrix
106
107 # Set aside previous buffer clear color
108 var user_r = glGetFloatv(gl_COLOR_CLEAR_VALUE, 0)
109 var user_g = glGetFloatv(gl_COLOR_CLEAR_VALUE, 1)
110 var user_b = glGetFloatv(gl_COLOR_CLEAR_VALUE, 2)
111 var user_a = glGetFloatv(gl_COLOR_CLEAR_VALUE, 3)
112
113 glClearColor(0.0, 0.0, 0.0, 1.0)
114 glClear(gl_DEPTH_BUFFER_BIT | gl_COLOR_BUFFER_BIT)
115
116 # TODO restrict the list of actors with a valid ID, maybe with an `active_actors` list?
117
118 var id = 1
119 for actor in actors do
120 selection_map[id] = actor
121 for leaf in actor.model.leaves do
122 leaf.material.draw_selection(actor, leaf, id)
123 end
124
125 id += 1
126 #id += 100 # Debug
127 end
128
129 # Debug, show the selection values for half a second
130 #display.flip
131 #0.5.sleep
132
133 glClearColor(user_r, user_g, user_b, user_a)
134 end
135
136 redef fun frame_core(display)
137 do
138 super
139
140 # Invalidate the selection values
141 selection_calculated = false
142 end
143 end
144
145 redef class Material
146
147 # Draw `actor` to selection values
148 protected fun draw_selection(actor: Actor, model: LeafModel, id: Int)
149 do
150 var program = app.selection_program
151 var mesh = model.mesh
152
153 draw_selection_texture(actor, model)
154
155 program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
156 program.scale.uniform actor.scale
157
158 program.coord.array_enabled = true
159 program.coord.array(mesh.vertices, 3)
160 program.rotation.uniform new Matrix.rotation(actor.rotation, 0.0, 1.0, 0.0)
161
162 var display = app.display
163 assert display != null
164 var r = display.red_bits
165 var g = display.green_bits
166 var b = display.blue_bits
167
168 # Build ID as a color
169 var p1 = id & ((2**r)-1)
170 var p2 = id >> r & ((2**g)-1)
171 var p3 = id >> (r+g) & ((2**b)-1)
172 program.color_id.uniform(
173 p1.to_f/((2**r)-1).to_f,
174 p2.to_f/((2**g)-1).to_f,
175 p3.to_f/((2**b)-1).to_f, 1.0)
176
177 if mesh.indices.is_empty then
178 glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
179 else
180 glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
181 end
182 end
183
184 private fun draw_selection_texture(actor: Actor, model: LeafModel)
185 do
186 var program = app.selection_program
187 program.use_map_diffuse.uniform false
188 end
189 end
190
191 redef class TexturedMaterial
192 redef fun draw_selection_texture(actor, model)
193 do
194 var program = app.selection_program
195 var mesh = model.mesh
196
197 # One of the textures used, if any
198 var sample_used_texture = null
199 var texture = diffuse_texture
200 if texture != null then
201 glActiveTexture gl_TEXTURE1
202 glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
203 program.use_map_diffuse.uniform true
204 program.map_diffuse.uniform 1
205 sample_used_texture = texture
206 else
207 program.use_map_diffuse.uniform false
208 end
209
210 # If using a texture, set `texture_coords`
211 program.tex_coord.array_enabled = sample_used_texture != null
212 if sample_used_texture != null then
213 if sample_used_texture isa RootTexture then
214 # Coordinates are directly valid
215 program.tex_coord.array(mesh.texture_coords, 2)
216 else
217 # Correlate texture coordinates from the subtexture sand the mesh.
218 # This is slow, but should be cached on the GPU.
219 var xa = sample_used_texture.offset_left
220 var xd = sample_used_texture.offset_right - xa
221 var ya = sample_used_texture.offset_top
222 var yd = sample_used_texture.offset_bottom - ya
223
224 var tex_coords = new Array[Float].with_capacity(mesh.texture_coords.length)
225 for i in [0..mesh.texture_coords.length/2[ do
226 tex_coords[i*2] = xa + xd * mesh.texture_coords[i*2]
227 tex_coords[i*2+1] = ya + yd * mesh.texture_coords[i*2+1]
228 end
229
230 program.tex_coord.array(tex_coords, 2)
231 end
232 end
233 end
234 end
235
236 # Program to draw selection values
237 class SelectionProgram
238 super GamnitProgramFromSource
239
240 redef var vertex_shader_source = """
241 // Vertex coordinates
242 attribute vec4 coord;
243
244 // Vertex translation
245 uniform vec4 translation;
246
247 // Vertex scaling
248 uniform float scale;
249
250 // Vertex coordinates on textures
251 attribute vec2 tex_coord;
252
253 // Model view projection matrix
254 uniform mat4 mvp;
255
256 // Model rotation
257 uniform mat4 rotation;
258
259 // Output for the fragment shader
260 varying vec2 v_tex_coord;
261
262 void main()
263 {
264 v_tex_coord = vec2(tex_coord.x, 1.0 - tex_coord.y);
265
266 gl_Position = (vec4(coord.xyz * scale, 1.0) * rotation + translation) * mvp;
267 }
268 """ @ glsl_vertex_shader
269
270 #
271 redef var fragment_shader_source = """
272 precision highp float;
273
274 varying vec2 v_tex_coord;
275
276 // Map used as reference for opacity
277 uniform sampler2D map_diffuse;
278
279 // Should `map_diffuse` be used?
280 uniform bool use_map_diffuse;
281
282 // Color ID
283 uniform vec4 color;
284
285 void main()
286 {
287 gl_FragColor = vec4(color.rgb, 1.0);
288
289 if (use_map_diffuse && texture2D(map_diffuse, v_tex_coord).a < 0.1) {
290 gl_FragColor.a = 0.0;
291 }
292 }
293 """ @ glsl_fragment_shader
294
295 # Vertices coordinates
296 var coord = attributes["coord"].as(AttributeVec4) is lazy
297
298 # Should this program use the texture `map_diffuse`?
299 var use_map_diffuse = uniforms["use_map_diffuse"].as(UniformBool) is lazy
300
301 # Diffuse texture unit
302 var map_diffuse = uniforms["map_diffuse"].as(UniformSampler2D) is lazy
303
304 # Coordinates on the textures, per vertex
305 var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
306
307 # Translation applied to each vertex
308 var translation = uniforms["translation"].as(UniformVec4) is lazy
309
310 # Rotation matrix
311 var rotation = uniforms["rotation"].as(UniformMat4) is lazy
312
313 # Scaling per vertex
314 var scale = uniforms["scale"].as(UniformFloat) is lazy
315
316 # Model view projection matrix
317 var mvp = uniforms["mvp"].as(UniformMat4) is lazy
318
319 # ID as a color
320 var color_id = uniforms["color"].as(UniformVec4) is lazy
321 end