1
2
3 #include"Base.h"
4 #include"Scene.h"
5 #include"ScenePicking.h"
6 #include"SceneRender.h"
7 #include"ShaderMgr.h"
8 #include"MemoryDebug.h"
9 #include"PyMOL.h"
10 #include"P.h"
11 #include "Err.h"
12 #include "Picking.h"
13
14 #define cRange 7
15
SceneDoXYPick(PyMOLGlobals * G,int x,int y,int click_side)16 int SceneDoXYPick(PyMOLGlobals * G, int x, int y, int click_side)
17 {
18 CScene *I = G->Scene;
19 int defer_builds_mode = SettingGetGlobal_i(G, cSetting_defer_builds_mode);
20
21 if(defer_builds_mode == 5) /* force generation of a pickable version */
22 SceneUpdate(G, true);
23 if(OrthoGetOverlayStatus(G) || SettingGetGlobal_i(G, cSetting_text))
24 SceneRender(G, NULL, 0, 0, NULL, 0, 0, 0, 0); /* remove overlay if present */
25 SceneDontCopyNext(G);
26
27 I->LastPicked.context.object = NULL;
28 SceneRender(G, &I->LastPicked, x, y, NULL, 0, 0, click_side, 0);
29 return (I->LastPicked.context.object != NULL);
30 /* did we pick something? */
31 }
32
33 /**
34 * Query the number of RED, GREEN, BLUE, and ALPHA bitplanes from OpenGL
35 * @param[out] pickconv picking instance to update
36 */
PickColorConverterSetRgbaBitsFromGL(PyMOLGlobals * G,PickColorConverter & pickconv)37 static void PickColorConverterSetRgbaBitsFromGL(
38 PyMOLGlobals* G, PickColorConverter& pickconv)
39 {
40 GLint rgba_bits[4] = {4, 4, 4, 0};
41 int max_check_bits = 0;
42
43 #ifdef _WEBGL
44 // Can't turn off antialiasing in WebPyMOL, so we need to add some check bits
45 // to filter out antialiased pixels.
46 max_check_bits = 2;
47 #endif
48
49 if (!SettingGet<bool>(G, cSetting_pick32bit)) {
50 pickconv.setRgbaBits(rgba_bits, max_check_bits);
51 return;
52 }
53
54 GLint currentFrameBuffer = G->ShaderMgr->default_framebuffer_id;
55
56 if (SettingGet<bool>(G, cSetting_use_shaders)) {
57 glGetIntegerv(GL_FRAMEBUFFER_BINDING, ¤tFrameBuffer);
58 }
59
60 if (currentFrameBuffer != G->ShaderMgr->default_framebuffer_id) {
61 glBindFramebuffer(GL_FRAMEBUFFER, G->ShaderMgr->default_framebuffer_id);
62 }
63
64 glGetIntegerv(GL_RED_BITS, rgba_bits + 0);
65 glGetIntegerv(GL_GREEN_BITS, rgba_bits + 1);
66 glGetIntegerv(GL_BLUE_BITS, rgba_bits + 2);
67 glGetIntegerv(GL_ALPHA_BITS, rgba_bits + 3);
68
69 PRINTFD(G, FB_Scene)
70 " %s: GL RGBA BITS: (%d, %d, %d, %d)\n", __func__, rgba_bits[0], rgba_bits[1],
71 rgba_bits[2], rgba_bits[3] ENDFD;
72
73 if (currentFrameBuffer != G->ShaderMgr->default_framebuffer_id) {
74 glBindFramebuffer(GL_FRAMEBUFFER, currentFrameBuffer);
75 }
76
77 pickconv.setRgbaBits(rgba_bits, max_check_bits);
78 }
79
80 /**
81 * Get picking indices for the rectangle (x,y,w,h)
82 */
SceneGetPickIndices(PyMOLGlobals * G,SceneUnitContext * context,int x,int y,int w,int h,GLenum gl_buffer)83 static std::vector<unsigned> SceneGetPickIndices(PyMOLGlobals* G,
84 SceneUnitContext* context, int x, int y, int w, int h, GLenum gl_buffer)
85 {
86 CScene *I = G->Scene;
87 auto& pickmgr = I->pickmgr;
88 const auto use_shaders = SettingGet<bool>(G, cSetting_use_shaders);
89
90 SceneGLClearColor(0.0, 0.0, 0.0, 0.);
91
92 if (!pickmgr.m_valid) {
93 PickColorConverterSetRgbaBitsFromGL(G, pickmgr);
94 }
95
96 const auto shift_per_pass = pickmgr.getTotalBits();
97 const auto pass_max = use_shaders ? SHADER_PICKING_PASSES_MAX : 99;
98
99 std::vector<unsigned> indices(w * h);
100
101 if(I->grid.active)
102 GridGetGLViewport(G, &I->grid);
103
104 for (int pass = 0;; ++pass) {
105 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
106
107 pickmgr.m_pass = pass;
108
109 if (!pickmgr.m_valid || !use_shaders) {
110 pickmgr.resetCount();
111 }
112 {
113 int slot;
114 for(slot = 0; slot <= I->grid.last_slot; slot++) {
115 if(I->grid.active) {
116 GridSetGLViewport(&I->grid, slot);
117 }
118 SceneRenderAll(
119 G, context, NULL, &pickmgr, 0, true, 0.0F, &I->grid, 0, 0);
120 }
121 }
122
123 #ifndef _PYMOL_NO_MAIN
124 const auto debug_pick = SettingGet<int>(G, cSetting_debug_pick);
125 if(debug_pick) {
126 PyMOL_SwapBuffers(G->PyMOL);
127 PSleep(G, 1000000 * debug_pick / 4);
128 PyMOL_SwapBuffers(G->PyMOL);
129 }
130 #endif
131
132 #ifndef PURE_OPENGL_ES_2
133 if (!hasFrameBufferBinding()) {
134 glReadBuffer(gl_buffer);
135 }
136 #endif
137
138 // assume glReadPixels does not overrun the buffer - previous version of
139 // PyMOL allocated a larger buffer to account for "buggy glReadPixels"
140 std::vector<unsigned char> buffer(4 * indices.size());
141 PyMOLReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, &buffer[0]);
142
143 for (size_t i = 0; i < indices.size(); ++i) {
144 const auto* c = &buffer[4 * i];
145 indices[i] |= pickmgr.indexFromColor(c) << (shift_per_pass * pass);
146 }
147
148 if (pickmgr.count() < (1ULL << shift_per_pass * (pass + 1))) {
149 break;
150 }
151
152 if (pass + 1 == pass_max) {
153 PRINTFB(G, FB_Scene, FB_Warnings)
154 " Scene-Warning: Maximum number of picking passes exceeded\n"
155 " (%u picking colors, %u color bits)\n",
156 pickmgr.count(), shift_per_pass ENDFB(G);
157 break;
158 }
159 }
160
161 if(I->grid.active)
162 GridSetGLViewport(&I->grid, -1);
163
164 pickmgr.m_valid = true;
165
166 return indices;
167 }
168
169 /**
170 * Pick a single point
171 *
172 * @param[out] pick Copy picking result here
173 * @param x X position in the window to pick
174 * @param y Y position in the window to pick
175 */
SceneRenderPickingSinglePick(PyMOLGlobals * G,SceneUnitContext * context,Picking * pick,int x,int y,GLenum render_buffer)176 static void SceneRenderPickingSinglePick(PyMOLGlobals* G,
177 SceneUnitContext* context, Picking* pick, int x, int y,
178 GLenum render_buffer)
179 {
180 CScene *I = G->Scene;
181 const int debug_pick = SettingGet<int>(G, cSetting_debug_pick);
182 const int cRangeVal = DIP2PIXEL(cRange);
183 const int h = (cRangeVal * 2 + 1), w = (cRangeVal * 2 + 1);
184
185 auto indices = SceneGetPickIndices(
186 G, context, x - cRangeVal, y - cRangeVal, w, h, render_buffer);
187
188 assert(!indices.empty());
189
190 /* now find the correct pixel */
191 unsigned int index = 0;
192 for (int d = 0; (d < cRangeVal); ++d) {
193 for (int a = -d; (a <= d); ++a) {
194 for (int b = -d; (b <= d); ++b) {
195 index = indices[a + cRangeVal + (b + cRangeVal) * w];
196 if (index) {
197 a = d = cRangeVal;
198 break;
199 }
200 }
201 }
202 }
203
204 auto* pik = I->pickmgr.getIdentifier(index);
205
206 if (pik) {
207 *pick = *pik;
208 if(debug_pick) {
209 PRINTFB(G, FB_Scene, FB_Details)
210 " SceneClick-Detail: obj %p index %d bond %d\n",
211 pick->context.object, pick->src.index, pick->src.bond ENDFB(G);
212 }
213 // if cPickableNoPick then set object to NULL since nothing picked
214 if (pick->src.bond == cPickableNoPick)
215 pick->context.object = NULL;
216 } else {
217 pick->context.object = NULL;
218 }
219
220 #ifndef PURE_OPENGL_ES_2
221 /* Picking changes the Shading model to GL_FLAT,
222 we need to change it back to GL_SMOOTH. This is because
223 bg_grad() might be called in OrthoDoDraw() before GL
224 settings are set in SceneRender() */
225 // glEnable(GL_COLOR_MATERIAL);
226 glShadeModel(SettingGetGlobal_b(G, cSetting_pick_shading) ? GL_FLAT : GL_SMOOTH);
227 #endif
228 }
229
230 /**
231 * Pick all items in a rectangle
232 * @param[in,out] smp Defines the (x,y,w,h) rectangle (input), and takes the
233 * picking result in `smp->picked` (output)
234 */
235 static
SceneRenderPickingMultiPick(PyMOLGlobals * G,SceneUnitContext * context,Multipick * smp,GLenum render_buffer)236 void SceneRenderPickingMultiPick(PyMOLGlobals * G, SceneUnitContext *context, Multipick * smp, GLenum render_buffer){
237 CScene *I = G->Scene;
238 Picking previous;
239
240 assert(smp->picked.empty());
241
242 auto indices = SceneGetPickIndices(G, context, smp->x, smp->y,
243 std::max(1, smp->w), std::max(1, smp->h), render_buffer);
244
245 /* need to scissor this */
246 for (auto index : indices) {
247 const auto* pik = I->pickmgr.getIdentifier(index);
248 if (pik) {
249 if (pik->src.index != previous.src.index ||
250 pik->context.object != previous.context.object) {
251 if (pik->context.object->type == cObjectMolecule) {
252 smp->picked.push_back(*pik);
253 }
254 previous = *pik;
255 }
256 }
257 }
258 #ifndef PURE_OPENGL_ES_2
259 /* Picking changes the Shading model to GL_FLAT,
260 we need to change it back to GL_SMOOTH. This is because
261 bg_grad() might be called in OrthoDoDraw() before GL
262 settings are set in SceneRender() */
263 // glEnable(GL_COLOR_MATERIAL);
264 glShadeModel(SettingGetGlobal_b(G, cSetting_pick_shading) ? GL_FLAT : GL_SMOOTH);
265 #endif
266 }
267
SceneRenderPicking(PyMOLGlobals * G,int stereo_mode,int * click_side,int stereo_double_pump_mono,Picking * pick,int x,int y,Multipick * smp,SceneUnitContext * context,GLenum render_buffer)268 void SceneRenderPicking(PyMOLGlobals * G, int stereo_mode, int *click_side, int stereo_double_pump_mono,
269 Picking * pick, int x, int y, Multipick * smp, SceneUnitContext *context,
270 GLenum render_buffer){
271 CScene *I = G->Scene;
272
273 if (render_buffer == GL_BACK) {
274 render_buffer = G->DRAW_BUFFER0;
275 }
276
277 SceneSetupGLPicking(G);
278
279 if (!stereo_double_pump_mono){
280 switch (stereo_mode) {
281 case cStereo_crosseye:
282 case cStereo_walleye:
283 case cStereo_sidebyside:
284 glViewport(I->rect.left, I->rect.bottom, I->Width / 2, I->Height);
285 break;
286 case cStereo_geowall:
287 *click_side = OrthoGetWrapClickSide(G);
288 break;
289 }
290 }
291 #ifndef PURE_OPENGL_ES_2
292 glPushMatrix(); /* 1 */
293 #endif
294
295 switch (stereo_mode) {
296 case cStereo_crosseye:
297 ScenePrepareMatrix(G, (*click_side > 0) ? 1 : 2);
298 break;
299 case cStereo_walleye:
300 case cStereo_geowall:
301 case cStereo_sidebyside:
302 ScenePrepareMatrix(G, (*click_side < 0) ? 1 : 2);
303 break;
304 #ifdef _PYMOL_OPENVR
305 case cStereo_openvr:
306 ScenePrepareMatrix(G, 0, cStereo_openvr);
307 break;
308 #endif
309 }
310 G->ShaderMgr->SetIsPicking(true);
311 if(pick) {
312 SceneRenderPickingSinglePick(G, context, pick, x, y, render_buffer);
313 } else if(smp) {
314 SceneRenderPickingMultiPick(G, context, smp, render_buffer);
315 }
316 G->ShaderMgr->SetIsPicking(false);
317 #ifndef PURE_OPENGL_ES_2
318 glPopMatrix(); /* 1 */
319 #endif
320 }
321
322 /*========================================================================*/
SceneMultipick(PyMOLGlobals * G,Multipick * smp)323 int SceneMultipick(PyMOLGlobals * G, Multipick * smp)
324 {
325 CScene *I = G->Scene;
326 int click_side = 0;
327 int defer_builds_mode = SettingGetGlobal_i(G, cSetting_defer_builds_mode);
328
329 if(defer_builds_mode == 5) /* force generation of a pickable version */
330 SceneUpdate(G, true);
331
332 if(OrthoGetOverlayStatus(G) || SettingGetGlobal_i(G, cSetting_text))
333 SceneRender(G, NULL, 0, 0, NULL, 0, 0, 0, 0); /* remove overlay if present */
334 SceneDontCopyNext(G);
335 if(StereoIsAdjacent(G)) {
336 if(smp->x > (I->Width / 2))
337 click_side = 1;
338 else
339 click_side = -1;
340 smp->x = smp->x % (I->Width / 2);
341 }
342 SceneRender(G, NULL, 0, 0, smp, 0, 0, click_side, 0);
343 SceneDirty(G);
344 return (1);
345 }
346