Files
pymol-open-source/layer1/ScenePicking.cpp
Jarrett Johnson 0313aeba9d PYMOL-4365: Filter gizmo and selections from thumbnails and exports
Some other restructuring (SceneRenderWhich and SceneRenderOrder) to support this
2025-01-04 20:40:13 -05:00

361 lines
10 KiB
C++

#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 "Err.h"
#include "Picking.h"
#include "Feedback.h"
#define cRange 7
int SceneDoXYPick(PyMOLGlobals * G, int x, int y, ClickSide click_side)
{
CScene *I = G->Scene;
int defer_builds_mode = SettingGet<int>(G, cSetting_defer_builds_mode);
if(defer_builds_mode == 5) /* force generation of a pickable version */
SceneUpdate(G, true);
if (OrthoGetOverlayStatus(G) || SettingGet<int>(G, cSetting_text)) {
SceneRenderInfo renderInfo{};
SceneRender(G, renderInfo); /* remove overlay if present */
}
SceneDontCopyNext(G);
I->LastPicked.context.object = nullptr;
SceneRenderInfo renderInfo{};
renderInfo.pick = &I->LastPicked;
renderInfo.mousePos = Offset2D{x, y};
renderInfo.clickSide = click_side;
SceneRender(G, renderInfo);
return (I->LastPicked.context.object != nullptr);
/* did we pick something? */
}
/**
* Query the number of RED, GREEN, BLUE, and ALPHA bitplanes from OpenGL
* @param[out] pickconv picking instance to update
*/
static void PickColorConverterSetRgbaBitsFromGL(
PyMOLGlobals* G, PickColorConverter& pickconv)
{
GLint rgba_bits[4] = {4, 4, 4, 0};
int max_check_bits = 0;
#ifdef _WEBGL
// Can't turn off antialiasing in WebPyMOL, so we need to add some check bits
// to filter out antialiased pixels.
max_check_bits = 2;
#endif
if (!SettingGet<bool>(G, cSetting_pick32bit)) {
pickconv.setRgbaBits(rgba_bits, max_check_bits);
return;
}
GLint currentFrameBuffer = G->ShaderMgr->defaultBackbuffer.framebuffer;
if (SettingGet<bool>(G, cSetting_use_shaders)) {
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &currentFrameBuffer);
}
if (currentFrameBuffer != G->ShaderMgr->defaultBackbuffer.framebuffer) {
glBindFramebuffer(GL_FRAMEBUFFER, G->ShaderMgr->defaultBackbuffer.framebuffer);
}
glGetIntegerv(GL_RED_BITS, rgba_bits + 0);
glGetIntegerv(GL_GREEN_BITS, rgba_bits + 1);
glGetIntegerv(GL_BLUE_BITS, rgba_bits + 2);
glGetIntegerv(GL_ALPHA_BITS, rgba_bits + 3);
PRINTFD(G, FB_Scene)
" %s: GL RGBA BITS: (%d, %d, %d, %d)\n", __func__, rgba_bits[0], rgba_bits[1],
rgba_bits[2], rgba_bits[3] ENDFD;
if (currentFrameBuffer != G->ShaderMgr->defaultBackbuffer.framebuffer) {
glBindFramebuffer(GL_FRAMEBUFFER, currentFrameBuffer);
}
pickconv.setRgbaBits(rgba_bits, max_check_bits);
}
/**
* Get picking indices for the rectangle (x,y,w,h)
*/
static std::vector<unsigned> SceneGetPickIndices(PyMOLGlobals* G,
SceneUnitContext* context, int x, int y, int w, int h, GLenum gl_buffer)
{
CScene *I = G->Scene;
auto& pickmgr = I->pickmgr;
const auto use_shaders = SettingGet<bool>(G, cSetting_use_shaders);
SceneGLClearColor(0.0, 0.0, 0.0, 0.);
if (!pickmgr.m_valid) {
PickColorConverterSetRgbaBitsFromGL(G, pickmgr);
}
const auto shift_per_pass = pickmgr.getTotalBits();
const auto pass_max = use_shaders ? SHADER_PICKING_PASSES_MAX : 99;
std::vector<unsigned> indices(w * h);
if (I->grid.active) {
I->grid.cur_view = SceneGetViewport(G);
}
for (int pass = 0;; ++pass) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
pickmgr.m_pass = pass;
if (!pickmgr.m_valid || !use_shaders) {
pickmgr.resetCount();
}
{
int slot;
for(slot = 0; slot <= I->grid.last_slot; slot++) {
if(I->grid.active) {
GridSetViewport(G, &I->grid, slot);
}
SceneRenderAll(G, context, nullptr, &pickmgr, RenderPass::Antialias, true,
0.0F, &I->grid, 0, SceneRenderWhich::All, SceneRenderOrder::GadgetsLast);
}
}
#ifndef _PYMOL_NO_MAIN
const auto debug_pick = SettingGet<int>(G, cSetting_debug_pick);
if(debug_pick) {
PyMOL_SwapBuffers(G->PyMOL);
PSleep(G, 1000000 * debug_pick / 4);
PyMOL_SwapBuffers(G->PyMOL);
}
#endif
#ifndef PURE_OPENGL_ES_2
if (!hasFrameBufferBinding()) {
glReadBuffer(gl_buffer);
}
#endif
// assume glReadPixels does not overrun the buffer - previous version of
// PyMOL allocated a larger buffer to account for "buggy glReadPixels"
std::vector<unsigned char> buffer(4 * indices.size());
PyMOLReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, &buffer[0]);
for (size_t i = 0; i < indices.size(); ++i) {
const auto* c = &buffer[4 * i];
indices[i] |= pickmgr.indexFromColor(c) << (shift_per_pass * pass);
}
if (pickmgr.count() < (1ULL << shift_per_pass * (pass + 1))) {
break;
}
if (pass + 1 == pass_max) {
PRINTFB(G, FB_Scene, FB_Warnings)
" Scene-Warning: Maximum number of picking passes exceeded\n"
" (%u picking colors, %u color bits)\n",
pickmgr.count(), shift_per_pass ENDFB(G);
break;
}
}
if(I->grid.active)
GridSetViewport(G, &I->grid, -1);
pickmgr.m_valid = true;
return indices;
}
/**
* Pick a single point
*
* @param[out] pick Copy picking result here
* @param x X position in the window to pick
* @param y Y position in the window to pick
*/
static void SceneRenderPickingSinglePick(PyMOLGlobals* G,
SceneUnitContext* context, Picking* pick, int x, int y,
GLenum render_buffer)
{
CScene *I = G->Scene;
const int debug_pick = SettingGet<int>(G, cSetting_debug_pick);
const int cRangeVal = DIP2PIXEL(cRange);
const int h = (cRangeVal * 2 + 1), w = (cRangeVal * 2 + 1);
auto indices = SceneGetPickIndices(
G, context, x - cRangeVal, y - cRangeVal, w, h, render_buffer);
assert(!indices.empty());
/* now find the correct pixel */
unsigned int index = 0;
for (int d = 0; (d < cRangeVal); ++d) {
for (int a = -d; (a <= d); ++a) {
for (int b = -d; (b <= d); ++b) {
index = indices[a + cRangeVal + (b + cRangeVal) * w];
if (index) {
a = d = cRangeVal;
break;
}
}
}
}
auto* pik = I->pickmgr.getIdentifier(index);
if (pik) {
*pick = *pik;
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 nullptr since nothing picked
if (pick->src.bond == cPickableNoPick)
pick->context.object = nullptr;
} else {
pick->context.object = nullptr;
}
#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
}
/**
* Pick all items in a rectangle
* @param[in,out] smp Defines the (x,y,w,h) rectangle (input), and takes the
* picking result in `smp->picked` (output)
*/
static
void SceneRenderPickingMultiPick(PyMOLGlobals * G, SceneUnitContext *context, Multipick * smp, GLenum render_buffer){
CScene *I = G->Scene;
Picking previous;
assert(smp->picked.empty());
auto indices = SceneGetPickIndices(G, context, smp->x, smp->y,
std::max(1, smp->w), std::max(1, smp->h), render_buffer);
/* need to scissor this */
for (auto index : indices) {
const auto* pik = I->pickmgr.getIdentifier(index);
if (pik) {
if (pik->src.index != previous.src.index ||
pik->context.object != previous.context.object) {
if (pik->context.object->type == cObjectMolecule) {
smp->picked.push_back(*pik);
}
previous = *pik;
}
}
}
#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
}
void SceneRenderPicking(PyMOLGlobals* G, int stereo_mode, ClickSide 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->ShaderMgr->defaultBackbuffer.drawBuffer;
}
SceneSetupGLPicking(G);
if (!stereo_double_pump_mono){
switch (stereo_mode) {
case cStereo_crosseye:
case cStereo_walleye:
case cStereo_sidebyside:
SceneSetViewport(G, 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 == ClickSide::Right ? 1 : 2);
break;
case cStereo_walleye:
case cStereo_geowall:
case cStereo_sidebyside:
ScenePrepareMatrix(G, click_side == ClickSide::Left ? 1 : 2);
break;
#ifdef _PYMOL_OPENVR
case cStereo_openvr:
ScenePrepareMatrix(G, 0, cStereo_openvr);
break;
#endif
}
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 defer_builds_mode = SettingGet<int>(G, cSetting_defer_builds_mode);
if(defer_builds_mode == 5) /* force generation of a pickable version */
SceneUpdate(G, true);
if (OrthoGetOverlayStatus(G) || SettingGet<int>(G, cSetting_text)) {
SceneRenderInfo renderInfo{};
SceneRender(G, renderInfo); /* remove overlay if present */
}
SceneDontCopyNext(G);
auto click_side = ClickSide::None;
if (StereoIsAdjacent(G)) {
if(smp->x > (I->Width / 2))
click_side = ClickSide::Right;
else
click_side = ClickSide::Left;
smp->x = smp->x % (I->Width / 2);
}
SceneRenderInfo renderInfo{};
renderInfo.sceneMultipick = smp;
renderInfo.clickSide = click_side;
SceneRender(G, renderInfo);
SceneDirty(G);
return (1);
}