mirror of
https://github.com/schrodinger/pymol-open-source.git
synced 2026-06-04 20:04:21 +08:00
361 lines
10 KiB
C++
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, ¤tFrameBuffer);
|
|
}
|
|
|
|
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);
|
|
}
|