layer1/ScenePicking.cpp (461 lines of code) (raw):
#include"Base.h"
#include"Scene.h"
#include"ScenePicking.h"
#include"SceneRender.h"
#include"ShaderMgr.h"
#include"MemoryDebug.h"
#include"PyMOL.h"
#include"P.h"
#include "MacPyMOL.h"
#include "Err.h"
typedef unsigned char pix[4];
#define cRange 7
int SceneDoXYPick(PyMOLGlobals * G, int x, int y, int click_side)
{
CScene *I = G->Scene;
int defer_builds_mode = SettingGetGlobal_i(G, cSetting_defer_builds_mode);
if(defer_builds_mode == 5) /* force generation of a pickable version */
SceneUpdate(G, true);
if(OrthoGetOverlayStatus(G) || SettingGetGlobal_i(G, cSetting_text))
SceneRender(G, NULL, 0, 0, NULL, 0, 0, 0, 0); /* remove overlay if present */
SceneDontCopyNext(G);
I->LastPicked.context.object = NULL;
SceneRender(G, &I->LastPicked, x, y, NULL, 0, 0, click_side, 0);
I->invPick = false;
return (I->LastPicked.context.object != NULL);
/* did we pick something? */
}
static
unsigned int SceneFindTriplet(PyMOLGlobals * G, int x, int y, GLenum gl_buffer, bool bits32)
{
unsigned int result = 0;
/*int before_check[100];
int *int_ptr;
*/
pix *buffer = NULL;
pix *extra_safe_buffer = NULL;
/*int after_check[100]; */
/* pix_array *array_ptr;
char *safe_place;
*/
int a, b, d, flag;
float contentScaleFactor = DIP2PIXEL(1);
int cRangeVal = contentScaleFactor < 1.5 ? 7 : 21;
int h = (cRangeVal * 2 + 1), w = (cRangeVal * 2 + 1);
int debug = false;
unsigned char *c;
int bits15 = false;
GLint rb, gb, bb, ab;
int bkrd_alpha = 0xFF;
int check_alpha = false;
if(G->HaveGUI && G->ValidContext) { /*just in case */
glGetIntegerv(GL_RED_BITS, &rb);
glGetIntegerv(GL_GREEN_BITS, &gb);
glGetIntegerv(GL_BLUE_BITS, &bb);
glGetIntegerv(GL_ALPHA_BITS, &ab);
bits15 = (rb == 5) && (gb == 5) && (bb == 5);
if((rb < 4) && (gb < 4) && (bb < 4)){
PRINTFB(G, FB_Scene, FB_Errors) "SceneFindTriplet: ERROR: not enough colors to pick: rb=%d gb=%d bb=%d\n", rb, gb, bb ENDFB(G);
return 0;
}
if(Feedback(G, FB_Scene, FB_Debugging))
debug = true;
#ifndef PURE_OPENGL_ES_2
if (!hasFrameBufferBinding())
glReadBuffer(gl_buffer);
#endif
extra_safe_buffer = Alloc(pix, w * h * 21);
buffer = extra_safe_buffer + (w * h * 10);
PyMOLReadPixels(x - cRangeVal, y - cRangeVal, cRangeVal * 2 + 1, cRangeVal * 2 + 1, GL_RGBA,
GL_UNSIGNED_BYTE, &buffer[0][0]);
if(debug) {
for(a = 0; a <= (cRangeVal * 2); a++) {
for(b = 0; b <= (cRangeVal * 2); b++)
printf("%2x ",
(buffer[a + b * w][0] + buffer[a + b * w][1] +
buffer[a + b * w][2]) & 0xFF);
printf("\n");
}
printf("\n");
for(a = 0; a <= (cRangeVal * 2); a++) {
for(b = 0; b <= (cRangeVal * 2); b++)
printf("%02x ", (buffer[a + b * w][3]) & 0xFF);
printf("\n");
}
printf("\n");
for(a = 0; a <= (cRangeVal * 2); a++) {
for(b = 0; b <= (cRangeVal * 2); b++)
printf("%02x%02x%02x ", (buffer[a + b * w][0]) & 0xFF,
(buffer[a + b * w][1]) & 0xFF, (buffer[a + b * w][2]) & 0xFF);
printf("\n");
}
printf("\n");
}
/* first, check to make sure bkrd_alpha is correct
(this is a bug for systems with broken alpha, such as Extreme 3D on Solaris 8 */
if (bits32){
check_alpha = false;
} else {
flag = true;
for(d = 0; ab && flag && (d < cRangeVal); d++)
for(a = -d; flag && (a <= d); a++)
for(b = -d; flag && (b <= d); b++) {
c = &buffer[(a + cRangeVal) + (b + cRangeVal) * w][0];
if(c[3] == bkrd_alpha) {
check_alpha = true;
flag = false;
}
}
}
/* now find the correct pixel */
flag = true;
bool check_green_bit = !bits32;
bool strict = true;
if (bits32 || bits15)
strict = false;
for(d = 0; flag && (d < cRangeVal); d++)
for(a = -d; flag && (a <= d); a++)
for(b = -d; flag && (b <= d); b++) {
c = &buffer[(a + cRangeVal) + (b + cRangeVal) * w][0];
if(
((c[3] == bkrd_alpha) || (!check_alpha)) &&
((bits15 && c[1]) || (c[1] & 0x8) || !check_green_bit) &&
((!strict) || ((c[1] & 0xF) == 8 && (c[0] & 0xF) == 0 && (c[2] & 0xF) == 0))
) { /* only consider intact, saturated pixels */
if (bits15){ /* workaround for 15 bit rendering, for some reason red/green need rounding */
c[0] += 0x8;
c[2] += 0x8;
}
if (bits32){
result = (c[0] & 0xFF) + ((c[1] & 0xFF) << 8) + ((c[2] & 0xFF) << 16) + ((c[3] & 0xFF) << 24);
if (result)
flag = false;
}
else {
result = ((c[0] >> 4) & 0xF) + (c[1] & 0xF0) + ((c[2] << 4) & 0xF00);
flag = false;
}
}
}
FreeP(extra_safe_buffer);
}
return (result);
}
bool SceneHas32BitColor(PyMOLGlobals * G) {
#ifndef PURE_OPENGL_ES_2
GLint bits;
GLint currentFrameBuffer;
ok_assert(1, SettingGetGlobal_b(G, cSetting_use_shaders));
ok_assert(1, SettingGetGlobal_b(G, cSetting_pick32bit));
glGetIntegerv(GL_FRAMEBUFFER_BINDING, ¤tFrameBuffer);
if (currentFrameBuffer != G->ShaderMgr->default_framebuffer_id) {
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, G->ShaderMgr->default_framebuffer_id);
}
glGetIntegerv(GL_ALPHA_BITS, &bits); ok_assert(2, bits >= 8);
glGetIntegerv(GL_BLUE_BITS, &bits); ok_assert(2, bits >= 8);
glGetIntegerv(GL_GREEN_BITS, &bits); ok_assert(2, bits >= 8);
glGetIntegerv(GL_RED_BITS, &bits);
ok_except2:
if (currentFrameBuffer != G->ShaderMgr->default_framebuffer_id) {
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, currentFrameBuffer);
}
ok_assert(1, bits >= 8);
PRINTFD(G, FB_Scene) "Scene-DEBUG: 32bit picking\n" ENDFD;
return true;
ok_except1:
PRINTFD(G, FB_Scene) "Scene-DEBUG: 16bit picking\n" ENDFD;
#endif
return false;
}
static
void SceneRenderPickingSinglePick(PyMOLGlobals * G, SceneUnitContext *context, Picking * pick, int x, int y, GLenum render_buffer){
/* atom picking HACK - obfuscative coding */
CScene *I = G->Scene;
int debug_pick = 0;
unsigned int lowBits = 0, highBits = 0;
unsigned int index;
bool bits32 = SceneHas32BitColor(G);
debug_pick = SettingGetGlobal_i(G, cSetting_debug_pick);
SceneGLClearColor(0.0, 0.0, 0.0, 0.);
if (I->pickVLA.empty()){
I->pickVLA.resize(5000);
}
if(I->grid.active)
GridGetGLViewport(G, &I->grid);
// two passes in 16bit picking mode, if needed
for (int pass = 0;; ++pass) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (I->invPick || !SettingGetGlobal_b(G, cSetting_use_shaders)){
I->pickVLA.begin()->src.index = 0;
I->pickVLA.begin()->src.bond = 2 + pass;
} else {
I->pickVLA.begin()->src.bond = 0 + pass;
}
{
int slot;
for(slot = 0; slot <= I->grid.last_slot; slot++) {
if(I->grid.active) {
GridSetGLViewport(&I->grid, slot);
}
SceneRenderAll(G, context, NULL, std::addressof(I->pickVLA), 0, true, 0.0F, &I->grid, 0, 0, bits32);
}
}
if(debug_pick) {
PyMOL_SwapBuffers(G->PyMOL);
PSleep(G, 1000000 * debug_pick / 4);
PyMOL_SwapBuffers(G->PyMOL);
}
if (pass == 1) {
highBits = SceneFindTriplet(G, x, y, render_buffer, false);
index += (highBits << 12);
break;
}
index = lowBits = SceneFindTriplet(G, x, y, render_buffer, bits32);
if (bits32 || I->pickVLA[0].src.index < (1 << 12)) {
// no need for a second pass
break;
}
}
if(I->grid.active)
GridSetGLViewport(&I->grid, -1);
if(debug_pick) {
if (bits32){
PRINTFB(G, FB_Scene, FB_Details)
" SceneClick-Detail: lowBits=%u index %u < %u?\n", lowBits, index, I->pickVLA.begin()->src.index ENDFB(G);
} else {
PRINTFB(G, FB_Scene, FB_Details)
" SceneClick-Detail: lowBits=%u highBits=%u index %u < %u?\n", lowBits, highBits, index, I->pickVLA.begin()->src.index ENDFB(G);
}
}
if(index && (index <= I->pickVLA.begin()->src.index)) {
*pick = I->pickVLA[index]; /* return object info */
if(debug_pick) {
PRINTFB(G, FB_Scene, FB_Details)
" SceneClick-Detail: obj %p index %d bond %d\n",
pick->context.object, pick->src.index, pick->src.bond ENDFB(G);
}
// if cPickableNoPick then set object to NULL since nothing picked
if (pick->src.bond == cPickableNoPick)
pick->context.object = NULL;
} else {
pick->context.object = NULL;
}
#ifndef PURE_OPENGL_ES_2
/* Picking changes the Shading model to GL_FLAT,
we need to change it back to GL_SMOOTH. This is because
bg_grad() might be called in OrthoDoDraw() before GL
settings are set in SceneRender() */
// glEnable(GL_COLOR_MATERIAL);
glShadeModel(SettingGetGlobal_b(G, cSetting_pick_shading) ? GL_FLAT : GL_SMOOTH);
#endif
}
/*========================================================================*/
static
unsigned int *SceneReadTriplets(PyMOLGlobals * G, int x, int y, int w, int h,
GLenum gl_buffer, bool bits32)
{
unsigned int *result = NULL;
pix *buffer = NULL;
pix *extra_safe_buffer = NULL;
int a, b;
unsigned char *c;
int cc = 0;
int bits15 = false;
int bkrd_alpha = 0xFF;
int check_alpha = false;
GLint rb, gb, bb, ab;
if(w < 1)
w = 1;
if(h < 1)
h = 1;
if(G->HaveGUI && G->ValidContext) { /*just in case */
glGetIntegerv(GL_RED_BITS, &rb);
glGetIntegerv(GL_GREEN_BITS, &gb);
glGetIntegerv(GL_BLUE_BITS, &bb);
glGetIntegerv(GL_ALPHA_BITS, &ab);
bits15 = (rb == 5) && (gb == 5) && (bb == 5);
if((rb < 4) && (gb < 4) && (bb < 4)){
PRINTFB(G, FB_Scene, FB_Errors) "SceneReadTriplet: ERROR: not enough colors to pick: rb=%d gb=%d bb=%d\n", rb, gb, bb ENDFB(G);
return 0;
}
/* create some safe RAM on either side of the read buffer -- buggy
ReadPixels implementations tend to trash RAM surrounding the
target block */
extra_safe_buffer = Alloc(pix, w * h * 11);
buffer = extra_safe_buffer + (w * h * 5);
result = VLAlloc(unsigned int, w * h);
#ifndef PURE_OPENGL_ES_2
if (!hasFrameBufferBinding())
glReadBuffer(gl_buffer);
#endif
PyMOLReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, &buffer[0][0]);
/* first, check to make sure bkrd_alpha is correct
(this is a bug for systems with broken alpha, such as Extreme 3D on Solaris 8 */
if (bits32)
check_alpha = false;
else
for(a = 0; ab && a < w; a++)
for(b = 0; b < h; b++) {
c = &buffer[a + b * w][0];
if(c[3] == bkrd_alpha) {
check_alpha = true;
}
}
bool check_green_bit = !bits32;
bool strict = true;
if (bits32 || bits15)
strict = false;
/* now read pixels */
for(a = 0; a < w; a++)
for(b = 0; b < h; b++) {
c = &buffer[a + b * w][0];
if(
((c[3] == bkrd_alpha) || (!check_alpha)) &&
((bits15 && c[1]) || (c[1] & 0x8) || !check_green_bit) &&
((!strict) || ((c[1] & 0xF) == 8 && (c[0] & 0xF) == 0 && (c[2] & 0xF) == 0))
) { /* only consider intact, saturated pixels */
VLACheck(result, unsigned int, cc + 1);
if (bits15){ /* workaround for 15 bit rendering, for some reason red/green need rounding */
c[0] += 0x8;
c[2] += 0x8;
}
if (bits32)
result[cc] = (c[0] & 0xFF) + ((c[1] & 0xFF) << 8) + ((c[2] & 0xFF) << 16) + ((c[3] & 0xFF) << 24);
else {
result[cc] = ((c[0] >> 4) & 0xF) + (c[1] & 0xF0) + ((c[2] << 4) & 0xF00);
}
result[cc + 1] = b + a * h; // resulting pixel offset
if (result[cc] || (!bits32)) // !bits32 because it should hit the high-order bits
cc += 2;
}
}
FreeP(extra_safe_buffer);
VLASize(result, unsigned int, cc);
}
return (result);
}
static
void SceneRenderPickingMultiPick(PyMOLGlobals * G, SceneUnitContext *context, Multipick * smp, GLenum render_buffer){
/* multiple atom picking HACK - even more obfuscative coding */
CScene *I = G->Scene;
Picking *pik;
unsigned int *lowBitVLA = NULL, *highBitVLA = NULL;
int high, low;
unsigned int lastIndex = 0;
unsigned int index;
void *lastPtr = NULL;
int nPick;
int nHighBits = 0, nLowBits;
bool bits32 = SceneHas32BitColor(G);
SceneGLClearColor(0.0, 0.0, 0.0, 0.0);
if (I->pickVLA.empty()){
I->pickVLA.resize(5000);
}
if(I->grid.active)
GridGetGLViewport(G, &I->grid);
// two passes in 16bit picking mode, if needed
for (int pass = 0;; ++pass) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (I->invPick || !SettingGetGlobal_b(G, cSetting_use_shaders)){
I->pickVLA.begin()->src.index = 0;
I->pickVLA.begin()->src.bond = 2 + pass;
} else {
I->pickVLA.begin()->src.bond = 0 + pass;
}
{
int slot;
for(slot = 0; slot <= I->grid.last_slot; slot++) {
if(I->grid.active) {
GridSetGLViewport(&I->grid, slot);
}
SceneRenderAll(G, context, NULL, std::addressof(I->pickVLA), 0, true, 0.0F, &I->grid, 0, 0, bits32);
}
}
if (pass == 1) {
highBitVLA = SceneReadTriplets(G, smp->x, smp->y, smp->w, smp->h, render_buffer, false);
nHighBits = VLAGetSize(highBitVLA);
break;
}
lowBitVLA = SceneReadTriplets(G, smp->x, smp->y, smp->w, smp->h, render_buffer, bits32);
nLowBits = VLAGetSize(lowBitVLA);
if (bits32 || I->pickVLA.begin()->src.index < (1 << 12)) {
// no need for a second pass
bits32 = true; // continue like with 32bit picking
break;
}
}
if(I->grid.active)
GridSetGLViewport(&I->grid, -1);
/* need to scissor this */
nPick = 0;
if(nLowBits && (bits32 || nHighBits)) {
low = 0;
high = 0;
while((low < nLowBits) && (bits32 || high < nHighBits)) {
if(bits32 || lowBitVLA[low + 1] == highBitVLA[high + 1]) {
if (bits32){
// 32bit picking
index = lowBitVLA[low];
} else {
index = lowBitVLA[low] + (highBitVLA[high] << 12);
}
if(index && (index <= I->pickVLA.begin()->src.index)) {
pik = I->pickVLA.data() + index; /* just using as a tmp */
if((pik->src.index != lastIndex) || (pik->context.object != lastPtr)) {
if(((CObject *) pik->context.object)->type == cObjectMolecule) {
nPick++; /* start from 1 */
VLACheck(smp->picked, Picking, nPick);
smp->picked[nPick] = *pik; /* return atom/object info -- will be redundant */
}
lastIndex = pik->src.index;
lastPtr = pik->context.object;
}
}
low += 2;
high += 2;
} else if(lowBitVLA[low + 1] < highBitVLA[high + 1])
low += 2;
else
high += 2;
}
}
smp->picked[0].src.index = nPick;
#ifndef PURE_OPENGL_ES_2
/* Picking changes the Shading model to GL_FLAT,
we need to change it back to GL_SMOOTH. This is because
bg_grad() might be called in OrthoDoDraw() before GL
settings are set in SceneRender() */
// glEnable(GL_COLOR_MATERIAL);
glShadeModel(SettingGetGlobal_b(G, cSetting_pick_shading) ? GL_FLAT : GL_SMOOTH);
#endif
VLAFreeP(lowBitVLA);
VLAFreeP(highBitVLA);
}
void 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){
CScene *I = G->Scene;
if (render_buffer == GL_BACK) {
render_buffer = G->DRAW_BUFFER0;
}
SceneSetupGLPicking(G);
if (!stereo_double_pump_mono){
switch (stereo_mode) {
case cStereo_crosseye:
case cStereo_walleye:
case cStereo_sidebyside:
glViewport(I->rect.left, I->rect.bottom, I->Width / 2, I->Height);
break;
case cStereo_geowall:
*click_side = OrthoGetWrapClickSide(G);
break;
}
}
#ifndef PURE_OPENGL_ES_2
glPushMatrix(); /* 1 */
#endif
switch (stereo_mode) {
case cStereo_crosseye:
ScenePrepareMatrix(G, (*click_side > 0) ? 1 : 2);
break;
case cStereo_walleye:
case cStereo_geowall:
case cStereo_sidebyside:
ScenePrepareMatrix(G, (*click_side < 0) ? 1 : 2);
break;
}
G->ShaderMgr->SetIsPicking(true);
if(pick) {
SceneRenderPickingSinglePick(G, context, pick, x, y, render_buffer);
} else if(smp) {
SceneRenderPickingMultiPick(G, context, smp, render_buffer);
}
G->ShaderMgr->SetIsPicking(false);
#ifndef PURE_OPENGL_ES_2
glPopMatrix(); /* 1 */
#endif
}
/*========================================================================*/
int SceneMultipick(PyMOLGlobals * G, Multipick * smp)
{
CScene *I = G->Scene;
int click_side = 0;
int defer_builds_mode = SettingGetGlobal_i(G, cSetting_defer_builds_mode);
if(defer_builds_mode == 5) /* force generation of a pickable version */
SceneUpdate(G, true);
if(OrthoGetOverlayStatus(G) || SettingGetGlobal_i(G, cSetting_text))
SceneRender(G, NULL, 0, 0, NULL, 0, 0, 0, 0); /* remove overlay if present */
SceneDontCopyNext(G);
if(StereoIsAdjacent(G)) {
if(smp->x > (I->Width / 2))
click_side = 1;
else
click_side = -1;
smp->x = smp->x % (I->Width / 2);
}
SceneRender(G, NULL, 0, 0, smp, 0, 0, click_side, 0);
SceneDirty(G);
return (1);
}