1 # This file is part of NIT ( http://www.nitlanguage.org ).
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 # Select `Actor` from a screen coordinate
17 # The two main services are `App::visible_at` and ; App::visible_in_center`.
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.
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.
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.
33 # TODO support `sprites` and `ui_sprites`
36 intrude import depth_core
40 # Which `Actor` is at the center of the screen?
41 fun visible_in_center
: nullable Actor
44 assert display
!= null
45 return visible_at
(display
.width
/2, display
.height
/2)
48 # Which `Actor` is on screen at `x, y`?
49 fun visible_at
(x
, y
: Numeric): nullable Actor
52 assert display
!= null
54 if not selection_calculated
then draw_selection_screen
58 y
= display
.height
- y
60 # Read selection values
61 var data
= once
new NativeCByteArray(4)
62 glReadPixels
(x
, y
, 1, 1, gl_RGBA
, gl_UNSIGNED_BYTE
, data
)
65 var r
= display
.red_bits
66 var g
= display
.green_bits
67 var b
= display
.blue_bits
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
)
78 # ID 0 is the background
79 if id
== 0 then return null
81 # Wrongful selection? This should not happen.
82 if not selection_map
.keys
.has
(id
) then
83 print_error
"Gamnit Warning: Invalid selection {id}"
87 return selection_map
[id
]
90 # Program drawing selection values to the buffer
91 var selection_program
= new SelectionProgram
94 private var selection_map
= new Map[Int, Actor]
96 # Is there a valid selection draw in the buffer?
97 private var selection_calculated
= false
99 # Draw the selection values to the buffer
100 private fun draw_selection_screen
102 selection_calculated
= true
104 app
.selection_program
.use
105 app
.selection_program
.mvp
.uniform app
.world_camera
.mvp_matrix
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)
113 glClearColor
(0.0, 0.0, 0.0, 1.0)
114 glClear
(gl_DEPTH_BUFFER_BIT
| gl_COLOR_BUFFER_BIT
)
116 # TODO restrict the list of actors with a valid ID, maybe with an `active_actors` list?
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
)
129 # Debug, show the selection values for half a second
133 glClearColor
(user_r
, user_g
, user_b
, user_a
)
136 redef fun frame_core
(display
)
140 # Invalidate the selection values
141 selection_calculated
= false
147 # Draw `actor` to selection values
148 protected fun draw_selection
(actor
: Actor, model
: LeafModel, id
: Int)
150 var program
= app
.selection_program
151 var mesh
= model
.mesh
153 draw_selection_texture
(actor
, model
)
155 program
.translation
.uniform
(actor
.center
.x
, actor
.center
.y
, actor
.center
.z
, 0.0)
156 program
.scale
.uniform actor
.scale
158 program
.coord
.array_enabled
= true
159 program
.coord
.array
(mesh
.vertices
, 3)
160 program
.rotation
.uniform
new Matrix.gamnit_euler_rotation
(actor
.pitch
, actor
.yaw
, actor
.roll
)
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
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)
177 if mesh
.indices
.is_empty
then
178 glDrawArrays
(mesh
.draw_mode
, 0, mesh
.vertices
.length
/3)
180 glDrawElements
(mesh
.draw_mode
, mesh
.indices
.length
, gl_UNSIGNED_SHORT
, mesh
.indices_c
.native_array
)
184 private fun draw_selection_texture
(actor
: Actor, model
: LeafModel)
186 var program
= app
.selection_program
187 program
.use_map_diffuse
.uniform
false
191 redef class TexturedMaterial
192 redef fun draw_selection_texture
(actor
, model
)
194 var program
= app
.selection_program
195 var mesh
= model
.mesh
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
207 program
.use_map_diffuse
.uniform
false
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)
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
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]
230 program
.tex_coord
.array
(tex_coords
, 2)
236 # Program to draw selection values
237 class SelectionProgram
238 super GamnitProgramFromSource
240 redef var vertex_shader_source
= """
241 // Vertex coordinates
242 attribute vec4 coord;
244 // Vertex translation
245 uniform vec4 translation;
250 // Vertex coordinates on textures
251 attribute vec2 tex_coord;
253 // Model view projection matrix
257 uniform mat4 rotation;
259 // Output for the fragment shader
260 varying vec2 v_tex_coord;
264 v_tex_coord = vec2(tex_coord.x, 1.0 - tex_coord.y);
266 gl_Position = (vec4(coord.xyz * scale, 1.0) * rotation + translation) * mvp;
268 """ @ glsl_vertex_shader
271 redef var fragment_shader_source
= """
272 precision highp float;
274 varying vec2 v_tex_coord;
276 // Map used as reference for opacity
277 uniform sampler2D map_diffuse;
279 // Should `map_diffuse` be used?
280 uniform bool use_map_diffuse;
287 gl_FragColor = vec4(color.rgb, 1.0);
289 if (use_map_diffuse && texture2D(map_diffuse, v_tex_coord).a < 0.1) {
290 gl_FragColor.a = 0.0;
293 """ @ glsl_fragment_shader
295 # Vertices coordinates
296 var coord
= attributes
["coord"].as(AttributeVec4) is lazy
298 # Should this program use the texture `map_diffuse`?
299 var use_map_diffuse
= uniforms
["use_map_diffuse"].as(UniformBool) is lazy
301 # Diffuse texture unit
302 var map_diffuse
= uniforms
["map_diffuse"].as(UniformSampler2D) is lazy
304 # Coordinates on the textures, per vertex
305 var tex_coord
= attributes
["tex_coord"].as(AttributeVec2) is lazy
307 # Translation applied to each vertex
308 var translation
= uniforms
["translation"].as(UniformVec4) is lazy
311 var rotation
= uniforms
["rotation"].as(UniformMat4) is lazy
314 var scale
= uniforms
["scale"].as(UniformFloat) is lazy
316 # Model view projection matrix
317 var mvp
= uniforms
["mvp"].as(UniformMat4) is lazy
320 var color_id
= uniforms
["color"].as(UniformVec4) is lazy