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, &currentFrameBuffer);
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